Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/GPU/ShaderTranslation.cpp
3187 views
1
// Copyright (c) 2017- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "ppsspp_config.h"
19
20
#include <memory>
21
#include <vector>
22
#include <sstream>
23
24
// DbgNew is not compatible with Glslang
25
#ifdef DBG_NEW
26
#undef new
27
#undef free
28
#undef malloc
29
#undef realloc
30
#endif
31
32
// Weird issue
33
#if PPSSPP_PLATFORM(WINDOWS) && PPSSPP_ARCH(ARM)
34
#undef free
35
#endif
36
37
#include "Common/Log.h"
38
#include "Common/StringUtils.h"
39
#include "Common/GPU/Shader.h"
40
41
#include "Common/GPU/ShaderTranslation.h"
42
#include "ext/glslang/SPIRV/GlslangToSpv.h"
43
#include "Common/GPU/thin3d.h"
44
#include "Common/GPU/Shader.h"
45
#include "Common/GPU/OpenGL/GLFeatures.h"
46
47
#include "ext/SPIRV-Cross/spirv.hpp"
48
#include "ext/SPIRV-Cross/spirv_common.hpp"
49
#include "ext/SPIRV-Cross/spirv_cross.hpp"
50
#include "ext/SPIRV-Cross/spirv_glsl.hpp"
51
#ifdef _WIN32
52
#include "ext/SPIRV-Cross/spirv_hlsl.hpp"
53
#endif
54
55
static EShLanguage GetShLanguageFromStage(const ShaderStage stage) {
56
switch (stage) {
57
case ShaderStage::Vertex: return EShLangVertex;
58
case ShaderStage::Geometry: return EShLangGeometry;
59
case ShaderStage::Fragment: return EShLangFragment;
60
case ShaderStage::Compute: return EShLangCompute;
61
default: return EShLangVertex;
62
}
63
}
64
65
void ShaderTranslationInit() {
66
glslang::InitializeProcess();
67
}
68
void ShaderTranslationShutdown() {
69
glslang::FinalizeProcess();
70
}
71
72
struct Builtin {
73
const char *needle;
74
const char *replacement;
75
};
76
77
static const char * const cbufferDecl = R"(
78
cbuffer data : register(b0) {
79
float2 u_texelDelta;
80
float2 u_pixelDelta;
81
float4 u_time;
82
float4 u_timeDelta;
83
float4 u_setting;
84
float u_video;
85
float u_vr;
86
};
87
)";
88
89
static const char * const vulkanPrologue =
90
R"(#version 450
91
#extension GL_ARB_separate_shader_objects : enable
92
#extension GL_ARB_shading_language_420pack : enable
93
)";
94
95
static const char * const vulkanUboDecl = R"(
96
layout (std140, set = 0, binding = 0) uniform Data {
97
vec2 u_texelDelta;
98
vec2 u_pixelDelta;
99
vec4 u_time;
100
vec4 u_timeDelta;
101
vec4 u_setting;
102
float u_video;
103
float u_vr;
104
};
105
)";
106
107
108
// SPIRV-Cross' HLSL output has some deficiencies we need to work around.
109
// Also we need to rip out single uniforms and replace them with blocks.
110
// Should probably do it in the source shader instead and then back translate to old style GLSL, but
111
// SPIRV-Cross currently won't compile with the Android NDK so I can't be bothered.
112
std::string Postprocess(std::string code, ShaderLanguage lang, ShaderStage stage) {
113
if (lang != HLSL_D3D11)
114
return code;
115
116
std::stringstream out;
117
118
// Output the uniform buffer.
119
if (lang == HLSL_D3D11)
120
out << cbufferDecl;
121
122
// Alright, now let's go through it line by line and zap the single uniforms.
123
std::string line;
124
std::stringstream instream(code);
125
while (std::getline(instream, line)) {
126
if (line.find("uniform float") != std::string::npos) {
127
continue;
128
}
129
out << line << "\n";
130
}
131
std::string output = out.str();
132
return output;
133
}
134
135
static_assert(Draw::SEM_TEXCOORD0 == 3, "Semantic shader hardcoded in glsl below.");
136
137
bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetadata, std::string src, ShaderStage stage, std::string *errorMessage) {
138
std::stringstream out;
139
140
static const struct {
141
ShaderStage stage;
142
const char *needle;
143
const char *replacement;
144
} replacements[] = {
145
{ ShaderStage::Vertex, "attribute vec4 a_position;", "layout(location = 0) in vec4 a_position;" },
146
{ ShaderStage::Vertex, "attribute vec2 a_texcoord0;", "layout(location = 3) in vec2 a_texcoord0;"},
147
{ ShaderStage::Vertex, "varying vec2 v_position;", "layout(location = 0) out vec2 v_position;" },
148
{ ShaderStage::Fragment, "varying vec2 v_position;", "layout(location = 0) in vec2 v_position;" },
149
{ ShaderStage::Fragment, "texture2D(", "texture(" },
150
{ ShaderStage::Fragment, "gl_FragColor", "fragColor0" },
151
};
152
153
out << vulkanPrologue;
154
if (stage == ShaderStage::Fragment) {
155
out << "layout (location = 0) out vec4 fragColor0;\n";
156
}
157
// Output the uniform buffer.
158
out << vulkanUboDecl;
159
160
// Alright, now let's go through it line by line and zap the single uniforms
161
// and perform replacements.
162
std::string line;
163
std::stringstream instream(src);
164
while (std::getline(instream, line)) {
165
int vecSize, num;
166
if (line.find("uniform bool") != std::string::npos) {
167
continue;
168
} else if (line.find("uniform sampler2D") == 0) {
169
if (sscanf(line.c_str(), "uniform sampler2D sampler%d", &num) == 1)
170
line = StringFromFormat("layout(set = 0, binding = %d) ", num + 1) + line;
171
else if (line.find("sampler0") != line.npos)
172
line = "layout(set = 0, binding = 1) " + line;
173
else
174
line = "layout(set = 0, binding = 2) " + line;
175
} else if (line.find("uniform ") != std::string::npos) {
176
continue;
177
} else if (2 == sscanf(line.c_str(), "varying vec%d v_texcoord%d;", &vecSize, &num)) {
178
if (stage == ShaderStage::Fragment) {
179
line = StringFromFormat("layout(location = %d) in vec%d v_texcoord%d;", num, vecSize, num);
180
} else {
181
line = StringFromFormat("layout(location = %d) out vec%d v_texcoord%d;", num, vecSize, num);
182
}
183
}
184
for (int i = 0; i < ARRAY_SIZE(replacements); i++) {
185
if (replacements[i].stage == stage)
186
line = ReplaceAll(line, replacements[i].needle, replacements[i].replacement);
187
}
188
out << line << "\n";
189
}
190
191
// DUMPLOG(src.c_str());
192
// INFO_LOG(Log::System, "---->");
193
// DUMPLOG(LineNumberString(out.str()).c_str());
194
195
*dest = out.str();
196
return true;
197
}
198
199
bool TranslateShader(std::string *dest, ShaderLanguage destLang, const ShaderLanguageDesc &desc, TranslatedShaderMetadata *destMetadata, std::string src, ShaderLanguage srcLang, ShaderStage stage, std::string *errorMessage) {
200
_assert_(errorMessage != nullptr);
201
202
if (srcLang != GLSL_3xx && srcLang != GLSL_1xx) {
203
*errorMessage = StringFromFormat("Bad src shader language: %s", ShaderLanguageAsString(srcLang));
204
return false;
205
}
206
207
if ((srcLang == GLSL_1xx || srcLang == GLSL_3xx) && destLang == GLSL_VULKAN) {
208
// Let's just mess about at the string level, no need to recompile.
209
bool result = ConvertToVulkanGLSL(dest, destMetadata, src, stage, errorMessage);
210
return result;
211
}
212
213
errorMessage->clear();
214
215
glslang::TProgram program;
216
const char *shaderStrings[1]{};
217
218
TBuiltInResource Resources{};
219
InitShaderResources(Resources);
220
221
// Don't enable SPIR-V and Vulkan rules when parsing GLSL. Our postshaders are written in oldschool GLES 2.0.
222
EShMessages messages = EShMessages::EShMsgDefault;
223
224
EShLanguage shaderStage = GetShLanguageFromStage(stage);
225
226
glslang::TShader shader(shaderStage);
227
228
shaderStrings[0] = src.c_str();
229
shader.setStrings(shaderStrings, 1);
230
231
// TODO: Should set settings here based on srcLang.
232
if (!shader.parse(&Resources, 100, EProfile::ECompatibilityProfile, false, false, messages)) {
233
*errorMessage = StringFromFormat("%s parser failure: %s\n%s", ShaderStageAsString(stage), shader.getInfoLog(), shader.getInfoDebugLog());
234
return false; // something didn't work
235
}
236
237
// Note that program does not take ownership of &shader, so this is fine.
238
program.addShader(&shader);
239
240
if (!program.link(messages)) {
241
*errorMessage = StringFromFormat("%s linker failure: %s\n%s", ShaderStageAsString(stage), shader.getInfoLog(), shader.getInfoDebugLog());
242
return false;
243
}
244
245
std::vector<unsigned int> spirv;
246
// Can't fail, parsing worked, "linking" worked.
247
glslang::SpvOptions options;
248
options.disableOptimizer = false;
249
options.optimizeSize = false;
250
options.generateDebugInfo = false;
251
glslang::GlslangToSpv(*program.getIntermediate(shaderStage), spirv, &options);
252
253
// For whatever reason, with our config, the above outputs an invalid SPIR-V version, 0.
254
// Patch it up so spirv-cross accepts it.
255
spirv[1] = glslang::EShTargetSpv_1_0;
256
257
// Alright, step 1 done. Now let's take this SPIR-V shader and output in our desired format.
258
259
switch (destLang) {
260
#ifdef _WIN32
261
case HLSL_D3D11:
262
{
263
spirv_cross::CompilerHLSL hlsl(spirv);
264
spirv_cross::ShaderResources resources = hlsl.get_shader_resources();
265
266
int i = 0;
267
for (auto &resource : resources.sampled_images) {
268
const std::string &name = hlsl.get_name(resource.id);
269
int num;
270
if (sscanf(name.c_str(), "sampler%d", &num) != 1)
271
num = i;
272
hlsl.set_decoration(resource.id, spv::DecorationBinding, num);
273
i++;
274
}
275
spirv_cross::CompilerHLSL::Options options{};
276
options.shader_model = 50;
277
spirv_cross::CompilerGLSL::Options options_common{};
278
options_common.vertex.fixup_clipspace = true;
279
hlsl.set_hlsl_options(options);
280
hlsl.set_common_options(options_common);
281
std::string raw = hlsl.compile();
282
*dest = Postprocess(raw, destLang, stage);
283
return true;
284
}
285
#endif
286
case GLSL_1xx:
287
{
288
spirv_cross::CompilerGLSL glsl(std::move(spirv));
289
// The SPIR-V is now parsed, and we can perform reflection on it.
290
spirv_cross::ShaderResources resources = glsl.get_shader_resources();
291
// Get all sampled images in the shader.
292
for (auto &resource : resources.sampled_images) {
293
unsigned set = glsl.get_decoration(resource.id, spv::DecorationDescriptorSet);
294
unsigned binding = glsl.get_decoration(resource.id, spv::DecorationBinding);
295
printf("Image %s at set = %u, binding = %u\n", resource.name.c_str(), set, binding);
296
// Modify the decoration to prepare it for GLSL.
297
glsl.unset_decoration(resource.id, spv::DecorationDescriptorSet);
298
// Some arbitrary remapping if we want.
299
glsl.set_decoration(resource.id, spv::DecorationBinding, set * 16 + binding);
300
}
301
// Set some options.
302
spirv_cross::CompilerGLSL::Options options;
303
options.version = 140;
304
options.es = true;
305
glsl.set_common_options(options);
306
307
// Compile to GLSL, ready to give to GL driver.
308
*dest = glsl.compile();
309
return true;
310
}
311
case GLSL_3xx:
312
{
313
spirv_cross::CompilerGLSL glsl(std::move(spirv));
314
// The SPIR-V is now parsed, and we can perform reflection on it.
315
spirv_cross::ShaderResources resources = glsl.get_shader_resources();
316
// Set some options.
317
spirv_cross::CompilerGLSL::Options options;
318
options.es = desc.gles;
319
options.version = gl_extensions.GLSLVersion();
320
// macOS OpenGL 4.1 implementation does not support GL_ARB_shading_language_420pack.
321
// Prevent explicit binding location emission enabled in SPIRV-Cross by default.
322
options.enable_420pack_extension = gl_extensions.ARB_shading_language_420pack;
323
glsl.set_common_options(options);
324
// Compile to GLSL, ready to give to GL driver.
325
*dest = glsl.compile();
326
return true;
327
}
328
default:
329
*errorMessage = StringFromFormat("Unsupported destination language: %s", ShaderLanguageAsString(destLang));
330
return false;
331
}
332
}
333
334