diff --git a/MobileGL/Config.h b/MobileGL/Config.h index 341e6bff..97b928da 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -66,14 +66,12 @@ namespace MobileGL::MG_Config { // - DISPLAY: X11 session variable, not MobileGL configuration. // - MOBILEGL_LOG_FILE_PATH: log-file init runs before MG_ConfigLoader::Init // (see MG_Util/Debug/Log.cpp). - // - MOBILEGL_VALIDATE_SPIRV: test suites like SpirvPassTest exercise - // ShaderCompiler without ever running MobileGL::Initialize(), and every - // Initialize() re-runs MG_ConfigLoader::Init, which would clobber a - // programmatic override stored here (see ShaderCompiler.cpp, - // SpirvValidationEnabled). struct FeaturesTable { // MOBILEGL_DISABLE_TIMERQUERY: do not advertise or use GPU timer queries. Bool DisableTimerQuery = false; + // MOBILEGL_ENABLE_SPIRV_VALIDATION: validate generated and transformed SPIR-V. + // Disabled by default because validation is a diagnostics-only cost. + Bool EnableSpirvValidation = false; // MOBILEGL_USE_ANGLE: load ANGLE EGL/GLES libraries. Bool UseAngle = false; #if defined(MOBILEGL_TRACE_ANGLE_VARIANTS) diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index e37dabfe..476019a1 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -162,6 +162,7 @@ namespace MobileGL::MG_ConfigLoader { inline void InitFeatures() { auto& features = MG_Config::Features; features.DisableTimerQuery = QueryEnvFlag("MOBILEGL_DISABLE_TIMERQUERY"); + features.EnableSpirvValidation = QueryEnvFlag("MOBILEGL_ENABLE_SPIRV_VALIDATION"); features.UseAngle = QueryEnvFlag("MOBILEGL_USE_ANGLE"); #if defined(MOBILEGL_TRACE_ANGLE_VARIANTS) QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, ""); diff --git a/MobileGL/Init.cpp b/MobileGL/Init.cpp index 905be751..7611c791 100644 --- a/MobileGL/Init.cpp +++ b/MobileGL/Init.cpp @@ -85,6 +85,8 @@ 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_State/GLState/ProgramState/ProgramSpirvTask.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp index 08f479c0..906d250b 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp @@ -102,7 +102,9 @@ namespace MobileGL::MG_State::GLState { } MGLOG_D("ProgramObject %u: Starting SPIR-V generation", externalIndex); - GenerateSpirv(handoff, externalIndex); + const Bool deferOutputValidationForDirectVulkan = + m_phaseA->in.env != nullptr && m_phaseA->in.env->backend == BackendType::DirectVulkan; + GenerateSpirv(handoff, externalIndex, deferOutputValidationForDirectVulkan); // 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 @@ -137,7 +139,8 @@ namespace MobileGL::MG_State::GLState { artifacts.generatedSpirv.size()); } - void ProgramSpirvTask::GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, const Uint externalIndex) { + void ProgramSpirvTask::GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, const Uint externalIndex, + const Bool deferOutputValidationForDirectVulkan) { /* As we passed first stage compilation/linking, * we'll assume all the operations here should * pass. We may be able to employ some optimizations @@ -169,7 +172,8 @@ namespace MobileGL::MG_State::GLState { Bool allOptimized = true; { for (auto& spv : artifacts.generatedSpirv) { - auto success = ShaderCompiler::SanitizeAndOptimizeBinary(spv, spv); + auto success = ShaderCompiler::SanitizeAndOptimizeBinary( + spv, spv, !deferOutputValidationForDirectVulkan); 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 c983e388..4147fe9e 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h @@ -65,7 +65,8 @@ namespace MobileGL::MG_State::GLState { private: void RunBody() override; - void GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex); + void GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex, + Bool deferOutputValidationForDirectVulkan); 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_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 17271264..8da5ffb8 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -369,12 +369,10 @@ namespace MobileGL { return allSpirv; } - // -1 unresolved, 0 off, 1 on. Resolved once from MOBILEGL_VALIDATE_SPIRV on first - // use. A live getenv rather than an MG_Config::Features field, for the same reason - // Config.h already exempts MOBILEGL_LOG_FILE_PATH: suites like SpirvPassTest never - // run MobileGL::Initialize(), and every Initialize() re-runs MG_ConfigLoader::Init, - // which would clobber a programmatic override stored in the feature table. - static std::atomic g_validateSpirv{-1}; + // 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 @@ -383,28 +381,6 @@ namespace MobileGL { static std::atomic g_spirvValidationFailures{0}; namespace { - // Test lanes (desktop/CI/WSL) validate by default; device builds do not - - // validation costs real time per module, and on device the driver is the - // final validator anyway. MOBILEGL_VALIDATE_SPIRV overrides in either - // direction, using the ConfigLoader truthy rule. - constexpr bool kValidateSpirvDefault = -#if defined(__ANDROID__) - false; -#else - true; -#endif - - bool IsTruthySpirvEnvValue(const char* value) { - if (value == nullptr || value[0] == '\0') { - return false; - } - String lowered(value); - for (auto& c : lowered) { - c = static_cast(std::tolower(static_cast(c))); - } - return lowered != "0" && lowered != "false"; - } - // spirv-tools' validator lazily constructs function-local static tables on // its first run, which on this codebase happens on a ShaderCompilePool // worker. Function-local statics are destroyed in reverse construction @@ -502,32 +478,23 @@ namespace MobileGL { // spirv-tools drops pass diagnostics on the floor. bool RunOptimizerChecked(const char* site, spvtools::Optimizer& optimizer, const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + bool validateOutput = true) { spvtools::OptimizerOptions options; options.set_run_validator(false); optimizer.SetMessageConsumer(MakeSpirvMessageConsumer(site)); if (!optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options)) { return false; } - ValidateOrLatch(site, outputBinary); + if (validateOutput) { + ValidateOrLatch(site, outputBinary); + } return true; } } // namespace bool ShaderCompiler::SpirvValidationEnabled() { - int state = g_validateSpirv.load(std::memory_order_acquire); - if (state < 0) { - const char* env = std::getenv("MOBILEGL_VALIDATE_SPIRV"); - const bool resolved = env != nullptr ? IsTruthySpirvEnvValue(env) : kValidateSpirvDefault; - int expected = -1; - g_validateSpirv.compare_exchange_strong(expected, resolved ? 1 : 0, - std::memory_order_acq_rel); - state = g_validateSpirv.load(std::memory_order_acquire); - if (state == 1) { - PinValidatorTablesForProcessExit(); - } - } - return state == 1; + return g_validateSpirv.load(std::memory_order_acquire) == 1; } void ShaderCompiler::SetSpirvValidationEnabled(bool enabled) { @@ -613,7 +580,8 @@ namespace MobileGL { } bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector& inputBinary, - Vector& outputBinary) { + Vector& outputBinary, + bool validateOutput) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); @@ -663,7 +631,7 @@ namespace MobileGL { optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass()); return RunOptimizerChecked("SanitizeAndOptimizeBinary", optimizer, inputBinary, - outputBinary); + outputBinary, validateOutput); } bool ShaderCompiler::LowerDrawParametersForEssl(const Vector& inputBinary, diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h index 3469823a..4b0479eb 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h @@ -23,7 +23,8 @@ namespace MobileGL { static Result> LinkProgram(const ProgramAttrib& attrib); static Result>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib); static bool SanitizeAndOptimizeBinary(const Vector& inputBinary, - Vector& outputBinary); + Vector& outputBinary, + bool validateOutput = true); // 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). @@ -185,16 +186,15 @@ namespace MobileGL { // no way left to warm it. static void ResetPrewarmLatch(); - // Test-environment SPIR-V validation. 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 (via MGLOG_I; see the consumer for - // why not MGLOG_E) and bumps the failure latch below WITHOUT changing the - // wrapper's return value: control flow must stay identical between the - // validating and shipping configurations, or fail-open call sites would make - // the two render differently. Resolved lazily from MOBILEGL_VALIDATE_SPIRV; - // defaults on for desktop/CI/WSL builds and off for device (__ANDROID__) - // builds. The setter wins over the environment and is safe to call from test - // fixtures at any time. + // 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);