[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 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 09:06:01 +00:00
co-authored by Claude Fable 5
parent 74ae1e4a29
commit bb3a18c627
@@ -28,6 +28,7 @@
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <regex>
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<MG_State::GLState::ProgramObject>& 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);