mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 12:18:30 +09:00
[Fix, Test] (GLImpl, ShaderTranspiler): reconcile the compute work-group limits glGetIntegeri_v and glslang advertise
This commit is contained in:
@@ -47,7 +47,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr GLint kFrontendMaxComputeUniformComponents = 1024;
|
||||
// Shared with the glslang resource table for the same reason as the atomic-counter
|
||||
// limits below: gl_MaxComputeUniformComponents expands from BuildTBuiltInResource.
|
||||
constexpr GLint kFrontendMaxComputeUniformComponents =
|
||||
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_COMPUTE_UNIFORM_COMPONENTS);
|
||||
// Every atomic-counter limit is shared with the glslang resource table
|
||||
// (BuildTBuiltInResource) through MG_Util/ShaderTranspiler/Types.h: GL 4.6 requires
|
||||
// glGetIntegerv and the gl_MaxAtomicCounter* built-in constants to agree, and the two
|
||||
@@ -118,12 +121,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
constexpr GLint kFrontendSubpixelBits = 4;
|
||||
constexpr GLint kFrontendMaxSamples = 4;
|
||||
|
||||
// The floors under GL_MAX_COMPUTE_WORK_GROUP_COUNT / _SIZE. Shared with the compile
|
||||
// pipeline (CaptureCompileEnv floors the same driver answers at them, and
|
||||
// BuildTBuiltInResource expands gl_MaxComputeWorkGroup* from the result), because a
|
||||
// shader is allowed to compare the built-in constant against this query.
|
||||
constexpr GLint GetMinComputeWorkGroupCount(GLuint index) {
|
||||
return index < 3 ? 65535 : 0;
|
||||
return index < 3 ? static_cast<GLint>(MG_Util::ShaderTranspiler::MIN_COMPUTE_WORK_GROUP_COUNT[index]) : 0;
|
||||
}
|
||||
|
||||
constexpr GLint GetMinComputeWorkGroupSize(GLuint index) {
|
||||
return index < 2 ? 1024 : (index == 2 ? 64 : 0);
|
||||
return index < 3 ? static_cast<GLint>(MG_Util::ShaderTranspiler::MIN_COMPUTE_WORK_GROUP_SIZE[index]) : 0;
|
||||
}
|
||||
|
||||
GLint GetMaxCombinedUniformComponents(GLint maxDefaultUniformComponents, GLint maxUniformBlocks,
|
||||
|
||||
@@ -994,6 +994,68 @@ void main() {
|
||||
MG_State::pGLContext = Move(previousContext);
|
||||
}
|
||||
|
||||
// KHR-GL43.compute_shader.max: the test queries every GL_MAX_COMPUTE_* value through the API and
|
||||
// then makes a compute shader compare the matching gl_MaxCompute* constant against it. The two
|
||||
// used to be independent tables and gl_MaxComputeWorkGroupSize.z disagreed - glslang compiled
|
||||
// against a permissive 1024 while the context advertises the 64 the GL 4.6 minimum (and every ES
|
||||
// driver) reports.
|
||||
TEST(GetterSanity, ComputeWorkGroupQueriesMatchShaderCompilerLimits) {
|
||||
using namespace MobileGL;
|
||||
|
||||
auto previousContext = Move(MG_State::pGLContext);
|
||||
auto previousBackend = Move(MG_Backend::pActiveBackendObject);
|
||||
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(MG_Backend::DynamicBackendParameters{});
|
||||
|
||||
GLint size[3] = {0, 0, 0};
|
||||
GLint count[3] = {0, 0, 0};
|
||||
for (GLuint index = 0; index < 3; ++index) {
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, index, &size[index]);
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, index, &count[index]);
|
||||
}
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
// A negative array size is a compile error, so the stage only compiles when EVERY component
|
||||
// of both built-in constants equals what the query above reported. Two-sided by construction:
|
||||
// a resource table that is too permissive fails it exactly like one that is too tight.
|
||||
const String source = R"(#version 430 core
|
||||
layout(local_size_x = 1) in;
|
||||
const int mgAgree = (gl_MaxComputeWorkGroupSize == ivec3()" +
|
||||
std::to_string(size[0]) + ", " + std::to_string(size[1]) + ", " +
|
||||
std::to_string(size[2]) + R"() &&
|
||||
gl_MaxComputeWorkGroupCount == ivec3()" +
|
||||
std::to_string(count[0]) + ", " + std::to_string(count[1]) + ", " +
|
||||
std::to_string(count[2]) + R"()) ? 1 : -1;
|
||||
int mgProbe[mgAgree];
|
||||
void main() {
|
||||
mgProbe[0] = 0;
|
||||
}
|
||||
)";
|
||||
auto compiled = MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({
|
||||
.shaderType = GL_COMPUTE_SHADER,
|
||||
.sourceStr = source,
|
||||
});
|
||||
EXPECT_TRUE(compiled) << (compiled ? "" : compiled.error().log);
|
||||
|
||||
// The z ceiling is also what glslang checks a declared local_size_z against, so it has to
|
||||
// reject one invocation past the advertised limit and accept the limit itself.
|
||||
const String atLimit = "#version 430 core\nlayout(local_size_z = " + std::to_string(size[2]) +
|
||||
") in;\nvoid main() {}\n";
|
||||
const String pastLimit = "#version 430 core\nlayout(local_size_z = " + std::to_string(size[2] + 1) +
|
||||
") in;\nvoid main() {}\n";
|
||||
EXPECT_TRUE(MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({
|
||||
.shaderType = GL_COMPUTE_SHADER,
|
||||
.sourceStr = atLimit,
|
||||
}));
|
||||
EXPECT_FALSE(MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({
|
||||
.shaderType = GL_COMPUTE_SHADER,
|
||||
.sourceStr = pastLimit,
|
||||
}));
|
||||
|
||||
MG_Backend::pActiveBackendObject = Move(previousBackend);
|
||||
MG_State::pGLContext = Move(previousContext);
|
||||
}
|
||||
|
||||
TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) {
|
||||
using namespace MobileGL;
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
HashValue(state, env.maxComputeWorkGroupSize[0]);
|
||||
HashValue(state, env.maxComputeWorkGroupSize[1]);
|
||||
HashValue(state, env.maxComputeWorkGroupSize[2]);
|
||||
HashValue(state, env.maxComputeWorkGroupCount[0]);
|
||||
HashValue(state, env.maxComputeWorkGroupCount[1]);
|
||||
HashValue(state, env.maxComputeWorkGroupCount[2]);
|
||||
HashValue(state, env.maxComputeWorkGroupInvocations);
|
||||
HashValue(state, env.backend);
|
||||
// DynamicBackendParameters is a plain aggregate of scalars; hashing its object
|
||||
@@ -51,19 +54,23 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
env->advertisedExtensions = activeBackend->GetRendererInfo().RendererGLInfo.Extensions;
|
||||
}
|
||||
|
||||
// GL_MAX_COMPUTE_WORK_GROUP_SIZE. This is a REAL driver call on DirectGLES; it must
|
||||
// happen here, on the context thread, and exactly once per context. The frontend
|
||||
// minimum is the floor, matching what GL_Getter reports.
|
||||
// TODO: Share these exposed compute limit helpers with GL_Getter.cpp instead of duplicating the frontend minima.
|
||||
constexpr Uint kFrontendMinComputeWorkGroupSizes[3] = {1024, 1024, 64};
|
||||
// GL_MAX_COMPUTE_WORK_GROUP_SIZE / _COUNT. These are REAL driver calls on DirectGLES; they
|
||||
// must happen here, on the context thread, and exactly once per context. The frontend
|
||||
// minimum is the floor, matching what GL_Getter reports - both sides now floor at the
|
||||
// shared MIN_COMPUTE_WORK_GROUP_* constants rather than at their own copy of them.
|
||||
for (Uint index = 0; index < 3; ++index) {
|
||||
Int backendValue = 0;
|
||||
Int backendSize = 0;
|
||||
Int backendCount = 0;
|
||||
if (MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v) {
|
||||
MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, index,
|
||||
&backendValue);
|
||||
&backendSize);
|
||||
MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, index,
|
||||
&backendCount);
|
||||
}
|
||||
env->maxComputeWorkGroupSize[index] =
|
||||
std::max(static_cast<Uint>(std::max(backendValue, 0)), kFrontendMinComputeWorkGroupSizes[index]);
|
||||
std::max(static_cast<Uint>(std::max(backendSize, 0)), MIN_COMPUTE_WORK_GROUP_SIZE[index]);
|
||||
env->maxComputeWorkGroupCount[index] =
|
||||
std::max(static_cast<Uint>(std::max(backendCount, 0)), MIN_COMPUTE_WORK_GROUP_COUNT[index]);
|
||||
}
|
||||
|
||||
constexpr Uint64 kFrontendMaxComputeWorkGroupInvocations = 1024;
|
||||
|
||||
@@ -12,6 +12,18 @@
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
|
||||
namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
// GL_MAX_COMPUTE_WORK_GROUP_COUNT / _SIZE core minimums (GL 4.6 core table 23.45), in ONE
|
||||
// place because three separate readers have to agree on them: CaptureCompileEnv (which floors
|
||||
// the backend's answer at them), GL_Getter (which answers the same query the same way) and
|
||||
// BuildTBuiltInResource (whose gl_MaxComputeWorkGroup* constants a shader compares against
|
||||
// the query - KHR-GL43.compute_shader.max does exactly that). They used to be three copies,
|
||||
// and the z one disagreed: glslang compiled against 1024 while the context advertised 64.
|
||||
inline constexpr Uint MIN_COMPUTE_WORK_GROUP_COUNT[3] = {65535, 65535, 65535};
|
||||
inline constexpr Uint MIN_COMPUTE_WORK_GROUP_SIZE[3] = {1024, 1024, 64};
|
||||
// GL_MAX_COMPUTE_UNIFORM_COMPONENTS, the same invariant with no backend input: the number
|
||||
// glGetIntegerv answers and the number gl_MaxComputeUniformComponents expands to.
|
||||
inline constexpr Int MAX_COMPUTE_UNIFORM_COMPONENTS = 1024;
|
||||
|
||||
// everything outside (stage, source) this reads - advertised extensions and backend limits -
|
||||
// so the transformation is a pure function of its three arguments and can run on a worker
|
||||
// thread.
|
||||
@@ -34,7 +46,13 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
struct CompileEnv {
|
||||
// --- compute limits: the ONLY former real-driver read in the pipeline ---
|
||||
// GL_MAX_COMPUTE_WORK_GROUP_SIZE, already max()'d with the frontend minimum.
|
||||
Uint maxComputeWorkGroupSize[3] = {1024, 1024, 64};
|
||||
Uint maxComputeWorkGroupSize[3] = {MIN_COMPUTE_WORK_GROUP_SIZE[0], MIN_COMPUTE_WORK_GROUP_SIZE[1],
|
||||
MIN_COMPUTE_WORK_GROUP_SIZE[2]};
|
||||
// GL_MAX_COMPUTE_WORK_GROUP_COUNT, likewise. Carried for the same reason the size is:
|
||||
// gl_MaxComputeWorkGroupCount expands from it at parse time, so the compile pipeline
|
||||
// needs the number the context advertises without reaching back to the live backend.
|
||||
Uint maxComputeWorkGroupCount[3] = {MIN_COMPUTE_WORK_GROUP_COUNT[0], MIN_COMPUTE_WORK_GROUP_COUNT[1],
|
||||
MIN_COMPUTE_WORK_GROUP_COUNT[2]};
|
||||
// GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, likewise.
|
||||
Uint64 maxComputeWorkGroupInvocations = 1024;
|
||||
|
||||
|
||||
@@ -83,14 +83,7 @@ namespace MobileGL {
|
||||
Resources.minProgramTexelOffset = -8;
|
||||
Resources.maxProgramTexelOffset = 7;
|
||||
Resources.maxClipDistances = 8;
|
||||
Resources.maxComputeWorkGroupCountX = 65535;
|
||||
Resources.maxComputeWorkGroupCountY = 65535;
|
||||
Resources.maxComputeWorkGroupCountZ = 65535;
|
||||
Resources.maxComputeWorkGroupSizeX = 1024;
|
||||
Resources.maxComputeWorkGroupSizeY = 1024;
|
||||
// TODO: Drive glslang compute resource limits from the active backend instead of this permissive cap.
|
||||
Resources.maxComputeWorkGroupSizeZ = 1024;
|
||||
Resources.maxComputeUniformComponents = 1024;
|
||||
Resources.maxComputeUniformComponents = MAX_COMPUTE_UNIFORM_COMPONENTS;
|
||||
Resources.maxComputeTextureImageUnits = 16;
|
||||
Resources.maxComputeImageUniforms = 8;
|
||||
Resources.maxComputeAtomicCounters = MAX_ATOMIC_COUNTERS_PER_STAGE;
|
||||
@@ -179,6 +172,25 @@ namespace MobileGL {
|
||||
Resources.maxFragmentImageUniforms = dynamicParameters.MaxFragmentImageUniforms;
|
||||
Resources.maxComputeImageUniforms = dynamicParameters.MaxComputeImageUniforms;
|
||||
Resources.maxCombinedImageUniforms = dynamicParameters.MaxCombinedImageUniforms;
|
||||
Resources.maxComputeTextureImageUnits = dynamicParameters.MaxComputeTextureImageUnits;
|
||||
|
||||
// The compute work-group limits are the env's, not the backend parameters': they
|
||||
// are the only ones that come from a REAL indexed driver query, which
|
||||
// CaptureCompileEnv already issued once on the GL thread and floored at the core
|
||||
// minimum exactly as GL_Getter does. Reading the same snapshot here is what makes
|
||||
// gl_MaxComputeWorkGroupSize and glGetIntegeri_v agree by construction
|
||||
// (KHR-GL43.compute_shader.max compares them); the z component was 1024 here
|
||||
// against the 64 every ES driver reports. A null env is the standalone/test entry
|
||||
// point, which has no context to have queried one - the core minimums stand, which
|
||||
// is what a default-constructed CompileEnv carries anyway.
|
||||
const Uint* maxWorkGroupSize = env ? env->maxComputeWorkGroupSize : MIN_COMPUTE_WORK_GROUP_SIZE;
|
||||
const Uint* maxWorkGroupCount = env ? env->maxComputeWorkGroupCount : MIN_COMPUTE_WORK_GROUP_COUNT;
|
||||
Resources.maxComputeWorkGroupSizeX = static_cast<int>(maxWorkGroupSize[0]);
|
||||
Resources.maxComputeWorkGroupSizeY = static_cast<int>(maxWorkGroupSize[1]);
|
||||
Resources.maxComputeWorkGroupSizeZ = static_cast<int>(maxWorkGroupSize[2]);
|
||||
Resources.maxComputeWorkGroupCountX = static_cast<int>(maxWorkGroupCount[0]);
|
||||
Resources.maxComputeWorkGroupCountY = static_cast<int>(maxWorkGroupCount[1]);
|
||||
Resources.maxComputeWorkGroupCountZ = static_cast<int>(maxWorkGroupCount[2]);
|
||||
|
||||
Resources.limits.nonInductiveForLoops = true;
|
||||
Resources.limits.whileLoops = true;
|
||||
|
||||
Reference in New Issue
Block a user