mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Fix] (MG_Util/ShaderTranspiler): normalize legacy desktop shaders to GLSL 330
This commit is contained in:
@@ -107,7 +107,7 @@ void main() {
|
|||||||
|
|
||||||
PreprocessShaderSource(ShaderStage::Vertex, source);
|
PreprocessShaderSource(ShaderStage::Vertex, source);
|
||||||
|
|
||||||
EXPECT_EQ(source.find("#version 460 core\n"), 0);
|
EXPECT_EQ(source.find("#version 330 core\n"), 0);
|
||||||
EXPECT_NE(source.find("in vec3 position;"), String::npos);
|
EXPECT_NE(source.find("in vec3 position;"), String::npos);
|
||||||
EXPECT_NE(source.find("out vec2 uv;"), String::npos);
|
EXPECT_NE(source.find("out vec2 uv;"), String::npos);
|
||||||
EXPECT_EQ(source.find("attribute"), String::npos);
|
EXPECT_EQ(source.find("attribute"), String::npos);
|
||||||
@@ -136,7 +136,7 @@ void main() {
|
|||||||
|
|
||||||
PreprocessShaderSource(ShaderStage::Fragment, source);
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
EXPECT_EQ(source.find("#version 460 core\n"), 0);
|
EXPECT_EQ(source.find("#version 330 core\n"), 0);
|
||||||
EXPECT_NE(source.find("out vec4 mg_FragColor;\n"), String::npos);
|
EXPECT_NE(source.find("out vec4 mg_FragColor;\n"), String::npos);
|
||||||
EXPECT_NE(source.find("in vec2 uv;"), String::npos);
|
EXPECT_NE(source.find("in vec2 uv;"), String::npos);
|
||||||
EXPECT_NE(source.find("texture(texture0, uv)"), String::npos);
|
EXPECT_NE(source.find("texture(texture0, uv)"), String::npos);
|
||||||
@@ -153,6 +153,243 @@ void main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessMinecraft112BlurShaderKeepsLegacySampleIdentifier) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
// assets/minecraft/shaders/program/blur.fsh from the unmodified Minecraft 1.12 client jar.
|
||||||
|
String source = R"(#version 120
|
||||||
|
|
||||||
|
uniform sampler2D DiffuseSampler;
|
||||||
|
|
||||||
|
varying vec2 texCoord;
|
||||||
|
varying vec2 oneTexel;
|
||||||
|
|
||||||
|
uniform vec2 InSize;
|
||||||
|
|
||||||
|
uniform vec2 BlurDir;
|
||||||
|
uniform float Radius;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 blurred = vec4(0.0);
|
||||||
|
float totalStrength = 0.0;
|
||||||
|
float totalAlpha = 0.0;
|
||||||
|
float totalSamples = 0.0;
|
||||||
|
for(float r = -Radius; r <= Radius; r += 1.0) {
|
||||||
|
vec4 sample = texture2D(DiffuseSampler, texCoord + oneTexel * r * BlurDir);
|
||||||
|
|
||||||
|
// Accumulate average alpha
|
||||||
|
totalAlpha = totalAlpha + sample.a;
|
||||||
|
totalSamples = totalSamples + 1.0;
|
||||||
|
|
||||||
|
// Accumulate smoothed blur
|
||||||
|
float strength = 1.0 - abs(r / Radius);
|
||||||
|
totalStrength = totalStrength + strength;
|
||||||
|
blurred = blurred + sample;
|
||||||
|
}
|
||||||
|
gl_FragColor = vec4(blurred.rgb / (Radius * 2.0 + 1.0), totalAlpha);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
EXPECT_EQ(source.find("#version 330 core\n"), 0);
|
||||||
|
EXPECT_NE(source.find("vec4 sample = texture(DiffuseSampler"), String::npos);
|
||||||
|
EXPECT_NE(source.find("totalAlpha = totalAlpha + sample.a;"), String::npos);
|
||||||
|
EXPECT_NE(source.find("float totalSamples = 0.0;"), String::npos);
|
||||||
|
EXPECT_NE(source.find("totalSamples = totalSamples + 1.0;"), String::npos);
|
||||||
|
EXPECT_NE(source.find("blurred = blurred + sample;"), String::npos);
|
||||||
|
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
if (!res) {
|
||||||
|
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessLegacySampleInterfaceIdentifiersKeepNames) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String vertexSource = R"(#version 150
|
||||||
|
attribute vec3 sample;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(sample, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Vertex, vertexSource);
|
||||||
|
|
||||||
|
EXPECT_EQ(vertexSource.find("#version 330 core\n"), 0);
|
||||||
|
EXPECT_NE(vertexSource.find("in vec3 sample;"), String::npos);
|
||||||
|
|
||||||
|
ShaderAttrib vertexAttrib{.shaderType = GL_VERTEX_SHADER, .sourceStr = vertexSource};
|
||||||
|
auto vertexResult = ShaderCompiler::CompileShader(vertexAttrib);
|
||||||
|
if (!vertexResult) {
|
||||||
|
FAIL() << "errc: " << vertexResult.error().errc << "\nlog: " << vertexResult.error().log
|
||||||
|
<< "\nsource:\n" << vertexSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fragmentSource = R"(#version 150
|
||||||
|
uniform sampler2D sample;
|
||||||
|
varying vec2 texCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = texture2D(sample, texCoord);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, fragmentSource);
|
||||||
|
|
||||||
|
EXPECT_EQ(fragmentSource.find("#version 330 core\n"), 0);
|
||||||
|
EXPECT_NE(fragmentSource.find("uniform sampler2D sample;"), String::npos);
|
||||||
|
EXPECT_NE(fragmentSource.find("texture(sample, texCoord)"), String::npos);
|
||||||
|
|
||||||
|
ShaderAttrib fragmentAttrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = fragmentSource};
|
||||||
|
auto fragmentResult = ShaderCompiler::CompileShader(fragmentAttrib);
|
||||||
|
if (!fragmentResult) {
|
||||||
|
FAIL() << "errc: " << fragmentResult.error().errc << "\nlog: " << fragmentResult.error().log
|
||||||
|
<< "\nsource:\n" << fragmentSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessEsslVersionsRemainVulkanCompatible) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
const auto verifyVersion = [](const char* inputVersion, const char* expectedVersion) {
|
||||||
|
SCOPED_TRACE(inputVersion);
|
||||||
|
String source = inputVersion;
|
||||||
|
source += R"(
|
||||||
|
precision mediump float;
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = vec4(1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
EXPECT_EQ(source.find(expectedVersion), 0);
|
||||||
|
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
if (!res) {
|
||||||
|
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Preserve the pre-existing desktop-core route: the current resource table cannot parse ESSL built-ins.
|
||||||
|
verifyVersion("#version 300 es", "#version 460 core\n");
|
||||||
|
verifyVersion("#version 310 es", "#version 460 core\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessModernDesktopVersionsRecognizesUtf8Bom) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
const auto verifyVersion = [](const char* inputVersion) {
|
||||||
|
SCOPED_TRACE(inputVersion);
|
||||||
|
String source = "\xef\xbb\xbf";
|
||||||
|
source += inputVersion;
|
||||||
|
source += R"(
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = vec4(1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
EXPECT_EQ(source.find("#version 460 core\n"), 0);
|
||||||
|
EXPECT_EQ(source.find("\xef\xbb\xbf"), String::npos);
|
||||||
|
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
if (!res) {
|
||||||
|
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
verifyVersion("#version 400 core");
|
||||||
|
verifyVersion("#version 460 core");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessUsesRealSpacedVersionDirectiveForInjectedOutput) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String source = R"(// #version 460 core
|
||||||
|
/* "#version 400 core" */
|
||||||
|
#line 7 "#version 460 core"
|
||||||
|
# version 120
|
||||||
|
varying vec2 uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = vec4(uv, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
const SizeT versionPos = source.find("#version 330 core\n");
|
||||||
|
const SizeT outputPos = source.find("out vec4 mg_FragColor;\n");
|
||||||
|
EXPECT_NE(versionPos, String::npos);
|
||||||
|
EXPECT_EQ(outputPos, versionPos + std::strlen("#version 330 core\n"));
|
||||||
|
EXPECT_NE(source.find("// #version 460 core"), String::npos);
|
||||||
|
EXPECT_EQ(source.find("#line"), String::npos);
|
||||||
|
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
if (!res) {
|
||||||
|
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessModernSampleQualifierStaysAtVersion460) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String source = R"(#version 400 core
|
||||||
|
sample in vec4 interpolatedColor;
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = interpolatedColor;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
EXPECT_EQ(source.find("#version 460 core\n"), 0);
|
||||||
|
EXPECT_NE(source.find("sample in vec4 interpolatedColor;"), String::npos);
|
||||||
|
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
if (!res) {
|
||||||
|
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessGpuShader5SampleQualifierUsesVersion460) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
for (const char* extension : {"GL_ARB_gpu_shader5", "GL_NV_gpu_shader5"}) {
|
||||||
|
SCOPED_TRACE(extension);
|
||||||
|
String source = "#version 150\n#extension ";
|
||||||
|
source += extension;
|
||||||
|
source += R"( : enable
|
||||||
|
sample in vec4 interpolatedColor;
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = interpolatedColor;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
EXPECT_EQ(source.find("#version 460 core\n"), 0);
|
||||||
|
EXPECT_NE(source.find("sample in vec4 interpolatedColor;"), String::npos);
|
||||||
|
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
if (!res) {
|
||||||
|
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(ProgramUtilTest, PreprocessLegacyFragmentShaderModernizesFragData) {
|
TEST_F(ProgramUtilTest, PreprocessLegacyFragmentShaderModernizesFragData) {
|
||||||
using namespace MG_Util::ShaderTranspiler;
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
@@ -164,7 +401,7 @@ void main() {
|
|||||||
|
|
||||||
PreprocessShaderSource(ShaderStage::Fragment, source);
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
EXPECT_EQ(source.find("#version 460 core\n"), 0);
|
EXPECT_EQ(source.find("#version 330 core\n"), 0);
|
||||||
EXPECT_NE(source.find("layout(location = 0) out vec4 mg_FragData[8];\n"), String::npos);
|
EXPECT_NE(source.find("layout(location = 0) out vec4 mg_FragData[8];\n"), String::npos);
|
||||||
EXPECT_NE(source.find("mg_FragData[0] = vec4(1.0);"), String::npos);
|
EXPECT_NE(source.find("mg_FragData[0] = vec4(1.0);"), String::npos);
|
||||||
EXPECT_NE(source.find("mg_FragData[1].a = 0.5;"), String::npos);
|
EXPECT_NE(source.find("mg_FragData[1].a = 0.5;"), String::npos);
|
||||||
@@ -182,7 +419,7 @@ TEST_F(ProgramUtilTest, PreprocessKeepsDefaultPrecisionStatements) {
|
|||||||
|
|
||||||
// Mirrors the GL CTS helper shaders (e.g. glcPixelStorageModesTests): the old qualifier strip
|
// Mirrors the GL CTS helper shaders (e.g. glcPixelStorageModesTests): the old qualifier strip
|
||||||
// turned "precision highp float;" into invalid "precision float;". Precision qualifiers are
|
// turned "precision highp float;" into invalid "precision float;". Precision qualifiers are
|
||||||
// legal (and ignored) in the forced 460 core profile, so they now pass through untouched.
|
// legal (and ignored) in the normalized desktop core profile, so they now pass through untouched.
|
||||||
String source = R"(#version 330
|
String source = R"(#version 330
|
||||||
precision highp float;
|
precision highp float;
|
||||||
precision mediump int;
|
precision mediump int;
|
||||||
@@ -212,7 +449,7 @@ TEST_F(ProgramUtilTest, PreprocessKeepsPrecisionInLegacyShaderForGlslang) {
|
|||||||
using namespace MG_Util::ShaderTranspiler;
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
// Legacy ES-style shader: precision statements and qualifier macros are left for glslang
|
// Legacy ES-style shader: precision statements and qualifier macros are left for glslang
|
||||||
// (its preprocessor expands the #define; the 460 core parse ignores the qualifiers).
|
// (its preprocessor expands the #define; the normalized 330 core parse ignores the qualifiers).
|
||||||
String source = R"(#define HIGHP_OR_DEFAULT highp
|
String source = R"(#define HIGHP_OR_DEFAULT highp
|
||||||
precision HIGHP_OR_DEFAULT float;
|
precision HIGHP_OR_DEFAULT float;
|
||||||
precision mediump int;
|
precision mediump int;
|
||||||
|
|||||||
@@ -19,6 +19,220 @@ namespace {
|
|||||||
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_';
|
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsIdentifierStart(char ch) {
|
||||||
|
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileGL::String MaskCommentsAndQuotedText(const MobileGL::String& source) {
|
||||||
|
enum class Region { Code, SingleLineComment, MultiLineComment, QuotedText };
|
||||||
|
|
||||||
|
MobileGL::String masked = source;
|
||||||
|
Region region = Region::Code;
|
||||||
|
char quote = '\0';
|
||||||
|
bool escaped = false;
|
||||||
|
|
||||||
|
for (SizeT pos = 0; pos < source.size(); pos++) {
|
||||||
|
const char ch = source[pos];
|
||||||
|
const char next = pos + 1 < source.size() ? source[pos + 1] : '\0';
|
||||||
|
|
||||||
|
if (region == Region::Code) {
|
||||||
|
if (ch == '/' && next == '/') {
|
||||||
|
masked[pos] = ' ';
|
||||||
|
masked[pos + 1] = ' ';
|
||||||
|
pos++;
|
||||||
|
region = Region::SingleLineComment;
|
||||||
|
} else if (ch == '/' && next == '*') {
|
||||||
|
masked[pos] = ' ';
|
||||||
|
masked[pos + 1] = ' ';
|
||||||
|
pos++;
|
||||||
|
region = Region::MultiLineComment;
|
||||||
|
} else if (ch == '"' || ch == '\'') {
|
||||||
|
masked[pos] = ' ';
|
||||||
|
quote = ch;
|
||||||
|
escaped = false;
|
||||||
|
region = Region::QuotedText;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (region == Region::SingleLineComment) {
|
||||||
|
if (ch == '\n' || ch == '\r') {
|
||||||
|
region = Region::Code;
|
||||||
|
} else {
|
||||||
|
masked[pos] = ' ';
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (region == Region::MultiLineComment) {
|
||||||
|
if (ch == '*' && next == '/') {
|
||||||
|
masked[pos] = ' ';
|
||||||
|
masked[pos + 1] = ' ';
|
||||||
|
pos++;
|
||||||
|
region = Region::Code;
|
||||||
|
} else if (ch != '\n' && ch != '\r') {
|
||||||
|
masked[pos] = ' ';
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch != '\n' && ch != '\r') {
|
||||||
|
masked[pos] = ' ';
|
||||||
|
}
|
||||||
|
if (escaped) {
|
||||||
|
escaped = false;
|
||||||
|
} else if (ch == '\\') {
|
||||||
|
escaped = true;
|
||||||
|
} else if (ch == quote) {
|
||||||
|
region = Region::Code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return masked;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkipDirectiveWhitespace(const MobileGL::String& source, SizeT& pos, SizeT lineEnd) {
|
||||||
|
while (pos < lineEnd && std::isspace(static_cast<unsigned char>(source[pos]))) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileGL::String ReadDirectiveIdentifier(const MobileGL::String& source, SizeT& pos, SizeT lineEnd) {
|
||||||
|
if (pos >= lineEnd || !IsIdentifierStart(source[pos])) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const SizeT start = pos++;
|
||||||
|
while (pos < lineEnd && IsIdentifierChar(source[pos])) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
return source.substr(start, pos - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasUtf8Bom(const MobileGL::String& source) {
|
||||||
|
return source.size() >= 3 && static_cast<unsigned char>(source[0]) == 0xef &&
|
||||||
|
static_cast<unsigned char>(source[1]) == 0xbb && static_cast<unsigned char>(source[2]) == 0xbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShaderLanguageInfo {
|
||||||
|
unsigned version = 110;
|
||||||
|
MobileGL::ShaderProfile profile = MobileGL::ShaderProfile::Core;
|
||||||
|
SizeT versionDirectiveStart = MobileGL::String::npos;
|
||||||
|
SizeT versionDirectiveEnd = MobileGL::String::npos;
|
||||||
|
bool hasUtf8Bom = false;
|
||||||
|
bool enablesGpuShader5 = false;
|
||||||
|
|
||||||
|
bool HasVersionDirective() const { return versionDirectiveStart != MobileGL::String::npos; }
|
||||||
|
};
|
||||||
|
|
||||||
|
ShaderLanguageInfo InspectShaderLanguage(const MobileGL::String& source) {
|
||||||
|
const MobileGL::String code = MaskCommentsAndQuotedText(source);
|
||||||
|
ShaderLanguageInfo info;
|
||||||
|
info.hasUtf8Bom = HasUtf8Bom(source);
|
||||||
|
|
||||||
|
SizeT lineStart = 0;
|
||||||
|
while (lineStart < code.size()) {
|
||||||
|
SizeT lineEnd = code.find('\n', lineStart);
|
||||||
|
const bool hasLineBreak = lineEnd != MobileGL::String::npos;
|
||||||
|
if (!hasLineBreak) {
|
||||||
|
lineEnd = code.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeT probe = lineStart;
|
||||||
|
if (lineStart == 0 && info.hasUtf8Bom) {
|
||||||
|
probe = 3;
|
||||||
|
}
|
||||||
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
|
if (probe < lineEnd && code[probe] == '#') {
|
||||||
|
const SizeT directiveStart = probe;
|
||||||
|
probe++;
|
||||||
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
|
const MobileGL::String directive = ReadDirectiveIdentifier(code, probe, lineEnd);
|
||||||
|
|
||||||
|
if (directive == "version" && !info.HasVersionDirective()) {
|
||||||
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
|
unsigned version = 0;
|
||||||
|
bool hasVersionDigits = false;
|
||||||
|
while (probe < lineEnd && code[probe] >= '0' && code[probe] <= '9') {
|
||||||
|
hasVersionDigits = true;
|
||||||
|
version = version * 10 + static_cast<unsigned>(code[probe] - '0');
|
||||||
|
probe++;
|
||||||
|
}
|
||||||
|
if (hasVersionDigits) {
|
||||||
|
info.version = version;
|
||||||
|
info.versionDirectiveStart = directiveStart;
|
||||||
|
info.versionDirectiveEnd = lineEnd + (hasLineBreak ? 1 : 0);
|
||||||
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
|
const MobileGL::String profile = ReadDirectiveIdentifier(code, probe, lineEnd);
|
||||||
|
if (profile == "es" || profile == "ES") {
|
||||||
|
info.profile = MobileGL::ShaderProfile::ES;
|
||||||
|
} else if (profile == "compatibility") {
|
||||||
|
info.profile = MobileGL::ShaderProfile::Compatibility;
|
||||||
|
} else {
|
||||||
|
info.profile = MobileGL::ShaderProfile::Core;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (directive == "extension") {
|
||||||
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
|
const MobileGL::String extension = ReadDirectiveIdentifier(code, probe, lineEnd);
|
||||||
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
|
if (probe < lineEnd && code[probe] == ':') {
|
||||||
|
probe++;
|
||||||
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
|
const MobileGL::String behavior = ReadDirectiveIdentifier(code, probe, lineEnd);
|
||||||
|
const bool isGpuShader5 = extension == "GL_ARB_gpu_shader5" ||
|
||||||
|
extension == "GL_NV_gpu_shader5";
|
||||||
|
const bool enablesExtension = behavior == "enable" || behavior == "require" ||
|
||||||
|
behavior == "warn";
|
||||||
|
// Gate the whole source if it ever opts into either extension. This is deliberately
|
||||||
|
// conservative around conditional directives and keeps legal sample qualifiers intact.
|
||||||
|
info.enablesGpuShader5 = info.enablesGpuShader5 || (isGpuShader5 && enablesExtension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lineStart = lineEnd + (hasLineBreak ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileGL::String GetNormalizedVersionDirective(const ShaderLanguageInfo& info) {
|
||||||
|
if (info.profile == MobileGL::ShaderProfile::ES) {
|
||||||
|
// Preserve the pre-existing behavior for standard lowercase "es" directives. MobileGL's Vulkan
|
||||||
|
// glslang resource table cannot parse its ESSL built-ins today, even at ESSL 310, whereas the same
|
||||||
|
// source is accepted through the normalized desktop core path.
|
||||||
|
return "#version 460 core\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep compatibility-profile handling on its pre-existing 460 path. Vulkan glslang does not accept that
|
||||||
|
// profile today, and this legacy-sample fix must not broaden or otherwise alter that separate limitation.
|
||||||
|
if (info.profile == MobileGL::ShaderProfile::Compatibility) {
|
||||||
|
return "#version 460 compatibility\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool useLegacyDesktopVersion =
|
||||||
|
info.version < 400 && !info.enablesGpuShader5;
|
||||||
|
return useLegacyDesktopVersion ? "#version 330 core\n" : "#version 460 core\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void NormalizeVersionDirective(MobileGL::String& source, const ShaderLanguageInfo& info) {
|
||||||
|
const MobileGL::String replacement = GetNormalizedVersionDirective(info);
|
||||||
|
if (info.HasVersionDirective()) {
|
||||||
|
source.replace(info.versionDirectiveStart, info.versionDirectiveEnd - info.versionDirectiveStart,
|
||||||
|
replacement);
|
||||||
|
if (info.hasUtf8Bom) {
|
||||||
|
source.erase(0, 3);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.hasUtf8Bom) {
|
||||||
|
source.erase(0, 3);
|
||||||
|
}
|
||||||
|
source.insert(0, replacement);
|
||||||
|
}
|
||||||
|
|
||||||
bool HasSingleLineFunctionDefinition(const MobileGL::String& source, const MobileGL::String& functionName) {
|
bool HasSingleLineFunctionDefinition(const MobileGL::String& source, const MobileGL::String& functionName) {
|
||||||
SizeT lineStart = 0;
|
SizeT lineStart = 0;
|
||||||
while (lineStart < source.size()) {
|
while (lineStart < source.size()) {
|
||||||
@@ -153,12 +367,8 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SizeT FindAfterVersionDirective(const MobileGL::String& source) {
|
SizeT FindAfterVersionDirective(const MobileGL::String& source) {
|
||||||
const SizeT versionPos = source.find("#version");
|
const ShaderLanguageInfo info = InspectShaderLanguage(source);
|
||||||
if (versionPos == MobileGL::String::npos) {
|
return info.HasVersionDirective() ? info.versionDirectiveEnd : 0;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const SizeT lineEnd = source.find('\n', versionPos);
|
|
||||||
return lineEnd == MobileGL::String::npos ? source.size() : lineEnd + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsExtensionAdvertised(MobileGL::GLExtension extension) {
|
bool IsExtensionAdvertised(MobileGL::GLExtension extension) {
|
||||||
@@ -263,7 +473,7 @@ namespace {
|
|||||||
|
|
||||||
void ModernizeLegacyGLSL(MobileGL::ShaderStage stage, MobileGL::String& source) {
|
void ModernizeLegacyGLSL(MobileGL::ShaderStage stage, MobileGL::String& source) {
|
||||||
// Precision qualifiers (highp/mediump/lowp and default-precision statements) are legal and
|
// Precision qualifiers (highp/mediump/lowp and default-precision statements) are legal and
|
||||||
// ignored in the forced "#version 460 core" profile, so glslang handles them natively.
|
// ignored in the normalized desktop core profiles, so glslang handles them natively.
|
||||||
|
|
||||||
ReplaceIdentifier(source, "texture2D", "texture");
|
ReplaceIdentifier(source, "texture2D", "texture");
|
||||||
ReplaceIdentifier(source, "texture2DProj", "textureProj");
|
ReplaceIdentifier(source, "texture2DProj", "textureProj");
|
||||||
@@ -308,6 +518,11 @@ namespace MobileGL {
|
|||||||
namespace MG_Util {
|
namespace MG_Util {
|
||||||
namespace ShaderTranspiler {
|
namespace ShaderTranspiler {
|
||||||
void PreprocessShaderSource(ShaderStage stage, String& source) {
|
void PreprocessShaderSource(ShaderStage stage, String& source) {
|
||||||
|
// Normalize while the inspector's source span still refers to the untouched input. Later passes
|
||||||
|
// remove comments and directives, so any subsequent insertion re-inspects the current source.
|
||||||
|
const ShaderLanguageInfo originalLanguage = InspectShaderLanguage(source);
|
||||||
|
NormalizeVersionDirective(source, originalLanguage);
|
||||||
|
|
||||||
// remove multi-line comment
|
// remove multi-line comment
|
||||||
size_t commentStartPos = source.find("/*");
|
size_t commentStartPos = source.find("/*");
|
||||||
while (commentStartPos != String::npos) {
|
while (commentStartPos != String::npos) {
|
||||||
@@ -345,43 +560,6 @@ namespace MobileGL {
|
|||||||
noperspectivePos = source.find(str_np);
|
noperspectivePos = source.find(str_np);
|
||||||
}
|
}
|
||||||
|
|
||||||
// force #version
|
|
||||||
ShaderProfile profile = ShaderProfile::Core;
|
|
||||||
SizeT versionPos = source.find("#version");
|
|
||||||
SizeT lineEnd = source.find('\n', versionPos);
|
|
||||||
|
|
||||||
if (versionPos != String::npos) {
|
|
||||||
String versionLine = source.substr(versionPos, lineEnd - versionPos);
|
|
||||||
|
|
||||||
if (versionLine.find("ES") != String::npos)
|
|
||||||
profile = ShaderProfile::ES;
|
|
||||||
else if (versionLine.find("compatibility") != String::npos)
|
|
||||||
profile = ShaderProfile::Compatibility;
|
|
||||||
else
|
|
||||||
profile = ShaderProfile::Core;
|
|
||||||
} else {
|
|
||||||
profile = ShaderProfile::Core;
|
|
||||||
source.insert(0, "#version 460 core\n");
|
|
||||||
versionPos = 0;
|
|
||||||
lineEnd = source.find('\n', versionPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
SizeT firstLineEnd = lineEnd;
|
|
||||||
|
|
||||||
if (profile != ShaderProfile::ES) {
|
|
||||||
constexpr const char* versionDirectiveCore = "#version 460 core\n";
|
|
||||||
constexpr const char* versionDirectiveCompat = "#version 460 compatibility\n";
|
|
||||||
|
|
||||||
const char* replacement =
|
|
||||||
(profile == ShaderProfile::Compatibility) ? versionDirectiveCompat : versionDirectiveCore;
|
|
||||||
|
|
||||||
if (firstLineEnd != String::npos) {
|
|
||||||
source.replace(versionPos, firstLineEnd - versionPos + 1, replacement);
|
|
||||||
} else {
|
|
||||||
source = replacement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterUnsupportedGpuShaderInt64(source);
|
FilterUnsupportedGpuShaderInt64(source);
|
||||||
|
|
||||||
// Some shader packs define helpers with built-in GLSL names such as round(), tanh(), or fma().
|
// Some shader packs define helpers with built-in GLSL names such as round(), tanh(), or fma().
|
||||||
|
|||||||
Reference in New Issue
Block a user