[Fix, Test] (GLImpl, ShaderTranspiler): reconcile the atomic-counter limits glGetIntegerv and glslang advertise

This commit is contained in:
2026-08-20 11:20:21 -04:00
parent 0995dfea35
commit 6159166d38
4 changed files with 174 additions and 23 deletions
+59 -15
View File
@@ -25,6 +25,7 @@
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
#include <MG_Util/Texture/TextureFormatProcessor.h>
#include <MG_Util/Async/ShaderCompilePool.h>
#include <MG_Util/ShaderTranspiler/Types.h>
#include <MG_Backend/BackendObjects.h>
namespace MobileGL::MG_Impl::GLImpl {
@@ -47,12 +48,25 @@ namespace MobileGL::MG_Impl::GLImpl {
}
constexpr GLint kFrontendMaxComputeUniformComponents = 1024;
constexpr GLint kFrontendMaxComputeAtomicCounters = 8;
constexpr GLint kFrontendMaxComputeAtomicCounterBuffers = 8;
// 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
// used to be independent tables that disagreed on both the binding count and the buffer
// size. Never move one of these without the other.
constexpr GLint kFrontendMaxComputeAtomicCounters =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTERS_PER_STAGE);
constexpr GLint kFrontendMaxComputeAtomicCounterBuffers =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE);
constexpr GLint kFrontendMaxComputeSharedMemorySize = 32768;
constexpr GLint kFrontendMaxComputeWorkGroupInvocations = 1024;
constexpr GLint kFrontendMaxCombinedAtomicCounters = 8;
constexpr GLint kFrontendMaxFragmentAtomicCounters = 8;
constexpr GLint kFrontendMaxCombinedAtomicCounters =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTERS_PER_STAGE);
constexpr GLint kFrontendMaxCombinedAtomicCounterBuffers =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE);
constexpr GLint kFrontendMaxFragmentAtomicCounters =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTERS_PER_STAGE);
constexpr GLint kFrontendMaxFragmentAtomicCounterBuffers =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE);
constexpr GLint kFrontendMaxGeometryAtomicCounters = 0;
constexpr GLint kFrontendMaxTessControlAtomicCounters = 0;
constexpr GLint kFrontendMaxTessEvaluationAtomicCounters = 0;
@@ -66,10 +80,14 @@ namespace MobileGL::MG_Impl::GLImpl {
constexpr GLint kFrontendMaxTessControlAtomicCounterBuffers = 0;
constexpr GLint kFrontendMaxTessEvaluationAtomicCounterBuffers = 0;
constexpr GLint kFrontendMaxVertexAtomicCounterBuffers = 0;
// One atomic counter is a uint, and a buffer never has to hold more counters than the
// combined limit the frontend advertises. GL 4.6 table 23.63 floors this at 32 bytes.
// GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS / _SIZE. Both come from the shared table above:
// the binding count is how many counter buffers a backend can actually address (each one
// costs a shader-storage binding point once glslang has lowered the counters onto a
// storage block), and the size is the byte offset ceiling a counter may be declared at.
constexpr GLint kFrontendMaxAtomicCounterBufferBindings =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFER_BINDINGS);
constexpr GLint kFrontendMaxAtomicCounterBufferSize =
kFrontendMaxCombinedAtomicCounters * static_cast<GLint>(sizeof(GLuint));
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_ATOMIC_COUNTER_BUFFER_SIZE);
// KHR_debug minima (GL 4.6 table 23.66); the debug entry points are stubs, but the
// limits they advertise still have to be legal.
constexpr GLint kFrontendMaxDebugGroupStackDepth = 64;
@@ -1532,6 +1550,9 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_MAX_COMBINED_ATOMIC_COUNTERS:
*params = kFrontendMaxCombinedAtomicCounters;
return;
case GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS:
*params = kFrontendMaxCombinedAtomicCounterBuffers;
return;
case GL_MAX_COMBINED_UNIFORM_BLOCKS:
*params = ClampUniformBlockCount(kFrontendMaxCombinedUniformBlocks);
return;
@@ -1547,6 +1568,9 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_MAX_FRAGMENT_ATOMIC_COUNTERS:
*params = kFrontendMaxFragmentAtomicCounters;
return;
case GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS:
*params = kFrontendMaxFragmentAtomicCounterBuffers;
return;
case GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS:
*params = ClampStorageBlockCount(16); // TODO
return;
@@ -1984,6 +2008,24 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_UNIFORM_BUFFER_START:
RecordIndexedOnlyGetterError(__func__, pname);
return;
// glBindBufferBase/Range set the GENERIC binding point too (GL 4.6 core 6.1.1), and this
// is the one indexed-buffer family whose non-indexed query was never answered - so it
// fell through to INVALID_ENUM and left the caller's variable holding whatever was in its
// stack slot. _START/_SIZE stay indexed-only, exactly like their uniform-buffer siblings.
case GL_ATOMIC_COUNTER_BUFFER_BINDING:
if (const auto& obj =
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::AtomicCounter).GetBoundObject()) {
*params = static_cast<GLint>(obj->GetExternalIndex());
} else {
*params = 0;
}
return;
case GL_ATOMIC_COUNTER_BUFFER_START:
RecordIndexedOnlyGetterError(__func__, pname);
return;
case GL_ATOMIC_COUNTER_BUFFER_SIZE:
RecordIndexedOnlyGetterError(__func__, pname);
return;
case GL_UNPACK_ALIGNMENT:
*params = MG_State::pGLContext->GetPixelStoreParam(PixelStoreParam::UnpackAlignment);
return;
@@ -2219,18 +2261,20 @@ namespace MobileGL::MG_Impl::GLImpl {
static_cast<Uint64>(INT32_MAX)));
break;
case GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS:
*params = static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::AtomicCounter));
// NOT the frontend's binding-point array size (36). A counter buffer only reaches a
// shader as a lowered storage block, so the number an implementation can serve is
// the reserved shader-storage range, and it has to be the same number glslang
// compiles a layout(binding = N) atomic_uint against.
*params = std::min<GLint>(kFrontendMaxAtomicCounterBufferBindings,
static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::AtomicCounter)));
break;
case GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE:
// The conformance suite splits this evenly across every advertised binding point and
// binds all of them in one glBindBuffersRange
// (KHR-GL44.multi_bind.functional_bind_buffers_range), so the pair has to divide:
// 32 bytes over 36 binding points is a zero-sized range, which BindBufferRange
// rejects with INVALID_VALUE before it binds anything. Floor the advertised size at
// one counter per binding point.
*params = std::max<GLint>(
kFrontendMaxAtomicCounterBufferSize,
static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::AtomicCounter) * sizeof(GLuint)));
// (KHR-GL44.multi_bind.functional_bind_buffers_range), so the pair has to divide -
// a zero-sized range is INVALID_VALUE before BindBufferRange binds anything. The
// shared constant is 16384 over 8 binding points, which divides.
*params = kFrontendMaxAtomicCounterBufferSize;
break;
case GL_MAX_TEXTURE_BUFFER_SIZE:
*params = dynamicParameters.MaxTextureBufferSize;