[Fix] (Espryt): keep the invalid-image placeholder on ANGLE instead of probing an empty unit it faults on

This commit is contained in:
2026-10-14 10:30:17 -04:00
parent 2afbcaaf4f
commit 28f937d539
4 changed files with 27 additions and 19 deletions
+3 -2
View File
@@ -217,8 +217,9 @@ namespace MobileGL::MG_Config {
// MOBILEGL_ESPRYT_INVALID_IMAGE_PLACEHOLDER: DirectGLES binds an image unit GL 4.6 8.26
// calls invalid (a level the texture does not have) to a zeroed 1x1 texture of the bind's
// shape instead of leaving it empty. The Adreno ES driver answers a load through an EMPTY
// unit with alpha 1 where GL says 0; the driver POST measures that and arms this by
// itself, so Auto is right everywhere. The placeholder's one texel is real, so a store
// unit with alpha 1 where GL says 0, and ANGLE faults on any use of an empty unit; the
// driver POST measures the first and Auto arms this on ANGLE without probing, so Auto is
// right everywhere. The placeholder's one texel is real, so a store
// through such a unit can be read back through the same unit in the same pass - which is
// why a healthy driver keeps the empty unit. ForceOn exercises the placeholder on a
// healthy driver; ForceOff is the negative control.
@@ -2871,9 +2871,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
// What such a unit is bound to instead, on a driver whose EMPTY unit is not enough
// (GLESCapabilities::EmptyImageUnitsReadNonzero): ES 3.2 8.22 leaves an invalid load's
// alpha undefined, and Adreno answers 1 where GL says 0. Elsewhere the unit stays empty,
// which is exact. A texel outside
// (GLESCapabilities::UseInvalidImagePlaceholder): ES 3.2 8.22 leaves an invalid load's
// alpha undefined, Adreno answers 1 where GL says 0, and ANGLE faults outright.
// Elsewhere the unit stays empty, which is exact. A texel outside
// a level is invalid too, and that Adreno answers (0, 0, 0, 0) with the store dropped - so
// the unit gets a 1x1 texture of the shape and format the bind names, cleared to zero each
// time it is bound. Only texel (0, 0[, 0]) of it exists; it is private to the unit, so a
@@ -2990,7 +2990,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
if (ImageViewNamesNoTexel(view)) {
if (!g_GLESCapabilities.EmptyImageUnitsReadNonzero) {
if (!g_GLESCapabilities.UseInvalidImagePlaceholder) {
g_GLESFuncs.glBindImageTexture(unit, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
return;
}
@@ -2125,24 +2125,30 @@ namespace MobileGL::MG_Util::BackendLoader {
? "true (the subgroup emulation reads its scratch through atomic loads)"
: "false");
caps.IsAngleRenderer = caps.GLESRendererString.find("ANGLE") != String::npos;
switch (MG_Config::Features.EsprytInvalidImagePlaceholder) {
case MG_Config::QuirkOverride::ForceOn:
caps.EmptyImageUnitsReadNonzero = true;
caps.UseInvalidImagePlaceholder = true;
break;
case MG_Config::QuirkOverride::ForceOff:
caps.EmptyImageUnitsReadNonzero = false;
caps.UseInvalidImagePlaceholder = false;
break;
case MG_Config::QuirkOverride::Auto:
default:
caps.EmptyImageUnitsReadNonzero = SelfTest::EmptyImageUnitsReadNonzero(glesFuncs);
// ANGLE dereferences a null texture when a dispatch or draw reaches an EMPTY image
// unit the program uses, so the probe cannot be run there: its subject variant is
// exactly that dispatch, and a crash is no verdict. ANGLE keeps the placeholder
// without being asked.
caps.UseInvalidImagePlaceholder =
caps.IsAngleRenderer || SelfTest::EmptyImageUnitsReadNonzero(glesFuncs);
break;
}
MGLOG_I(" Empty image units read nonzero: %s",
caps.EmptyImageUnitsReadNonzero
? "true (an invalid image unit is bound to a zeroed placeholder)"
: "false");
MGLOG_I(" Invalid image units bound to a placeholder: %s",
!caps.UseInvalidImagePlaceholder ? "false (an invalid image unit stays empty)"
: caps.IsAngleRenderer ? "true (ANGLE faults on a use of an empty image unit)"
: "true (empty image units read nonzero)");
caps.IsAngleRenderer = caps.GLESRendererString.find("ANGLE") != String::npos;
caps.IsAngleLlvmpipeRenderer =
caps.IsAngleRenderer && caps.GLESRendererString.find("llvmpipe") != String::npos;
caps.AvoidSamplerMipmapMinFilter =
@@ -1329,11 +1329,12 @@ namespace MobileGL {
// Adreno). The subgroup emulation then reads its scratch through atomic loads.
// Defaults FALSE: never assumed broken on an unmeasured driver.
Bool SharedMemoryLoadsGoStale = false;
// True when a load through an EMPTY image unit returns a nonzero texel
// (SelfTest::EmptyImageUnitsReadNonzero - measured on Adreno: alpha 1). DirectGLES
// then binds an invalid unit to a zeroed placeholder instead of leaving it empty.
// Defaults FALSE: never assumed broken on an unmeasured driver.
Bool EmptyImageUnitsReadNonzero = false;
// True when DirectGLES binds an invalid image unit to a zeroed placeholder instead of
// leaving it empty: on a driver whose load through an EMPTY unit returns a nonzero
// texel (SelfTest::EmptyImageUnitsReadNonzero - measured on Adreno: alpha 1), and on
// ANGLE, which faults on a dispatch or draw that reaches an empty unit and so cannot
// be probed. Defaults FALSE: never assumed broken on an unmeasured driver.
Bool UseInvalidImagePlaceholder = false;
Int UniformBufferOffsetAlignment = 256;
// Its storage-buffer counterpart, queried separately because it is a separate limit:
// Adreno 830 answers 32 for GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT and 64 for