CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/GPU/ShaderTranslation.cpp
Views: 1401
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
};
86
)";
87
88
static const char * const vulkanPrologue =
89
R"(#version 450
90
#extension GL_ARB_separate_shader_objects : enable
91
#extension GL_ARB_shading_language_420pack : enable
92
)";
93
94
static const char * const vulkanUboDecl = R"(
95
layout (std140, set = 0, binding = 0) uniform Data {
96
vec2 u_texelDelta;
97
vec2 u_pixelDelta;
98
vec4 u_time;
99
vec4 u_timeDelta;
100
vec4 u_setting;
101
float u_video;
102
};
103
)";
104
105
static const char * const d3d9RegisterDecl = R"(
106
float4 gl_HalfPixel : register(c0);
107
float2 u_texelDelta : register(c1);
108
float2 u_pixelDelta : register(c2);
109
float4 u_time : register(c3);
110
float4 u_timeDelta : register(c4);
111
float4 u_setting : register(c5);
112
float u_video : register(c6);
113
)";
114
115
// SPIRV-Cross' HLSL output has some deficiencies we need to work around.
116
// Also we need to rip out single uniforms and replace them with blocks.
117
// Should probably do it in the source shader instead and then back translate to old style GLSL, but
118
// SPIRV-Cross currently won't compile with the Android NDK so I can't be bothered.
119
std::string Postprocess(std::string code, ShaderLanguage lang, ShaderStage stage) {
120
if (lang != HLSL_D3D11 && lang != HLSL_D3D9)
121
return code;
122
123
std::stringstream out;
124
125
// Output the uniform buffer.
126
if (lang == HLSL_D3D11)
127
out << cbufferDecl;
128
else if (lang == HLSL_D3D9)
129
out << d3d9RegisterDecl;
130
131
// Alright, now let's go through it line by line and zap the single uniforms.
132
std::string line;
133
std::stringstream instream(code);
134
while (std::getline(instream, line)) {
135
int num;
136
if (lang == HLSL_D3D9 && sscanf(line.c_str(), "uniform sampler2D sampler%d;", &num) == 1) {
137
out << "sampler2D sampler" << num << " : register(s" << num << ");\n";
138
continue;
139
}
140
if (line.find("uniform float") != std::string::npos) {
141
continue;
142
}
143
out << line << "\n";
144
}
145
std::string output = out.str();
146
return output;
147
}
148
149
static_assert(Draw::SEM_TEXCOORD0 == 3, "Semantic shader hardcoded in glsl below.");
150
151
bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetadata, std::string src, ShaderStage stage, std::string *errorMessage) {
152
std::stringstream out;
153
154
static const struct {
155
ShaderStage stage;
156
const char *needle;
157
const char *replacement;
158
} replacements[] = {
159
{ ShaderStage::Vertex, "attribute vec4 a_position;", "layout(location = 0) in vec4 a_position;" },
160
{ ShaderStage::Vertex, "attribute vec2 a_texcoord0;", "layout(location = 3) in vec2 a_texcoord0;"},
161
{ ShaderStage::Vertex, "varying vec2 v_position;", "layout(location = 0) out vec2 v_position;" },
162
{ ShaderStage::Fragment, "varying vec2 v_position;", "layout(location = 0) in vec2 v_position;" },
163
{ ShaderStage::Fragment, "texture2D(", "texture(" },
164
{ ShaderStage::Fragment, "gl_FragColor", "fragColor0" },
165
};
166
167
out << vulkanPrologue;
168
if (stage == ShaderStage::Fragment) {
169
out << "layout (location = 0) out vec4 fragColor0;\n";
170
}
171
// Output the uniform buffer.
172
out << vulkanUboDecl;
173
174
// Alright, now let's go through it line by line and zap the single uniforms
175
// and perform replacements.
176
std::string line;
177
std::stringstream instream(src);
178
while (std::getline(instream, line)) {
179
int vecSize, num;
180
if (line.find("uniform bool") != std::string::npos) {
181
continue;
182
} else if (line.find("uniform sampler2D") == 0) {
183
if (sscanf(line.c_str(), "uniform sampler2D sampler%d", &num) == 1)
184
line = StringFromFormat("layout(set = 0, binding = %d) ", num + 1) + line;
185
else if (line.find("sampler0") != line.npos)
186
line = "layout(set = 0, binding = 1) " + line;
187
else
188
line = "layout(set = 0, binding = 2) " + line;
189
} else if (line.find("uniform ") != std::string::npos) {
190
continue;
191
} else if (2 == sscanf(line.c_str(), "varying vec%d v_texcoord%d;", &vecSize, &num)) {
192
if (stage == ShaderStage::Fragment) {
193
line = StringFromFormat("layout(location = %d) in vec%d v_texcoord%d;", num, vecSize, num);
194
} else {
195
line = StringFromFormat("layout(location = %d) out vec%d v_texcoord%d;", num, vecSize, num);
196
}
197
}
198
for (int i = 0; i < ARRAY_SIZE(replacements); i++) {
199
if (replacements[i].stage == stage)
200
line = ReplaceAll(line, replacements[i].needle, replacements[i].replacement);
201
}
202
out << line << "\n";
203
}
204
205
// DUMPLOG(src.c_str());
206
// INFO_LOG(Log::System, "---->");
207
// DUMPLOG(LineNumberString(out.str()).c_str());
208
209
*dest = out.str();
210
return true;
211
}
212
213
bool TranslateShader(std::string *dest, ShaderLanguage destLang, const ShaderLanguageDesc &desc, TranslatedShaderMetadata *destMetadata, std::string src, ShaderLanguage srcLang, ShaderStage stage, std::string *errorMessage) {
214
_assert_(errorMessage != nullptr);
215
216
if (srcLang != GLSL_3xx && srcLang != GLSL_1xx) {
217
*errorMessage = StringFromFormat("Bad src shader language: %s", ShaderLanguageAsString(srcLang));
218
return false;
219
}
220
221
if ((srcLang == GLSL_1xx || srcLang == GLSL_3xx) && destLang == GLSL_VULKAN) {
222
// Let's just mess about at the string level, no need to recompile.
223
bool result = ConvertToVulkanGLSL(dest, destMetadata, src, stage, errorMessage);
224
return result;
225
}
226
227
errorMessage->clear();
228
229
glslang::TProgram program;
230
const char *shaderStrings[1]{};
231
232
TBuiltInResource Resources{};
233
InitShaderResources(Resources);
234
235
// Don't enable SPIR-V and Vulkan rules when parsing GLSL. Our postshaders are written in oldschool GLES 2.0.
236
EShMessages messages = EShMessages::EShMsgDefault;
237
238
EShLanguage shaderStage = GetShLanguageFromStage(stage);
239
240
glslang::TShader shader(shaderStage);
241
242
shaderStrings[0] = src.c_str();
243
shader.setStrings(shaderStrings, 1);
244
245
// TODO: Should set settings here based on srcLang.
246
if (!shader.parse(&Resources, 100, EProfile::ECompatibilityProfile, false, false, messages)) {
247
*errorMessage = StringFromFormat("%s parser failure: %s\n%s", ShaderStageAsString(stage), shader.getInfoLog(), shader.getInfoDebugLog());
248
return false; // something didn't work
249
}
250
251
// Note that program does not take ownership of &shader, so this is fine.
252
program.addShader(&shader);
253
254
if (!program.link(messages)) {
255
*errorMessage = StringFromFormat("%s linker failure: %s\n%s", ShaderStageAsString(stage), shader.getInfoLog(), shader.getInfoDebugLog());
256
return false;
257
}
258
259
std::vector<unsigned int> spirv;
260
// Can't fail, parsing worked, "linking" worked.
261
glslang::SpvOptions options;
262
options.disableOptimizer = false;
263
options.optimizeSize = false;
264
options.generateDebugInfo = false;
265
glslang::GlslangToSpv(*program.getIntermediate(shaderStage), spirv, &options);
266
267
// For whatever reason, with our config, the above outputs an invalid SPIR-V version, 0.
268
// Patch it up so spirv-cross accepts it.
269
spirv[1] = glslang::EShTargetSpv_1_0;
270
271
// Alright, step 1 done. Now let's take this SPIR-V shader and output in our desired format.
272
273
switch (destLang) {
274
#ifdef _WIN32
275
case HLSL_D3D9:
276
{
277
spirv_cross::CompilerHLSL hlsl(spirv);
278
spirv_cross::CompilerHLSL::Options options{};
279
options.shader_model = 30;
280
spirv_cross::CompilerGLSL::Options options_common{};
281
options_common.vertex.fixup_clipspace = true;
282
hlsl.set_hlsl_options(options);
283
hlsl.set_common_options(options_common);
284
std::string raw = hlsl.compile();
285
*dest = Postprocess(raw, destLang, stage);
286
return true;
287
}
288
case HLSL_D3D11:
289
{
290
spirv_cross::CompilerHLSL hlsl(spirv);
291
spirv_cross::ShaderResources resources = hlsl.get_shader_resources();
292
293
int i = 0;
294
for (auto &resource : resources.sampled_images) {
295
const std::string &name = hlsl.get_name(resource.id);
296
int num;
297
if (sscanf(name.c_str(), "sampler%d", &num) != 1)
298
num = i;
299
hlsl.set_decoration(resource.id, spv::DecorationBinding, num);
300
i++;
301
}
302
spirv_cross::CompilerHLSL::Options options{};
303
options.shader_model = 50;
304
spirv_cross::CompilerGLSL::Options options_common{};
305
options_common.vertex.fixup_clipspace = true;
306
hlsl.set_hlsl_options(options);
307
hlsl.set_common_options(options_common);
308
std::string raw = hlsl.compile();
309
*dest = Postprocess(raw, destLang, stage);
310
return true;
311
}
312
#endif
313
case GLSL_1xx:
314
{
315
spirv_cross::CompilerGLSL glsl(std::move(spirv));
316
// The SPIR-V is now parsed, and we can perform reflection on it.
317
spirv_cross::ShaderResources resources = glsl.get_shader_resources();
318
// Get all sampled images in the shader.
319
for (auto &resource : resources.sampled_images) {
320
unsigned set = glsl.get_decoration(resource.id, spv::DecorationDescriptorSet);
321
unsigned binding = glsl.get_decoration(resource.id, spv::DecorationBinding);
322
printf("Image %s at set = %u, binding = %u\n", resource.name.c_str(), set, binding);
323
// Modify the decoration to prepare it for GLSL.
324
glsl.unset_decoration(resource.id, spv::DecorationDescriptorSet);
325
// Some arbitrary remapping if we want.
326
glsl.set_decoration(resource.id, spv::DecorationBinding, set * 16 + binding);
327
}
328
// Set some options.
329
spirv_cross::CompilerGLSL::Options options;
330
options.version = 140;
331
options.es = true;
332
glsl.set_common_options(options);
333
334
// Compile to GLSL, ready to give to GL driver.
335
*dest = glsl.compile();
336
return true;
337
}
338
case GLSL_3xx:
339
{
340
spirv_cross::CompilerGLSL glsl(std::move(spirv));
341
// The SPIR-V is now parsed, and we can perform reflection on it.
342
spirv_cross::ShaderResources resources = glsl.get_shader_resources();
343
// Set some options.
344
spirv_cross::CompilerGLSL::Options options;
345
options.es = desc.gles;
346
options.version = gl_extensions.GLSLVersion();
347
// macOS OpenGL 4.1 implementation does not support GL_ARB_shading_language_420pack.
348
// Prevent explicit binding location emission enabled in SPIRV-Cross by default.
349
options.enable_420pack_extension = gl_extensions.ARB_shading_language_420pack;
350
glsl.set_common_options(options);
351
// Compile to GLSL, ready to give to GL driver.
352
*dest = glsl.compile();
353
return true;
354
}
355
default:
356
*errorMessage = StringFromFormat("Unsupported destination language: %s", ShaderLanguageAsString(destLang));
357
return false;
358
}
359
}
360
361