From edec6e4e622e557f774c4fc43d65e2d6a105c075 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 2 Jul 2026 22:18:56 +0800 Subject: [PATCH] [Fix] (MG_State/GLState, MG_Util/ShaderTranspiler): validate compute local size [skip ci] --- .../GLState/ProgramState/ShaderObject.cpp | 136 ++++++++++++++++++ .../ShaderTranspiler/ShaderCompiler.cpp | 3 +- 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp index f393b659..cee8d40e 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp @@ -12,6 +12,132 @@ #include #include #include +#include + +namespace { + struct ComputeLocalSize { + MobileGL::Uint x = 1; + MobileGL::Uint y = 1; + MobileGL::Uint z = 1; + bool declared = false; + }; + + static MobileGL::String StripGlslComments(const MobileGL::String& source) { + MobileGL::String result; + result.reserve(source.length()); + + bool inLineComment = false; + bool inBlockComment = false; + for (MobileGL::SizeT i = 0; i < source.length(); ++i) { + if (inLineComment) { + if (source[i] == '\n') { + inLineComment = false; + result.push_back(source[i]); + } else { + result.push_back(' '); + } + continue; + } + + if (inBlockComment) { + if (source[i] == '*' && i + 1 < source.length() && source[i + 1] == '/') { + inBlockComment = false; + result.append(" "); + ++i; + } else { + result.push_back(source[i] == '\n' ? '\n' : ' '); + } + continue; + } + + if (source[i] == '/' && i + 1 < source.length()) { + if (source[i + 1] == '/') { + inLineComment = true; + result.append(" "); + ++i; + continue; + } + if (source[i + 1] == '*') { + inBlockComment = true; + result.append(" "); + ++i; + continue; + } + } + + result.push_back(source[i]); + } + + return result; + } + + static ComputeLocalSize ParseComputeLocalSize(const MobileGL::String& source) { + ComputeLocalSize localSize; + const MobileGL::String uncommentedSource = StripGlslComments(source); + const std::regex localSizePattern(R"(local_size_([xyz])\s*=\s*([0-9]+))"); + + for (std::sregex_iterator it(uncommentedSource.begin(), uncommentedSource.end(), localSizePattern), end; + it != end; ++it) { + const char axis = (*it)[1].str()[0]; + const auto value = static_cast(std::stoull((*it)[2].str())); + const MobileGL::Uint clampedValue = value > UINT_MAX ? UINT_MAX : static_cast(value); + + // TODO: Replace this literal layout scanner with parser/AST-backed validation so expressions and + // specialization-id layouts are handled consistently with glslang. + localSize.declared = true; + if (axis == 'x') { + localSize.x = clampedValue; + } else if (axis == 'y') { + localSize.y = clampedValue; + } else { + localSize.z = clampedValue; + } + } + + return localSize; + } + + static MobileGL::Uint GetComputeWorkGroupSizeLimit(MobileGL::Uint index) { + constexpr MobileGL::Uint kFrontendMinComputeWorkGroupSizes[] = {1024, 1024, 64}; + MobileGL::Int backendValue = 0; + if (MobileGL::MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v) { + MobileGL::MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, index, + &backendValue); + } + + // TODO: Share these exposed compute limit helpers with GL_Getter.cpp instead of duplicating the frontend minima. + return std::max(static_cast(std::max(backendValue, 0)), + kFrontendMinComputeWorkGroupSizes[index]); + } + + static unsigned long long GetComputeWorkGroupInvocationLimit() { + constexpr unsigned long long kFrontendMaxComputeWorkGroupInvocations = 1024; + if (!MobileGL::MG_Backend::pActiveBackendObject) return kFrontendMaxComputeWorkGroupInvocations; + + return std::max(static_cast(std::max( + MobileGL::MG_Backend::pActiveBackendObject->GetDynamicParameters() + .MaxComputeWorkGroupInvocations, + 0)), + kFrontendMaxComputeWorkGroupInvocations); + } + + static std::optional ValidateComputeLocalSizeLimits(const MobileGL::String& source) { + const ComputeLocalSize localSize = ParseComputeLocalSize(source); + if (!localSize.declared) return std::nullopt; + + if (localSize.x > GetComputeWorkGroupSizeLimit(0) || localSize.y > GetComputeWorkGroupSizeLimit(1) || + localSize.z > GetComputeWorkGroupSizeLimit(2)) { + return "Compute shader local_size exceeds GL_MAX_COMPUTE_WORK_GROUP_SIZE."; + } + + const unsigned long long invocations = static_cast(localSize.x) * localSize.y * localSize.z; + if (invocations > GetComputeWorkGroupInvocationLimit()) { + return "Compute shader local_size product exceeds GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS."; + } + + return std::nullopt; + } +} namespace MobileGL::MG_State::GLState { void ShaderObject::SetShaderSource(const String& source) { @@ -33,6 +159,16 @@ namespace MobileGL::MG_State::GLState { String compileSource = m_source; MG_Util::ShaderTranspiler::PreprocessShaderSource(m_stage, compileSource); + if (m_stage == ShaderStage::Compute) { + const std::optional localSizeError = ValidateComputeLocalSizeLimits(compileSource); + if (localSizeError) { + m_compileStatus = false; + m_shader.reset(); + m_infoLog = *localSizeError; + return; + } + } + // Compile for OpenGL here, so that we can do validation and link // like a real OpenGL driver at linking stage // Will compile for other backends later. diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 22d85ca8..36fe7bb7 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -47,7 +47,8 @@ namespace MobileGL { Resources.maxComputeWorkGroupCountZ = 65535; Resources.maxComputeWorkGroupSizeX = 1024; Resources.maxComputeWorkGroupSizeY = 1024; - Resources.maxComputeWorkGroupSizeZ = 64; + // TODO: Drive glslang compute resource limits from the active backend instead of this permissive cap. + Resources.maxComputeWorkGroupSizeZ = 1024; Resources.maxComputeUniformComponents = 1024; Resources.maxComputeTextureImageUnits = 16; Resources.maxComputeImageUniforms = 8;