mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08: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
|
// Every atomic-counter limit is shared with the glslang resource table
|
||||||
// (BuildTBuiltInResource) through MG_Util/ShaderTranspiler/Types.h: GL 4.6 requires
|
// (BuildTBuiltInResource) through MG_Util/ShaderTranspiler/Types.h: GL 4.6 requires
|
||||||
// glGetIntegerv and the gl_MaxAtomicCounter* built-in constants to agree, and the two
|
// 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 kFrontendSubpixelBits = 4;
|
||||||
constexpr GLint kFrontendMaxSamples = 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) {
|
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) {
|
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,
|
GLint GetMaxCombinedUniformComponents(GLint maxDefaultUniformComponents, GLint maxUniformBlocks,
|
||||||
|
|||||||
@@ -994,6 +994,68 @@ void main() {
|
|||||||
MG_State::pGLContext = Move(previousContext);
|
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) {
|
TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) {
|
||||||
using namespace MobileGL;
|
using namespace MobileGL;
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
|||||||
HashValue(state, env.maxComputeWorkGroupSize[0]);
|
HashValue(state, env.maxComputeWorkGroupSize[0]);
|
||||||
HashValue(state, env.maxComputeWorkGroupSize[1]);
|
HashValue(state, env.maxComputeWorkGroupSize[1]);
|
||||||
HashValue(state, env.maxComputeWorkGroupSize[2]);
|
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.maxComputeWorkGroupInvocations);
|
||||||
HashValue(state, env.backend);
|
HashValue(state, env.backend);
|
||||||
// DynamicBackendParameters is a plain aggregate of scalars; hashing its object
|
// 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;
|
env->advertisedExtensions = activeBackend->GetRendererInfo().RendererGLInfo.Extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GL_MAX_COMPUTE_WORK_GROUP_SIZE. This is a REAL driver call on DirectGLES; it must
|
// GL_MAX_COMPUTE_WORK_GROUP_SIZE / _COUNT. These are REAL driver calls on DirectGLES; they
|
||||||
// happen here, on the context thread, and exactly once per context. The frontend
|
// must happen here, on the context thread, and exactly once per context. The frontend
|
||||||
// minimum is the floor, matching what GL_Getter reports.
|
// minimum is the floor, matching what GL_Getter reports - both sides now floor at the
|
||||||
// TODO: Share these exposed compute limit helpers with GL_Getter.cpp instead of duplicating the frontend minima.
|
// shared MIN_COMPUTE_WORK_GROUP_* constants rather than at their own copy of them.
|
||||||
constexpr Uint kFrontendMinComputeWorkGroupSizes[3] = {1024, 1024, 64};
|
|
||||||
for (Uint index = 0; index < 3; ++index) {
|
for (Uint index = 0; index < 3; ++index) {
|
||||||
Int backendValue = 0;
|
Int backendSize = 0;
|
||||||
|
Int backendCount = 0;
|
||||||
if (MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v) {
|
if (MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v) {
|
||||||
MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, index,
|
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] =
|
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;
|
constexpr Uint64 kFrontendMaxComputeWorkGroupInvocations = 1024;
|
||||||
|
|||||||
@@ -12,6 +12,18 @@
|
|||||||
#include <MG_Backend/BackendObject.h>
|
#include <MG_Backend/BackendObject.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Util::ShaderTranspiler {
|
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 -
|
// 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
|
// so the transformation is a pure function of its three arguments and can run on a worker
|
||||||
// thread.
|
// thread.
|
||||||
@@ -34,7 +46,13 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
|||||||
struct CompileEnv {
|
struct CompileEnv {
|
||||||
// --- compute limits: the ONLY former real-driver read in the pipeline ---
|
// --- compute limits: the ONLY former real-driver read in the pipeline ---
|
||||||
// GL_MAX_COMPUTE_WORK_GROUP_SIZE, already max()'d with the frontend minimum.
|
// 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.
|
// GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, likewise.
|
||||||
Uint64 maxComputeWorkGroupInvocations = 1024;
|
Uint64 maxComputeWorkGroupInvocations = 1024;
|
||||||
|
|
||||||
|
|||||||
@@ -83,14 +83,7 @@ namespace MobileGL {
|
|||||||
Resources.minProgramTexelOffset = -8;
|
Resources.minProgramTexelOffset = -8;
|
||||||
Resources.maxProgramTexelOffset = 7;
|
Resources.maxProgramTexelOffset = 7;
|
||||||
Resources.maxClipDistances = 8;
|
Resources.maxClipDistances = 8;
|
||||||
Resources.maxComputeWorkGroupCountX = 65535;
|
Resources.maxComputeUniformComponents = MAX_COMPUTE_UNIFORM_COMPONENTS;
|
||||||
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.maxComputeTextureImageUnits = 16;
|
Resources.maxComputeTextureImageUnits = 16;
|
||||||
Resources.maxComputeImageUniforms = 8;
|
Resources.maxComputeImageUniforms = 8;
|
||||||
Resources.maxComputeAtomicCounters = MAX_ATOMIC_COUNTERS_PER_STAGE;
|
Resources.maxComputeAtomicCounters = MAX_ATOMIC_COUNTERS_PER_STAGE;
|
||||||
@@ -179,6 +172,25 @@ namespace MobileGL {
|
|||||||
Resources.maxFragmentImageUniforms = dynamicParameters.MaxFragmentImageUniforms;
|
Resources.maxFragmentImageUniforms = dynamicParameters.MaxFragmentImageUniforms;
|
||||||
Resources.maxComputeImageUniforms = dynamicParameters.MaxComputeImageUniforms;
|
Resources.maxComputeImageUniforms = dynamicParameters.MaxComputeImageUniforms;
|
||||||
Resources.maxCombinedImageUniforms = dynamicParameters.MaxCombinedImageUniforms;
|
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.nonInductiveForLoops = true;
|
||||||
Resources.limits.whileLoops = true;
|
Resources.limits.whileLoops = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user