From 14d3901d303737868c53ee76ef29523b04e073a1 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 15 Aug 2026 22:58:28 -0400 Subject: [PATCH] [Chore, Test] (MG_State, MG_Util, DirectGLES, DirectVulkan): make SPIR-V validation task-local --- MobileGL/Init.cpp | 2 - MobileGL/MG_Backend/DirectGLES/Managers.cpp | 20 +-- .../DirectVulkan/Renderer/ProgramFactory.cpp | 21 ++-- .../DirectVulkan/Renderer/ProgramFactory.h | 5 + .../DirectVulkan/Renderer/VulkanRenderer.cpp | 1 + .../GLState/ProgramState/ProgramLinkTask.h | 2 + .../GLState/ProgramState/ProgramObject.cpp | 1 + .../GLState/ProgramState/ProgramObject.h | 4 + .../GLState/ProgramState/ProgramSpirvTask.cpp | 9 +- .../GLState/ProgramState/ProgramSpirvTask.h | 2 +- MobileGL/MG_Test/Program/ProgramUtilTest.cpp | 73 ++++------- .../ShaderTranspiler/DemoteFloat64Test.cpp | 27 ++-- .../FlattenXfbInterfaceBlocksTest.cpp | 11 +- .../ShaderTranspiler/ShaderCompiler.cpp | 118 +++++++++--------- .../MG_Util/ShaderTranspiler/ShaderCompiler.h | 72 ++++++----- 15 files changed, 188 insertions(+), 180 deletions(-) diff --git a/MobileGL/Init.cpp b/MobileGL/Init.cpp index 7611c791..905be751 100644 --- a/MobileGL/Init.cpp +++ b/MobileGL/Init.cpp @@ -85,8 +85,6 @@ namespace MobileGL { MG_Util::Debug::InitFile(); MGLOG_I("Initializing MobileGL..."); MG_ConfigLoader::Init(); - MG_Util::ShaderTranspiler::ShaderCompiler::SetSpirvValidationEnabled( - MG_Config::Features.EnableSpirvValidation); MGLOG_I("Config loaded"); MG_State::Init(); MGLOG_D("MG_State initialized"); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 31eb5d68..df87f205 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -4741,6 +4741,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MGLOG_D("%s:", src.empty() ? "" : src.c_str()); } auto& shaderSpirvs = stateProgramObject->GetGeneratedSpirv(); + const Bool enableSpirvValidation = stateProgramObject->GetSpirvValidationEnabled(); // Blocks a transform-feedback capture request names a member of ("StageData" of // "StageData.attrib[0]"). The Adreno ES driver accepts such a request, links, and @@ -4794,7 +4795,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Vector loweredSpirv; const Vector* effectiveSpirv = &spirvCode; if (glShaderType == GL_VERTEX_SHADER && - MG_Util::ShaderTranspiler::ShaderCompiler::LowerDrawParametersForEssl(spirvCode, loweredSpirv) && + MG_Util::ShaderTranspiler::ShaderCompiler::LowerDrawParametersForEssl(spirvCode, loweredSpirv, enableSpirvValidation) && !loweredSpirv.empty()) { effectiveSpirv = &loweredSpirv; } @@ -4804,7 +4805,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Vector splitArrayInputSpirv; if (glShaderType == GL_VERTEX_SHADER && MG_Util::ShaderTranspiler::ShaderCompiler::SplitArrayVertexInputsForEssl( - *effectiveSpirv, splitArrayInputSpirv) && + *effectiveSpirv, splitArrayInputSpirv, enableSpirvValidation) && !splitArrayInputSpirv.empty() && splitArrayInputSpirv != *effectiveSpirv) { // Only when the pass ACTUALLY split something. The optimizer hands back a // re-serialised copy either way, and adopting that copy for every vertex @@ -4826,7 +4827,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (!xfbCaptureBlockNames.empty() && MG_Util::ShaderTranspiler::ShaderCompiler::FlattenXfbInterfaceBlocksForEssl( *effectiveSpirv, xfbCaptureBlockNames, stageFlattenedXfbBlockNames, - flattenedXfbSpirv) && + flattenedXfbSpirv, enableSpirvValidation) && !flattenedXfbSpirv.empty() && !stageFlattenedXfbBlockNames.empty()) { effectiveSpirv = &flattenedXfbSpirv; flattenedXfbBlockNames.insert(stageFlattenedXfbBlockNames.begin(), @@ -4842,7 +4843,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // declare the member highp; nothing else about emission changes. Vector uboPrecisionSpirv; if (MG_Util::ShaderTranspiler::ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl( - *effectiveSpirv, uboPrecisionSpirv) && + *effectiveSpirv, uboPrecisionSpirv, enableSpirvValidation) && !uboPrecisionSpirv.empty()) { effectiveSpirv = &uboPrecisionSpirv; } @@ -4857,7 +4858,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Vector noperspectiveSpirv; if (!g_GLESCapabilities.SupportsNoperspectiveInterpolation && MG_Util::ShaderTranspiler::ShaderCompiler::EmulateNoPerspectiveForEssl( - *effectiveSpirv, noperspectiveSpirv) && + *effectiveSpirv, noperspectiveSpirv, enableSpirvValidation) && !noperspectiveSpirv.empty()) { effectiveSpirv = &noperspectiveSpirv; } @@ -4867,7 +4868,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // divides the coordinate of every normalized-coordinate lookup by the texture // size, which is the whole of the difference between the two. Vector rectLoweredSpirv; - if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(*effectiveSpirv, rectLoweredSpirv) && + if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(*effectiveSpirv, rectLoweredSpirv, enableSpirvValidation) && !rectLoweredSpirv.empty()) { effectiveSpirv = &rectLoweredSpirv; } @@ -4881,7 +4882,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // coordinate to (u, 0, layer) - before SPIRV-Cross can apply its own. Vector arrayImageSpirv; if (MG_Util::ShaderTranspiler::ShaderCompiler::Lower1DArrayImagesForEssl(*effectiveSpirv, - arrayImageSpirv) && + arrayImageSpirv, enableSpirvValidation) && !arrayImageSpirv.empty()) { effectiveSpirv = &arrayImageSpirv; } @@ -4900,7 +4901,8 @@ namespace MobileGL::MG_Backend::DirectGLES { if (!imageFormatBake.glFormatByUniformName.empty() && MG_Util::ShaderTranspiler::ShaderCompiler::DeclaresFormatlessStorageImage(*effectiveSpirv) && MG_Util::ShaderTranspiler::ShaderCompiler::BakeImageFormatsForEssl( - *effectiveSpirv, imageFormatBake.glFormatByUniformName, imageFormatSpirv) && + *effectiveSpirv, imageFormatBake.glFormatByUniformName, imageFormatSpirv, + enableSpirvValidation) && !imageFormatSpirv.empty()) { effectiveSpirv = &imageFormatSpirv; } @@ -4916,7 +4918,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Vector outputIndexSpirv; if (glShaderType == GL_FRAGMENT_SHADER && MG_Util::ShaderTranspiler::ShaderCompiler::LegalizeFragmentOutputIndexingForEssl( - *effectiveSpirv, outputIndexSpirv) && + *effectiveSpirv, outputIndexSpirv, enableSpirvValidation) && !outputIndexSpirv.empty()) { effectiveSpirv = &outputIndexSpirv; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index d313f94c..4a5fe2fc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -3119,6 +3119,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& shaders = program.GetAttachedShaders(); auto& spirv = program.GetGeneratedSpirv(); Vector> moduleSpirvs(spirv.size()); + const Bool enableSpirvValidation = program.GetSpirvValidationEnabled(); + if (enableSpirvValidation) { + MG_Util::ShaderTranspiler::ShaderCompiler::PrepareSpirvValidation(); + } const ShaderStage fixupStage = PickClipFixupStage(shaders); @@ -3164,7 +3168,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // stored as - which addresses [0,1] where the application addressed texels. { Vector rectLoweredSpirv; - if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(moduleSpirvs[i], rectLoweredSpirv) && + if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(moduleSpirvs[i], rectLoweredSpirv, enableSpirvValidation) && !rectLoweredSpirv.empty()) { moduleSpirvs[i] = Move(rectLoweredSpirv); } @@ -3177,7 +3181,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { { Vector invariantSpirv; if (MG_Util::ShaderTranspiler::ShaderCompiler::DecoratePositionInvariantForVulkan( - moduleSpirvs[i], invariantSpirv)) { + moduleSpirvs[i], invariantSpirv, enableSpirvValidation)) { moduleSpirvs[i] = std::move(invariantSpirv); } else { // The pass round-trips through SPIRV-Tools IR, so an unparseable module @@ -3201,7 +3205,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_shaderDrawParametersEnabled) { Vector rebasedSpirv; if (MG_Util::ShaderTranspiler::ShaderCompiler::RebaseInstanceIndexForVulkan(moduleSpirvs[i], - rebasedSpirv)) { + rebasedSpirv, enableSpirvValidation)) { moduleSpirvs[i] = std::move(rebasedSpirv); } else { MGLOG_E("ProgramFactory: failed to rebase gl_InstanceID for program %u; " @@ -3219,7 +3223,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { (flags & CompileOptionBit::ZeroBaseVertex)) { Vector zeroedSpirv; if (MG_Util::ShaderTranspiler::ShaderCompiler::ZeroBaseVertexForVulkan(moduleSpirvs[i], - zeroedSpirv)) { + zeroedSpirv, enableSpirvValidation)) { moduleSpirvs[i] = std::move(zeroedSpirv); } else { // Failing open keeps the native builtin, which is the pre-fix behavior: @@ -3242,7 +3246,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (shaders[i] && shaders[i]->GetShaderStage() == ShaderStage::Vertex) { Vector packedSpirv; const Bool packOk = MG_Util::ShaderTranspiler::ShaderCompiler::PackDoubleVertexInputsForVulkan( - moduleSpirvs[i], packedSpirv); + moduleSpirvs[i], packedSpirv, enableSpirvValidation); MOBILEGL_ASSERT(packOk, "ProgramFactory: 64-bit vertex input packing failed for program %u; the " "vertex-input format and the shader input type now disagree", @@ -3266,7 +3270,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_unformattedFloatStorageImagesEnabled) { Vector unformattedSpirv; if (MG_Util::ShaderTranspiler::ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan( - moduleSpirvs[i], unformattedSpirv)) { + moduleSpirvs[i], unformattedSpirv, enableSpirvValidation)) { moduleSpirvs[i] = std::move(unformattedSpirv); } else { MGLOG_E("ProgramFactory: failed to make float storage images unformatted for program %u", @@ -3287,7 +3291,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { #else // Final module the driver receives; also checked in the INFO-level CI/test // lanes, where the DEBUG gate above is compiled out. - if (MG_Util::ShaderTranspiler::ShaderCompiler::SpirvValidationEnabled()) { + if (enableSpirvValidation) { ValidateTransformedSpirv(moduleSpv, shaders[i]->GetShaderStage(), program.GetExternalIndex()); } #endif @@ -3505,7 +3509,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG ValidateTransformedSpirv(spirv, ShaderStage::TessControl, 0); #else - if (MG_Util::ShaderTranspiler::ShaderCompiler::SpirvValidationEnabled()) { + if (m_enableSpirvValidation) { + MG_Util::ShaderTranspiler::ShaderCompiler::PrepareSpirvValidation(); ValidateTransformedSpirv(spirv, ShaderStage::TessControl, 0); } #endif diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index d667d9fb..e64e5895 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -375,10 +375,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings, Bool shaderDrawParametersEnabled, Bool unformattedFloatStorageImagesEnabled, + Bool enableSpirvValidation, UpdateAfterBindLimits updateAfterBindLimits) : m_device(device), m_maxBindings(maxBindings), m_config(config), m_shaderDrawParametersEnabled(shaderDrawParametersEnabled), m_unformattedFloatStorageImagesEnabled(unformattedFloatStorageImagesEnabled), + m_enableSpirvValidation(enableSpirvValidation), m_updateAfterBindLimits(updateAfterBindLimits) { VkProgramObject::s_device = device; } @@ -502,6 +504,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // True only when the logical device enabled both // shaderStorageImageReadWithoutFormat and shaderStorageImageWriteWithoutFormat. Bool m_unformattedFloatStorageImagesEnabled = false; + // Startup snapshot used only by internally synthesized shader modules, which do not + // originate from a ProgramLinkTask. + Bool m_enableSpirvValidation = false; // Device feature and limit gate resolved before vkCreateDevice. Keeping it in // the factory lets each reflected layout choose ordinary descriptors when its // own counts would exceed the update-after-bind budget. diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 359b0b65..4cf9d052 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -3061,6 +3061,7 @@ void main() { m_programFactory = MakeUnique(m_device, m_config, maxProgramBindings, m_shaderDrawParametersFeatureEnabled, m_unformattedFloatStorageImagesEnabled, + MG_Config::Features.EnableSpirvValidation, m_updateAfterBindLimits); MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed."); // The swapchain already exists at this point (Initialize creates it first), so seed the diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.h b/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.h index 36534289..1486ab4b 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.h @@ -60,6 +60,8 @@ namespace MobileGL::MG_State::GLState { Uint externalIndex = 0; // logs only Vector shaders; // already stage-sorted SharedPtr env; + // Startup configuration copied with the task, never read from worker code. + Bool enableSpirvValidation = false; // The four "takes effect at the next link" request maps. Snapshotted rather than // referenced, which is precisely what makes glBindAttribLocation and friends // legal to call over a pending link without cancelling it: the pending link keeps diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index a3235809..fc6c3294 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -494,6 +494,7 @@ namespace MobileGL::MG_State::GLState { auto task = MakeShared(); task->in.externalIndex = m_externalIndex; task->in.env = MG_Util::ShaderTranspiler::GetCurrentCompileEnv(); + task->in.enableSpirvValidation = MG_Config::Features.EnableSpirvValidation; task->in.explicitAttribLocations = m_explicitAttribLocations; task->in.explicitFragDataLocation = m_explicitFragDataLocation; task->in.explicitFragDataIndex = m_explicitFragDataIndex; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 515a9a2e..16173bff 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -819,6 +819,9 @@ namespace MobileGL::MG_State::GLState { // backend asks this exactly where it used to ask GetLinkStatus(), i.e. right before // it builds or draws with the program. Bool GetSpirvStatus() const { return Spirv().spirvStatus; } + // Copied from the link task that generated this program's SPIR-V. Backends use it for + // their final transforms, which must honor the same diagnostic setting as phase B. + Bool GetSpirvValidationEnabled() const { return Spirv().enableSpirvValidation; } // The linked glslang reflection itself, for the ONE consumer that needs resource // lists no typed getter above exposes: the GL program-interface query layer @@ -985,6 +988,7 @@ namespace MobileGL::MG_State::GLState { // cannot be lifted out of glslang's reflection instead. struct SpirvArtifacts { Vector> generatedSpirv; + Bool enableSpirvValidation = false; // Byte offset of each uniform location inside globalUboScratch, or // kInvalidUniformOffset. Sized maxUniformLocation + 1 by the routing pass. Vector uniformOffsets; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp index 906d250b..d12cd49a 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp @@ -104,7 +104,9 @@ namespace MobileGL::MG_State::GLState { MGLOG_D("ProgramObject %u: Starting SPIR-V generation", externalIndex); const Bool deferOutputValidationForDirectVulkan = m_phaseA->in.env != nullptr && m_phaseA->in.env->backend == BackendType::DirectVulkan; - GenerateSpirv(handoff, externalIndex, deferOutputValidationForDirectVulkan); + const Bool enableSpirvValidation = m_phaseA->in.enableSpirvValidation; + artifacts.enableSpirvValidation = enableSpirvValidation; + GenerateSpirv(handoff, externalIndex, deferOutputValidationForDirectVulkan, enableSpirvValidation); // GlslangToSpv was the only consumer of the parsed ASTs; everything after this point // works on the SPIR-V and on the TProgram's own self-contained reflection pool. Drop // them here rather than at the end of the body, which is ~87% of this node's runtime @@ -140,7 +142,8 @@ namespace MobileGL::MG_State::GLState { } void ProgramSpirvTask::GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, const Uint externalIndex, - const Bool deferOutputValidationForDirectVulkan) { + const Bool deferOutputValidationForDirectVulkan, + const Bool enableSpirvValidation) { /* As we passed first stage compilation/linking, * we'll assume all the operations here should * pass. We may be able to employ some optimizations @@ -173,7 +176,7 @@ namespace MobileGL::MG_State::GLState { { for (auto& spv : artifacts.generatedSpirv) { auto success = ShaderCompiler::SanitizeAndOptimizeBinary( - spv, spv, !deferOutputValidationForDirectVulkan); + spv, spv, !deferOutputValidationForDirectVulkan, enableSpirvValidation); if (!success) { // The one genuine phase-B failure mode: one of the seven optimizer passes // reported failure, so `spv` is whatever the run left behind. A fordebug diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h index 4147fe9e..e48fa3dd 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h @@ -66,7 +66,7 @@ namespace MobileGL::MG_State::GLState { void RunBody() override; void GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex, - Bool deferOutputValidationForDirectVulkan); + Bool deferOutputValidationForDirectVulkan, Bool enableSpirvValidation); void BuildGlobalUboRouting(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex); // Worker-side MGLOG replacement, replayed by the join on the GL thread. Same reason as diff --git a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp index 22c5b3b2..ab162764 100644 --- a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp @@ -2849,16 +2849,6 @@ vec4 helperTint() { return vec4(1.0); } return binaryResult->front(); } - struct SpirvValidationScope { - bool previous; - explicit SpirvValidationScope(bool enabled) - : previous(MG_Util::ShaderTranspiler::ShaderCompiler::SpirvValidationEnabled()) { - MG_Util::ShaderTranspiler::ShaderCompiler::SetSpirvValidationEnabled(enabled); - } - ~SpirvValidationScope() { - MG_Util::ShaderTranspiler::ShaderCompiler::SetSpirvValidationEnabled(previous); - } - }; } // namespace TEST_F(ProgramUtilTest, DeadPrivateChainVertexInputIsEliminatedFromOptimizedBinary) { @@ -2880,7 +2870,7 @@ TEST_F(ProgramUtilTest, DeadPrivateChainVertexInputIsEliminatedFromOptimizedBina << "entry-point-with-calls shape it exists for"; Vector optimized; - ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized)); + ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true)); const SpirvVariableCensus after = TakeVariableCensus(optimized); EXPECT_EQ(after.inputCount, 1u) @@ -2918,7 +2908,7 @@ void main() { ASSERT_GE(before.outputCount, 3u); Vector optimized; - ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized)); + ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true)); EXPECT_EQ(TakeVariableCensus(optimized).outputCount, before.outputCount) << "a declared-but-unwritten output was deleted; a fragment stage reading it now " << "fails to link (ES) or breaks the Vulkan stage interface"; @@ -2977,17 +2967,15 @@ void main() { // succeeds - fail-open call sites downstream must not see a different world), // and the failure latch is the signal. This is the catch that took a device // bisect to find when the validator was off everywhere. - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); - EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized)); + EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true)); EXPECT_GT(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) << "an invalid optimized module must bump the validation-failure latch"; } { // The shipping configuration: same result, no validation, latch untouched. - SpirvValidationScope validationOff(false); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); - EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized)); + EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, false)); EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore); } } @@ -3057,10 +3045,9 @@ void main() { ASSERT_FALSE(raw.empty()); ASSERT_GE(CountRectImageTypes(raw), 1u) << "glslang no longer emits Dim::Rect for sampler2DRect"; - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector optimized; - ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized)); + ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true)); EXPECT_EQ(CountRectImageTypes(optimized), 0u); EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) << "a rectangle module must leave the chain valid, not latched as a failure"; @@ -3085,10 +3072,9 @@ void main() { ASSERT_TRUE(AnyLocationOnUniformStorage(raw)) << "glslang no longer keeps the explicit uniform location; the strip pass may be obsolete"; - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector optimized; - ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized)); + ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true)); EXPECT_FALSE(AnyLocationOnUniformStorage(optimized)); EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) << "the stripped module must validate clean"; @@ -3185,11 +3171,10 @@ void main() { << "the fixture must reproduce the defect before the fix is asked to remove it:\n" << DisassembleSpirv(raw); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector legalized; - ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized)); + ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized, true)); ASSERT_FALSE(legalized.empty()); const String disassembly = DisassembleSpirv(legalized); @@ -3226,11 +3211,10 @@ void main() { ASSERT_TRUE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw)) << DisassembleSpirv(raw); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector legalized; - ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized)); + ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized, true)); ASSERT_FALSE(legalized.empty()); const String disassembly = DisassembleSpirv(legalized); @@ -3270,11 +3254,10 @@ void main() { ASSERT_FALSE(raw.empty()); ASSERT_TRUE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw)); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector legalized; - ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized)); + ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized, true)); ASSERT_FALSE(legalized.empty()); const String disassembly = DisassembleSpirv(legalized); @@ -3313,7 +3296,7 @@ void main() { ASSERT_FALSE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw)); Vector legalized; - ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized)); + ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized, true)); EXPECT_EQ(legalized, raw) << "the module must not be rewritten - not even re-serialized - when " "nothing indexes a fragment output dynamically"; } @@ -3563,11 +3546,10 @@ TEST_F(ProgramUtilTest, Lower1DArrayImagesRewritesTheTypeAndWidensTheCoordinate) ASSERT_EQ(Count1DArrayStorageImageTypes(spirv), 1u) << "the shared chain must leave the 1D-array image for this pass to handle"; - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector lowered; - ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered)); + ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true)); ASSERT_FALSE(lowered.empty()); EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 0u) @@ -3615,11 +3597,10 @@ void main() { ssb.sum = imageLoad(i0, ivec2(2, 3)).r + imageLoad(i1, ivec3(1, 1, ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, spirv)); ASSERT_EQ(Count1DArrayStorageImageTypes(spirv), 1u); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector lowered; - ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered)); + ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true)); ASSERT_FALSE(lowered.empty()); EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 0u) << DisassembleSpirv(lowered); @@ -3649,7 +3630,7 @@ void main() { ssb.sum = imageLoad(i0, 2).r; } ASSERT_FALSE(spirv.empty()); Vector lowered; - ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered)); + ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true)); EXPECT_EQ(lowered, spirv) << "a non-arrayed 1D storage image must pass through byte for byte"; const String essl = DecompileToEssl(lowered); @@ -3673,7 +3654,7 @@ void main() { fragColor = texture(uTex, vUv); } ASSERT_FALSE(spirv.empty()); Vector lowered; - ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered)); + ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true)); EXPECT_EQ(lowered, spirv) << "a sampled 1D-array image must pass through byte for byte"; } @@ -3697,7 +3678,7 @@ void main() { ssb.sum = uint(imageSize(i0).x) + imageLoad(i0, ivec2(0, 0)).r; } << "the fixture must contain the shape the pass declines"; Vector lowered; - ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered)); + ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true)); EXPECT_EQ(lowered, spirv) << "a declined module must be handed back untouched, not partly rewritten"; EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 1u) << "declining means the 1D-array type is still there for the driver to reject"; @@ -3748,11 +3729,10 @@ void main() { imageStore(uni_image, ivec2(gl_GlobalInvocationID.xy), uvec4(15u, // Precondition: SPIRV-Cross prints no format for it, which is the ESSL the driver refuses. EXPECT_EQ(DecompileToEssl(spirv).find("r32ui"), String::npos); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector baked; - ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked)); + ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked, true)); ASSERT_FALSE(baked.empty()); EXPECT_FALSE(ShaderCompiler::DeclaresFormatlessStorageImage(baked)) << DisassembleSpirv(baked); EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) @@ -3813,7 +3793,7 @@ void main() { imageStore(uni_image, ivec2(0), uvec4(1u)); } Vector baked; // Even asked to, with a format of the right component class. - ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked)); + ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked, true)); EXPECT_EQ(baked, spirv) << "a module with nothing format-less must pass through byte for byte"; EXPECT_NE(DecompileToEssl(baked).find("rgba32ui"), String::npos); } @@ -3835,11 +3815,10 @@ void main() { writeIt(uni_image); } ASSERT_FALSE(spirv.empty()); ASSERT_TRUE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv)); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector baked; - ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked)); + ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked, true)); EXPECT_EQ(baked, spirv) << "a shape the retype cannot follow must leave the module untouched, " "not partly rewritten:\n" << DisassembleSpirv(baked); @@ -3861,18 +3840,17 @@ void main() { imageStore(uni_image, ivec2(0), vec4(1.0)); } GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector baked; - ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked)); + ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked, true)); EXPECT_EQ(baked, spirv) << "a declined module must be handed back untouched, not partly rewritten"; EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore); // ...and the same image with a float bind format is baked, so the decline above is about the // class and not about the pass refusing float images. Vector bakedFloat; - ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32f}}, bakedFloat)); + ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32f}}, bakedFloat, true)); EXPECT_NE(DecompileToEssl(bakedFloat).find("r32f"), String::npos) << DisassembleSpirv(bakedFloat); } @@ -3896,12 +3874,11 @@ void main() { ASSERT_EQ(CountSpirvOpcode(DisassembleSpirv(spirv), "OpTypeImage"), 1u) << "the fixture must have the two images sharing one type:\n" << DisassembleSpirv(spirv); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector baked; ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl( - spirv, {{"imgA", kGlR32ui}, {"imgB", kGlRgba32ui}}, baked)); + spirv, {{"imgA", kGlR32ui}, {"imgB", kGlRgba32ui}}, baked, true)); ASSERT_FALSE(baked.empty()); EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) << "splitting the shared type must not leave a dangling or duplicate declaration:\n" @@ -3934,11 +3911,10 @@ void main() { ASSERT_EQ(CountSpirvOpcode(DisassembleSpirv(spirv), "OpTypeImage"), 2u) << "the fixture needs one Unknown-format and one r32ui image type:\n" << DisassembleSpirv(spirv); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector baked; - ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"formatless", kGlR32ui}}, baked)); + ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"formatless", kGlR32ui}}, baked, true)); ASSERT_FALSE(baked.empty()); EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) << "the baked image collided with the module's own r32ui image and left a duplicate type:\n" @@ -3963,11 +3939,10 @@ void main() { ASSERT_FALSE(spirv.empty()); ASSERT_TRUE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv)); - SpirvValidationScope validationOn(true); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector baked; - ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"imgs", kGlR32ui}}, baked)); + ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"imgs", kGlR32ui}}, baked, true)); ASSERT_FALSE(baked.empty()); EXPECT_FALSE(ShaderCompiler::DeclaresFormatlessStorageImage(baked)) << DisassembleSpirv(baked); EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) @@ -3993,7 +3968,7 @@ void main() { fragColor = texture(uni_sampler, vUv); } << "a sampled image must not read as a format-less STORAGE image:\n" << DisassembleSpirv(spirv); Vector baked; - ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_sampler", kGlR32ui}}, baked)); + ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_sampler", kGlR32ui}}, baked, true)); EXPECT_EQ(baked, spirv) << "a sampled image must pass through byte for byte"; } diff --git a/MobileGL/MG_Test/ShaderTranspiler/DemoteFloat64Test.cpp b/MobileGL/MG_Test/ShaderTranspiler/DemoteFloat64Test.cpp index b1dff973..6128dd99 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/DemoteFloat64Test.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/DemoteFloat64Test.cpp @@ -154,7 +154,6 @@ class DemoteFloat64Test : public ::testing::Test { protected: void SetUp() override { MobileGL::Initialize(); - ShaderCompiler::SetSpirvValidationEnabled(true); m_validationFailuresAtStart = ShaderCompiler::SpirvValidationFailureCount(); } @@ -175,7 +174,7 @@ TEST_F(DemoteFloat64Test, DemotesEveryWidthAndDropsTheCapability) { ASSERT_TRUE(DeclaresFloat64Capability(input)); Vector output; - ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output)); + ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true)); EXPECT_EQ(CountFloatTypesOfWidth(output, 64), 0u) << Disassemble(output); // And exactly one 32-bit float type survives: the merge has to happen, or spirv-val rejects @@ -210,7 +209,7 @@ void main() { << Disassemble(input); Vector output; - ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output)); + ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true)); // std140 for the demoted members: float at 4, vec2 at 8, vec3 at 16 (aligned like a vec4), // vec4 at 32, mat4 at 48 with a 16-byte column stride, the array at 112 with the std140 @@ -241,7 +240,7 @@ void main() { EXPECT_EQ(CollectOffsetsOf(input, "Ssbo"), (Vector{0, 32, 64})) << Disassemble(input); Vector output; - ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output)); + ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true)); // std430, so the array packs at its element size rather than being rounded to 16: float at 0, // vec4 at 16, float[4] at 32 with a 4-byte stride. A storage block must NOT come out std140, @@ -273,7 +272,7 @@ void main() { ASSERT_FALSE(before.empty()); Vector output; - ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output)); + ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true)); // Only the block that actually narrowed is re-laid-out. Touching the other one would be // churn at best, and a disagreement with glslang's own layout at worst. @@ -287,7 +286,7 @@ TEST_F(DemoteFloat64Test, FoldsTheConversionsThatBecameIdentities) { ASSERT_GT(CountFConverts(input), 0u) << "the fixture no longer converts between the two widths"; Vector output; - ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output)); + ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true)); // SPIR-V requires the two component widths of an OpFConvert to differ, so every one of them // has to be gone: both sides are 32 bits now. @@ -307,7 +306,7 @@ void main() { ASSERT_FALSE(input.empty()); Vector output; - ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output)); + ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true)); // A 64-bit literal is two words wide and a 32-bit one is a single word, so a constant left // unconverted is not merely imprecise - it is an unparseable instruction. Disassembling both @@ -326,7 +325,7 @@ void main() { gl_Position = inPos; } ASSERT_FALSE(input.empty()); Vector output; - ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output)); + ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true)); // The pass reports SuccessWithoutChange here, and SPIRV-Tools asserts (in assert-enabled // builds) that such a run round-trips byte-identically. EXPECT_EQ(output, input); @@ -351,7 +350,7 @@ void main() { ASSERT_EQ(CountFloatTypesOfWidth(input, 64), 1u) << Disassemble(input); Vector output; - ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output)); + ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true)); EXPECT_EQ(output, input) << Disassemble(output); EXPECT_TRUE(ShaderCompiler::ModuleDeclaresFloat64(output)); } @@ -362,7 +361,7 @@ TEST_F(DemoteFloat64Test, ModuleDeclaresFloat64AnswersBothWays) { EXPECT_TRUE(ShaderCompiler::ModuleDeclaresFloat64(wide)); Vector demoted; - ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(wide, demoted)); + ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(wide, demoted, true)); EXPECT_FALSE(ShaderCompiler::ModuleDeclaresFloat64(demoted)); EXPECT_FALSE(ShaderCompiler::ModuleDeclaresFloat64({})); @@ -375,7 +374,7 @@ TEST_F(DemoteFloat64Test, TheSharedChainDemotesToo) { ASSERT_FALSE(input.empty()); Vector output; - ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output)); + ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output, true, true)); EXPECT_FALSE(ShaderCompiler::ModuleDeclaresFloat64(output)) << Disassemble(output); } @@ -459,7 +458,7 @@ TEST_P(DemoteFloat64EsslTest, TheDemotedModuleCanBeEmittedAsEssl) { ASSERT_FALSE(input.empty()); Vector output; - ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output)); + ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output, true, true)); SpvcSession session(output, SessionUsageBit::Transpile); spvc_compiler_options options; @@ -482,7 +481,7 @@ TEST_P(DemoteFloat64EsslTest, TheDemotedModuleCanBeEmittedAsEssl) { TEST_F(DemoteFloat64Test, RejectsGarbageInput) { const Vector notSpirv{0xdeadbeefu, 0u, 0u, 0u, 0u}; Vector output; - EXPECT_FALSE(ShaderCompiler::DemoteFloat64ToFloat32(notSpirv, output)); + EXPECT_FALSE(ShaderCompiler::DemoteFloat64ToFloat32(notSpirv, output, true)); } // EliminateFloatEqualsZeroPass turns a comparison against 0.0 into an epsilon test, a @@ -502,7 +501,7 @@ namespace { EXPECT_FALSE(input.empty()); if (input.empty()) return false; Vector output; - EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output)); + EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output, true, true)); return Disassemble(output).find("FAbs") != String::npos; } diff --git a/MobileGL/MG_Test/ShaderTranspiler/FlattenXfbInterfaceBlocksTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/FlattenXfbInterfaceBlocksTest.cpp index d8f7e232..1688d3f1 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/FlattenXfbInterfaceBlocksTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/FlattenXfbInterfaceBlocksTest.cpp @@ -98,7 +98,6 @@ class FlattenXfbInterfaceBlocksTest : public ::testing::Test { protected: void SetUp() override { MobileGL::Initialize(); - ShaderCompiler::SetSpirvValidationEnabled(true); m_validationFailuresAtStart = ShaderCompiler::SpirvValidationFailureCount(); } @@ -116,7 +115,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, FlattensACapturedBlockIntoOneVariablePerMe std::set flattened; Vector output; - ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output)); + ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output, true)); ASSERT_FALSE(output.empty()); EXPECT_EQ(flattened, (std::set{"StageData"})); @@ -145,7 +144,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, TheEmittedDeclarationIsAPlainArrayNotABloc std::set flattened; Vector output; - ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output)); + ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output, true)); const String after = Transpile(output); EXPECT_NE(after.find("StageData_attrib[16]"), String::npos) << after; @@ -162,7 +161,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, GivesEachMemberItsOwnConsecutiveLocations) std::set flattened; Vector output; - ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output)); + ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output, true)); ASSERT_FALSE(output.empty()); const String dis = Disassemble(output); @@ -184,7 +183,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, LeavesABlockNoCaptureNamesAlone) { std::set flattened; Vector output; ASSERT_TRUE( - ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"SomeOtherBlock"}, flattened, output)); + ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"SomeOtherBlock"}, flattened, output, true)); EXPECT_TRUE(flattened.empty()); const String after = Transpile(output); @@ -200,7 +199,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, DeclinesAnEmptyRequestWithoutRewriting) { std::set flattened; Vector output; - EXPECT_FALSE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {}, flattened, output)); + EXPECT_FALSE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {}, flattened, output, true)); EXPECT_TRUE(flattened.empty()); EXPECT_TRUE(output.empty()); } diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 8da5ffb8..661d373a 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -369,10 +369,6 @@ namespace MobileGL { return allSpirv; } - // Published by MobileGL::Initialize() before shader workers are created. Standalone - // compiler users and tests start with validation disabled and can opt in through the - // setter without reading process environment from parallel work. - static std::atomic g_validateSpirv{0}; // Total validation failures observed this process. This latch - not the wrappers' // return values - is the test-lane signal: validation must never change what a // wrapper returns, or the validating lanes would render differently from the @@ -421,11 +417,6 @@ namespace MobileGL { tools.Validate(warmup); } std::atexit(+[] { - // Flip validation off first: a validator table this warmup does - // not know about (a future spirv-tools bump) would still be - // destroyed before this handler, and workers must stop entering - // Validate before the drain waits for them. - g_validateSpirv.store(0, std::memory_order_release); Async::ShaderCompilePool::StopAndDrainProcessPoolAtExit(); }); }); @@ -454,10 +445,10 @@ namespace MobileGL { // Validation is decoupled from control flow on purpose: a failure logs and // bumps the latch, and the caller proceeds exactly as the shipping (non- // validating) configuration would. Tests assert on the latch delta. - void ValidateOrLatch(const char* site, const Vector& binary) { - if (!ShaderCompiler::SpirvValidationEnabled()) { - return; - } + void ValidateOrLatch(const char* site, const Vector& binary, + const bool enableSpirvValidation) { + if (!enableSpirvValidation) return; + PinValidatorTablesForProcessExit(); spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); tools.SetMessageConsumer(MakeSpirvMessageConsumer(site)); if (!tools.Validate(binary)) { @@ -478,8 +469,8 @@ namespace MobileGL { // spirv-tools drops pass diagnostics on the floor. bool RunOptimizerChecked(const char* site, spvtools::Optimizer& optimizer, const Vector& inputBinary, - Vector& outputBinary, - bool validateOutput = true) { + Vector& outputBinary, const bool validateOutput, + const bool enableSpirvValidation) { spvtools::OptimizerOptions options; options.set_run_validator(false); optimizer.SetMessageConsumer(MakeSpirvMessageConsumer(site)); @@ -487,21 +478,14 @@ namespace MobileGL { return false; } if (validateOutput) { - ValidateOrLatch(site, outputBinary); + ValidateOrLatch(site, outputBinary, enableSpirvValidation); } return true; } } // namespace - bool ShaderCompiler::SpirvValidationEnabled() { - return g_validateSpirv.load(std::memory_order_acquire) == 1; - } - - void ShaderCompiler::SetSpirvValidationEnabled(bool enabled) { - g_validateSpirv.store(enabled ? 1 : 0, std::memory_order_release); - if (enabled) { - PinValidatorTablesForProcessExit(); - } + void ShaderCompiler::PrepareSpirvValidation() { + PinValidatorTablesForProcessExit(); } Uint64 ShaderCompiler::NoteSpirvValidationFailure() { @@ -571,17 +555,19 @@ namespace MobileGL { } bool ShaderCompiler::DemoteFloat64ToFloat32(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass()); - return RunOptimizerChecked("DemoteFloat64ToFloat32", optimizer, inputBinary, outputBinary); + return RunOptimizerChecked("DemoteFloat64ToFloat32", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector& inputBinary, Vector& outputBinary, - bool validateOutput) { + const bool validateOutput, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); @@ -631,38 +617,41 @@ namespace MobileGL { optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass()); return RunOptimizerChecked("SanitizeAndOptimizeBinary", optimizer, inputBinary, - outputBinary, validateOutput); + outputBinary, validateOutput, enableSpirvValidation); } bool ShaderCompiler::LowerDrawParametersForEssl(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(LowerDrawParametersPass::CreateLowerDrawParametersPass()); return RunOptimizerChecked("LowerDrawParametersForEssl", optimizer, inputBinary, - outputBinary); + outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::SplitArrayVertexInputsForEssl(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(SplitArrayVertexInputsPass::CreateSplitArrayVertexInputsPass()); return RunOptimizerChecked("SplitArrayVertexInputsForEssl", optimizer, inputBinary, - outputBinary); + outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::BakeImageFormatsForEssl(const Vector& inputBinary, const UnorderedMap& glFormatByName, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; if (glFormatByName.empty()) return false; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(BakeImageFormatsPass::CreateBakeImageFormatsPass(glFormatByName)); - return RunOptimizerChecked("BakeImageFormatsForEssl", optimizer, inputBinary, outputBinary); + return RunOptimizerChecked("BakeImageFormatsForEssl", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::DeclaresFormatlessStorageImage(const Vector& binary) { @@ -686,7 +675,8 @@ namespace MobileGL { bool ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(const Vector& inputBinary, const std::set& blockNames, std::set& flattenedBlockNames, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; if (blockNames.empty()) return false; Optimizer optimizer(SPV_ENV_VULKAN_1_1); @@ -694,7 +684,7 @@ namespace MobileGL { blockNames, &flattenedBlockNames)); return RunOptimizerChecked("FlattenXfbInterfaceBlocksForEssl", optimizer, inputBinary, - outputBinary); + outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock( @@ -704,48 +694,53 @@ namespace MobileGL { } bool ShaderCompiler::PackDoubleVertexInputsForVulkan(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(PackDoubleVertexInputsPass::CreatePackDoubleVertexInputsPass()); return RunOptimizerChecked("PackDoubleVertexInputsForVulkan", optimizer, inputBinary, - outputBinary); + outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass( StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass()); return RunOptimizerChecked("StripUboMemberRelaxedPrecisionForEssl", optimizer, - inputBinary, outputBinary); + inputBinary, outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::StripNoPerspectiveForEssl(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(StripNoPerspectivePass::CreateStripNoPerspectivePass()); return RunOptimizerChecked("StripNoPerspectiveForEssl", optimizer, inputBinary, - outputBinary); + outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::EmulateNoPerspectiveForEssl(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(EmulateNoPerspectivePass::CreateEmulateNoPerspectivePass()); return RunOptimizerChecked("EmulateNoPerspectiveForEssl", optimizer, inputBinary, - outputBinary); + outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; // Detection gates everything: a module with no dynamically indexed fragment @@ -778,7 +773,7 @@ namespace MobileGL { Vector folded; if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.fold", folder, inputBinary, - folded) || + folded, true, enableSpirvValidation) || folded.empty()) { // Fail open onto the fallback rather than onto the illegal module. folded = inputBinary; @@ -797,7 +792,7 @@ namespace MobileGL { lowerer.RegisterPass(CreateAggressiveDCEPass(false)); if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.lower", lowerer, folded, - outputBinary) || + outputBinary, true, enableSpirvValidation) || outputBinary.empty()) { outputBinary = folded; return true; @@ -814,16 +809,17 @@ namespace MobileGL { } bool ShaderCompiler::LowerRectImages(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(NormalizeRectCoordinatesPass::CreateNormalizeRectCoordinatesPass()); - return RunOptimizerChecked("LowerRectImages", optimizer, inputBinary, outputBinary); + return RunOptimizerChecked("LowerRectImages", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::Lower1DArrayImagesForEssl(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, const bool enableSpirvValidation) { using namespace spvtools; // Declined rather than half-translated: after the rewrite the image is a 2D @@ -865,40 +861,41 @@ namespace MobileGL { // second Shader. Deduplicating afterwards collapses all three at once. optimizer.RegisterPass(CreateRemoveDuplicatesPass()); - return RunOptimizerChecked("Lower1DArrayImagesForEssl", optimizer, inputBinary, outputBinary); + return RunOptimizerChecked("Lower1DArrayImagesForEssl", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(RebaseInstanceIndexPass::CreateRebaseInstanceIndexPass()); return RunOptimizerChecked("RebaseInstanceIndexForVulkan", optimizer, inputBinary, - outputBinary); + outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::ZeroBaseVertexForVulkan(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(ZeroBaseVertexPass::CreateZeroBaseVertexPass()); - return RunOptimizerChecked("ZeroBaseVertexForVulkan", optimizer, inputBinary, outputBinary); + return RunOptimizerChecked("ZeroBaseVertexForVulkan", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::DecoratePositionInvariantForVulkan(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass()); return RunOptimizerChecked("DecoratePositionInvariantForVulkan", optimizer, inputBinary, - outputBinary); + outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan( - const Vector& inputBinary, Vector& outputBinary) { + const Vector& inputBinary, Vector& outputBinary, + const bool enableSpirvValidation) { constexpr SizeT kSpirvHeaderWordCount = 5; outputBinary.clear(); if (inputBinary.size() < kSpirvHeaderWordCount || inputBinary[0] != spv::MagicNumber) { @@ -1026,7 +1023,8 @@ namespace MobileGL { addedCapabilities.begin(), addedCapabilities.end()); // Hand-rolled word walk, so no Optimizer wrapper ever sees this rewrite; // check the modified module explicitly in validating lanes. - ValidateOrLatch("UseUnformattedFloatStorageImagesForVulkan", outputBinary); + ValidateOrLatch("UseUnformattedFloatStorageImagesForVulkan", outputBinary, + enableSpirvValidation); return true; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h index 4b0479eb..1864dc3a 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h @@ -24,19 +24,22 @@ namespace MobileGL { static Result>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib); static bool SanitizeAndOptimizeBinary(const Vector& inputBinary, Vector& outputBinary, - bool validateOutput = true); + bool validateOutput = true, + bool enableSpirvValidation = false); // Demotes DrawIndex/BaseInstance/BaseVertex builtins to plain Private globals // (mg_DrawID/mg_BaseInstance/mg_BaseVertex) so SPIRV-Cross can emit ESSL. // Only for backends without native draw-parameter support (DirectGLES). static bool LowerDrawParametersForEssl(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Replaces an ARRAY vertex input with one input per element at consecutive // locations, seeding a Private copy of the array so indexed reads still work. // GLSL ES has no array vertex inputs and SPIRV-Cross refuses the whole module // rather than emulating them, so without this the stage never reaches the // driver. Only for the DirectGLES transpile path. static bool SplitArrayVertexInputsForEssl(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Replaces the named interface BLOCKS with one variable per member, named // "_", shadowing the block itself so the body is untouched. The // Adreno ES driver silently captures NOTHING for a transform-feedback varying @@ -47,7 +50,8 @@ namespace MobileGL { static bool FlattenXfbInterfaceBlocksForEssl(const Vector& inputBinary, const std::set& blockNames, std::set& flattenedBlockNames, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // The capture request "StageData.attrib[0]" as the pass above renamed it, // "StageData_attrib[0]", or false when it does not name a member of a block // that was flattened. @@ -59,17 +63,20 @@ namespace MobileGL { // drivers reject cross-stage uniform blocks whose member precisions differ. // Only for the DirectGLES transpile path. static bool StripUboMemberRelaxedPrecisionForEssl(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Removes NoPerspective decorations so SPIRV-Cross emits plain (smooth) ESSL varyings. // DirectGLES fallback only, for devices lacking GL_NV_shader_noperspective_interpolation // (SPIRV-Cross would otherwise require that extension and the driver would reject it). static bool StripNoPerspectiveForEssl(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Emulates noperspective (screen-linear) interpolation via gl_Position.w / gl_FragCoord.w // so no NV extension is needed; strips what it cannot emulate. DirectGLES fallback for // devices lacking GL_NV_shader_noperspective_interpolation. See EmulateNoPerspectivePass. static bool EmulateNoPerspectiveForEssl(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Makes every index into a fragment-output array a constant integral // expression, which is what GLSL ES requires and SPIR-V does not. Runs the // stock folding chain first (loop unrolling folds the loop-derived indices @@ -80,7 +87,8 @@ namespace MobileGL { // dynamically, which is every shader but a handful. // See LegalizeFragmentOutputIndexPass. static bool LegalizeFragmentOutputIndexingForEssl(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so // shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan // backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex, @@ -89,7 +97,8 @@ namespace MobileGL { // divides the coordinate of each normalized-coordinate lookup by the texture // size and rewrites the image type to 2D. See NormalizeRectCoordinatesPass for // what it declines and why. - static bool LowerRectImages(const Vector& inputBinary, Vector& outputBinary); + static bool LowerRectImages(const Vector& inputBinary, Vector& outputBinary, + bool enableSpirvValidation = false); // GL_TEXTURE_1D_ARRAY storage images rewritten to the 2D-array shape the texture // is actually stored in on ES, with the layer moved from the coordinate's second // component to its third. DirectGLES transpile path only - Vulkan binds a real @@ -97,7 +106,8 @@ namespace MobileGL { // through untouched when the module declares no such image, which is every shader // but a handful. See Lower1DArrayImagesPass for what it declines and why. static bool Lower1DArrayImagesForEssl(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Gives each format-less storage image the format bound to its image unit, so // the emitted ESSL can carry the format layout qualifier GLSL ES requires of // every image and desktop GLSL lets a writeonly declaration omit. `glFormatByName` @@ -106,7 +116,8 @@ namespace MobileGL { // natively. See BakeImageFormatsPass for what it declines and why. static bool BakeImageFormatsForEssl(const Vector& inputBinary, const UnorderedMap& glFormatByName, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Whether the module declares a storage image with no format qualifier at all, // i.e. whether BakeImageFormatsForEssl could change anything. One module parse, // so the ~every shader that declares none pays no optimizer run. @@ -125,27 +136,31 @@ namespace MobileGL { // emitted text instead. static bool SpirvCrossCanPrintEsslImageFormat(Uint glInternalFormat); static bool RebaseInstanceIndexForVulkan(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Builds the non-indexed-draw variant of a vertex shader: every gl_BaseVertex // read becomes zero, which is what GL defines for a command carrying no // baseVertex parameter while Vulkan's builtin would report firstVertex. // See ZeroBaseVertexPass. static bool ZeroBaseVertexForVulkan(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Re-declares 64-bit float vertex inputs as their 32-bit unsigned word pair // (double -> uvec2, dvec2 -> uvec4) and bitcasts them back to double at entry, so no // VK_FORMAT_R64*_SFLOAT is needed - lavapipe advertises none of them for vertex // buffers. Vertex stage, DirectVulkan only; pairs with the Float64 case in // VertexInputStateFactory::ToVkVertexFormat. static bool PackDoubleVertexInputsForVulkan(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Adds the Invariant decoration to every Position builtin output. GL apps // routinely rely on cross-program position invariance for multi-pass // equality depth tests (e.g. GEQUAL re-draws of the same geometry), and // mobile drivers that optimize per-pipeline break that without the // decoration. DirectVulkan only. static bool DecoratePositionInvariantForVulkan(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); // Replaces the declared format of float storage images with Unknown and adds the // matching SPIR-V capabilities. DirectVulkan uses this only when both Vulkan // shaderStorageImage*WithoutFormat features are enabled, allowing the @@ -153,13 +168,15 @@ namespace MobileGL { // storage images deliberately keep their declared format for GL-compatible bit // reinterpretation paths (for example, R32F storage accessed as r32ui). static bool UseUnformattedFloatStorageImagesForVulkan( - const Vector& inputBinary, Vector& outputBinary); + const Vector& inputBinary, Vector& outputBinary, + bool enableSpirvValidation = false); // Rewrites every 64-bit float in the module to a 32-bit one, preserving every // block offset and stride exactly (see DemoteFloat64Pass). Already part of // SanitizeAndOptimizeBinary, which is where production reaches it; exposed // separately so a test can drive the demotion on its own. static bool DemoteFloat64ToFloat32(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool enableSpirvValidation = false); static Result DecompileShader(SpvcSession& session); // Parses one trivial shader in each configuration the production path can @@ -186,17 +203,16 @@ namespace MobileGL { // no way left to warm it. static void ResetPrewarmLatch(); - // SPIR-V validation is disabled by default because it is diagnostics-only - // overhead. MOBILEGL_ENABLE_SPIRV_VALIDATION is parsed once during - // MobileGL::Initialize() and published before workers are started. When enabled, - // every Optimizer wrapper in this file validates its OUTPUT binary - the bytes a - // driver can actually receive - and a failure logs the VUID and bumps the failure - // latch below WITHOUT changing the wrapper's return value: control flow must stay - // identical between validating and shipping configurations, or fail-open call - // sites would make the two render differently. The setter is safe for test - // fixtures and other standalone compiler users. - static bool SpirvValidationEnabled(); - static void SetSpirvValidationEnabled(bool enabled); + // Validation is an explicit immutable option of each compiler operation. The + // program-link task snapshots MOBILEGL_ENABLE_SPIRV_VALIDATION before it can run + // on a worker; standalone callers pass true directly. A failure logs the VUID and + // bumps the latch below WITHOUT changing a wrapper's return value, so validating + // and shipping configurations preserve identical rendering control flow. + + // Makes validator table lifetime safe before an external final-module validator + // runs. This has no configuration state; callers invoke it only for an enabled + // task-local validation option. + static void PrepareSpirvValidation(); // The test-lane enforcement signal: total validation failures observed this // process. Tests snapshot it, run the operation under scrutiny, and assert