mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Chore] (MG_Config, MG_State, MG_Util): gate SPIR-V validation at startup
This commit is contained in:
+3
-5
@@ -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)
|
||||
|
||||
@@ -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, "");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<int> 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<int> 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<Uint64> 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<char>(std::tolower(static_cast<unsigned char>(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<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary) {
|
||||
Vector<uint32_t>& 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<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary) {
|
||||
Vector<uint32_t>& 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<Uint32>& inputBinary,
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace MobileGL {
|
||||
static Result<SharedPtr<glslang::TProgram>> LinkProgram(const ProgramAttrib& attrib);
|
||||
static Result<Vector<Vector<unsigned>>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib);
|
||||
static bool SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary);
|
||||
Vector<uint32_t>& 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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user