mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +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.
|
// - DISPLAY: X11 session variable, not MobileGL configuration.
|
||||||
// - MOBILEGL_LOG_FILE_PATH: log-file init runs before MG_ConfigLoader::Init
|
// - MOBILEGL_LOG_FILE_PATH: log-file init runs before MG_ConfigLoader::Init
|
||||||
// (see MG_Util/Debug/Log.cpp).
|
// (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 {
|
struct FeaturesTable {
|
||||||
// MOBILEGL_DISABLE_TIMERQUERY: do not advertise or use GPU timer queries.
|
// MOBILEGL_DISABLE_TIMERQUERY: do not advertise or use GPU timer queries.
|
||||||
Bool DisableTimerQuery = false;
|
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.
|
// MOBILEGL_USE_ANGLE: load ANGLE EGL/GLES libraries.
|
||||||
Bool UseAngle = false;
|
Bool UseAngle = false;
|
||||||
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
inline void InitFeatures() {
|
inline void InitFeatures() {
|
||||||
auto& features = MG_Config::Features;
|
auto& features = MG_Config::Features;
|
||||||
features.DisableTimerQuery = QueryEnvFlag("MOBILEGL_DISABLE_TIMERQUERY");
|
features.DisableTimerQuery = QueryEnvFlag("MOBILEGL_DISABLE_TIMERQUERY");
|
||||||
|
features.EnableSpirvValidation = QueryEnvFlag("MOBILEGL_ENABLE_SPIRV_VALIDATION");
|
||||||
features.UseAngle = QueryEnvFlag("MOBILEGL_USE_ANGLE");
|
features.UseAngle = QueryEnvFlag("MOBILEGL_USE_ANGLE");
|
||||||
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||||
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
|
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ namespace MobileGL {
|
|||||||
MG_Util::Debug::InitFile();
|
MG_Util::Debug::InitFile();
|
||||||
MGLOG_I("Initializing MobileGL...");
|
MGLOG_I("Initializing MobileGL...");
|
||||||
MG_ConfigLoader::Init();
|
MG_ConfigLoader::Init();
|
||||||
|
MG_Util::ShaderTranspiler::ShaderCompiler::SetSpirvValidationEnabled(
|
||||||
|
MG_Config::Features.EnableSpirvValidation);
|
||||||
MGLOG_I("Config loaded");
|
MGLOG_I("Config loaded");
|
||||||
MG_State::Init();
|
MG_State::Init();
|
||||||
MGLOG_D("MG_State initialized");
|
MGLOG_D("MG_State initialized");
|
||||||
|
|||||||
@@ -102,7 +102,9 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MGLOG_D("ProgramObject %u: Starting SPIR-V generation", externalIndex);
|
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
|
// 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
|
// 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
|
// 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());
|
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,
|
/* As we passed first stage compilation/linking,
|
||||||
* we'll assume all the operations here should
|
* we'll assume all the operations here should
|
||||||
* pass. We may be able to employ some optimizations
|
* pass. We may be able to employ some optimizations
|
||||||
@@ -169,7 +172,8 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Bool allOptimized = true;
|
Bool allOptimized = true;
|
||||||
{
|
{
|
||||||
for (auto& spv : artifacts.generatedSpirv) {
|
for (auto& spv : artifacts.generatedSpirv) {
|
||||||
auto success = ShaderCompiler::SanitizeAndOptimizeBinary(spv, spv);
|
auto success = ShaderCompiler::SanitizeAndOptimizeBinary(
|
||||||
|
spv, spv, !deferOutputValidationForDirectVulkan);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
// The one genuine phase-B failure mode: one of the seven optimizer passes
|
// 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
|
// reported failure, so `spv` is whatever the run left behind. A fordebug
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
private:
|
private:
|
||||||
void RunBody() override;
|
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);
|
void BuildGlobalUboRouting(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex);
|
||||||
|
|
||||||
// Worker-side MGLOG replacement, replayed by the join on the GL thread. Same reason as
|
// Worker-side MGLOG replacement, replayed by the join on the GL thread. Same reason as
|
||||||
|
|||||||
@@ -369,12 +369,10 @@ namespace MobileGL {
|
|||||||
return allSpirv;
|
return allSpirv;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -1 unresolved, 0 off, 1 on. Resolved once from MOBILEGL_VALIDATE_SPIRV on first
|
// Published by MobileGL::Initialize() before shader workers are created. Standalone
|
||||||
// use. A live getenv rather than an MG_Config::Features field, for the same reason
|
// compiler users and tests start with validation disabled and can opt in through the
|
||||||
// Config.h already exempts MOBILEGL_LOG_FILE_PATH: suites like SpirvPassTest never
|
// setter without reading process environment from parallel work.
|
||||||
// run MobileGL::Initialize(), and every Initialize() re-runs MG_ConfigLoader::Init,
|
static std::atomic<int> g_validateSpirv{0};
|
||||||
// which would clobber a programmatic override stored in the feature table.
|
|
||||||
static std::atomic<int> g_validateSpirv{-1};
|
|
||||||
// Total validation failures observed this process. This latch - not the wrappers'
|
// Total validation failures observed this process. This latch - not the wrappers'
|
||||||
// return values - is the test-lane signal: validation must never change what a
|
// return values - is the test-lane signal: validation must never change what a
|
||||||
// wrapper returns, or the validating lanes would render differently from the
|
// wrapper returns, or the validating lanes would render differently from the
|
||||||
@@ -383,28 +381,6 @@ namespace MobileGL {
|
|||||||
static std::atomic<Uint64> g_spirvValidationFailures{0};
|
static std::atomic<Uint64> g_spirvValidationFailures{0};
|
||||||
|
|
||||||
namespace {
|
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
|
// spirv-tools' validator lazily constructs function-local static tables on
|
||||||
// its first run, which on this codebase happens on a ShaderCompilePool
|
// its first run, which on this codebase happens on a ShaderCompilePool
|
||||||
// worker. Function-local statics are destroyed in reverse construction
|
// worker. Function-local statics are destroyed in reverse construction
|
||||||
@@ -502,32 +478,23 @@ namespace MobileGL {
|
|||||||
// spirv-tools drops pass diagnostics on the floor.
|
// spirv-tools drops pass diagnostics on the floor.
|
||||||
bool RunOptimizerChecked(const char* site, spvtools::Optimizer& optimizer,
|
bool RunOptimizerChecked(const char* site, spvtools::Optimizer& optimizer,
|
||||||
const Vector<Uint32>& inputBinary,
|
const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool validateOutput = true) {
|
||||||
spvtools::OptimizerOptions options;
|
spvtools::OptimizerOptions options;
|
||||||
options.set_run_validator(false);
|
options.set_run_validator(false);
|
||||||
optimizer.SetMessageConsumer(MakeSpirvMessageConsumer(site));
|
optimizer.SetMessageConsumer(MakeSpirvMessageConsumer(site));
|
||||||
if (!optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options)) {
|
if (!optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (validateOutput) {
|
||||||
ValidateOrLatch(site, outputBinary);
|
ValidateOrLatch(site, outputBinary);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool ShaderCompiler::SpirvValidationEnabled() {
|
bool ShaderCompiler::SpirvValidationEnabled() {
|
||||||
int state = g_validateSpirv.load(std::memory_order_acquire);
|
return g_validateSpirv.load(std::memory_order_acquire) == 1;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderCompiler::SetSpirvValidationEnabled(bool enabled) {
|
void ShaderCompiler::SetSpirvValidationEnabled(bool enabled) {
|
||||||
@@ -613,7 +580,8 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool validateOutput) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
|
|
||||||
@@ -663,7 +631,7 @@ namespace MobileGL {
|
|||||||
optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass());
|
optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass());
|
||||||
|
|
||||||
return RunOptimizerChecked("SanitizeAndOptimizeBinary", optimizer, inputBinary,
|
return RunOptimizerChecked("SanitizeAndOptimizeBinary", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, validateOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ namespace MobileGL {
|
|||||||
static Result<SharedPtr<glslang::TProgram>> LinkProgram(const ProgramAttrib& attrib);
|
static Result<SharedPtr<glslang::TProgram>> LinkProgram(const ProgramAttrib& attrib);
|
||||||
static Result<Vector<Vector<unsigned>>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib);
|
static Result<Vector<Vector<unsigned>>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib);
|
||||||
static bool SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
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
|
// Demotes DrawIndex/BaseInstance/BaseVertex builtins to plain Private globals
|
||||||
// (mg_DrawID/mg_BaseInstance/mg_BaseVertex) so SPIRV-Cross can emit ESSL.
|
// (mg_DrawID/mg_BaseInstance/mg_BaseVertex) so SPIRV-Cross can emit ESSL.
|
||||||
// Only for backends without native draw-parameter support (DirectGLES).
|
// Only for backends without native draw-parameter support (DirectGLES).
|
||||||
@@ -185,16 +186,15 @@ namespace MobileGL {
|
|||||||
// no way left to warm it.
|
// no way left to warm it.
|
||||||
static void ResetPrewarmLatch();
|
static void ResetPrewarmLatch();
|
||||||
|
|
||||||
// Test-environment SPIR-V validation. When enabled, every Optimizer wrapper
|
// SPIR-V validation is disabled by default because it is diagnostics-only
|
||||||
// in this file validates its OUTPUT binary - the bytes a driver can actually
|
// overhead. MOBILEGL_ENABLE_SPIRV_VALIDATION is parsed once during
|
||||||
// receive - and a failure logs the VUID (via MGLOG_I; see the consumer for
|
// MobileGL::Initialize() and published before workers are started. When enabled,
|
||||||
// why not MGLOG_E) and bumps the failure latch below WITHOUT changing the
|
// every Optimizer wrapper in this file validates its OUTPUT binary - the bytes a
|
||||||
// wrapper's return value: control flow must stay identical between the
|
// driver can actually receive - and a failure logs the VUID and bumps the failure
|
||||||
// validating and shipping configurations, or fail-open call sites would make
|
// latch below WITHOUT changing the wrapper's return value: control flow must stay
|
||||||
// the two render differently. Resolved lazily from MOBILEGL_VALIDATE_SPIRV;
|
// identical between validating and shipping configurations, or fail-open call
|
||||||
// defaults on for desktop/CI/WSL builds and off for device (__ANDROID__)
|
// sites would make the two render differently. The setter is safe for test
|
||||||
// builds. The setter wins over the environment and is safe to call from test
|
// fixtures and other standalone compiler users.
|
||||||
// fixtures at any time.
|
|
||||||
static bool SpirvValidationEnabled();
|
static bool SpirvValidationEnabled();
|
||||||
static void SetSpirvValidationEnabled(bool enabled);
|
static void SetSpirvValidationEnabled(bool enabled);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user