[Fix, Test] (ShaderTranspiler, WGL, TraceReplay): remove subgroup pack quirks and tune iterationRP

This commit is contained in:
2026-08-17 01:55:17 -04:00
parent 0ecfdff4e7
commit 7769156cfc
10 changed files with 14 additions and 1016 deletions
-5
View File
@@ -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
-1
View File
@@ -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");
@@ -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:
+2 -2
View File
@@ -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";
@@ -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<const CompileEnv> kDefault = [] {
auto env = MakeShared<CompileEnv>();
env->subgroupPrefixScanQuirk = MG_Config::Features.SubgroupPrefixScanQuirk;
env->fingerprint = ComputeCompileEnvFingerprint(*env);
return SharedPtr<const CompileEnv>(Move(env));
}();
@@ -12,9 +12,9 @@
#include <MG_Backend/BackendObject.h>
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<GLExtension> 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; }
@@ -182,529 +182,6 @@ namespace {
return std::all_of(token.text.begin() + 1, token.text.end(), IsIdentifierChar);
}
class TokenCursor {
public:
TokenCursor(const Vector<CodeToken>& 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<CodeToken>& m_tokens;
SizeT m_position;
};
SizeT CountToken(const Vector<CodeToken>& tokens, const String& tokenText) {
return static_cast<SizeT>(std::count_if(tokens.begin(), tokens.end(),
[&](const CodeToken& token) { return token.text == tokenText; }));
}
bool HasIdentifierWithPrefixOutsideAllowed(const Vector<CodeToken>& tokens, const String& prefix,
std::initializer_list<const char*> 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<CodeToken>& tokens, SizeT position,
std::initializer_list<const char*> 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<CodeToken>& tokens,
std::initializer_list<const char*> expected) {
SizeT count = 0;
for (SizeT position = 0; position < tokens.size(); ++position) {
if (MatchTokenSequence(tokens, position, expected)) {
++count;
}
}
return count;
}
bool FindUniqueTokenSequence(const Vector<CodeToken>& tokens, const Vector<CodeToken>& 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<CodeToken>& 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<CodeToken>& 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<CodeToken> 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<unsigned char>(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<CodeToken> 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<CodeToken> 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) {
@@ -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
+7 -4
View File
@@ -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 <stdint.h>
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
/*
+2 -1
View File
@@ -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",