[Fix, Test] (GLState): keep the atomic-counter and storage blocks out of the GL uniform-block list

This commit is contained in:
2026-08-21 11:54:45 -04:00
parent fdbe0b3117
commit f5a0779385
6 changed files with 368 additions and 43 deletions
@@ -129,6 +129,34 @@ namespace {
return element;
}
// Blocks come out of reflection in three kinds and only one of them is a GL uniform block.
// The same split ProgramInterface::ClassifyBlock makes (it reads the flattened
// TypeFacts::isBuffer, which is this very qualifier), reachable here from the live TProgram
// because the block index spaces are built before the reflection snapshot exists.
// The transpiler lowers every atomic_uint onto a synthesized "gl_AtomicCounterBlock_<binding>"
// buffer block, which reflection then reports as an ordinary block. It is not one: GL
// enumerates it through GL_ACTIVE_ATOMIC_COUNTER_BUFFERS instead.
static MobileGL::Bool IsAtomicCounterBlockName(const MobileGL::String& name) {
namespace Transpiler = MobileGL::MG_Util::ShaderTranspiler;
const MobileGL::SizeT prefixLength = std::strlen(Transpiler::ATOMIC_COUNTER_BLOCK_PREFIX);
return name.compare(0, prefixLength, Transpiler::ATOMIC_COUNTER_BLOCK_PREFIX) == 0;
}
// A shader storage block: GL enumerates it through GL_SHADER_STORAGE_BLOCK and its members
// through GL_BUFFER_VARIABLE. The counter blocks above are buffer blocks too, hence the
// exclusion. A block whose type reflection did not survive is treated as a uniform block,
// which is what every caller assumed before this classification existed.
static MobileGL::Bool IsStorageBlock(const glslang::TObjectReflection& block) {
if (IsAtomicCounterBlockName(block.name)) return false;
const glslang::TType* type = block.getType();
return type != nullptr && type->getQualifier().storage == glslang::EvqBuffer;
}
static MobileGL::Bool IsGlUniformBlock(const glslang::TObjectReflection& block) {
return !IsAtomicCounterBlockName(block.name) && !IsStorageBlock(block);
}
// GL 4.6 core 7.7 / ARB_shader_atomic_counters: within one binding no two atomic counters
// may occupy the same bytes, every offset is a multiple of 4, and no counter may reach past
// GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE. glslang enforces all three in fixOffset(), which the
@@ -1009,6 +1037,30 @@ namespace MobileGL::MG_State::GLState {
artifacts.glBlockIndexToTProgram.push_back(i);
}
// The GL_UNIFORM_BLOCK subsequence of that space. MobileGL does not pass
// EShReflectionSeparateBuffers to buildReflection above, so glslang files BUFFER blocks
// under indexToUniformBlock as well and the list just built also holds every shader
// storage block and every synthesized gl_AtomicCounterBlock_N. GL 4.6 core 7.6 says
// GL_ACTIVE_UNIFORM_BLOCKS / glGetActiveUniformBlockiv / glGetUniformBlockIndex see
// uniform blocks and nothing else; an atomic counter buffer is enumerated by
// GL_ACTIVE_ATOMIC_COUNTER_BUFFERS and a storage block by GL_SHADER_STORAGE_BLOCK.
//
// A SECOND space rather than a filter of the first, deliberately: the block space is
// what the backends walk (DirectGLES hands out one ESSL uniform-buffer binding point per
// entry as it goes) and what "tProgramBlockIndexToGl[i] < 0 means MGL_GLOBAL_UBO" reads,
// and neither may move.
artifacts.blockIndexToGlUniformBlock.assign(artifacts.glBlockIndexToTProgram.size(), -1);
artifacts.glUniformBlockIndexToBlock.clear();
for (SizeT blockIndex = 0; blockIndex < artifacts.glBlockIndexToTProgram.size(); ++blockIndex) {
const auto& block = artifacts.program->getUniformBlock(artifacts.glBlockIndexToTProgram[blockIndex]);
if (!IsGlUniformBlock(block)) continue;
artifacts.blockIndexToGlUniformBlock[blockIndex] =
static_cast<Int>(artifacts.glUniformBlockIndexToBlock.size());
artifacts.glUniformBlockIndexToBlock.push_back(static_cast<Int>(blockIndex));
}
MGLOG_D("ProgramObject %u: Reflection - %zu block(s), %zu of them GL uniform blocks", in.externalIndex,
artifacts.glBlockIndexToTProgram.size(), artifacts.glUniformBlockIndexToBlock.size());
// ------------ Uniforms (GL Plain) ----------------
// The relaxed parse sweeps every DECLARED default-block uniform into
// MGL_GLOBAL_UBO whether or not any stage reads it. GL requires a
@@ -1472,14 +1524,21 @@ namespace MobileGL::MG_State::GLState {
}
// ---------- UBO ----------
// GL-visible blocks only (MGL_GLOBAL_UBO was filtered out above).
// The BLOCK space (MGL_GLOBAL_UBO was filtered out above, storage and atomic counter
// blocks were not): these tables are what the backends index, and what the GL
// uniform-block entry points reach after translating out of the GL_UNIFORM_BLOCK space.
const Int uboCount = static_cast<Int>(artifacts.glBlockIndexToTProgram.size());
MGLOG_D("ProgramObject %u: Reflection - uniform block count (UBO) = %d", in.externalIndex, uboCount);
artifacts.uniformBlockBinding.resize(uboCount, -1);
for (Int i = 0; i < uboCount; i++) {
auto& ubo = artifacts.program->getUniformBlock(artifacts.glBlockIndexToTProgram[i]);
artifacts.uniformBlockNameMaxLength =
std::max(artifacts.uniformBlockNameMaxLength, (Int)ubo.name.length());
// GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH is measured over the names
// glGetActiveUniformBlockName can report, so only the GL uniform blocks count -
// a long storage-block name must not size the caller's buffer.
if (artifacts.blockIndexToGlUniformBlock[i] >= 0) {
artifacts.uniformBlockNameMaxLength =
std::max(artifacts.uniformBlockNameMaxLength, (Int)ubo.name.length());
}
artifacts.uniformBlockIndexByName[ubo.name] = i;
// if there's binding defined in shader as layout(binding = ...),
// retrieve it here.
@@ -331,6 +331,8 @@ namespace MobileGL::MG_State::GLState {
artifacts.tProgramUniformIndexToGl.clear();
artifacts.glBlockIndexToTProgram.clear();
artifacts.tProgramBlockIndexToGl.clear();
artifacts.glUniformBlockIndexToBlock.clear();
artifacts.blockIndexToGlUniformBlock.clear();
artifacts.linkedExplicitUniformLocations.clear();
artifacts.uniformInitialValues.clear();
artifacts.uniformIndexInTProgram.clear();
@@ -279,12 +279,12 @@ namespace MobileGL::MG_State::GLState {
if (tIndex < 0 || tIndex >= static_cast<Int>(Artifacts().tProgramUniformIndexToGl.size())) return -1;
return Artifacts().tProgramUniformIndexToGl[tIndex];
}
// GL uniform-block index -> glslang TProgram block index (the inverse of
// Block index -> glslang TProgram block index (the inverse of
// GlBlockIndexFromTProgram). The interface-query layer needs it to reach block
// properties glslang exposes but no typed getter here does.
Int TProgramBlockIndex(Uint glBlockIndex) const {
return glBlockIndex < Artifacts().glBlockIndexToTProgram.size()
? Artifacts().glBlockIndexToTProgram[glBlockIndex]
Int TProgramBlockIndex(Uint blockIndex) const {
return blockIndex < Artifacts().glBlockIndexToTProgram.size()
? Artifacts().glBlockIndexToTProgram[blockIndex]
: -1;
}
Int GlBlockIndexFromTProgram(Int tBlockIndex) const {
@@ -292,6 +292,41 @@ namespace MobileGL::MG_State::GLState {
return Artifacts().tProgramBlockIndexToGl[tBlockIndex];
}
// ---- GL_UNIFORM_BLOCK index <-> block index translation ----
// The block index space above carries the storage blocks and the synthesized atomic
// counter blocks as well; GL_ACTIVE_UNIFORM_BLOCKS counts only actual uniform blocks
// (GL 4.6 core 7.6). Every glGetActiveUniformBlock* / glGetUniformBlockIndex /
// glUniformBlockBinding entry point speaks THIS space and translates into the block
// space before touching any of the block-keyed tables; the backends keep speaking the
// block space directly. See LinkArtifacts::glUniformBlockIndexToBlock.
Int GetGlUniformBlockCount() const {
return static_cast<Int>(Artifacts().glUniformBlockIndexToBlock.size());
}
Bool IsActiveGlUniformBlock(Uint glUniformBlockIndex) const {
return glUniformBlockIndex < Artifacts().glUniformBlockIndexToBlock.size();
}
Int BlockIndexFromGlUniformBlock(Uint glUniformBlockIndex) const {
return glUniformBlockIndex < Artifacts().glUniformBlockIndexToBlock.size()
? Artifacts().glUniformBlockIndexToBlock[glUniformBlockIndex]
: -1;
}
Int GlUniformBlockIndexFromBlock(Int blockIndex) const {
if (blockIndex < 0 || blockIndex >= static_cast<Int>(Artifacts().blockIndexToGlUniformBlock.size())) {
return -1;
}
return Artifacts().blockIndexToGlUniformBlock[blockIndex];
}
// glGetUniformBlockIndex: GL_INVALID_INDEX for a name that is not an active UNIFORM
// block, which includes every storage block and every atomic counter block even though
// GetUniformBlockIndex() below resolves them (it answers in the block space, which the
// backends need to keep reaching them by name).
Uint GetGlUniformBlockIndex(const char* name) const {
const Uint blockIndex = GetUniformBlockIndex(name);
if (blockIndex == 0xFFFFFFFFu) return 0xFFFFFFFFu;
const Int glIndex = GlUniformBlockIndexFromBlock(static_cast<Int>(blockIndex));
return glIndex < 0 ? 0xFFFFFFFFu : static_cast<Uint>(glIndex);
}
Int GetActiveUniformIndex(const String& name) const {
// uniformIndexByName is keyed by the REFLECTED name, so a lookup that hits is
// already the exact-match the old code re-verified with a string compare after
@@ -340,7 +375,10 @@ namespace MobileGL::MG_State::GLState {
return GetUniformArraySizeByTIndex(TProgramUniformIndex(index));
}
Int GetActiveUniformBlockIndex(Uint index) const {
// The BLOCK index of the block owning this active uniform, or -1 when it owns none as
// far as GL is concerned. Internal: pair it with another block-space index, never with
// a GL_UNIFORM_BLOCK one (GetActiveUniformBlockIndex below is that one).
Int GetActiveUniformOwnerBlockIndex(Uint index) const {
// An atomic counter is a DEFAULT-BLOCK uniform to GL, whatever block the
// transpiler lowered it onto (GL 4.6 core 7.6, table 7.6): -1.
if (IsActiveUniformAtomicCounter(index)) return -1;
@@ -348,6 +386,13 @@ namespace MobileGL::MG_State::GLState {
return GlBlockIndexFromTProgram(UniformAt(TProgramUniformIndex(index)).index);
}
// GL_UNIFORM_BLOCK_INDEX: an index into the GL_ACTIVE_UNIFORM_BLOCKS list, or -1. A
// buffer variable owns a storage block, which is not in that list, so it answers -1 too
// (and after the enumeration filter it is not an active uniform in the first place).
Int GetActiveUniformBlockIndex(Uint index) const {
return GlUniformBlockIndexFromBlock(GetActiveUniformOwnerBlockIndex(index));
}
// The transpiler lowers every atomic_uint onto a synthesized gl_AtomicCounterBlock_N
// block, but GL keeps seeing an atomic counter as a default-block uniform of type
// GL_UNSIGNED_INT_ATOMIC_COUNTER that points at an atomic-counter BUFFER. These two
@@ -875,14 +920,19 @@ namespace MobileGL::MG_State::GLState {
Int GetActiveAttributesCount() const {
return static_cast<Int>(Artifacts().pipeInputReflection.size());
}
// GL-visible uniform blocks only: the synthesized MGL_GLOBAL_UBO the relaxed parse
// materializes for default-block uniforms is filtered out by DoReflection.
// Size of the BLOCK index space - every block the relaxed parse produced except the
// synthesized MGL_GLOBAL_UBO, which DoReflection filters out. NOT the answer to
// glGetProgramiv(GL_ACTIVE_UNIFORM_BLOCKS): storage blocks and atomic counter blocks
// live in here too, and GetGlUniformBlockCount() is the one that excludes them.
Int GetActiveUniformBlocksCount() const { return static_cast<Int>(Artifacts().glBlockIndexToTProgram.size()); }
GLuint GetComputeLocalSize(Uint dim) const {
return dim < 3u ? Artifacts().computeLocalSize[dim] : 0u;
}
Int GetActiveAttributesMaxLength() const { return Artifacts().attribInNameMaxLength; }
Int GetActiveUniformBlocksMaxNameLength() const { return Artifacts().uniformBlockNameMaxLength; }
// Answers in the BLOCK space, so it resolves storage and atomic counter blocks too -
// the backends reach those by name. glGetUniformBlockIndex must NOT: use
// GetGlUniformBlockIndex() for the GL entry point.
Uint GetUniformBlockIndex(const char* name) const {
auto it = Artifacts().uniformBlockIndexByName.find(name);
if (it != Artifacts().uniformBlockIndexByName.end()) return it->second;
@@ -893,12 +943,11 @@ namespace MobileGL::MG_State::GLState {
if (it != Artifacts().uniformBlockIndexByName.end()) return it->second;
return 0xFFFFFFFFu; // GL_INVALID_INDEX
}
Bool IsActiveUniformBlock(Uint index) const {
if (index >= GetActiveUniformBlocksCount()) return false;
return true;
}
// Takes a BLOCK index. The GL entry points validate their argument against the
// GL_UNIFORM_BLOCK space with IsActiveGlUniformBlock() first and translate; the bound
// test here is only the range of the space this index actually lives in.
Uint GetUBOSizeAt(Uint index) const {
if (!IsActiveUniformBlock(index)) return 0;
if (index >= Artifacts().glBlockIndexToTProgram.size()) return 0;
// glslang reports the unpadded end offset of the last member, but a std140 block
// (like a std140 struct) occupies a vec4-rounded size, and that is what the
// backend compiles: ES drivers reject draws whose bound UBO range is smaller
@@ -928,11 +977,14 @@ namespace MobileGL::MG_State::GLState {
// fills GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, so the two queries always agree
// (glslang's numMembers counts declared members, which diverges from the reflected
// entry list for struct arrays and arrayed block instances).
// Takes a BLOCK index, and scans in the block space: GetUniformBlockMemberOwnerIndex
// answers there, so pairing it with the GL_UNIFORM_BLOCK-space
// GetActiveUniformBlockIndex would compare two different numberings.
Int GetUniformBlockActiveUniformCount(Uint index) const {
const Int ownerIndex = static_cast<Int>(GetUniformBlockMemberOwnerIndex(index));
Int count = 0;
for (Uint uniformIndex = 0; uniformIndex < Artifacts().activeUniformCount; ++uniformIndex) {
if (GetActiveUniformBlockIndex(uniformIndex) == ownerIndex) ++count;
if (GetActiveUniformOwnerBlockIndex(uniformIndex) == ownerIndex) ++count;
}
return count;
}
@@ -1131,6 +1183,28 @@ namespace MobileGL::MG_State::GLState {
Vector<Int> tProgramUniformIndexToGl;
Vector<Int> glBlockIndexToTProgram;
Vector<Int> tProgramBlockIndexToGl;
// GL_UNIFORM_BLOCK index space: ACTUAL uniform blocks only, a strict subsequence of
// glBlockIndexToTProgram above.
//
// That list is the BLOCK space - everything the relaxed parse produced except
// MGL_GLOBAL_UBO - and it is what the backends walk and what every block-keyed table
// here (uniformBlockBinding, uniformBlockIndexByName, blockReflection ordering) is
// indexed by. It is NOT the GL uniform-block list: MobileGL does not pass
// EShReflectionSeparateBuffers to buildReflection, so glslang routes BUFFER blocks
// through indexToUniformBlock too, and the list therefore also carries every shader
// storage block and every synthesized gl_AtomicCounterBlock_N. GL 4.6 core 7.6 gives
// those their own enumerations (GL_SHADER_STORAGE_BLOCK and
// GL_ACTIVE_ATOMIC_COUNTER_BUFFERS respectively), and GL_ACTIVE_UNIFORM_BLOCKS /
// glGetActiveUniformBlock*/glGetUniformBlockIndex must not see either.
//
// Kept as a SECOND space rather than filtering the first in place: DirectGLES assigns
// one ESSL uniform-buffer binding point per entry of the block list as it walks it
// (Managers.cpp CacheResourceLocations and the matching per-draw loop in
// DirectGLES.cpp), so compacting that list would renumber every backend binding
// point, and tProgramBlockIndexToGl[i] < 0 is what DoReflection and
// BuildGlobalUboRouting read as "member of the synthesized global UBO".
Vector<Int> glUniformBlockIndexToBlock; // GL uniform-block index -> block index
Vector<Int> blockIndexToGlUniformBlock; // block index -> GL uniform-block index (-1)
// Per-link merged snapshot of the attached shaders' lexically extracted
// layout(location = N) default-block uniform qualifiers (the relaxed parse drops
// them from reflection; the DoReflection assigner restores them from here).