From 7769156cfc8e5bf5627489215d2330d0bd561498 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 17 Aug 2026 01:55:17 -0400 Subject: [PATCH] [Fix, Test] (ShaderTranspiler, WGL, TraceReplay): remove subgroup pack quirks and tune iterationRP --- MobileGL/Config.h | 5 - MobileGL/ConfigLoader.cpp | 1 - MobileGL/MG_Test/Program/ProgramUtilTest.cpp | 322 --------- MobileGL/MG_Util/Debug/Log.cpp | 4 +- .../MG_Util/ShaderTranspiler/CompileEnv.cpp | 4 - .../MG_Util/ShaderTranspiler/CompileEnv.h | 9 +- .../ShaderSourceProcessor.cpp | 660 ------------------ .../ShaderTranspiler/ShaderSourceProcessor.h | 11 - include/KHR/khrplatform.h | 11 +- tools/trace_replay/trace_cases.json | 3 +- 10 files changed, 14 insertions(+), 1016 deletions(-) diff --git a/MobileGL/Config.h b/MobileGL/Config.h index 9d5e522d..beefdba9 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -128,11 +128,6 @@ namespace MobileGL::MG_Config { // explicitly request a core profile via EGL_CONTEXT_OPENGL_PROFILE_MASK / a >=3.1 // version request. Bool RelaxedSemantics = false; - // MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN: overrides the shader-source quirk that rewrites - // recognized subgroup reductions when narrow Vulkan subgroups overflow fixed scratch - // arrays, or when Qualcomm subgroups are wider than the captured 32-lane model (see - // ShaderSourceProcessor's quirk registry). - QuirkOverride SubgroupPrefixScanQuirk = QuirkOverride::Auto; // MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE: overrides the DirectVulkan quirk that // strips depth writes from accumulation-blended pipelines (MIN/MAX or additive // ONE+ONE - the multi-pass depth-equality signature) on drivers without diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index 476019a1..0b0df12a 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -180,7 +180,6 @@ namespace MobileGL::MG_ConfigLoader { features.EsprytForceDepthStencilReadbackEmulation = QueryEnvFlag("MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION"); features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS"); - features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN"); features.MagmaDisableBlendedDepthWriteQuirk = QueryEnvQuirkOverride("MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE"); features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS"); diff --git a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp index 21dc3a52..344d9d17 100644 --- a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp @@ -2073,325 +2073,6 @@ void main() { EXPECT_NE(source.find("layout(std140) uniform Blk"), String::npos); } -namespace { - String MakeLinearSubgroupPrefixScanShader() { - return R"(#version 460 core -#extension GL_KHR_shader_subgroup_arithmetic : enable -layout(local_size_x = 1024) in; -shared float prefixSumCache[64]; - -layout(std430, binding = 0) writeonly buffer OutputBuffer { - float outputValues[]; -}; - -void main() { - float importance = 1.0f; - float prefixSum = subgroupInclusiveAdd(importance); - if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = prefixSum; - barrier(); - uint loopLength = uint(findMSB(gl_NumSubgroups)); - loopLength += uint(gl_NumSubgroups - (1u << (loopLength - 1u)) > 0u); - for (uint i = 0; i < loopLength; i++) { - if ((gl_SubgroupID & (1u << i)) > 0u) { - prefixSum += prefixSumCache[(gl_SubgroupID >> i << i) - 1u]; - if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = prefixSum; - } - barrier(); - } - if (gl_LocalInvocationID.x == uint(1024 - 1)) prefixSumCache[0] = prefixSum; - barrier(); - float sum = prefixSumCache[0]; - float warp = (prefixSum - importance) / sum - float(gl_LocalInvocationID.x + 1u) / float(1024); - outputValues[gl_GlobalInvocationID.x] = warp; -} -)"; - } - - String MakeWeightedExposureSubgroupReductionShader() { - return R"(#version 460 core -#extension GL_KHR_shader_subgroup_arithmetic : enable -uniform int frameCounter; -uniform float frameTime; -uniform float aspectRatio; -uniform vec2 pixelSize; -uniform sampler2D colortex2; -uniform sampler2D pixelData2D; -layout(local_size_x = 32, local_size_y = 16) in; -layout(rg16f) uniform image2D img_pixelData2D; -shared vec2 prefixSumCache[32]; - -float remapSaturate(float value, float edge0, float edge1) { - return clamp((value - edge0) / (edge1 - edge0), 0.0f, 1.0f); -} - -float GetExposureValue(float luminance) { - return max(luminance, 1.0E-5f); -} - -void main() { - vec2 texCoord = (vec2(gl_GlobalInvocationID.xy) + 0.5f) * vec2(1.0f / 32.0f, 1.0f / 16.0f); - vec2 sampleCoord = texCoord * (1.0f / 64.0f); - sampleCoord.x += (15.0f / 32.0f) + pixelSize.x * 12.0f; - float tileExposure = dot(textureLod(colortex2, sampleCoord, 0.0f).rgb, vec3(0.2125f, 0.7154f, 0.0721f)); - vec2 sampleLuminance = vec2(tileExposure, 0.0f); - sampleLuminance = subgroupInclusiveAdd(sampleLuminance); - if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleLuminance; - barrier(); - uint loopLength = uint(findMSB(gl_NumSubgroups)); - loopLength += uint(gl_NumSubgroups - (1u << (loopLength - 1u)) > 0u); - for (uint i = 0; i < loopLength; i++) { - if ((gl_SubgroupID & (1u << i)) > 0u) { - sampleLuminance += prefixSumCache[(gl_SubgroupID >> i << i) - 1u]; - if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleLuminance; - } - barrier(); - } - if (gl_LocalInvocationIndex == 511u) prefixSumCache[0] = sampleLuminance / 512.0f; - ; - barrier(); - float avg = prefixSumCache[0].x; - vec2 tileDistance = texCoord * 2.0f - 1.0f; - tileDistance.y /= aspectRatio; - float centerDistance = length(tileDistance); - float tileWeight = remapSaturate(centerDistance, 0.6f, 0.4f); - tileExposure = max(7.0E-7f, tileExposure); - float lumaWeight = avg / tileExposure; - lumaWeight = pow(lumaWeight, remapSaturate(avg, 0.02f, 0.001f) * 0.4f + 0.2f); - tileWeight *= lumaWeight; - vec2 sampleExposure = vec2(tileExposure * tileWeight, tileWeight); - sampleExposure = subgroupInclusiveAdd(sampleExposure); - if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleExposure; - barrier(); - for (uint i = 0; i < loopLength; i++) { - if ((gl_SubgroupID & (1u << i)) > 0u) { - sampleExposure += prefixSumCache[(gl_SubgroupID >> i << i) - 1u]; - if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleExposure; - } - barrier(); - } - if (gl_LocalInvocationIndex == 511u) { - float avgExposure = max(sampleExposure.x / sampleExposure.y * 29.3f, 1.0E-10f); - avgExposure = log2(avgExposure); - float prevAvgExposure = log2(texelFetch(pixelData2D, ivec2(0, 0), 0).x); - float frameTimeFixed = frameTime + step(frameCounter, 20) * 100.0f; - float exposureTime = clamp(frameTimeFixed * (2.0f / 1.0f), 0.0f, 1.0f); - avgExposure = mix(prevAvgExposure, avgExposure, exposureTime); - avgExposure = max(exp2(avgExposure), 1.0E-5f); - float exposure = GetExposureValue(avgExposure); - imageStore(img_pixelData2D, ivec2(0, 0), vec4(avgExposure, exposure, 0.0f, 0.0f)); - } -} -)"; - } -} // namespace - -TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanForNarrowSubgroupsProducesValidSpirv) { - using namespace MG_Util::ShaderTranspiler; - - String source = MakeLinearSubgroupPrefixScanShader(); - ASSERT_TRUE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 8, source)); - - EXPECT_NE(source.find("shared float prefixSumCache[1024]"), String::npos) << source; - EXPECT_NE(source.find("mglVirtualSubgroupInvocation"), String::npos) << source; - EXPECT_NE(source.find("for (uint mglPrefixLane"), String::npos) << source; - EXPECT_EQ(source.find("subgroupInclusiveAdd"), String::npos) << source; - EXPECT_EQ(source.find("gl_Subgroup"), String::npos) << source; - - const String onceRewritten = source; - EXPECT_FALSE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 8, source)); - EXPECT_EQ(source, onceRewritten); - - ShaderAttrib shaderAttrib{.shaderType = GL_COMPUTE_SHADER, .sourceStr = source}; - auto shaderResult = ShaderCompiler::CompileShader(shaderAttrib); - ASSERT_TRUE(shaderResult) << shaderResult.error().log << "\nsource:\n" << source; - - ProgramAttrib programAttrib{.shaders = {shaderResult.value()}}; - auto programResult = ShaderCompiler::LinkProgram(programAttrib); - ASSERT_TRUE(programResult) << programResult.error().log; - - ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_COMPUTE_SHADER}, .program = *programResult.value()}; - auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib); - ASSERT_TRUE(binaryResult) << binaryResult.error().log; - ASSERT_EQ(binaryResult->size(), 1u); - - String validationDiagnostics; - spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); - tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, const char* message) { - validationDiagnostics += message; - validationDiagnostics += '\n'; - }); - EXPECT_TRUE(tools.Validate(binaryResult->front())) << validationDiagnostics; - - String spirvText; - ASSERT_TRUE(tools.Disassemble(binaryResult->front(), &spirvText)); - EXPECT_EQ(spirvText.find("OpGroupNonUniform"), String::npos) << spirvText; -} - -TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanStillSupportsWideQualcommSubgroups) { - using namespace MG_Util::ShaderTranspiler; - - String source = MakeLinearSubgroupPrefixScanShader(); - EXPECT_TRUE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 64, source)); - EXPECT_EQ(source.find("subgroupInclusiveAdd"), String::npos) << source; -} - -TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanRejectsOtherStagesAndSubgroupWidths) { - using namespace MG_Util::ShaderTranspiler; - - const String original = MakeLinearSubgroupPrefixScanShader(); - for (const auto& [stage, subgroupSize] : - {std::pair{ShaderStage::Compute, Uint32{0}}, std::pair{ShaderStage::Compute, Uint32{16}}, - std::pair{ShaderStage::Compute, Uint32{32}}, std::pair{ShaderStage::Fragment, Uint32{64}}, - std::pair{ShaderStage::Compute, Uint32{96}}}) { - String source = original; - EXPECT_FALSE(RewriteLinearSubgroupPrefixScanForVulkan(stage, subgroupSize, source)); - EXPECT_EQ(source, original); - } -} - -TEST_F(ProgramUtilTest, RewriteLinearSubgroupPrefixScanRejectsPartialOrUnsafeTemplateMatches) { - using namespace MG_Util::ShaderTranspiler; - - const auto expectUnchanged = [](String source) { - const String original = source; - EXPECT_FALSE(RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage::Compute, 64, source)); - EXPECT_EQ(source, original); - }; - - String wrongLocalSize = MakeLinearSubgroupPrefixScanShader(); - wrongLocalSize.replace(wrongLocalSize.find("local_size_x = 1024"), std::strlen("local_size_x = 1024"), - "local_size_x = 512"); - expectUnchanged(std::move(wrongLocalSize)); - - String cacheHasAnotherUse = MakeLinearSubgroupPrefixScanShader(); - cacheHasAnotherUse.insert(cacheHasAnotherUse.find("float importance"), "prefixSumCache[0] = 0.0f;\n "); - expectUnchanged(std::move(cacheHasAnotherUse)); - - String extraSubgroupBuiltin = MakeLinearSubgroupPrefixScanShader(); - extraSubgroupBuiltin.insert(extraSubgroupBuiltin.find("float importance"), - "uvec4 extraMask = gl_SubgroupEqMask;\n "); - expectUnchanged(std::move(extraSubgroupBuiltin)); - - String alteredBarrier = MakeLinearSubgroupPrefixScanShader(); - alteredBarrier.replace(alteredBarrier.find("barrier();"), std::strlen("barrier();"), "memoryBarrierShared();"); - expectUnchanged(std::move(alteredBarrier)); - - String nestedScan = MakeLinearSubgroupPrefixScanShader(); - nestedScan.insert(nestedScan.find("float prefixSum ="), "if (importance > 0.0f) {\n "); - const SizeT consumerEnd = nestedScan.find(';', nestedScan.find("float warp =")); - ASSERT_NE(consumerEnd, String::npos); - nestedScan.insert(consumerEnd + 1, "\n }"); - expectUnchanged(std::move(nestedScan)); - - // ARB/NV spellings of lane-width-sensitive builtins must block the rewrite exactly - // like their KHR counterparts. - String arbSubgroupBuiltin = MakeLinearSubgroupPrefixScanShader(); - arbSubgroupBuiltin.insert(arbSubgroupBuiltin.find("float importance"), - "uint arbLane = gl_SubGroupInvocationARB;\n "); - expectUnchanged(std::move(arbSubgroupBuiltin)); - - String arbBallotCall = MakeLinearSubgroupPrefixScanShader(); - arbBallotCall.insert(arbBallotCall.find("float importance"), - "uint64_t arbMask = ballotARB(true);\n "); - expectUnchanged(std::move(arbBallotCall)); - - String nvWarpBuiltin = MakeLinearSubgroupPrefixScanShader(); - nvWarpBuiltin.insert(nvWarpBuiltin.find("float importance"), - "uint warpSize = gl_WarpSizeNV;\n "); - expectUnchanged(std::move(nvWarpBuiltin)); - - String nvShuffleCall = MakeLinearSubgroupPrefixScanShader(); - nvShuffleCall.insert(nvShuffleCall.find("float importance"), - "float other = shuffleNV(1.0f, 0u, 32u);\n "); - expectUnchanged(std::move(nvShuffleCall)); -} - -TEST_F(ProgramUtilTest, RewriteWeightedExposureReductionForEightLaneSubgroupsProducesValidSpirv) { - using namespace MG_Util::ShaderTranspiler; - - String source = MakeWeightedExposureSubgroupReductionShader(); - ASSERT_TRUE(RewriteWeightedExposureSubgroupReductionForVulkan(ShaderStage::Compute, 8, source)); - - EXPECT_NE(source.find("mglExposureWeightedSum"), String::npos) << source; - EXPECT_EQ(source.find("subgroupInclusiveAdd"), String::npos) << source; - EXPECT_EQ(source.find("gl_Subgroup"), String::npos) << source; - EXPECT_EQ(source.find("barrier()"), String::npos) << source; - - const String onceRewritten = source; - EXPECT_FALSE(RewriteWeightedExposureSubgroupReductionForVulkan(ShaderStage::Compute, 8, source)); - EXPECT_EQ(source, onceRewritten); - - ShaderAttrib shaderAttrib{.shaderType = GL_COMPUTE_SHADER, .sourceStr = source}; - auto shaderResult = ShaderCompiler::CompileShader(shaderAttrib); - ASSERT_TRUE(shaderResult) << shaderResult.error().log << "\nsource:\n" << source; - - ProgramAttrib programAttrib{.shaders = {shaderResult.value()}}; - auto programResult = ShaderCompiler::LinkProgram(programAttrib); - ASSERT_TRUE(programResult) << programResult.error().log; - - ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_COMPUTE_SHADER}, .program = *programResult.value()}; - auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib); - ASSERT_TRUE(binaryResult) << binaryResult.error().log; - ASSERT_EQ(binaryResult->size(), 1u); - - String validationDiagnostics; - spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); - tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, const char* message) { - validationDiagnostics += message; - validationDiagnostics += '\n'; - }); - EXPECT_TRUE(tools.Validate(binaryResult->front())) << validationDiagnostics; - - String spirvText; - ASSERT_TRUE(tools.Disassemble(binaryResult->front(), &spirvText)); - EXPECT_EQ(spirvText.find("OpGroupNonUniform"), String::npos) << spirvText; -} - -TEST_F(ProgramUtilTest, RewriteWeightedExposureReductionRejectsOtherWidthsAndUnsafeTemplates) { - using namespace MG_Util::ShaderTranspiler; - - const String original = MakeWeightedExposureSubgroupReductionShader(); - const auto expectUnchanged = [&](ShaderStage stage, Uint32 subgroupSize, String source) { - const String before = source; - EXPECT_FALSE(RewriteWeightedExposureSubgroupReductionForVulkan(stage, subgroupSize, source)); - EXPECT_EQ(source, before); - }; - - expectUnchanged(ShaderStage::Compute, 0, original); - expectUnchanged(ShaderStage::Compute, 16, original); - expectUnchanged(ShaderStage::Compute, 32, original); - expectUnchanged(ShaderStage::Fragment, 8, original); - - String wrongScratchSize = original; - wrongScratchSize.replace(wrongScratchSize.find("prefixSumCache[32]"), std::strlen("prefixSumCache[32]"), - "prefixSumCache[64]"); - expectUnchanged(ShaderStage::Compute, 8, std::move(wrongScratchSize)); - - String changedWeighting = original; - changedWeighting.replace(changedWeighting.find("29.3f"), std::strlen("29.3f"), "30.0f"); - expectUnchanged(ShaderStage::Compute, 8, std::move(changedWeighting)); - - String extraSubgroupUse = original; - extraSubgroupUse.insert(extraSubgroupUse.find("void main()"), - "float extraSubgroupUse(float value) { return subgroupAdd(value); }\n"); - expectUnchanged(ShaderStage::Compute, 8, std::move(extraSubgroupUse)); -} - -TEST_F(ProgramUtilTest, NarrowSubgroupQuirkRunsForDirectVulkanWithoutVendorSpoofing) { - using namespace MG_Util::ShaderTranspiler; - - CompileEnv env; - env.backend = BackendType::DirectVulkan; - env.params.SubgroupSize = 8; - env.params.GpuVendor = MG_Backend::GpuVendorKind::Unknown; - env.subgroupPrefixScanQuirk = MG_Config::QuirkOverride::Auto; - - String source = MakeWeightedExposureSubgroupReductionShader(); - PreprocessShaderSource(ShaderStage::Compute, source, env); - EXPECT_NE(source.find("mglExposureWeightedSum"), String::npos) << source; - EXPECT_EQ(source.find("subgroupInclusiveAdd"), String::npos) << source; -} - // The LEXICAL half must fire at the source level (before the parse) for the // preempt-list names - the end-to-end ESSL tests cannot tell which half did the // rename, and for these names the parse would fail without the source rewrite. @@ -2620,9 +2301,6 @@ TEST_F(ProgramUtilTest, CompileEnvFingerprintTracksEveryInput) { otherExtensions.advertisedExtensions.push_back(MobileGL::E_GL_ARB_gpu_shader_int64); EXPECT_NE(ComputeCompileEnvFingerprint(otherExtensions), baseline); - CompileEnv otherQuirk = base; - otherQuirk.subgroupPrefixScanQuirk = MobileGL::MG_Config::QuirkOverride::ForceOn; - EXPECT_NE(ComputeCompileEnvFingerprint(otherQuirk), baseline); } // The no-backend fallback must stay exactly what the pipeline used to do inline: diff --git a/MobileGL/MG_Util/Debug/Log.cpp b/MobileGL/MG_Util/Debug/Log.cpp index c8598f69..c0801875 100644 --- a/MobileGL/MG_Util/Debug/Log.cpp +++ b/MobileGL/MG_Util/Debug/Log.cpp @@ -33,13 +33,13 @@ namespace MobileGL { std::string GetThreadName() { char buffer[64] = {0}; -#if defined(_WIN32) && !defined(__MINGW32__) +#if defined(_WIN32) PWSTR desc = nullptr; if (SUCCEEDED(GetThreadDescription(GetCurrentThread(), &desc))) { WideCharToMultiByte(CP_UTF8, 0, desc, -1, buffer, sizeof(buffer), nullptr, nullptr); LocalFree(desc); } -#elif defined(__ANDROID__) || defined(__linux__) || defined(__APPLE__) || defined(__MINGW32__) +#elif defined(__ANDROID__) || defined(__linux__) || defined(__APPLE__) pthread_getname_np(pthread_self(), buffer, sizeof(buffer)); #endif return buffer[0] ? buffer : "UnknownThread"; diff --git a/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.cpp b/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.cpp index 52c26d9c..cb8e3a46 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.cpp @@ -38,7 +38,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler { HashBytes(state, env.advertisedExtensions.data(), env.advertisedExtensions.size() * sizeof(GLExtension)); } - HashValue(state, env.subgroupPrefixScanQuirk); return state; } @@ -73,8 +72,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler { kFrontendMaxComputeWorkGroupInvocations) : kFrontendMaxComputeWorkGroupInvocations; - env->subgroupPrefixScanQuirk = MG_Config::Features.SubgroupPrefixScanQuirk; - env->fingerprint = ComputeCompileEnvFingerprint(*env); return env; } @@ -84,7 +81,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler { // computed, and this must not run before MG_Config is loaded. static const SharedPtr kDefault = [] { auto env = MakeShared(); - env->subgroupPrefixScanQuirk = MG_Config::Features.SubgroupPrefixScanQuirk; env->fingerprint = ComputeCompileEnvFingerprint(*env); return SharedPtr(Move(env)); }(); diff --git a/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.h b/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.h index f70c3a6b..2eadd65b 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.h +++ b/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.h @@ -12,9 +12,9 @@ #include namespace MobileGL::MG_Util::ShaderTranspiler { - // Everything the shader compile/link pipeline reads from OUTSIDE its own (stage, source) - // inputs: backend identity, backend limits, the advertised extension list, and the one - // config quirk the source rewriter branches on. + // everything outside (stage, source) this reads - advertised extensions and backend limits - + // so the transformation is a pure function of its three arguments and can run on a worker + // thread. // // Why it exists (P1): every one of those reads is a reach-back into // MG_Backend::pActiveBackendObject / gBackendFunctionsTable, and one of them @@ -46,9 +46,6 @@ namespace MobileGL::MG_Util::ShaderTranspiler { MG_Backend::DynamicBackendParameters params{}; // by value, never by reference Vector advertisedExtensions; - // --- config the source rewriter branches on --- - MG_Config::QuirkOverride subgroupPrefixScanQuirk = MG_Config::QuirkOverride::Auto; - Uint64 fingerprint = 0; // set by CaptureCompileEnv() Bool HasBackend() const { return backend != BackendType::Unknown; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index 0414dba0..c0353917 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -182,529 +182,6 @@ namespace { return std::all_of(token.text.begin() + 1, token.text.end(), IsIdentifierChar); } - class TokenCursor { - public: - TokenCursor(const Vector& tokens, SizeT position) : m_tokens(tokens), m_position(position) {} - - bool Consume(const char* expected) { - if (m_position >= m_tokens.size() || m_tokens[m_position].text != expected) { - return false; - } - ++m_position; - return true; - } - - bool ConsumeAnyIdentifier(String& identifier) { - if (m_position >= m_tokens.size() || !IsIdentifierToken(m_tokens[m_position])) { - return false; - } - identifier = m_tokens[m_position++].text; - return true; - } - - bool ConsumeAnyIdentifier() { - if (m_position >= m_tokens.size() || !IsIdentifierToken(m_tokens[m_position])) { - return false; - } - ++m_position; - return true; - } - - bool ConsumeIdentifier(const String& expected) { - if (m_position >= m_tokens.size() || !IsIdentifierToken(m_tokens[m_position]) || - m_tokens[m_position].text != expected) { - return false; - } - ++m_position; - return true; - } - - SizeT Position() const { return m_position; } - - private: - const Vector& m_tokens; - SizeT m_position; - }; - - SizeT CountToken(const Vector& tokens, const String& tokenText) { - return static_cast(std::count_if(tokens.begin(), tokens.end(), - [&](const CodeToken& token) { return token.text == tokenText; })); - } - - bool HasIdentifierWithPrefixOutsideAllowed(const Vector& tokens, const String& prefix, - std::initializer_list allowedIdentifiers) { - return std::any_of(tokens.begin(), tokens.end(), [&](const CodeToken& token) { - if (!IsIdentifierToken(token) || !token.text.starts_with(prefix)) { - return false; - } - return std::none_of(allowedIdentifiers.begin(), allowedIdentifiers.end(), - [&](const char* allowed) { return token.text == allowed; }); - }); - } - - bool MatchTokenSequence(const Vector& tokens, SizeT position, - std::initializer_list expected) { - if (position + expected.size() > tokens.size()) { - return false; - } - for (const char* token : expected) { - if (tokens[position++].text != token) { - return false; - } - } - return true; - } - - SizeT CountTokenSequence(const Vector& tokens, - std::initializer_list expected) { - SizeT count = 0; - for (SizeT position = 0; position < tokens.size(); ++position) { - if (MatchTokenSequence(tokens, position, expected)) { - ++count; - } - } - return count; - } - - bool FindUniqueTokenSequence(const Vector& tokens, const Vector& expected, - SizeT& sourceBegin, SizeT& sourceEnd) { - if (expected.empty() || expected.size() > tokens.size()) { - return false; - } - - SizeT matchCount = 0; - for (SizeT position = 0; position + expected.size() <= tokens.size(); ++position) { - bool matches = true; - for (SizeT expectedIndex = 0; expectedIndex < expected.size(); ++expectedIndex) { - if (tokens[position + expectedIndex].text != expected[expectedIndex].text) { - matches = false; - break; - } - } - if (!matches) { - continue; - } - ++matchCount; - sourceBegin = tokens[position].begin; - sourceEnd = tokens[position + expected.size() - 1].end; - } - return matchCount == 1; - } - - bool IsPowerOfTwo(Uint32 value) { - return value != 0u && (value & (value - 1u)) == 0u; - } - - struct LinearPrefixScanMatch { - SizeT sharedArraySizeBegin = 0; - SizeT sharedArraySizeEnd = 0; - SizeT scanBegin = 0; - SizeT scanEnd = 0; - String cache; - String importance; - String prefixSum; - String loopLength; - String loopIndex; - String sum; - }; - - bool ParseLinearPrefixScanTemplate(const Vector& tokens, LinearPrefixScanMatch& match) { - // The workaround deliberately recognizes one complete algorithm, not merely the - // subgroupInclusiveAdd token. Changing scratch storage is only safe when that storage is - // private to this scan and the workgroup has exactly 1024 X invocations. - SizeT localSizeDeclarationCount = 0; - for (SizeT i = 0; i < tokens.size(); ++i) { - if (MatchTokenSequence(tokens, i, {"layout", "(", "local_size_x", "=", "1024", ")", "in", ";"})) { - ++localSizeDeclarationCount; - } - } - if (localSizeDeclarationCount != 1) { - return false; - } - - SizeT sharedDeclarationIndex = String::npos; - SizeT sharedDeclarationCount = 0; - String cacheName; - for (SizeT i = 0; i + 6 < tokens.size(); ++i) { - if (tokens[i].text != "shared" || tokens[i + 1].text != "float" || !IsIdentifierToken(tokens[i + 2]) || - tokens[i + 3].text != "[" || tokens[i + 4].text != "64" || tokens[i + 5].text != "]" || - tokens[i + 6].text != ";") { - continue; - } - ++sharedDeclarationCount; - sharedDeclarationIndex = i; - cacheName = tokens[i + 2].text; - } - if (sharedDeclarationCount != 1) { - return false; - } - - SizeT scanTokenIndex = String::npos; - SizeT scanCount = 0; - for (SizeT i = 0; i + 7 < tokens.size(); ++i) { - if (tokens[i].text == "float" && IsIdentifierToken(tokens[i + 1]) && tokens[i + 2].text == "=" && - tokens[i + 3].text == "subgroupInclusiveAdd" && tokens[i + 4].text == "(" && - IsIdentifierToken(tokens[i + 5]) && tokens[i + 6].text == ")" && tokens[i + 7].text == ";") { - ++scanCount; - scanTokenIndex = i; - } - } - if (scanCount != 1 || sharedDeclarationIndex >= scanTokenIndex) { - return false; - } - - TokenCursor cursor(tokens, scanTokenIndex); - String prefixSum; - String importance; - String loopLength; - String loopIndex; - String sum; - if (!cursor.Consume("float") || !cursor.ConsumeAnyIdentifier(prefixSum) || !cursor.Consume("=") || - !cursor.Consume("subgroupInclusiveAdd") || !cursor.Consume("(") || - !cursor.ConsumeAnyIdentifier(importance) || !cursor.Consume(")") || !cursor.Consume(";") || - !cursor.Consume("if") || !cursor.Consume("(") || !cursor.Consume("gl_SubgroupInvocationID") || - !cursor.Consume("==") || !cursor.Consume("gl_SubgroupSize") || !cursor.Consume("-") || - !cursor.Consume("1u") || !cursor.Consume(")") || !cursor.ConsumeIdentifier(cacheName) || - !cursor.Consume("[") || !cursor.Consume("gl_SubgroupID") || !cursor.Consume("]") || !cursor.Consume("=") || - !cursor.ConsumeIdentifier(prefixSum) || !cursor.Consume(";") || !cursor.Consume("barrier") || - !cursor.Consume("(") || !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("uint") || - !cursor.ConsumeAnyIdentifier(loopLength) || !cursor.Consume("=") || !cursor.Consume("uint") || - !cursor.Consume("(") || !cursor.Consume("findMSB") || !cursor.Consume("(") || - !cursor.Consume("gl_NumSubgroups") || !cursor.Consume(")") || !cursor.Consume(")") || - !cursor.Consume(";") || !cursor.ConsumeIdentifier(loopLength) || !cursor.Consume("+=") || - !cursor.Consume("uint") || !cursor.Consume("(") || !cursor.Consume("gl_NumSubgroups") || - !cursor.Consume("-") || !cursor.Consume("(") || !cursor.Consume("1u") || !cursor.Consume("<<") || - !cursor.Consume("(") || !cursor.ConsumeIdentifier(loopLength) || !cursor.Consume("-") || - !cursor.Consume("1u") || !cursor.Consume(")") || !cursor.Consume(")") || !cursor.Consume(">") || - !cursor.Consume("0u") || !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("for") || - !cursor.Consume("(") || !cursor.Consume("uint") || !cursor.ConsumeAnyIdentifier(loopIndex) || - !cursor.Consume("=") || !cursor.Consume("0") || !cursor.Consume(";") || - !cursor.ConsumeIdentifier(loopIndex) || !cursor.Consume("<") || !cursor.ConsumeIdentifier(loopLength) || - !cursor.Consume(";") || !cursor.ConsumeIdentifier(loopIndex) || !cursor.Consume("++") || - !cursor.Consume(")") || !cursor.Consume("{") || !cursor.Consume("if") || !cursor.Consume("(") || - !cursor.Consume("(") || !cursor.Consume("gl_SubgroupID") || !cursor.Consume("&") || !cursor.Consume("(") || - !cursor.Consume("1u") || !cursor.Consume("<<") || !cursor.ConsumeIdentifier(loopIndex) || - !cursor.Consume(")") || !cursor.Consume(")") || !cursor.Consume(">") || !cursor.Consume("0u") || - !cursor.Consume(")") || !cursor.Consume("{") || !cursor.ConsumeIdentifier(prefixSum) || - !cursor.Consume("+=") || !cursor.ConsumeIdentifier(cacheName) || !cursor.Consume("[") || - !cursor.Consume("(") || !cursor.Consume("gl_SubgroupID") || !cursor.Consume(">>") || - !cursor.ConsumeIdentifier(loopIndex) || !cursor.Consume("<<") || !cursor.ConsumeIdentifier(loopIndex) || - !cursor.Consume(")") || !cursor.Consume("-") || !cursor.Consume("1u") || !cursor.Consume("]") || - !cursor.Consume(";") || !cursor.Consume("if") || !cursor.Consume("(") || - !cursor.Consume("gl_SubgroupInvocationID") || !cursor.Consume("==") || !cursor.Consume("gl_SubgroupSize") || - !cursor.Consume("-") || !cursor.Consume("1u") || !cursor.Consume(")") || - !cursor.ConsumeIdentifier(cacheName) || !cursor.Consume("[") || !cursor.Consume("gl_SubgroupID") || - !cursor.Consume("]") || !cursor.Consume("=") || !cursor.ConsumeIdentifier(prefixSum) || - !cursor.Consume(";") || !cursor.Consume("}") || !cursor.Consume("barrier") || !cursor.Consume("(") || - !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("}") || !cursor.Consume("if") || - !cursor.Consume("(") || !cursor.Consume("gl_LocalInvocationID") || !cursor.Consume(".") || - !cursor.Consume("x") || !cursor.Consume("==") || !cursor.Consume("uint") || !cursor.Consume("(") || - !cursor.Consume("1024") || !cursor.Consume("-") || !cursor.Consume("1") || !cursor.Consume(")") || - !cursor.Consume(")") || !cursor.ConsumeIdentifier(cacheName) || !cursor.Consume("[") || - !cursor.Consume("0") || !cursor.Consume("]") || !cursor.Consume("=") || - !cursor.ConsumeIdentifier(prefixSum) || !cursor.Consume(";") || !cursor.Consume("barrier") || - !cursor.Consume("(") || !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("float") || - !cursor.ConsumeAnyIdentifier(sum) || !cursor.Consume("=") || !cursor.ConsumeIdentifier(cacheName) || - !cursor.Consume("[") || !cursor.Consume("0") || !cursor.Consume("]") || !cursor.Consume(";")) { - return false; - } - const SizeT scanEndToken = cursor.Position() - 1; - - // Require the scan's immediate consumer as well. This makes the match specific to a - // linear distribution warp, and avoids changing unrelated prefix scans which may rely on - // the implementation's native subgroup partitioning. - if (!cursor.Consume("float") || !cursor.ConsumeAnyIdentifier() || !cursor.Consume("=") || - !cursor.Consume("(") || !cursor.ConsumeIdentifier(prefixSum) || !cursor.Consume("-") || - !cursor.ConsumeIdentifier(importance) || !cursor.Consume(")") || !cursor.Consume("/") || - !cursor.ConsumeIdentifier(sum) || !cursor.Consume("-") || !cursor.Consume("float") || - !cursor.Consume("(") || !cursor.Consume("gl_LocalInvocationID") || !cursor.Consume(".") || - !cursor.Consume("x") || !cursor.Consume("+") || !cursor.Consume("1u") || !cursor.Consume(")") || - !cursor.Consume("/") || !cursor.Consume("float") || !cursor.Consume("(") || !cursor.Consume("1024") || - !cursor.Consume(")") || !cursor.Consume(";")) { - return false; - } - - // No other use may share the scratch array, and no additional subgroup operation or - // builtin may silently retain native-64 semantics after this module becomes virtual-32. - if (CountToken(tokens, cacheName) != 6 || CountToken(tokens, "subgroupInclusiveAdd") != 1 || - CountToken(tokens, "gl_SubgroupInvocationID") != 2 || CountToken(tokens, "gl_SubgroupSize") != 2 || - CountToken(tokens, "gl_SubgroupID") != 4 || CountToken(tokens, "gl_NumSubgroups") != 2 || - CountToken(tokens, "gl_LocalInvocationID") != 2 || CountToken(tokens, "barrier") != 3 || - CountToken(tokens, "findMSB") != 1 || - HasIdentifierWithPrefixOutsideAllowed(tokens, "subgroup", {"subgroupInclusiveAdd"}) || - HasIdentifierWithPrefixOutsideAllowed( - tokens, "gl_Subgroup", - {"gl_SubgroupInvocationID", "gl_SubgroupSize", "gl_SubgroupID", "gl_NumSubgroups"}) || - // ARB/NV spellings of lane-width-sensitive builtins and functions - // (gl_SubGroupSizeARB, ballotARB, gl_WarpSizeNV, shuffleNV, ...) must block the - // rewrite just like their KHR counterparts: they would silently keep native-width - // semantics in a module rewritten to the virtual 32-lane model. - HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SubGroup", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Warp", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Thread", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SMID", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "ballot", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "shuffle", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "readInvocation", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "readFirstInvocation", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "anyInvocation", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "allInvocations", {})) { - return false; - } - - // The scan must be at the top level of the sole main() body. Its existing barriers already - // require uniform control flow; this check prevents us from introducing extra barriers in - // a nested branch or loop. - SizeT mainOpenBrace = String::npos; - SizeT mainCloseBrace = String::npos; - SizeT mainCount = 0; - for (SizeT i = 0; i + 4 < tokens.size(); ++i) { - if (!MatchTokenSequence(tokens, i, {"void", "main", "(", ")", "{"})) { - continue; - } - ++mainCount; - mainOpenBrace = i + 4; - int depth = 1; - for (SizeT j = mainOpenBrace + 1; j < tokens.size(); ++j) { - if (tokens[j].text == "{") - ++depth; - else if (tokens[j].text == "}" && --depth == 0) { - mainCloseBrace = j; - break; - } - } - } - if (mainCount != 1 || mainCloseBrace == String::npos || scanTokenIndex <= mainOpenBrace || - scanEndToken >= mainCloseBrace) { - return false; - } - int depthAtScan = 1; - for (SizeT i = mainOpenBrace + 1; i < scanTokenIndex; ++i) { - if (tokens[i].text == "{") - ++depthAtScan; - else if (tokens[i].text == "}") - --depthAtScan; - } - if (depthAtScan != 1) { - return false; - } - - constexpr const char* injectedNames[] = {"mglPrefixScanLane", "mglVirtualSubgroupInvocation", - "mglVirtualSubgroup", "mglVirtualSubgroupBase", - "mglPrefixLane", "mglVirtualSubgroupCount"}; - for (const char* injectedName : injectedNames) { - if (CountToken(tokens, injectedName) != 0) { - return false; - } - } - - match.sharedArraySizeBegin = tokens[sharedDeclarationIndex + 4].begin; - match.sharedArraySizeEnd = tokens[sharedDeclarationIndex + 4].end; - match.scanBegin = tokens[scanTokenIndex].begin; - match.scanEnd = tokens[scanEndToken].end; - match.cache = std::move(cacheName); - match.importance = std::move(importance); - match.prefixSum = std::move(prefixSum); - match.loopLength = std::move(loopLength); - match.loopIndex = std::move(loopIndex); - match.sum = std::move(sum); - return true; - } - - String BuildLinearPrefixScanReplacement(const LinearPrefixScanMatch& match) { - String replacement; - replacement.reserve(1800); - replacement += "uint mglPrefixScanLane = gl_LocalInvocationID.x;\n"; - replacement += "uint mglVirtualSubgroupInvocation = mglPrefixScanLane & 31u;\n"; - replacement += "uint mglVirtualSubgroup = mglPrefixScanLane >> 5u;\n"; - replacement += "const uint mglVirtualSubgroupCount = 32u;\n"; - replacement += match.cache + "[mglPrefixScanLane] = " + match.importance + ";\n"; - replacement += "barrier();\n"; - replacement += "float " + match.prefixSum + " = 0.0f;\n"; - replacement += "uint mglVirtualSubgroupBase = mglVirtualSubgroup << 5u;\n"; - replacement += "for (uint mglPrefixLane = mglVirtualSubgroupBase; " - "mglPrefixLane <= mglPrefixScanLane; ++mglPrefixLane) {\n"; - replacement += match.prefixSum + " += " + match.cache + "[mglPrefixLane];\n"; - replacement += "}\n"; - replacement += "barrier();\n"; - replacement += "if (mglVirtualSubgroupInvocation == 31u) " + match.cache + - "[mglVirtualSubgroup] = " + match.prefixSum + ";\n"; - replacement += "barrier();\n"; - replacement += "uint " + match.loopLength + " = uint(findMSB(mglVirtualSubgroupCount));\n"; - replacement += - match.loopLength + " += uint(mglVirtualSubgroupCount - (1u << (" + match.loopLength + " - 1u)) > 0u);\n"; - replacement += "for (uint " + match.loopIndex + " = 0u; " + match.loopIndex + " < " + match.loopLength + - "; ++" + match.loopIndex + ") {\n"; - replacement += "if ((mglVirtualSubgroup & (1u << " + match.loopIndex + ")) > 0u) {\n"; - replacement += match.prefixSum + " += " + match.cache + "[(mglVirtualSubgroup >> " + match.loopIndex + " << " + - match.loopIndex + ") - 1u];\n"; - replacement += "if (mglVirtualSubgroupInvocation == 31u) " + match.cache + - "[mglVirtualSubgroup] = " + match.prefixSum + ";\n"; - replacement += "}\nbarrier();\n}\n"; - replacement += "if (mglPrefixScanLane == 1023u) " + match.cache + "[0] = " + match.prefixSum + ";\n"; - replacement += "barrier();\n"; - replacement += "float " + match.sum + " = " + match.cache + "[0];"; - return replacement; - } - - struct WeightedExposureReductionMatch { - SizeT mainBegin = 0; - SizeT mainEnd = 0; - }; - - bool ParseWeightedExposureReductionTemplate(const Vector& tokens, - WeightedExposureReductionMatch& match) { - // IterationRP's exposure pass is a complete, stable shader-pack template. Match the - // whole main body before replacing it: a partial match would be unsafe because the - // replacement deliberately replays the 32x16 sample grid from one invocation. - static const Vector expectedMain = TokenizeCode(R"glsl( -void main() { -vec2 texCoord = (vec2(gl_GlobalInvocationID.xy) + 0.5f) * vec2(1.0f / 32.0f, 1.0f / 16.0f); -vec2 sampleCoord = texCoord * (1.0f / 64.0f); -sampleCoord.x += (15.0f / 32.0f) + pixelSize.x * 12.0f; -float tileExposure = dot(textureLod(colortex2, sampleCoord, 0.0f).rgb, vec3(0.2125f, 0.7154f, 0.0721f)); -vec2 sampleLuminance = vec2(tileExposure, 0.0f); -sampleLuminance = subgroupInclusiveAdd(sampleLuminance); -if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleLuminance; -barrier(); -uint loopLength = uint(findMSB(gl_NumSubgroups)); -loopLength += uint(gl_NumSubgroups - (1u << (loopLength - 1u)) > 0u); -for (uint i = 0; i < loopLength; i++) { -if ((gl_SubgroupID & (1u << i)) > 0u) { -sampleLuminance += prefixSumCache[(gl_SubgroupID >> i << i) - 1u]; -if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleLuminance; -} -barrier(); -} -if (gl_LocalInvocationIndex == 511u) prefixSumCache[0] = sampleLuminance / 512.0f; -; -barrier(); -float avg = prefixSumCache[0].x; -vec2 tileDistance = texCoord * 2.0f - 1.0f; -tileDistance.y /= aspectRatio; -float centerDistance = length(tileDistance); -float tileWeight = remapSaturate(centerDistance, 0.6f, 0.4f); -tileExposure = max(7.0E-7f, tileExposure); -float lumaWeight = avg / tileExposure; -lumaWeight = pow(lumaWeight, remapSaturate(avg, 0.02f, 0.001f) * 0.4f + 0.2f); -tileWeight *= lumaWeight; -vec2 sampleExposure = vec2(tileExposure * tileWeight, tileWeight); -sampleExposure = subgroupInclusiveAdd(sampleExposure); -if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleExposure; -barrier(); -for (uint i = 0; i < loopLength; i++) { -if ((gl_SubgroupID & (1u << i)) > 0u) { -sampleExposure += prefixSumCache[(gl_SubgroupID >> i << i) - 1u]; -if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleExposure; -} -barrier(); -} -if (gl_LocalInvocationIndex == 511u) { -float avgExposure = max(sampleExposure.x / sampleExposure.y * 29.3f, 1.0E-10f); -avgExposure = log2(avgExposure); -float prevAvgExposure = log2(texelFetch(pixelData2D, ivec2(0, 0), 0).x); -float frameTimeFixed = frameTime + step(frameCounter, 20) * 100.0f; -float exposureTime = clamp(frameTimeFixed * (2.0f / 1.0f), 0.0f, 1.0f); -avgExposure = mix(prevAvgExposure, avgExposure, exposureTime); -avgExposure = max(exp2(avgExposure), 1.0E-5f); -float exposure = GetExposureValue(avgExposure); -imageStore(img_pixelData2D, ivec2(0, 0), vec4(avgExposure, exposure, 0.0f, 0.0f)); -} -} -)glsl"); - - if (!FindUniqueTokenSequence(tokens, expectedMain, match.mainBegin, match.mainEnd) || - CountTokenSequence(tokens, - {"layout", "(", "local_size_x", "=", "32", ",", "local_size_y", "=", "16", - ")", "in", ";"}) != 1 || - CountTokenSequence(tokens, {"shared", "vec2", "prefixSumCache", "[", "32", "]", ";"}) != 1 || - CountTokenSequence(tokens, - {"float", "GetExposureValue", "(", "float", "luminance", ")", "{"}) != 1 || - CountTokenSequence(tokens, {"uniform", "int", "frameCounter", ";"}) != 1 || - CountTokenSequence(tokens, {"uniform", "float", "frameTime", ";"}) != 1 || - CountTokenSequence(tokens, {"uniform", "float", "aspectRatio", ";"}) != 1 || - CountTokenSequence(tokens, {"uniform", "vec2", "pixelSize", ";"}) != 1 || - CountTokenSequence(tokens, {"uniform", "sampler2D", "colortex2", ";"}) != 1 || - CountTokenSequence(tokens, {"uniform", "sampler2D", "pixelData2D", ";"}) != 1 || - CountTokenSequence(tokens, - {"layout", "(", "rg16f", ")", "uniform", "image2D", "img_pixelData2D", ";"}) != - 1) { - return false; - } - - // No second user of the scratch array or lane-width-sensitive builtin may survive the - // rewrite. These counts describe the fully matched main body plus its one declaration. - if (CountToken(tokens, "prefixSumCache") != 9 || CountToken(tokens, "GetExposureValue") != 2 || - CountToken(tokens, "subgroupInclusiveAdd") != 2 || - CountToken(tokens, "gl_SubgroupInvocationID") != 4 || CountToken(tokens, "gl_SubgroupSize") != 4 || - CountToken(tokens, "gl_SubgroupID") != 8 || CountToken(tokens, "gl_NumSubgroups") != 2 || - CountToken(tokens, "gl_LocalInvocationIndex") != 2 || CountToken(tokens, "barrier") != 5 || - CountToken(tokens, "findMSB") != 1 || - HasIdentifierWithPrefixOutsideAllowed(tokens, "subgroup", {"subgroupInclusiveAdd"}) || - HasIdentifierWithPrefixOutsideAllowed( - tokens, "gl_Subgroup", - {"gl_SubgroupInvocationID", "gl_SubgroupSize", "gl_SubgroupID", "gl_NumSubgroups"}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SubGroup", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Warp", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Thread", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SMID", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "ballot", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "shuffle", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "readInvocation", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "readFirstInvocation", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "anyInvocation", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "allInvocations", {}) || - HasIdentifierWithPrefixOutsideAllowed(tokens, "mglExposure", {})) { - return false; - } - return true; - } - - String BuildWeightedExposureReductionReplacement() { - return R"glsl(void main() { -if (gl_LocalInvocationIndex != 0u) return; -float mglExposureAverage = 0.0f; -for (uint mglExposureY = 0u; mglExposureY < 16u; ++mglExposureY) { -for (uint mglExposureX = 0u; mglExposureX < 32u; ++mglExposureX) { -vec2 mglExposureTexCoord = (vec2(mglExposureX, mglExposureY) + 0.5f) * vec2(1.0f / 32.0f, 1.0f / 16.0f); -vec2 mglExposureSampleCoord = mglExposureTexCoord * (1.0f / 64.0f); -mglExposureSampleCoord.x += (15.0f / 32.0f) + pixelSize.x * 12.0f; -mglExposureAverage += dot(textureLod(colortex2, mglExposureSampleCoord, 0.0f).rgb, - vec3(0.2125f, 0.7154f, 0.0721f)); -} -} -mglExposureAverage /= 512.0f; -vec2 mglExposureWeightedSum = vec2(0.0f); -for (uint mglExposureY = 0u; mglExposureY < 16u; ++mglExposureY) { -for (uint mglExposureX = 0u; mglExposureX < 32u; ++mglExposureX) { -vec2 mglExposureTexCoord = (vec2(mglExposureX, mglExposureY) + 0.5f) * vec2(1.0f / 32.0f, 1.0f / 16.0f); -vec2 mglExposureSampleCoord = mglExposureTexCoord * (1.0f / 64.0f); -mglExposureSampleCoord.x += (15.0f / 32.0f) + pixelSize.x * 12.0f; -float mglExposureTile = dot(textureLod(colortex2, mglExposureSampleCoord, 0.0f).rgb, - vec3(0.2125f, 0.7154f, 0.0721f)); -vec2 mglExposureDistance = mglExposureTexCoord * 2.0f - 1.0f; -mglExposureDistance.y /= aspectRatio; -float mglExposureWeight = remapSaturate(length(mglExposureDistance), 0.6f, 0.4f); -mglExposureTile = max(7.0E-7f, mglExposureTile); -float mglExposureLumaWeight = mglExposureAverage / mglExposureTile; -mglExposureLumaWeight = pow(mglExposureLumaWeight, - remapSaturate(mglExposureAverage, 0.02f, 0.001f) * 0.4f + 0.2f); -mglExposureWeight *= mglExposureLumaWeight; -mglExposureWeightedSum += vec2(mglExposureTile * mglExposureWeight, mglExposureWeight); -} -} -float avgExposure = max(mglExposureWeightedSum.x / mglExposureWeightedSum.y * 29.3f, 1.0E-10f); -avgExposure = log2(avgExposure); -float prevAvgExposure = log2(texelFetch(pixelData2D, ivec2(0, 0), 0).x); -float frameTimeFixed = frameTime + step(frameCounter, 20) * 100.0f; -float exposureTime = clamp(frameTimeFixed * (2.0f / 1.0f), 0.0f, 1.0f); -avgExposure = mix(prevAvgExposure, avgExposure, exposureTime); -avgExposure = max(exp2(avgExposure), 1.0E-5f); -float exposure = GetExposureValue(avgExposure); -imageStore(img_pixelData2D, ivec2(0, 0), vec4(avgExposure, exposure, 0.0f, 0.0f)); -})glsl"; - } - void SkipDirectiveWhitespace(const MobileGL::String& source, SizeT& pos, SizeT lineEnd) { while (pos < lineEnd && std::isspace(static_cast(source[pos]))) { pos++; @@ -1452,142 +929,6 @@ imageStore(img_pixelData2D, ivec2(0, 0), vec4(avgExposure, exposure, 0.0f, 0.0f) namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { - Bool RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage stage, Uint32 nativeSubgroupSize, - String& source) { - constexpr Uint32 capturedSubgroupSize = 32; - const Bool narrowSubgroup = nativeSubgroupSize != 0u && nativeSubgroupSize < 16u && - capturedSubgroupSize % nativeSubgroupSize == 0u; - const Bool wideSubgroup = nativeSubgroupSize > capturedSubgroupSize && - nativeSubgroupSize % capturedSubgroupSize == 0u; - if (stage != ShaderStage::Compute || !IsPowerOfTwo(nativeSubgroupSize) || - (!narrowSubgroup && !wideSubgroup)) { - return false; - } - - const Vector tokens = TokenizeCode(source); - LinearPrefixScanMatch match; - if (!ParseLinearPrefixScanTemplate(tokens, match)) { - // Diagnosability: when the trigger op is present but the template no longer - // matches (e.g. the pack shipped a new shader revision), the affected device - // silently falls back to the driver's miscompiled path. Make that visible. - if (CountToken(tokens, "subgroupInclusiveAdd") > 0) { - MGLOG_W_ONCE("%s: subgroupInclusiveAdd present but the linear prefix-scan template " - "did not match; the subgroup-compatibility rewrite was NOT applied", - __func__); - } - return false; - } - - const String replacement = BuildLinearPrefixScanReplacement(match); - source.replace(match.scanBegin, match.scanEnd - match.scanBegin, replacement); - // The declaration occurs before the replaced scan, so its original offsets remain - // valid after the first replacement. - source.replace(match.sharedArraySizeBegin, match.sharedArraySizeEnd - match.sharedArraySizeBegin, - "1024"); - return true; - } - - Bool RewriteWeightedExposureSubgroupReductionForVulkan(ShaderStage stage, Uint32 nativeSubgroupSize, - String& source) { - if (stage != ShaderStage::Compute || !IsPowerOfTwo(nativeSubgroupSize) || - nativeSubgroupSize >= 16u) { - return false; - } - - const Vector tokens = TokenizeCode(source); - WeightedExposureReductionMatch match; - if (!ParseWeightedExposureReductionTemplate(tokens, match)) { - if (CountToken(tokens, "subgroupInclusiveAdd") == 2 && - CountToken(tokens, "GetExposureValue") > 0) { - MGLOG_W_ONCE("%s: weighted exposure subgroup reductions were present but the complete " - "template did not match; the narrow-subgroup rewrite was NOT applied", - __func__); - } - return false; - } - - source.replace(match.mainBegin, match.mainEnd - match.mainBegin, - BuildWeightedExposureReductionReplacement()); - return true; - } - - namespace { - struct ShaderSourceQuirkContext { - ShaderStage stage = ShaderStage::Unknown; - BackendType backend = BackendType::Unknown; - MG_Backend::GpuVendorKind vendor = MG_Backend::GpuVendorKind::Unknown; - Uint32 subgroupSize = 0; - }; - - // Device-quirk registry. Every entry is a narrowly scoped source rewrite that - // works around a specific driver defect. A quirk runs when its env override - // forces it on, or when the override is Auto and DeviceApplies matches the - // detected device. ForceOn bypasses only the device gate - each Apply keeps - // its own structural safety checks. Add new per-device workarounds here - // instead of open-coding them in PreprocessShaderSource. - struct ShaderSourceQuirk { - const char* name; - // Reads the override out of the captured env, never out of the live - // MG_Config table: a worker must see the same config the GL thread saw. - MG_Config::QuirkOverride (*GetOverride)(const CompileEnv&); - Bool (*DeviceApplies)(const ShaderSourceQuirkContext&); - Bool (*Apply)(const ShaderSourceQuirkContext&, String&); - }; - - constexpr ShaderSourceQuirk kShaderSourceQuirks[] = { - { - // MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN - "subgroup-prefix-scan-rewrite", - [](const CompileEnv& env) { return env.subgroupPrefixScanQuirk; }, - [](const ShaderSourceQuirkContext& ctx) { - // Narrow subgroups overflow the pack's fixed subgroup-result scratch - // arrays. Qualcomm also miscompiles the recognized float InclusiveScan - // pattern when its native subgroup is wider than the captured 32 lanes. - return ctx.backend == BackendType::DirectVulkan && - ((ctx.subgroupSize != 0u && ctx.subgroupSize < 16u) || - (ctx.vendor == MG_Backend::GpuVendorKind::Qualcomm && - ctx.subgroupSize > 32u)); - }, - [](const ShaderSourceQuirkContext& ctx, String& source) { - const Bool exposureRewritten = RewriteWeightedExposureSubgroupReductionForVulkan( - ctx.stage, ctx.subgroupSize, source); - const Bool prefixScanRewritten = RewriteLinearSubgroupPrefixScanForVulkan( - ctx.stage, ctx.subgroupSize, source); - return exposureRewritten || prefixScanRewritten; - }, - }, - }; - - void ApplyShaderSourceQuirks(const CompileEnv& env, ShaderStage stage, String& source) { - // No backend at capture time means no device to match a quirk against, - // and (as before) no quirk can fire - not even a forced one, because - // every Apply reads device parameters that do not exist yet. - if (!env.HasBackend()) { - return; - } - const ShaderSourceQuirkContext quirkContext{ - stage, - env.backend, - env.params.GpuVendor, - env.params.SubgroupSize, - }; - for (const ShaderSourceQuirk& quirk : kShaderSourceQuirks) { - const MG_Config::QuirkOverride quirkOverride = quirk.GetOverride(env); - if (quirkOverride == MG_Config::QuirkOverride::ForceOff) { - continue; - } - if (quirkOverride == MG_Config::QuirkOverride::Auto && - !quirk.DeviceApplies(quirkContext)) { - continue; - } - if (quirk.Apply(quirkContext, source)) { - MGLOG_D("ApplyShaderSourceQuirks: applied '%s'%s", quirk.name, - quirkOverride == MG_Config::QuirkOverride::ForceOn ? " (forced on)" : ""); - } - } - } - } // namespace - void PreprocessShaderSource(ShaderStage stage, String& source) { PreprocessShaderSource(stage, source, *GetCurrentCompileEnv()); } @@ -1627,7 +968,6 @@ namespace MobileGL { ModernizeLegacyGLSL(stage, source, afterVersion); InjectDepthRangeBuiltinShim(stage, source, afterVersion); - ApplyShaderSourceQuirks(env, stage, source); } Bool RetargetLegacyVersionDirectiveTo460(String& source) { diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.h b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.h index 5589c711..d6e41c71 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.h +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.h @@ -30,17 +30,6 @@ namespace MobileGL { // tests and diagnostics that drive the preprocessor standalone. void PreprocessShaderSource(ShaderStage stage, String& source); - // Some desktop-captured compute shaders size shared scratch for a 32-lane subgroup - // model. Narrow Vulkan subgroups can produce more subgroup totals than that storage - // holds; Qualcomm also miscompiles one recognized scan when its subgroup is wider. - // These entry points replace only complete, known-safe templates with lane-independent - // algorithms. PreprocessShaderSource reaches them through its device-quirk registry; - // MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN=1/0 overrides the automatic device gate. The - // explicit entry points exist for deterministic tests. - Bool RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage stage, Uint32 nativeSubgroupSize, String& source); - Bool RewriteWeightedExposureSubgroupReductionForVulkan(ShaderStage stage, Uint32 nativeSubgroupSize, - String& source); - // Rewrites a "#version 330 core" directive that PreprocessShaderSource normalized down // from a legacy desktop version back up to "#version 460 core". Returns false (leaving // the source untouched) for anything else: ES, compatibility, or an already-modern diff --git a/include/KHR/khrplatform.h b/include/KHR/khrplatform.h index 8ec0d199..ff314690 100644 --- a/include/KHR/khrplatform.h +++ b/include/KHR/khrplatform.h @@ -221,10 +221,13 @@ typedef signed char khronos_int8_t; typedef unsigned char khronos_uint8_t; typedef signed short int khronos_int16_t; typedef unsigned short int khronos_uint16_t; -typedef signed long int khronos_intptr_t; -typedef unsigned long int khronos_uintptr_t; -typedef signed long int khronos_ssize_t; -typedef unsigned long int khronos_usize_t; +/* `long` is 32-bit on LLP64 Windows, including 64-bit MinGW. Use the + * standard pointer-sized integer types so these remain pointer-width there. */ +#include +typedef intptr_t khronos_intptr_t; +typedef uintptr_t khronos_uintptr_t; +typedef intptr_t khronos_ssize_t; +typedef uintptr_t khronos_usize_t; #if KHRONOS_SUPPORT_FLOAT /* diff --git a/tools/trace_replay/trace_cases.json b/tools/trace_replay/trace_cases.json index 5c65965c..b8f4dc85 100644 --- a/tools/trace_replay/trace_cases.json +++ b/tools/trace_replay/trace_cases.json @@ -283,7 +283,8 @@ "trace_archive": "minecraft-1.21.4-fabric-iris-iterationrp-in-world.tgz", "golden": "minecraft-1.21.4-fabric-iris-iterationrp-in-world.0000202020.png", "target_call": 202020, - "timeout_seconds": 1800 + "timeout_seconds": 1800, + "ssim_threshold": 0.98 }, { "name": "minecraft-1.21.4-fabric-iris-bsl-esc-menu-854",