diff --git a/MobileGL/Config.h b/MobileGL/Config.h index f83d9c51..c8566125 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -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. diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index dcd4f9d9..a446f2b7 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -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; } diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 3158aae8..bca501b2 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -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 = diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index 6fe3bf7b..6c2d5255 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -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