[Fix, Test] (MG_Backend/DirectGLES, MG_Util, CI): keep an explicit LOD constant under a new avoid flag - folding the bias uniform into it kills the ANGLE llvmpipe JIT

This commit is contained in:
2026-08-12 01:42:55 -04:00
parent b5e9339c66
commit 205d837942
16 changed files with 72 additions and 5 deletions
+3
View File
@@ -411,6 +411,9 @@ jobs:
if [ "${{ matrix.backend.name }}" = "DirectGLES" ] && [ "${{ matrix.case.name }}" = "minecraft-1.21.4-fabric-iris-bliss-in-world" ]; then
extra_retrace_args+=(--avoid-angle-llvmpipe-sampler-mipmap-min-filter)
fi
if [ "${{ matrix.backend.name }}" = "DirectGLES" ] && [ "${{ matrix.case.avoid_angle_llvmpipe_explicit_lod_bias || false }}" = "true" ]; then
extra_retrace_args+=(--avoid-angle-llvmpipe-explicit-lod-bias)
fi
if [ "${{ matrix.case.coherent_as_flush || false }}" = "true" ]; then
extra_retrace_args+=(--coherent-as-flush)
fi
+7
View File
@@ -89,6 +89,13 @@ namespace MobileGL::MG_Config {
// MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER: avoid mipmap min filters in samplers,
// resolves certain rendering bugs on ANGLE + llvmpipe.
Bool AvoidSamplerMipmapMinFilter = false;
// MOBILEGL_AVOID_EXPLICIT_LOD_BIAS: leave an already-explicit LOD argument alone when
// emulating GL_TEXTURE_LOD_BIAS, instead of adding the bias uniform to it. Injecting
// the uniform turns a compile-time-constant LOD into a runtime expression, which
// sends ANGLE + llvmpipe down a mip-selection path that dereferences a NULL
// descriptor and kills the process. Deviates from spec (Vulkan adds the bias to
// OpImageSampleExplicitLod), so it is an avoidance for that stack only.
Bool AvoidExplicitLodBias = false;
// MOBILEGL_COHERENT_AS_FLUSH: app-compat for engines (e.g. Flywheel) that write
// GPU-read data through persistent GL_MAP_FLUSH_EXPLICIT_BIT maps they never
// flush. Persistent FLUSH_EXPLICIT map requests are rewritten to coherent
+1
View File
@@ -171,6 +171,7 @@ namespace MobileGL::MG_ConfigLoader {
features.MagmaFramesInFlight = QueryEnvUint32("MOBILEGL_MAGMA_FRAMESINFLIGHT", 3, 1, 64);
features.AvoidSamplerMipmapMinFilter =
QueryEnvFlag("MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER");
features.AvoidExplicitLodBias = QueryEnvFlag("MOBILEGL_AVOID_EXPLICIT_LOD_BIAS");
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
+7 -1
View File
@@ -55,6 +55,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
return g_GLESCapabilities.AvoidSamplerMipmapMinFilter;
}
static Bool ShouldAvoidExplicitLodBiasOnAngleLlvmpipe() {
// IsAngleLlvmpipeRenderer combined with the MOBILEGL_AVOID_EXPLICIT_LOD_BIAS
// feature toggle, both resolved in FillInGLESCapabilities.
return g_GLESCapabilities.AvoidExplicitLodBias;
}
static GLenum ResolveBackendMinFilter(const SamplerParameters& samplerParams,
Bool avoidMipmapMinFilter) {
GLenum filter = MG_Util::ConvertSamplerFilterModeToGLEnum(samplerParams.minFilter,
@@ -4466,7 +4472,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
source = ProcessOutColorLocations(source);
source = ForceFlatIntegerVaryings(source, glShaderType);
source = BroadcastLegacyFragColor(std::move(source), glShaderType, m_fragColorBroadcastCount);
source = EmulateTextureLodBias(source);
source = EmulateTextureLodBias(source, ShouldAvoidExplicitLodBiasOnAngleLlvmpipe());
source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType);
source = ForceSupporterOutput(source);
+6 -1
View File
@@ -957,7 +957,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
} // namespace
String EmulateTextureLodBias(const String& glslCode) {
String EmulateTextureLodBias(const String& glslCode, Bool avoidExplicitLodBias) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
@@ -1018,6 +1018,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (samplerIt == samplerNames.end()) continue;
const String& biasName = samplerIt->second;
if (form->explicitLodArg >= 0 && avoidExplicitLodBias) {
// The lookup already names its level; leaving it alone keeps a constant
// LOD constant. Costs the bias on explicit-LOD lookups only.
continue;
}
if (form->explicitLodArg >= 0) {
// Explicit LOD: the bias adds to it, as Vulkan does for
// OpImageSampleExplicitLod and as the CTS reference expects.
+6 -1
View File
@@ -183,7 +183,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the bound texture's (or sampler object's) value into it; a shader whose samplers
// all have a zero bias is therefore unaffected. Returns the source unchanged when
// there is nothing to rewrite.
String EmulateTextureLodBias(const String& glslCode);
//
// avoidExplicitLodBias leaves lookups that already carry an explicit LOD untouched,
// so their constant level stays constant; only the implicit-LOD forms take the bias.
// Off by default and only ever set on ANGLE + llvmpipe, where injecting the uniform
// into a constant LOD crashes the driver (MOBILEGL_AVOID_EXPLICIT_LOD_BIAS).
String EmulateTextureLodBias(const String& glslCode, Bool avoidExplicitLodBias = false);
} // namespace PrgramImpl
namespace Utils {
@@ -1343,6 +1343,8 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.IsAngleRenderer && caps.GLESRendererString.find("llvmpipe") != String::npos;
caps.AvoidSamplerMipmapMinFilter =
caps.IsAngleLlvmpipeRenderer && MG_Config::Features.AvoidSamplerMipmapMinFilter;
caps.AvoidExplicitLodBias =
caps.IsAngleLlvmpipeRenderer && MG_Config::Features.AvoidExplicitLodBias;
MGLOG_I(" GL_EXT_disjoint_timer_query supported: %s",
caps.SupportsDisjointTimerQuery ? "true" : "false");
MGLOG_I(" GL_KHR_parallel_shader_compile supported: %s",
@@ -1351,6 +1353,7 @@ namespace MobileGL::MG_Util::BackendLoader {
MGLOG_I(" ANGLE llvmpipe renderer: %s", caps.IsAngleLlvmpipeRenderer ? "true" : "false");
MGLOG_I(" Avoid sampler mipmap min filter: %s",
caps.AvoidSamplerMipmapMinFilter ? "true" : "false");
MGLOG_I(" Avoid explicit LOD bias: %s", caps.AvoidExplicitLodBias ? "true" : "false");
return true;
}
@@ -1156,6 +1156,9 @@ namespace MobileGL {
// MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER feature toggle:
// sampler min filters should drop their mipmap component.
Bool AvoidSamplerMipmapMinFilter = false;
// IsAngleLlvmpipeRenderer combined with the MOBILEGL_AVOID_EXPLICIT_LOD_BIAS
// feature toggle: LOD-bias emulation should not touch explicit-LOD lookups.
Bool AvoidExplicitLodBias = false;
// True when indirect draws leak the command's baseInstance word ("reserved,
// must be zero" in unextended ES) into gl_InstanceID. Conforming ES drivers
// keep gl_InstanceID zero-based; ANGLE's Vulkan backend hands the command
@@ -154,6 +154,11 @@ bool LoadMobileGL(const Request& request, std::string& error) {
} else {
unsetenv("MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER");
}
if (request.avoidAngleLlvmpipeExplicitLodBias) {
setenv("MOBILEGL_AVOID_EXPLICIT_LOD_BIAS", "1", 1);
} else {
unsetenv("MOBILEGL_AVOID_EXPLICIT_LOD_BIAS");
}
if (request.coherentAsFlush) {
setenv("MOBILEGL_COHERENT_AS_FLUSH", "1", 1);
} else {
@@ -792,6 +797,8 @@ bool WriteResultJson(const Request& request, const Result& result) {
file << " \"usePbuffer\": " << (request.usePbuffer ? "true" : "false") << ",\n";
file << " \"avoidAngleLlvmpipeSamplerMipmapMinFilter\": "
<< (request.avoidAngleLlvmpipeSamplerMipmapMinFilter ? "true" : "false") << ",\n";
file << " \"avoidAngleLlvmpipeExplicitLodBias\": "
<< (request.avoidAngleLlvmpipeExplicitLodBias ? "true" : "false") << ",\n";
file << " \"holdMs\": " << request.holdMs << ",\n";
file << " \"mismatchPixels\": " << result.mismatchPixels << "\n";
file << "}\n";
@@ -39,6 +39,7 @@ struct Request {
bool useAngle = false;
bool usePbuffer = true;
bool avoidAngleLlvmpipeSamplerMipmapMinFilter = false;
bool avoidAngleLlvmpipeExplicitLodBias = false;
bool coherentAsFlush = false;
int holdMs = 0;
};
@@ -102,6 +102,7 @@ Java_top_mobilegl_plugin_trace_TraceReplayActivity_nativeRunTraceReplay(JNIEnv*
jboolean useAngle,
jboolean usePbuffer,
jboolean avoidAngleLlvmpipeSamplerMipmapMinFilter,
jboolean avoidAngleLlvmpipeExplicitLodBias,
jboolean coherentAsFlush) {
mobilegl_trace::Request request;
request.tracePath = ToString(env, tracePath);
@@ -127,6 +128,7 @@ Java_top_mobilegl_plugin_trace_TraceReplayActivity_nativeRunTraceReplay(JNIEnv*
request.usePbuffer = usePbuffer == JNI_TRUE;
request.avoidAngleLlvmpipeSamplerMipmapMinFilter =
avoidAngleLlvmpipeSamplerMipmapMinFilter == JNI_TRUE;
request.avoidAngleLlvmpipeExplicitLodBias = avoidAngleLlvmpipeExplicitLodBias == JNI_TRUE;
request.coherentAsFlush = coherentAsFlush == JNI_TRUE;
ScopedTraceReplayState replayState;
@@ -114,6 +114,7 @@ public final class TraceReplayActivity extends Activity {
request.useAngle,
request.usePbuffer,
request.avoidAngleLlvmpipeSamplerMipmapMinFilter,
request.avoidAngleLlvmpipeExplicitLodBias,
request.coherentAsFlush
);
Log.i(TAG, result.toString());
@@ -145,6 +146,7 @@ public final class TraceReplayActivity extends Activity {
boolean useAngle,
boolean usePbuffer,
boolean avoidAngleLlvmpipeSamplerMipmapMinFilter,
boolean avoidAngleLlvmpipeExplicitLodBias,
boolean coherentAsFlush
);
@@ -168,6 +170,7 @@ public final class TraceReplayActivity extends Activity {
final boolean useAngle;
final boolean usePbuffer;
final boolean avoidAngleLlvmpipeSamplerMipmapMinFilter;
final boolean avoidAngleLlvmpipeExplicitLodBias;
final boolean coherentAsFlush;
private TraceReplayRequest(
@@ -190,6 +193,7 @@ public final class TraceReplayActivity extends Activity {
boolean useAngle,
boolean usePbuffer,
boolean avoidAngleLlvmpipeSamplerMipmapMinFilter,
boolean avoidAngleLlvmpipeExplicitLodBias,
boolean coherentAsFlush
) {
this.tracePath = tracePath;
@@ -211,6 +215,7 @@ public final class TraceReplayActivity extends Activity {
this.useAngle = useAngle;
this.usePbuffer = usePbuffer;
this.avoidAngleLlvmpipeSamplerMipmapMinFilter = avoidAngleLlvmpipeSamplerMipmapMinFilter;
this.avoidAngleLlvmpipeExplicitLodBias = avoidAngleLlvmpipeExplicitLodBias;
this.coherentAsFlush = coherentAsFlush;
}
@@ -237,6 +242,7 @@ public final class TraceReplayActivity extends Activity {
intent.getBooleanExtra("use_angle", false),
intent.getBooleanExtra("use_pbuffer", false),
intent.getBooleanExtra("avoid_angle_llvmpipe_sampler_mipmap_min_filter", false),
intent.getBooleanExtra("avoid_angle_llvmpipe_explicit_lod_bias", false),
intent.getBooleanExtra("coherent_as_flush", false)
);
}
+12
View File
@@ -29,6 +29,7 @@ Usage:
--crop-height N \
[--use-pbuffer] \
[--avoid-angle-llvmpipe-sampler-mipmap-min-filter] \
[--avoid-angle-llvmpipe-explicit-lod-bias] \
[--coherent-as-flush] \
--timeout-seconds N
@@ -40,6 +41,9 @@ Set MOBILEGL_RETRACE_USE_PBUFFER=1 or pass --use-pbuffer to run DirectGLES
against an offscreen EGL pbuffer instead of the Activity surface.
Pass --avoid-angle-llvmpipe-sampler-mipmap-min-filter for DirectGLES traces that
need ANGLE llvmpipe sampler mipmap filters downgraded to avoid driver stalls.
Pass --avoid-angle-llvmpipe-explicit-lod-bias for DirectGLES traces whose shaders
sample with an explicit LOD that ANGLE llvmpipe cannot take a LOD bias on
(MOBILEGL_AVOID_EXPLICIT_LOD_BIAS=1).
Pass --coherent-as-flush for traces whose engine writes persistent
GL_MAP_FLUSH_EXPLICIT_BIT maps it never flushes (MOBILEGL_COHERENT_AS_FLUSH=1).
EOF
@@ -98,6 +102,7 @@ crop_width=""
crop_height=""
use_pbuffer=0
avoid_angle_llvmpipe_sampler_mipmap_min_filter=0
avoid_angle_llvmpipe_explicit_lod_bias=0
coherent_as_flush=0
timeout_seconds=""
@@ -134,6 +139,10 @@ while [ "$#" -gt 0 ]; do
avoid_angle_llvmpipe_sampler_mipmap_min_filter=1
shift 1
;;
--avoid-angle-llvmpipe-explicit-lod-bias)
avoid_angle_llvmpipe_explicit_lod_bias=1
shift 1
;;
--coherent-as-flush) coherent_as_flush=1; shift 1 ;;
--timeout-seconds) timeout_seconds="$(next_arg "$@")"; shift 2 ;;
-h|--help) usage; exit 0 ;;
@@ -320,6 +329,9 @@ run_retrace() {
if [ "${avoid_angle_llvmpipe_sampler_mipmap_min_filter}" -eq 1 ] && [ "${backend}" = "DirectGLES" ]; then
set -- "$@" --ez avoid_angle_llvmpipe_sampler_mipmap_min_filter true
fi
if [ "${avoid_angle_llvmpipe_explicit_lod_bias}" -eq 1 ] && [ "${backend}" = "DirectGLES" ]; then
set -- "$@" --ez avoid_angle_llvmpipe_explicit_lod_bias true
fi
if [ "${coherent_as_flush}" -eq 1 ]; then
set -- "$@" --ez coherent_as_flush true
fi
+4 -1
View File
@@ -275,7 +275,10 @@ default; pass `--ez use_pbuffer true` to use the offscreen pbuffer path. Always
process-local. For cases registered with `coherent_as_flush` (Flywheel-style
unflushed persistent maps, e.g. the Create fixtures), pass
`--ez coherent_as_flush true` so the replay runs with
`MOBILEGL_COHERENT_AS_FLUSH=1`.
`MOBILEGL_COHERENT_AS_FLUSH=1`. For cases registered with
`avoid_angle_llvmpipe_explicit_lod_bias` (DirectGLES on ANGLE llvmpipe, e.g. the
sundial-lite fixture), pass `--ez avoid_angle_llvmpipe_explicit_lod_bias true` so
the replay runs with `MOBILEGL_AVOID_EXPLICIT_LOD_BIAS=1`.
## Reproducing the Android DirectGLES lane on Linux (ANGLE on lavapipe)
@@ -184,6 +184,8 @@ def run_case(case, backend):
command.append("--use-pbuffer")
if backend_info["use_angle"] and case["name"] == BLISS_CASE:
command.append("--avoid-angle-llvmpipe-sampler-mipmap-min-filter")
if backend_info["use_angle"] and case.get("avoid_angle_llvmpipe_explicit_lod_bias"):
command.append("--avoid-angle-llvmpipe-explicit-lod-bias")
if case.get("coherent_as_flush"):
command.append("--coherent-as-flush")
env = dict(**__import__("os").environ)
+2 -1
View File
@@ -181,7 +181,8 @@
"name": "minecraft-1.21.4-fabric-iris-sundial-lite-in-world",
"trace_archive": "minecraft-1.21.4-fabric-iris-sundial-lite-in-world.tgz",
"golden": "minecraft-1.21.4-fabric-iris-sundial-lite-in-world.0000150023.png",
"target_call": 150023
"target_call": 150023,
"avoid_angle_llvmpipe_explicit_lod_bias": true
},
{
"name": "minecraft-1.21.4-fabric-iris-complementary-reimagined-in-world",