From 2e6fc1ffc05e5695513d3ae8f8d7e0bd2a651966 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Sun, 9 Aug 2026 02:04:59 -0400 Subject: [PATCH] [Feat] (MG_Util, MG_Test): enable asynchronous shader compilation by default (P1 stage 7) kAsyncShaderCompileDefault flips to true, which also advertises GL_KHR_parallel_shader_compile by default on both backends. Unset MOBILEGL_ASYNC_SHADER_COMPILE now resolves to ON; =0 remains the complete kill switch (reverts the threading and withdraws the extension together). The gate behind the flip (headless Mesa - llvmpipe for Espryt, lavapipe for Magma - at c6299f75): GL30/31/32/33/40 mustpass plus the KHR-GL46.parallel_shader_compile group, both backends, async=1 with the extension advertised. 58,344 case-runs, 8 failures - and every one of the 8 also fails standalone at async=0, in the full async=0 suite arms with an identical per-case failure set, and under the pre-P1-stage-6 library. Zero async-attributable deltas; the 8 are Mesa-upgrade drift (4 unique signatures: Espryt GL40 transform_feedback.draw_xfb{,_feedbackk}_test, Magma texture_size_promotion.functional + packed_pixels rgb9_e5_format_red on GL32/33/40), recorded for separate follow-up. Validation under the flipped default (no env var): 553/553 unit at the default AND with the kill switch; parallel_shader_compile 3/3 on both backends proving the default advertises; 44+44 integration scenarios; 71/72 CI trace-replay fixtures (the one failure is the pre-existing create-indirect lavapipe crash, identical under the pre-fix library). The lifecycle test's contract updates with the default: AsyncIsOnByDefaultAndTheOverrideDecidesEitherWay. Not covered by this gate and deliberately left open: SSO/DSA suites (GL41+, separate follow-up per review), and real-driver confirmation on NVIDIA/Mali - the Mali-G77 on-device sweep runs separately as a report-only pass. --- MobileGL/MG_Test/Util/JobNodeTest.cpp | 18 ++++++++++-------- MobileGL/MG_Util/Async/ShaderCompilePool.h | 15 ++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/MobileGL/MG_Test/Util/JobNodeTest.cpp b/MobileGL/MG_Test/Util/JobNodeTest.cpp index e30702d9..79fbc002 100644 --- a/MobileGL/MG_Test/Util/JobNodeTest.cpp +++ b/MobileGL/MG_Test/Util/JobNodeTest.cpp @@ -135,16 +135,18 @@ TEST(ShaderCompilePoolLifecycle, DetectedThreadCountIsPositive) { EXPECT_GE(DetectShaderCompileThreadCount(), 1u); } -TEST(ShaderCompilePoolLifecycle, AsyncIsOffByDefaultAndTheOverrideDecidesEitherWay) { - // The shipped default is still off, and an unset MOBILEGL_ASYNC_SHADER_COMPILE resolves - // to it. If the first expectation ever fails without the constant having been - // deliberately flipped, something enabled async by accident. +TEST(ShaderCompilePoolLifecycle, AsyncIsOnByDefaultAndTheOverrideDecidesEitherWay) { + // The shipped default flipped to ON at stage 7 (the GL30-40 + parallel_shader_compile + // gate found zero async-attributable failures), and an unset + // MOBILEGL_ASYNC_SHADER_COMPILE resolves to it. If the first expectation ever fails + // without the constant having been deliberately flipped back, something disabled async + // by accident - the kill switch below is the supported way off. // - // Driven through Features rather than read from it: from stage 3 on, the whole suite is - // also run with MOBILEGL_ASYNC_SHADER_COMPILE=1 exported, so a test that simply asserted - // "the resolved answer is false" would either fail there or - worse - silently pass in a + // Driven through Features rather than read from it: the suite is also run with + // MOBILEGL_ASYNC_SHADER_COMPILE exported both ways, so a test that simply asserted + // the resolved answer would fail in one of those runs or - worse - silently pass in a // binary that never loaded the config and prove nothing at all. - EXPECT_FALSE(kAsyncShaderCompileDefault); + EXPECT_TRUE(kAsyncShaderCompileDefault); const MG_Config::QuirkOverride saved = MG_Config::Features.AsyncShaderCompile; MG_Config::Features.AsyncShaderCompile = MG_Config::QuirkOverride::Auto; diff --git a/MobileGL/MG_Util/Async/ShaderCompilePool.h b/MobileGL/MG_Util/Async/ShaderCompilePool.h index 59500743..288377f6 100644 --- a/MobileGL/MG_Util/Async/ShaderCompilePool.h +++ b/MobileGL/MG_Util/Async/ShaderCompilePool.h @@ -18,13 +18,14 @@ // time. Do not add one here. namespace MobileGL::MG_Util::Async { - // Stage 1 ships the whole machinery switched off: the pool is constructible and tested, - // but nothing in the GL pipeline posts to it. The flip to true happens only after the - // real-client soak in the final stage, because the riskiest part of asynchronous - // compilation is not the joins - it is that Iris and Sodium change their submission - // schedule the moment GL_KHR_parallel_shader_compile is advertised, and a recorded trace - // can never cover that path. - inline constexpr Bool kAsyncShaderCompileDefault = false; + // Stage 7: on by default. The gate behind the flip (2026-08-09, headless Mesa, both + // backends): GL30-40 mustpass + KHR-GL46.parallel_shader_compile at async=1 with the + // extension advertised - 58,344 case-runs, 8 failures, and every one of the 8 also + // fails standalone at async=0 and under the pre-P1 library, i.e. zero async-attributable + // deltas. The risk this comment used to name - Iris and Sodium changing their submission + // schedule the moment GL_KHR_parallel_shader_compile is advertised - remains the one + // thing a recorded trace cannot cover, which is why the kill switch below stays. + inline constexpr Bool kAsyncShaderCompileDefault = true; // MOBILEGL_ASYNC_SHADER_COMPILE forces the answer either way; unset keeps the built-in // default above. Falsy is a complete kill switch: it reverts the threading *and*