diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 84c5a794..3fd530ed 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -12943,6 +12943,7 @@ namespace MobileGL::MG_Backend::DirectGLES { source = ProcessOutColorLocations(source); source = ForceFlatIntegerVaryings(source, glShaderType); source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount); + source = LowerZeroGradCubeArrayShadowLookups(source, glShaderType); source = EmulateTextureLodBias(source, ShouldAvoidExplicitLodBiasOnAngleLlvmpipe()); if (ClampsUnsignedMultisampleFetchesAtRuntime()) { source = ClampUnsignedMultisampleFetches(source); diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index e73a27c8..5fe82136 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -2057,6 +2057,56 @@ namespace MobileGL::MG_Backend::DirectGLES { return result; } + String LowerZeroGradCubeArrayShadowLookups(const String& glslCode, GLenum shaderType) { +#ifdef TRACY_ENABLE + ZoneScopedC(TRACY_ZONECOLOR_BACKEND); +#endif + if (shaderType == GL_FRAGMENT_SHADER || glslCode.find("samplerCubeArrayShadow") == String::npos || + glslCode.find("textureGrad") == String::npos) { + return glslCode; + } + // Every name declared with the type: uniforms, and the parameters of functions the + // sampler is passed to. + static const std::regex declRegex(R"(samplerCubeArrayShadow\s+([A-Za-z_][A-Za-z0-9_]*))"); + UnorderedMap names; + for (std::sregex_iterator it(glslCode.begin(), glslCode.end(), declRegex), end; it != end; ++it) { + names.emplace((*it)[1].str(), true); + } + if (names.empty()) return glslCode; + + static constexpr const char* kName = "textureGrad"; + static constexpr SizeT kNameLength = 11; + String result = glslCode; + Bool changed = false; + // Right to left, so an earlier call's offsets stay valid. + for (SizeT scan = result.rfind(kName); scan != String::npos; + scan = scan == 0 ? String::npos : result.rfind(kName, scan - 1)) { + if (scan > 0 && IsIdentifierChar(result[scan - 1])) continue; + const SizeT openParen = result.find_first_not_of(" \t", scan + kNameLength); + if (openParen == String::npos || result[openParen] != '(') continue; + const Vector marks = SplitCallArguments(result, openParen); + // sampler, P, compare, dPdx, dPdy + if (marks.size() != 5) continue; + SizeT nameStart = result.find_first_not_of(" \t", openParen + 1); + SizeT nameEnd = nameStart; + while (nameEnd < marks[0] && IsIdentifierChar(result[nameEnd])) ++nameEnd; + if (names.find(result.substr(nameStart, nameEnd - nameStart)) == names.end()) continue; + const auto argument = [&](SizeT index) { + const SizeT start = marks[index - 1] + 1; + String text = result.substr(start, marks[index] - start); + text.erase(std::remove_if(text.begin(), text.end(), + [](char c) { return c == ' ' || c == '\t'; }), + text.end()); + return text; + }; + if (argument(3) != "vec3(0.0)" || argument(4) != "vec3(0.0)") continue; + result.erase(marks[2], marks[4] - marks[2]); + result.replace(scan, kNameLength, "texture"); + changed = true; + } + return changed ? result : glslCode; + } + String ClampUnsignedMultisampleFetches(const String& glslCode) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.h b/MobileGL/MG_Backend/DirectGLES/Utils.h index 6248c4e3..d214d62e 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.h +++ b/MobileGL/MG_Backend/DirectGLES/Utils.h @@ -576,6 +576,15 @@ namespace MobileGL::MG_Backend::DirectGLES { // Off by default and only ever set on ANGLE + llvmpipe, where injecting the uniform // into a constant LOD crashes the driver (MOBILEGL_ESPRYT_AVOID_EXPLICIT_LOD_BIAS). String EmulateTextureLodBias(const String& glslCode, Bool avoidExplicitLodBias = false); + // A depth-compare lookup at an explicit LOD of 0 on a samplerCubeArrayShadow - what + // texture() is outside the fragment stage - has no GLSL ES spelling: there is no + // textureLod or textureGrad overload for that sampler. SPIRV-Cross prints it as + // textureGrad(s, P, compare, vec3(0.0), vec3(0.0)) anyway; Adreno compiles that and + // returns wrong comparisons (KHR-GL4x.texture_cube_map_array.sampling, DEPTH32F, vertex/ + // tessellation control/geometry/compute). Outside the fragment stage texture(s, P, + // compare) samples level 0 exactly (GLSL ES 3.20 8.9), so those calls are rewritten to + // it. The fragment stage is left alone: there texture() would pick its own level. + String LowerZeroGradCubeArrayShadowLookups(const String& glslCode, GLenum shaderType); // Prefix of the per-sampler int uniform that carries the bound multisample texture's // last real sample index into the shader (see ClampUnsignedMultisampleFetches); the // suffix is the sampler's own name. diff --git a/MobileGL/MG_Test/Backend/DirectGLES/EsslShaderPassTest.cpp b/MobileGL/MG_Test/Backend/DirectGLES/EsslShaderPassTest.cpp index 938fce30..f4ef6301 100644 --- a/MobileGL/MG_Test/Backend/DirectGLES/EsslShaderPassTest.cpp +++ b/MobileGL/MG_Test/Backend/DirectGLES/EsslShaderPassTest.cpp @@ -34,6 +34,7 @@ using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_SPLIT_READ_ALIAS_PREFI using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_WRITE_ALIAS_PREFIX; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_WRITEONLY_ALIAS_PREFIX; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::ImageArrayUnitPlan; +using MobileGL::MG_Backend::DirectGLES::PrgramImpl::LowerZeroGradCubeArrayShadowLookups; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RedeclareClipCullDistancesInPerVertexBlock; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RedeclareClipCullDistanceInputsInPerVertexBlock; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RemapImageArrayElementUnits; @@ -1848,3 +1849,57 @@ void main() { } EXPECT_EQ(CountOf(out, "uniform highp int mg_msSampleMax_stencilTex;"), 1u) << out; } + +// End to end: texture() on a samplerCubeArrayShadow outside the fragment stage is an explicit +// LOD 0 lookup in SPIR-V, which SPIRV-Cross prints as a textureGrad overload GLSL ES does not +// have (KHR-GL4x.texture_cube_map_array.sampling). The pass turns it back into texture(), which +// is level 0 in that stage. +TEST(LowerZeroGradCubeArrayShadowLookupsTest, RestoresTextureOutsideTheFragmentStage) { + using namespace MG_Util::ShaderTranspiler; + MobileGL::Initialize(); + const char* vertexSource = R"(#version 450 core +uniform samplerCubeArrayShadow depthTex[2]; +uniform samplerCubeArrayShadow single; +uniform vec4 coord; +out float result; +float lookup(samplerCubeArrayShadow s, vec4 p) { return texture(s, p, 0.5); } +void main() { + result = texture(depthTex[1], coord, 0.25) + texture(single, coord, 0.75) + lookup(single, coord); + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); +} +)"; + const char* fragmentSource = R"(#version 450 core +in float result; +out vec4 color; +void main() { color = vec4(result); } +)"; + Vector> shaders; + const Vector> stages{{GL_VERTEX_SHADER, vertexSource}, + {GL_FRAGMENT_SHADER, fragmentSource}}; + for (const auto& [stage, text] : stages) { + ShaderAttrib attrib{.shaderType = stage, .sourceStr = text}; + auto compiled = ShaderCompiler::CompileShader(attrib); + ASSERT_TRUE(compiled) << compiled.error().log; + shaders.push_back(compiled.value()); + } + ProgramAttrib programAttrib{.shaders = shaders}; + auto program = ShaderCompiler::LinkProgram(programAttrib); + ASSERT_TRUE(program) << program.error().log; + ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}, + .program = *program.value()}; + auto modules = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib); + ASSERT_TRUE(modules) << modules.error().log; + SpvcSession session(modules.value()[0], SessionUsageBit::Transpile); + auto essl = ShaderCompiler::DecompileShader(session); + ASSERT_TRUE(essl) << essl.error().log; + ASSERT_EQ(CountOf(essl.value(), "vec3(0.0), vec3(0.0))"), 3u) << essl.value(); + + const String out = LowerZeroGradCubeArrayShadowLookups(essl.value(), GL_VERTEX_SHADER); + EXPECT_FALSE(Contains(out, "textureGrad")) << out; + EXPECT_EQ(CountOf(out, "texture("), 3u) << out; + EXPECT_EQ(LowerZeroGradCubeArrayShadowLookups(essl.value(), GL_FRAGMENT_SHADER), essl.value()); + + const String other = "#version 320 es\nuniform highp samplerCube c;\n" + "void main() { highp vec4 v = textureGrad(c, vec3(1.0), vec3(0.0), vec3(0.0)); }\n"; + EXPECT_EQ(LowerZeroGradCubeArrayShadowLookups(other, GL_VERTEX_SHADER), other); +}