From 205d8379425c2ad3bebc0127a84b2593950e84ba Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 12 Aug 2026 01:37:59 -0400 Subject: [PATCH] [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 --- .github/workflows/apk.yml | 3 +++ MobileGL/Config.h | 7 +++++++ MobileGL/ConfigLoader.cpp | 1 + MobileGL/MG_Backend/DirectGLES/Managers.cpp | 8 +++++++- MobileGL/MG_Backend/DirectGLES/Utils.cpp | 7 ++++++- MobileGL/MG_Backend/DirectGLES/Utils.h | 7 ++++++- MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp | 3 +++ MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h | 3 +++ .../app/src/trace/cpp/trace_replay_core.cpp | 7 +++++++ .../app/src/trace/cpp/trace_replay_core.hpp | 1 + .../app/src/trace/cpp/trace_replay_jni.cpp | 2 ++ .../mobilegl/plugin/trace/TraceReplayActivity.java | 6 ++++++ android-plugin/trace-replay-ci.sh | 12 ++++++++++++ tools/trace_replay/README.md | 5 ++++- tools/trace_replay/run_android_retrace_local.py | 2 ++ tools/trace_replay/trace_cases.json | 3 ++- 16 files changed, 72 insertions(+), 5 deletions(-) diff --git a/.github/workflows/apk.yml b/.github/workflows/apk.yml index 4ec2e6af..062e2ca8 100644 --- a/.github/workflows/apk.yml +++ b/.github/workflows/apk.yml @@ -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 diff --git a/MobileGL/Config.h b/MobileGL/Config.h index 88949a94..ca65bd67 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -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 diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index e5059854..b8f4b7dc 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -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"); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index bb868dc6..b9f10d60 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -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); diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index 0456e53a..73deaa02 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -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. diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.h b/MobileGL/MG_Backend/DirectGLES/Utils.h index 04eaf707..25bc6c55 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.h +++ b/MobileGL/MG_Backend/DirectGLES/Utils.h @@ -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 { diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index d724eafe..787d3908 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -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; } diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index e32c5bf5..2b13941b 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -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 diff --git a/android-plugin/app/src/trace/cpp/trace_replay_core.cpp b/android-plugin/app/src/trace/cpp/trace_replay_core.cpp index b4af7c93..b67d8b35 100644 --- a/android-plugin/app/src/trace/cpp/trace_replay_core.cpp +++ b/android-plugin/app/src/trace/cpp/trace_replay_core.cpp @@ -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"; diff --git a/android-plugin/app/src/trace/cpp/trace_replay_core.hpp b/android-plugin/app/src/trace/cpp/trace_replay_core.hpp index ed50ec6a..9c81c254 100644 --- a/android-plugin/app/src/trace/cpp/trace_replay_core.hpp +++ b/android-plugin/app/src/trace/cpp/trace_replay_core.hpp @@ -39,6 +39,7 @@ struct Request { bool useAngle = false; bool usePbuffer = true; bool avoidAngleLlvmpipeSamplerMipmapMinFilter = false; + bool avoidAngleLlvmpipeExplicitLodBias = false; bool coherentAsFlush = false; int holdMs = 0; }; diff --git a/android-plugin/app/src/trace/cpp/trace_replay_jni.cpp b/android-plugin/app/src/trace/cpp/trace_replay_jni.cpp index 9fd2a876..88479748 100644 --- a/android-plugin/app/src/trace/cpp/trace_replay_jni.cpp +++ b/android-plugin/app/src/trace/cpp/trace_replay_jni.cpp @@ -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; diff --git a/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java b/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java index 75261604..0ab48d67 100644 --- a/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java +++ b/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java @@ -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) ); } diff --git a/android-plugin/trace-replay-ci.sh b/android-plugin/trace-replay-ci.sh index 6d3bf117..9e9be025 100644 --- a/android-plugin/trace-replay-ci.sh +++ b/android-plugin/trace-replay-ci.sh @@ -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 diff --git a/tools/trace_replay/README.md b/tools/trace_replay/README.md index 9441f207..dd19687e 100644 --- a/tools/trace_replay/README.md +++ b/tools/trace_replay/README.md @@ -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) diff --git a/tools/trace_replay/run_android_retrace_local.py b/tools/trace_replay/run_android_retrace_local.py index 6650d307..47a34fef 100644 --- a/tools/trace_replay/run_android_retrace_local.py +++ b/tools/trace_replay/run_android_retrace_local.py @@ -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) diff --git a/tools/trace_replay/trace_cases.json b/tools/trace_replay/trace_cases.json index eeab5dc5..2cb33bbe 100644 --- a/tools/trace_replay/trace_cases.json +++ b/tools/trace_replay/trace_cases.json @@ -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",