From bb3a18c62718bbdcf458bf3b2f5a13e155a04136 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 9 Jul 2026 09:06:01 +0000 Subject: [PATCH] [Fix] (MG_Backend/DirectGLES): rebind image uniforms to frontend image units 139de763 started preserving layout(binding) on SSBO/image declarations in transpiled ESSL (ES cannot rebind either through the API). That is correct for SSBOs and for images whose GL source carries an explicit binding (Flywheel), but wrong for image uniforms without one: glslang auto-assigns a binding during transpile, while the app addresses the unit through desktop-GL semantics - the link-time default (0) or glUniform1i, which ES forbids on image uniforms. Iris/Photon picks image units with glUniform1i, so its compute passes (auto exposure / colored light) read and wrote the transpiler-invented units instead: the photon-v1.3b retrace came out dark and orange-tinted (ssim 0.65 vs golden). Rewrite every image uniform declaration's binding qualifier to the frontend-tracked unit (layout binding reflected at link, overridden by any later glUniform1i) when transpiling for the backend. Flywheel's explicit bindings rewrite to the same value; Iris packs get the unit the app actually bound with glBindImageTexture. Verified on llvmpipe DirectGLES: photon-v1.3b retrace 0.652 -> 0.9988, photon-v1.1 control stays at 0.9991, all 147 unit tests pass. Co-Authored-By: Claude Fable 5 --- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 59 +++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 4eedbda5..3de867d4 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace MobileGL::MG_Backend::DirectGLES { constexpr Bool PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC = false; @@ -180,6 +181,63 @@ namespace MobileGL::MG_Backend::DirectGLES { return source; } + // The transpile pipeline invents image binding numbers: when the GL source declares + // an image uniform without layout(binding), glslang auto-assigns one (desktop GL + // allows that and lets the app pick the unit with glUniform1i, which ES forbids on + // image uniforms). The unit the app actually addresses lives in frontend state: the + // layout(binding) reflected at link time, or whatever glUniform1i stored afterwards. + // Rewrite every image uniform declaration to that unit so imageLoad/Store hits the + // unit the app bound with glBindImageTexture. + String RebindImageUniformsToFrontendUnits( + String source, const SharedPtr& stateProgramObject) { + if (!stateProgramObject || source.find("image") == String::npos) { + return source; + } + static const std::regex imageDeclRegex( + R"((layout\s*\(([^)]*)\)\s*)?uniform\s+(?:(?:readonly|writeonly|coherent|volatile|restrict|highp|mediump|lowp)\s+)*[iu]?image[A-Za-z0-9]+\s+([A-Za-z_][A-Za-z0-9_]*)\s*(\[[^\]]*\])?\s*;)"); + static const std::regex bindingValueRegex(R"(binding\s*=\s*\d+)"); + + String result; + result.reserve(source.size()); + SizeT lineStart = 0; + while (lineStart <= source.size()) { + const SizeT lineEnd = source.find('\n', lineStart); + const Bool lastLine = lineEnd == String::npos; + String line = source.substr(lineStart, lastLine ? String::npos : lineEnd - lineStart); + + std::smatch match; + if (std::regex_search(line, match, imageDeclRegex)) { + const String name = match[3].str(); + Int location = stateProgramObject->GetUniformLocation(name); + if (location < 0) { + location = stateProgramObject->GetUniformLocation(name + "[0]"); + } + if (location >= 0) { + const Int unit = stateProgramObject->GetUniformSamplerOrImageUnitIndex(location); + if (unit >= 0) { + const String bindingText = "binding = " + std::to_string(unit); + if (std::regex_search(line, bindingValueRegex)) { + line = std::regex_replace(line, bindingValueRegex, bindingText); + } else if (match[1].matched) { + const SizeT layoutOpen = line.find('(', match.position(1)); + line.insert(layoutOpen + 1, bindingText + ", "); + } else { + line.insert(match.position(0), "layout(" + bindingText + ") "); + } + } + } + } + + result += line; + if (lastLine) { + break; + } + result += '\n'; + lineStart = lineEnd + 1; + } + return result; + } + namespace BufferImpl { namespace { using MG_State::GLState::BackendBufferResource; @@ -2025,6 +2083,7 @@ namespace MobileGL::MG_Backend::DirectGLES { source = result; + source = RebindImageUniformsToFrontendUnits(std::move(source), stateProgramObject); source = RemoveLayoutBinding(source); source = ProcessOutColorLocations(source); source = ForceFlatIntegerVaryings(source, glShaderType);