diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index b6170424..89d4f86c 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -3384,14 +3384,18 @@ namespace MobileGL::MG_Backend::DirectGLES { } // ES has no rectangle sampler, and SPIRV-Cross refuses the whole module rather - // than approximating one. Where every use takes integer texel coordinates a - // rectangle image is indistinguishable from a 2D one, so rewrite the type and let - // it through; the pass declines anything it cannot convert exactly. + // than approximating one. Rewriting the type to 2D is exact for a lookup that + // takes integer texel coordinates and needs the coordinate divided by the + // texture size for one that does not - see NormalizeRectSamplerCoordinates + // below, which the ESSL the transpiler produces goes through. The pass declines + // anything neither step can convert. Vector rectLoweredSpirv; + Bool loweredRectImages = false; if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImagesForEssl(*effectiveSpirv, rectLoweredSpirv) && !rectLoweredSpirv.empty()) { effectiveSpirv = &rectLoweredSpirv; + loweredRectImages = true; } MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv, @@ -3428,6 +3432,26 @@ namespace MobileGL::MG_Backend::DirectGLES { source = ForceFlatIntegerVaryings(source, glShaderType); source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount); source = EmulateTextureLodBias(source); + if (loweredRectImages) { + // The image type is 2D now, so the transpiled lookups address [0,1]; the + // application wrote them in texels. Only the frontend still knows which + // samplers were declared rectangle. + Vector rectSamplerNames; + const Uint uniformCount = stateProgramObject->GetUniformCount(); + for (Uint i = 0; i < uniformCount; ++i) { + switch (stateProgramObject->GetActiveUniformType(i)) { + case GL_SAMPLER_2D_RECT: + case GL_SAMPLER_2D_RECT_SHADOW: + case GL_INT_SAMPLER_2D_RECT: + case GL_UNSIGNED_INT_SAMPLER_2D_RECT: + rectSamplerNames.push_back(stateProgramObject->GetActiveUniformName(i)); + break; + default: + break; + } + } + source = NormalizeRectSamplerCoordinates(source, rectSamplerNames); + } source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType); source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType); source = ForceSupporterOutput(source); diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index 0693b5e7..05ff69fd 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -596,6 +596,67 @@ namespace MobileGL::MG_Backend::DirectGLES { } return result; } + + String NormalizeRectSamplerCoordinates(const String& glslCode, + const Vector& rectSamplerNames) { +#ifdef TRACY_ENABLE + ZoneScopedC(TRACY_ZONECOLOR_BACKEND); +#endif + if (rectSamplerNames.empty() || glslCode.find("texture") == String::npos) { + return glslCode; + } + + // Lookups whose argument 1 is a plain (non-projective) texel-space coordinate on a + // rectangle sampler. texelFetch* is absent on purpose: its coordinates are integer + // texels on the 2D target too, so it already lands in the right place. + static const char* const kRectCoordinateLookups[] = { + "textureGatherOffsets", "textureGatherOffset", "textureGather", + "textureOffset", "texture", + }; + + String result = glslCode; + // Right to left, so the offsets of the not-yet-rewritten calls stay valid. + for (SizeT scan = result.size(); scan-- > 0;) { + if (result[scan] != 't') continue; + if (scan > 0 && IsIdentifierChar(result[scan - 1])) continue; + + SizeT openParen = 0; + Bool matched = false; + for (const char* name : kRectCoordinateLookups) { + const SizeT nameLength = std::strlen(name); + if (result.compare(scan, nameLength, name) != 0) continue; + const SizeT after = result.find_first_not_of(" \t", scan + nameLength); + if (after == String::npos || result[after] != '(') continue; + openParen = after; + matched = true; + break; + } + if (!matched) continue; + + const Vector marks = SplitCallArguments(result, openParen); + if (marks.size() < 2) continue; // needs a sampler and a coordinate + + const SizeT firstArgStart = result.find_first_not_of(" \t", openParen + 1); + SizeT firstArgEnd = marks.front(); + while (firstArgEnd > firstArgStart && + (result[firstArgEnd - 1] == ' ' || result[firstArgEnd - 1] == '\t')) { + --firstArgEnd; + } + if (firstArgStart == String::npos || firstArgEnd <= firstArgStart) continue; + const String samplerName = result.substr(firstArgStart, firstArgEnd - firstArgStart); + if (std::find(rectSamplerNames.begin(), rectSamplerNames.end(), samplerName) == + rectSamplerNames.end()) { + continue; + } + + // Wrap argument 1: (coord) / vec2(textureSize(sampler, 0)). + const SizeT coordStart = marks[0] + 1; + const SizeT coordEnd = marks[1]; + result.insert(coordEnd, String(") / vec2(textureSize(") + samplerName + ", 0)))"); + result.insert(coordStart, "(("); + } + return result; + } } // namespace PrgramImpl namespace Utils { diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.h b/MobileGL/MG_Backend/DirectGLES/Utils.h index e433df2a..1012b5df 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.h +++ b/MobileGL/MG_Backend/DirectGLES/Utils.h @@ -129,6 +129,15 @@ namespace MobileGL::MG_Backend::DirectGLES { // all have a zero bias is therefore unaffected. Returns the source unchanged when // there is nothing to rewrite. String EmulateTextureLodBias(const String& glslCode); + // GL_TEXTURE_RECTANGLE is emulated on an ES 2D texture and LowerRectImagesForEssl + // rewrites the image type to match, but a rectangle lookup addresses texels + // directly while a 2D one addresses [0,1] - so every lookup that takes normalized + // coordinates has to divide by the texture's size. `rectSamplerNames` is the set of + // samplers the program declared as rectangle; texelFetch is left alone (its + // coordinates are unnormalized on both targets) and so is anything projective, + // which LowerRectImagesForEssl still declines outright. + String NormalizeRectSamplerCoordinates(const String& glslCode, + const Vector& rectSamplerNames); } // namespace PrgramImpl namespace Utils { diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index c2b1c510..7aeb4f2b 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -397,11 +397,19 @@ namespace MobileGL { } } else { switch (opcode) { - // Everything that takes normalized coordinates. Tracing each one back to - // its image type would let a module mix a normalized 2D lookup with a - // rectangle fetch, but the extra reach is not worth the risk of getting - // the trace wrong: decline the whole module instead. - case spv::Op::OpImageSampleImplicitLod: + // Normalized-coordinate lookups whose ESSL form the backend's + // NormalizeRectSamplerCoordinates post-pass cannot repair: the + // coordinate is either fused with something else in a single argument + // (the Dref sample forms carry the compare value in coord.z) or the + // divide would have to happen after a projective divide. Tracing each + // one back to its image type would let a module mix a normalized 2D + // lookup with a rectangle fetch, but the extra reach is not worth the + // risk of getting the trace wrong: decline the whole module instead. + // + // OpImageSampleImplicitLod, OpImageGather and OpImageDrefGather are + // absent because all three become an ESSL call whose argument 1 is the + // bare texel-space coordinate, which the post-pass divides by the + // texture size. case spv::Op::OpImageSampleExplicitLod: case spv::Op::OpImageSampleDrefImplicitLod: case spv::Op::OpImageSampleDrefExplicitLod: @@ -409,8 +417,6 @@ namespace MobileGL { case spv::Op::OpImageSampleProjExplicitLod: case spv::Op::OpImageSampleProjDrefImplicitLod: case spv::Op::OpImageSampleProjDrefExplicitLod: - case spv::Op::OpImageGather: - case spv::Op::OpImageDrefGather: case spv::Op::OpImageSparseSampleImplicitLod: case spv::Op::OpImageSparseSampleExplicitLod: case spv::Op::OpImageSparseSampleDrefImplicitLod: