mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 12:18:30 +09:00
[Refactor] (ShaderTranspiler, DirectVulkan): replace the hand-rolled SPIR-V word walkers with a DecoratePositionInvariantPass and SPIRV-Reflect-based InstanceIndex detection
This commit is contained in:
@@ -188,6 +188,7 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp
|
||||
|
||||
@@ -312,34 +312,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return targetEnv;
|
||||
}
|
||||
|
||||
// Cheap raw-word scan for an `OpDecorate <id> BuiltIn InstanceIndex` decoration. Used
|
||||
// only to decide whether to warn when shaderDrawParameters is unavailable; a false
|
||||
// negative merely suppresses a diagnostic.
|
||||
Bool SpirvDeclaresInstanceIndexBuiltin(const Vector<Uint>& spirv) {
|
||||
constexpr Uint32 kSpirvMagicNumber = 0x07230203u;
|
||||
constexpr SizeT kHeaderWordCount = 5;
|
||||
if (spirv.size() <= kHeaderWordCount || spirv[0] != kSpirvMagicNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SizeT wordIndex = kHeaderWordCount;
|
||||
while (wordIndex < spirv.size()) {
|
||||
const Uint32 firstWord = spirv[wordIndex];
|
||||
const Uint32 wordCount = firstWord >> 16;
|
||||
const auto opcode = static_cast<spv::Op>(firstWord & 0xffffu);
|
||||
if (wordCount == 0 || wordIndex + wordCount > spirv.size()) {
|
||||
break;
|
||||
}
|
||||
if (opcode == spv::Op::OpDecorate && wordCount >= 4 &&
|
||||
static_cast<spv::Decoration>(spirv[wordIndex + 2]) == spv::Decoration::BuiltIn &&
|
||||
static_cast<spv::BuiltIn>(spirv[wordIndex + 3]) == spv::BuiltIn::InstanceIndex) {
|
||||
return true;
|
||||
}
|
||||
wordIndex += wordCount;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bool IsInterfaceVariableStaticallyUsed(const Vector<Uint>& spirv, Uint32 spirvId) {
|
||||
if (spirv.empty() || spirvId == 0) {
|
||||
return false;
|
||||
@@ -1234,6 +1206,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return false;
|
||||
}
|
||||
|
||||
// glslang's relaxed-Vulkan mode maps GL's gl_InstanceID onto the InstanceIndex builtin.
|
||||
// Without shaderDrawParameters there is no gl_BaseInstance to subtract, so such a shader
|
||||
// cannot be corrected and instanced draws with a non-zero baseInstance misrender; this
|
||||
// detects the case so the user gets one warning instead of silent corruption.
|
||||
Bool ProgramFactory::ReflectedReadsInstanceIndexBuiltin(const SpvReflectShaderModule& reflectModule) {
|
||||
for (Uint32 entryIndex = 0; entryIndex < reflectModule.entry_point_count; ++entryIndex) {
|
||||
const SpvReflectEntryPoint& entryPoint = reflectModule.entry_points[entryIndex];
|
||||
for (Uint32 variableIndex = 0; variableIndex < entryPoint.input_variable_count; ++variableIndex) {
|
||||
const SpvReflectInterfaceVariable* variable = entryPoint.input_variables[variableIndex];
|
||||
if (variable != nullptr &&
|
||||
(variable->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0 &&
|
||||
variable->built_in == SpvBuiltInInstanceIndex) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) {
|
||||
switch (stage) {
|
||||
case ShaderStage::Vertex:
|
||||
@@ -1481,6 +1472,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_shaderDrawParametersEnabled && ReflectedReadsInstanceIndexBuiltin(reflectModule)) {
|
||||
static Bool s_warnedInstanceIndexUnsupported = false;
|
||||
if (!s_warnedInstanceIndexUnsupported) {
|
||||
s_warnedInstanceIndexUnsupported = true;
|
||||
MGLOG_W("ProgramFactory: shaderDrawParameters is unavailable; gl_InstanceID cannot be "
|
||||
"rebased and instanced draws with a non-zero baseInstance may render incorrectly");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t inputCount = 0;
|
||||
SpvReflectResult reflectResult = spvReflectEnumerateInputVariables(&reflectModule, &inputCount, nullptr);
|
||||
MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
@@ -1892,8 +1892,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
moduleSpirvs[i], invariantSpirv)) {
|
||||
moduleSpirvs[i] = std::move(invariantSpirv);
|
||||
} else {
|
||||
MGLOG_W("ProgramFactory: position-invariant decoration failed for program %u; "
|
||||
"keeping the original module",
|
||||
// The pass round-trips through SPIRV-Tools IR, so an unparseable module
|
||||
// fails open and keeps the undecorated words - which silently reinstates
|
||||
// the multi-pass invariance bug rather than breaking anything loudly.
|
||||
MGLOG_E("ProgramFactory: position-invariant decoration failed for program %u; "
|
||||
"keeping the original module - multi-pass depth-equality chains "
|
||||
"(e.g. MC 26.3 OIT clouds) may drop primitives on this device",
|
||||
program.GetExternalIndex());
|
||||
}
|
||||
}
|
||||
@@ -1902,24 +1906,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// gl_InstanceIndex, which wrongly includes the draw's baseInstance. Rebase vertex-stage
|
||||
// loads to (InstanceIndex - BaseInstance) so shaders observe GL semantics. Reflection
|
||||
// below runs on the rebased words so the added BaseInstance builtin stays consistent.
|
||||
if (shaders[i] && shaders[i]->GetShaderStage() == ShaderStage::Vertex) {
|
||||
if (m_shaderDrawParametersEnabled) {
|
||||
Vector<Uint> rebasedSpirv;
|
||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::RebaseInstanceIndexForVulkan(moduleSpirvs[i],
|
||||
rebasedSpirv)) {
|
||||
moduleSpirvs[i] = std::move(rebasedSpirv);
|
||||
} else {
|
||||
MGLOG_E("ProgramFactory: failed to rebase gl_InstanceID for program %u; "
|
||||
"instanced draws with a non-zero baseInstance may render incorrectly",
|
||||
program.GetExternalIndex());
|
||||
}
|
||||
} else if (SpirvDeclaresInstanceIndexBuiltin(moduleSpirvs[i])) {
|
||||
static Bool s_warnedInstanceIndexUnsupported = false;
|
||||
if (!s_warnedInstanceIndexUnsupported) {
|
||||
s_warnedInstanceIndexUnsupported = true;
|
||||
MGLOG_W("ProgramFactory: shaderDrawParameters is unavailable; gl_InstanceID cannot be "
|
||||
"rebased and instanced draws with a non-zero baseInstance may render incorrectly");
|
||||
}
|
||||
// The unsupported-device counterpart of this rebase (warning when a shader reads
|
||||
// the builtin but shaderDrawParameters is missing) rides along with
|
||||
// ReflectVertexInputs, which already reflects this stage.
|
||||
if (shaders[i] && shaders[i]->GetShaderStage() == ShaderStage::Vertex &&
|
||||
m_shaderDrawParametersEnabled) {
|
||||
Vector<Uint> rebasedSpirv;
|
||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::RebaseInstanceIndexForVulkan(moduleSpirvs[i],
|
||||
rebasedSpirv)) {
|
||||
moduleSpirvs[i] = std::move(rebasedSpirv);
|
||||
} else {
|
||||
MGLOG_E("ProgramFactory: failed to rebase gl_InstanceID for program %u; "
|
||||
"instanced draws with a non-zero baseInstance may render incorrectly",
|
||||
program.GetExternalIndex());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// can be pinned by tests. A false negative loses the exemption, so such a shader is
|
||||
// stripped conservatively and forfeits its depth write.
|
||||
static Bool ReflectedFragmentReplacesDepth(const SpvReflectShaderModule& reflectModule);
|
||||
// True when an entry point reads the InstanceIndex builtin. Only gates a diagnostic:
|
||||
// without shaderDrawParameters such a shader cannot have gl_InstanceID rebased.
|
||||
static Bool ReflectedReadsInstanceIndexBuiltin(const SpvReflectShaderModule& reflectModule);
|
||||
|
||||
private:
|
||||
struct ProgramLookupCache {
|
||||
|
||||
@@ -73,6 +73,7 @@ add_subdirectory(VertexArray)
|
||||
add_subdirectory(Program)
|
||||
add_subdirectory(Query)
|
||||
add_subdirectory(Pipeline)
|
||||
add_subdirectory(ShaderTranspiler)
|
||||
if (ENABLE_INTEGRATION_TESTS)
|
||||
add_subdirectory(Backend/DirectVulkan)
|
||||
endif()
|
||||
|
||||
@@ -92,6 +92,93 @@ namespace {
|
||||
0x00000005u, 0x0003003eu, 0x00000009u, 0x0000000bu, 0x000100fdu, 0x00010038u,
|
||||
};
|
||||
|
||||
|
||||
// glslangValidator -V output for a vertex shader reading gl_InstanceIndex:
|
||||
// #version 450
|
||||
// layout(location = 0) in vec4 inPos;
|
||||
// void main() { gl_Position = inPos + vec4(float(gl_InstanceIndex)); }
|
||||
constexpr Uint32 kInstanceIndexVertexSpirv[] = {
|
||||
0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001bu, 0x00000000u, 0x00020011u,
|
||||
0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu,
|
||||
0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0008000fu, 0x00000000u,
|
||||
0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00000014u,
|
||||
0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du,
|
||||
0x00000000u, 0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u,
|
||||
0x00000000u, 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, 0x7469736fu,
|
||||
0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu,
|
||||
0x657a6953u, 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u,
|
||||
0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u,
|
||||
0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000000du,
|
||||
0x00000000u, 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00070005u,
|
||||
0x00000014u, 0x495f6c67u, 0x6174736eu, 0x4965636eu, 0x7865646eu, 0x00000000u,
|
||||
0x00030047u, 0x0000000bu, 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u,
|
||||
0x0000000bu, 0x00000000u, 0x00050048u, 0x0000000bu, 0x00000001u, 0x0000000bu,
|
||||
0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u,
|
||||
0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u,
|
||||
0x00000011u, 0x0000001eu, 0x00000000u, 0x00040047u, 0x00000014u, 0x0000000bu,
|
||||
0x0000002bu, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u,
|
||||
0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u,
|
||||
0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu,
|
||||
0x00000008u, 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u,
|
||||
0x00000009u, 0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au,
|
||||
0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu,
|
||||
0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u,
|
||||
0x00000001u, 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u,
|
||||
0x00000010u, 0x00000001u, 0x00000007u, 0x0004003bu, 0x00000010u, 0x00000011u,
|
||||
0x00000001u, 0x00040020u, 0x00000013u, 0x00000001u, 0x0000000eu, 0x0004003bu,
|
||||
0x00000013u, 0x00000014u, 0x00000001u, 0x00040020u, 0x00000019u, 0x00000003u,
|
||||
0x00000007u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u,
|
||||
0x000200f8u, 0x00000005u, 0x0004003du, 0x00000007u, 0x00000012u, 0x00000011u,
|
||||
0x0004003du, 0x0000000eu, 0x00000015u, 0x00000014u, 0x0004006fu, 0x00000006u,
|
||||
0x00000016u, 0x00000015u, 0x00070050u, 0x00000007u, 0x00000017u, 0x00000016u,
|
||||
0x00000016u, 0x00000016u, 0x00000016u, 0x00050081u, 0x00000007u, 0x00000018u,
|
||||
0x00000012u, 0x00000017u, 0x00050041u, 0x00000019u, 0x0000001au, 0x0000000du,
|
||||
0x0000000fu, 0x0003003eu, 0x0000001au, 0x00000018u, 0x000100fdu, 0x00010038u,
|
||||
};
|
||||
|
||||
|
||||
// Same, but reading gl_VertexIndex instead: a DIFFERENT input builtin. glslang emits
|
||||
// this for GL's gl_VertexID, so nearly every real vertex shader has one - it is what
|
||||
// separates "declares some builtin" from "declares the InstanceIndex builtin".
|
||||
constexpr Uint32 kVertexIndexVertexSpirv[] = {
|
||||
0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001bu, 0x00000000u, 0x00020011u,
|
||||
0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu,
|
||||
0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0008000fu, 0x00000000u,
|
||||
0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00000014u,
|
||||
0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du,
|
||||
0x00000000u, 0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u,
|
||||
0x00000000u, 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, 0x7469736fu,
|
||||
0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu,
|
||||
0x657a6953u, 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u,
|
||||
0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u,
|
||||
0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000000du,
|
||||
0x00000000u, 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00060005u,
|
||||
0x00000014u, 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, 0x00030047u,
|
||||
0x0000000bu, 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu,
|
||||
0x00000000u, 0x00050048u, 0x0000000bu, 0x00000001u, 0x0000000bu, 0x00000001u,
|
||||
0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u,
|
||||
0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000011u,
|
||||
0x0000001eu, 0x00000000u, 0x00040047u, 0x00000014u, 0x0000000bu, 0x0000002au,
|
||||
0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u,
|
||||
0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u,
|
||||
0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u,
|
||||
0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u,
|
||||
0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au, 0x0000000au,
|
||||
0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, 0x0000000cu,
|
||||
0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, 0x00000001u,
|
||||
0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u, 0x00000010u,
|
||||
0x00000001u, 0x00000007u, 0x0004003bu, 0x00000010u, 0x00000011u, 0x00000001u,
|
||||
0x00040020u, 0x00000013u, 0x00000001u, 0x0000000eu, 0x0004003bu, 0x00000013u,
|
||||
0x00000014u, 0x00000001u, 0x00040020u, 0x00000019u, 0x00000003u, 0x00000007u,
|
||||
0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u,
|
||||
0x00000005u, 0x0004003du, 0x00000007u, 0x00000012u, 0x00000011u, 0x0004003du,
|
||||
0x0000000eu, 0x00000015u, 0x00000014u, 0x0004006fu, 0x00000006u, 0x00000016u,
|
||||
0x00000015u, 0x00070050u, 0x00000007u, 0x00000017u, 0x00000016u, 0x00000016u,
|
||||
0x00000016u, 0x00000016u, 0x00050081u, 0x00000007u, 0x00000018u, 0x00000012u,
|
||||
0x00000017u, 0x00050041u, 0x00000019u, 0x0000001au, 0x0000000du, 0x0000000fu,
|
||||
0x0003003eu, 0x0000001au, 0x00000018u, 0x000100fdu, 0x00010038u,
|
||||
};
|
||||
|
||||
// Owns the reflection module so each test case cleans up after itself.
|
||||
class ReflectModule {
|
||||
public:
|
||||
@@ -339,3 +426,31 @@ TEST(ReflectedFragmentReplacesDepth, ReflectedFlagFlipsTheStripDecision) {
|
||||
payload.fragmentReplacesDepth = ProgramFactory::ReflectedFragmentReplacesDepth(depthWriter.Get());
|
||||
EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload));
|
||||
}
|
||||
|
||||
|
||||
// --- InstanceIndex reflection feeding the shaderDrawParameters diagnostic ---
|
||||
|
||||
TEST(ReflectedReadsInstanceIndexBuiltin, TrueForAShaderReadingInstanceIndex) {
|
||||
const ReflectModule module(kInstanceIndexVertexSpirv);
|
||||
ASSERT_TRUE(module.Created());
|
||||
EXPECT_TRUE(ProgramFactory::ReflectedReadsInstanceIndexBuiltin(module.Get()));
|
||||
}
|
||||
|
||||
TEST(ReflectedReadsInstanceIndexBuiltin, FalseForAShaderReadingADifferentBuiltin) {
|
||||
// Discriminates the builtin's identity, not merely its presence: weakening the check to
|
||||
// "has any BuiltIn decoration" would fire the diagnostic on every real vertex shader.
|
||||
const ReflectModule module(kVertexIndexVertexSpirv);
|
||||
ASSERT_TRUE(module.Created());
|
||||
EXPECT_FALSE(ProgramFactory::ReflectedReadsInstanceIndexBuiltin(module.Get()));
|
||||
}
|
||||
|
||||
TEST(ReflectedReadsInstanceIndexBuiltin, FalseForAShaderWithNoInputBuiltins) {
|
||||
const ReflectModule module(kPlainFragmentSpirv);
|
||||
ASSERT_TRUE(module.Created());
|
||||
EXPECT_FALSE(ProgramFactory::ReflectedReadsInstanceIndexBuiltin(module.Get()));
|
||||
}
|
||||
|
||||
TEST(ReflectedReadsInstanceIndexBuiltin, FalseForAnEmptyModule) {
|
||||
SpvReflectShaderModule emptyModule{};
|
||||
EXPECT_FALSE(ProgramFactory::ReflectedReadsInstanceIndexBuiltin(emptyModule));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
add_executable(
|
||||
SpirvPassTest
|
||||
SpirvPassTest.cpp
|
||||
)
|
||||
|
||||
target_include_directories(SpirvPassTest PRIVATE
|
||||
${MGL_ROOT}/include
|
||||
${MGL_ROOT}/MobileGL
|
||||
${MGL_ROOT}/3rdparty/SPIRV-Reflect
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
SpirvPassTest PRIVATE
|
||||
GTest::gtest_main
|
||||
${LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
target_compile_options(SpirvPassTest PRIVATE /Zc:preprocessor)
|
||||
endif()
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(SpirvPassTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
@@ -0,0 +1,170 @@
|
||||
// MobileGL - MobileGL/MG_Test/ShaderTranspiler/SpirvPassTest.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
||||
|
||||
#include <spirv_reflect.h>
|
||||
|
||||
using namespace MobileGL;
|
||||
using MobileGL::MG_Util::ShaderTranspiler::ShaderCompiler;
|
||||
|
||||
namespace {
|
||||
// glslangValidator -V output. Both are vertex shaders writing gl_Position through
|
||||
// the gl_PerVertex block, i.e. the Position builtin arrives as OpMemberDecorate rather
|
||||
// than a plain OpDecorate - the shape real glslang output actually takes.
|
||||
|
||||
// #version 450
|
||||
// layout(location = 0) in vec4 inPos;
|
||||
// void main() { gl_Position = inPos; }
|
||||
constexpr Uint32 kPlainVertexSpirv[] = {
|
||||
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000015u, 0x00000000u, 0x00020011u,
|
||||
0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu,
|
||||
0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000000u,
|
||||
0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00030003u,
|
||||
0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u,
|
||||
0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u,
|
||||
0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u,
|
||||
0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u,
|
||||
0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu,
|
||||
0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u,
|
||||
0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000000du, 0x00000000u,
|
||||
0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00030047u, 0x0000000bu,
|
||||
0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u,
|
||||
0x00050048u, 0x0000000bu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u,
|
||||
0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000000bu,
|
||||
0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000011u, 0x0000001eu,
|
||||
0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u,
|
||||
0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u,
|
||||
0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu,
|
||||
0x00000008u, 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u,
|
||||
0x00000009u, 0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au,
|
||||
0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu,
|
||||
0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u,
|
||||
0x00000001u, 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u,
|
||||
0x00000010u, 0x00000001u, 0x00000007u, 0x0004003bu, 0x00000010u, 0x00000011u,
|
||||
0x00000001u, 0x00040020u, 0x00000013u, 0x00000003u, 0x00000007u, 0x00050036u,
|
||||
0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u,
|
||||
0x0004003du, 0x00000007u, 0x00000012u, 0x00000011u, 0x00050041u, 0x00000013u,
|
||||
0x00000014u, 0x0000000du, 0x0000000fu, 0x0003003eu, 0x00000014u, 0x00000012u,
|
||||
0x000100fdu, 0x00010038u,
|
||||
};
|
||||
|
||||
// ... plus `invariant gl_Position;` - already carries OpMemberDecorate %gl_PerVertex 0
|
||||
// Invariant, so the pass must not add a duplicate.
|
||||
constexpr Uint32 kAlreadyInvariantVertexSpirv[] = {
|
||||
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000015u, 0x00000000u, 0x00020011u,
|
||||
0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu,
|
||||
0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000000u,
|
||||
0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00030003u,
|
||||
0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u,
|
||||
0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u,
|
||||
0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u,
|
||||
0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u,
|
||||
0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu,
|
||||
0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u,
|
||||
0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000000du, 0x00000000u,
|
||||
0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00030047u, 0x0000000bu,
|
||||
0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u,
|
||||
0x00040048u, 0x0000000bu, 0x00000000u, 0x00000012u, 0x00050048u, 0x0000000bu,
|
||||
0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u,
|
||||
0x0000000bu, 0x00000003u, 0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu,
|
||||
0x00000004u, 0x00040047u, 0x00000011u, 0x0000001eu, 0x00000000u, 0x00020013u,
|
||||
0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u,
|
||||
0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040015u,
|
||||
0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u, 0x00000009u,
|
||||
0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u, 0x0006001eu,
|
||||
0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au, 0x0000000au, 0x00040020u,
|
||||
0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du,
|
||||
0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, 0x00000001u, 0x0004002bu,
|
||||
0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u, 0x00000010u, 0x00000001u,
|
||||
0x00000007u, 0x0004003bu, 0x00000010u, 0x00000011u, 0x00000001u, 0x00040020u,
|
||||
0x00000013u, 0x00000003u, 0x00000007u, 0x00050036u, 0x00000002u, 0x00000004u,
|
||||
0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000007u,
|
||||
0x00000012u, 0x00000011u, 0x00050041u, 0x00000013u, 0x00000014u, 0x0000000du,
|
||||
0x0000000fu, 0x0003003eu, 0x00000014u, 0x00000012u, 0x000100fdu, 0x00010038u,
|
||||
};
|
||||
|
||||
// OpMemberDecorate <struct-id> <member> <decoration>
|
||||
constexpr Uint32 kOpMemberDecorate = 72;
|
||||
constexpr Uint32 kDecorationInvariant = 18;
|
||||
constexpr Uint32 kSpirvHeaderWordCount = 5;
|
||||
|
||||
// Test-side reference walker. Deliberately independent of the production code so a bug in
|
||||
// the pass cannot hide behind the same helper; only used to count what the pass emitted.
|
||||
Uint32 CountInvariantMemberDecorations(const Vector<Uint32>& spirv) {
|
||||
Uint32 count = 0;
|
||||
for (SizeT i = kSpirvHeaderWordCount; i < spirv.size();) {
|
||||
const Uint32 wordCount = spirv[i] >> 16;
|
||||
const Uint32 opcode = spirv[i] & 0xFFFFu;
|
||||
if (wordCount == 0 || i + wordCount > spirv.size()) {
|
||||
break;
|
||||
}
|
||||
if (opcode == kOpMemberDecorate && wordCount >= 4 && spirv[i + 3] == kDecorationInvariant) {
|
||||
++count;
|
||||
}
|
||||
i += wordCount;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
template <SizeT WordCount>
|
||||
Vector<Uint32> ToVector(const Uint32 (&words)[WordCount]) {
|
||||
return Vector<Uint32>(words, words + WordCount);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// --- DecoratePositionInvariantPass ---
|
||||
|
||||
TEST(DecoratePositionInvariant, AddsInvariantToThePositionMember) {
|
||||
const Vector<Uint32> input = ToVector(kPlainVertexSpirv);
|
||||
ASSERT_EQ(CountInvariantMemberDecorations(input), 0u);
|
||||
|
||||
Vector<Uint32> output;
|
||||
ASSERT_TRUE(ShaderCompiler::DecoratePositionInvariantForVulkan(input, output));
|
||||
EXPECT_EQ(CountInvariantMemberDecorations(output), 1u);
|
||||
}
|
||||
|
||||
TEST(DecoratePositionInvariant, DoesNotDuplicateAnExistingInvariant) {
|
||||
const Vector<Uint32> input = ToVector(kAlreadyInvariantVertexSpirv);
|
||||
ASSERT_EQ(CountInvariantMemberDecorations(input), 1u);
|
||||
|
||||
Vector<Uint32> output;
|
||||
ASSERT_TRUE(ShaderCompiler::DecoratePositionInvariantForVulkan(input, output));
|
||||
EXPECT_EQ(CountInvariantMemberDecorations(output), 1u);
|
||||
// The pass reports SuccessWithoutChange here, and SPIRV-Tools asserts (in assert-enabled
|
||||
// builds) that such a run round-trips byte-identically. Pin that from the outside so an
|
||||
// assert-enabled CI build cannot be the first thing to discover a violation.
|
||||
EXPECT_EQ(output, input);
|
||||
}
|
||||
|
||||
TEST(DecoratePositionInvariant, IsIdempotent) {
|
||||
Vector<Uint32> once;
|
||||
ASSERT_TRUE(ShaderCompiler::DecoratePositionInvariantForVulkan(ToVector(kPlainVertexSpirv), once));
|
||||
Vector<Uint32> twice;
|
||||
ASSERT_TRUE(ShaderCompiler::DecoratePositionInvariantForVulkan(once, twice));
|
||||
EXPECT_EQ(CountInvariantMemberDecorations(twice), 1u);
|
||||
}
|
||||
|
||||
TEST(DecoratePositionInvariant, OutputStaysAReflectableModule) {
|
||||
Vector<Uint32> output;
|
||||
ASSERT_TRUE(ShaderCompiler::DecoratePositionInvariantForVulkan(ToVector(kPlainVertexSpirv), output));
|
||||
|
||||
SpvReflectShaderModule module{};
|
||||
ASSERT_EQ(spvReflectCreateShaderModule(output.size() * sizeof(Uint32), output.data(), &module),
|
||||
SPV_REFLECT_RESULT_SUCCESS);
|
||||
EXPECT_EQ(module.entry_point_count, 1u);
|
||||
spvReflectDestroyShaderModule(&module);
|
||||
}
|
||||
|
||||
TEST(DecoratePositionInvariant, RejectsGarbageInput) {
|
||||
const Vector<Uint32> notSpirv{0xdeadbeefu, 0u, 0u, 0u, 0u};
|
||||
Vector<Uint32> output;
|
||||
EXPECT_FALSE(ShaderCompiler::DecoratePositionInvariantForVulkan(notSpirv, output));
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "SpirvPasses/FlattenInterfaceStructPass.h"
|
||||
#include "SpirvPasses/RenameSamplerFunctionParameterPass.h"
|
||||
#include "SpirvPasses/DecomposeWorkgroupVec3Pass.h"
|
||||
#include "SpirvPasses/DecoratePositionInvariantPass.h"
|
||||
#include "SpirvPasses/LowerDrawParametersPass.h"
|
||||
#include "SpirvPasses/RebaseInstanceIndexPass.h"
|
||||
#include "SpirvPasses/StripUboMemberRelaxedPrecisionPass.h"
|
||||
@@ -347,71 +348,14 @@ namespace MobileGL {
|
||||
|
||||
bool ShaderCompiler::DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary) {
|
||||
static constexpr Uint32 kHeaderWords = 5;
|
||||
static constexpr Uint32 kOpDecorate = 71;
|
||||
static constexpr Uint32 kOpMemberDecorate = 72;
|
||||
static constexpr Uint32 kDecorationInvariant = 18;
|
||||
static constexpr Uint32 kDecorationBuiltIn = 11;
|
||||
static constexpr Uint32 kBuiltInPosition = 0;
|
||||
if (inputBinary.size() < kHeaderWords) {
|
||||
return false;
|
||||
}
|
||||
using namespace spvtools;
|
||||
OptimizerOptions options;
|
||||
options.set_run_validator(false);
|
||||
|
||||
// First pass: find targets that already carry Invariant so we never duplicate.
|
||||
struct MemberKey {
|
||||
Uint32 id;
|
||||
Uint32 member;
|
||||
bool operator==(const MemberKey& o) const { return id == o.id && member == o.member; }
|
||||
};
|
||||
Vector<Uint32> invariantIds;
|
||||
Vector<MemberKey> invariantMembers;
|
||||
for (SizeT i = kHeaderWords; i < inputBinary.size();) {
|
||||
const Uint32 word0 = inputBinary[i];
|
||||
const Uint32 opcode = word0 & 0xFFFFu;
|
||||
const Uint32 length = word0 >> 16;
|
||||
if (length == 0 || i + length > inputBinary.size()) {
|
||||
return false;
|
||||
}
|
||||
if (opcode == kOpDecorate && length >= 3 && inputBinary[i + 2] == kDecorationInvariant) {
|
||||
invariantIds.push_back(inputBinary[i + 1]);
|
||||
} else if (opcode == kOpMemberDecorate && length >= 4 &&
|
||||
inputBinary[i + 3] == kDecorationInvariant) {
|
||||
invariantMembers.push_back({inputBinary[i + 1], inputBinary[i + 2]});
|
||||
}
|
||||
i += length;
|
||||
}
|
||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||
optimizer.RegisterPass(DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass());
|
||||
|
||||
outputBinary.clear();
|
||||
outputBinary.reserve(inputBinary.size() + 8);
|
||||
outputBinary.insert(outputBinary.end(), inputBinary.begin(), inputBinary.begin() + kHeaderWords);
|
||||
for (SizeT i = kHeaderWords; i < inputBinary.size();) {
|
||||
const Uint32 word0 = inputBinary[i];
|
||||
const Uint32 opcode = word0 & 0xFFFFu;
|
||||
const Uint32 length = word0 >> 16;
|
||||
outputBinary.insert(outputBinary.end(), inputBinary.begin() + i,
|
||||
inputBinary.begin() + i + length);
|
||||
if (opcode == kOpDecorate && length == 4 &&
|
||||
inputBinary[i + 2] == kDecorationBuiltIn && inputBinary[i + 3] == kBuiltInPosition) {
|
||||
const Uint32 target = inputBinary[i + 1];
|
||||
if (std::find(invariantIds.begin(), invariantIds.end(), target) == invariantIds.end()) {
|
||||
outputBinary.push_back((3u << 16) | kOpDecorate);
|
||||
outputBinary.push_back(target);
|
||||
outputBinary.push_back(kDecorationInvariant);
|
||||
}
|
||||
} else if (opcode == kOpMemberDecorate && length == 5 &&
|
||||
inputBinary[i + 3] == kDecorationBuiltIn && inputBinary[i + 4] == kBuiltInPosition) {
|
||||
const MemberKey key{inputBinary[i + 1], inputBinary[i + 2]};
|
||||
if (std::find(invariantMembers.begin(), invariantMembers.end(), key) ==
|
||||
invariantMembers.end()) {
|
||||
outputBinary.push_back((4u << 16) | kOpMemberDecorate);
|
||||
outputBinary.push_back(key.id);
|
||||
outputBinary.push_back(key.member);
|
||||
outputBinary.push_back(kDecorationInvariant);
|
||||
}
|
||||
}
|
||||
i += length;
|
||||
}
|
||||
return true;
|
||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||
}
|
||||
|
||||
bool ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "DecoratePositionInvariantPass.h"
|
||||
|
||||
#include "spirv.hpp"
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/util/make_unique.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
namespace {
|
||||
using spvtools::opt::Instruction;
|
||||
using spvtools::opt::IRContext;
|
||||
using spvtools::opt::Operand;
|
||||
|
||||
// Identifies one member of a decorated struct (gl_PerVertex's Position slot).
|
||||
struct MemberKey {
|
||||
uint32_t id = 0;
|
||||
uint32_t member = 0;
|
||||
bool operator==(const MemberKey& other) const {
|
||||
return id == other.id && member == other.member;
|
||||
}
|
||||
};
|
||||
|
||||
// OpDecorate <target-id> <decoration> [literals...]
|
||||
// OpMemberDecorate <struct-id> <member> <decoration> [literals...]
|
||||
constexpr uint32_t kDecorateTargetOperand = 0;
|
||||
constexpr uint32_t kDecorateDecorationOperand = 1;
|
||||
constexpr uint32_t kDecorateBuiltInOperand = 2;
|
||||
constexpr uint32_t kMemberDecorateStructOperand = 0;
|
||||
constexpr uint32_t kMemberDecorateMemberOperand = 1;
|
||||
constexpr uint32_t kMemberDecorateDecorationOperand = 2;
|
||||
constexpr uint32_t kMemberDecorateBuiltInOperand = 3;
|
||||
} // namespace
|
||||
|
||||
spvtools::opt::Pass::Status DecoratePositionInvariantPass::Process() {
|
||||
auto* irContext = context();
|
||||
|
||||
// Collect first: AddAnnotationInst mutates the list being walked.
|
||||
std::vector<uint32_t> invariantIds;
|
||||
std::vector<MemberKey> invariantMembers;
|
||||
std::vector<uint32_t> positionIds;
|
||||
std::vector<MemberKey> positionMembers;
|
||||
|
||||
for (const Instruction& annotation : irContext->annotations()) {
|
||||
if (annotation.opcode() == spv::Op::OpDecorate) {
|
||||
if (annotation.NumInOperands() <= kDecorateDecorationOperand) {
|
||||
continue;
|
||||
}
|
||||
const auto decoration = static_cast<spv::Decoration>(
|
||||
annotation.GetSingleWordInOperand(kDecorateDecorationOperand));
|
||||
const uint32_t target = annotation.GetSingleWordInOperand(kDecorateTargetOperand);
|
||||
if (decoration == spv::Decoration::Invariant) {
|
||||
invariantIds.push_back(target);
|
||||
} else if (decoration == spv::Decoration::BuiltIn &&
|
||||
annotation.NumInOperands() > kDecorateBuiltInOperand &&
|
||||
static_cast<spv::BuiltIn>(annotation.GetSingleWordInOperand(
|
||||
kDecorateBuiltInOperand)) == spv::BuiltIn::Position) {
|
||||
positionIds.push_back(target);
|
||||
}
|
||||
} else if (annotation.opcode() == spv::Op::OpMemberDecorate) {
|
||||
if (annotation.NumInOperands() <= kMemberDecorateDecorationOperand) {
|
||||
continue;
|
||||
}
|
||||
const auto decoration = static_cast<spv::Decoration>(
|
||||
annotation.GetSingleWordInOperand(kMemberDecorateDecorationOperand));
|
||||
const MemberKey key{
|
||||
annotation.GetSingleWordInOperand(kMemberDecorateStructOperand),
|
||||
annotation.GetSingleWordInOperand(kMemberDecorateMemberOperand)};
|
||||
if (decoration == spv::Decoration::Invariant) {
|
||||
invariantMembers.push_back(key);
|
||||
} else if (decoration == spv::Decoration::BuiltIn &&
|
||||
annotation.NumInOperands() > kMemberDecorateBuiltInOperand &&
|
||||
static_cast<spv::BuiltIn>(annotation.GetSingleWordInOperand(
|
||||
kMemberDecorateBuiltInOperand)) == spv::BuiltIn::Position) {
|
||||
positionMembers.push_back(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool changed = false;
|
||||
|
||||
for (const uint32_t target : positionIds) {
|
||||
if (std::find(invariantIds.begin(), invariantIds.end(), target) != invariantIds.end()) {
|
||||
continue;
|
||||
}
|
||||
irContext->AddAnnotationInst(spvtools::MakeUnique<Instruction>(
|
||||
irContext, spv::Op::OpDecorate, 0, 0,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {target}},
|
||||
{SPV_OPERAND_TYPE_DECORATION,
|
||||
{static_cast<uint32_t>(spv::Decoration::Invariant)}}}));
|
||||
// Guard against a second Position decoration on the same target.
|
||||
invariantIds.push_back(target);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
for (const MemberKey& key : positionMembers) {
|
||||
if (std::find(invariantMembers.begin(), invariantMembers.end(), key) !=
|
||||
invariantMembers.end()) {
|
||||
continue;
|
||||
}
|
||||
irContext->AddAnnotationInst(spvtools::MakeUnique<Instruction>(
|
||||
irContext, spv::Op::OpMemberDecorate, 0, 0,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {key.id}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_INTEGER, {key.member}},
|
||||
{SPV_OPERAND_TYPE_DECORATION,
|
||||
{static_cast<uint32_t>(spv::Decoration::Invariant)}}}));
|
||||
invariantMembers.push_back(key);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone);
|
||||
return Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
spvtools::Optimizer::PassToken
|
||||
DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass() {
|
||||
return spvtools::Optimizer::PassToken(MakeUnique<DecoratePositionInvariantPass>());
|
||||
}
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
@@ -0,0 +1,35 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
#include "source/opt/pass.h"
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
// Adds the Invariant decoration to every Position builtin output. GL apps
|
||||
// routinely rely on cross-program position invariance for multi-pass equality
|
||||
// depth tests - MC 26.3's OIT re-draws the cloud geometry with GEQUAL against the
|
||||
// depth its own first pass wrote - and a driver that optimizes each pipeline
|
||||
// separately may otherwise vary the position math between passes, dropping whole
|
||||
// primitives from the later ones. Both the plain (OpDecorate on a Position
|
||||
// variable) and the block-member (OpMemberDecorate on gl_PerVertex) spellings are
|
||||
// handled; targets that already carry Invariant are left alone. DirectVulkan only.
|
||||
class DecoratePositionInvariantPass : public spvtools::opt::Pass {
|
||||
public:
|
||||
const char* name() const override { return "decorate-position-invariant"; }
|
||||
Status Process() override;
|
||||
|
||||
static spvtools::Optimizer::PassToken CreateDecoratePositionInvariantPass();
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
Reference in New Issue
Block a user