[Feat] (MG_Impl/GLImpl, MG_State): implement glGetActiveUniformsiv (UBO reflection query)

Completes the uniform-block reflection chain: glGetUniformIndices, glGetActiveUniformName
and glGetActiveUniformBlockiv were already implemented; glGetActiveUniformsiv was the last
stub. Supports all 8 GL 3.3 Core pnames:

* GL_UNIFORM_TYPE / SIZE / NAME_LENGTH / BLOCK_INDEX / OFFSET / ARRAY_STRIDE come straight from
  glslang's TObjectReflection (the same reflection the existing uniform queries use).
* GL_UNIFORM_IS_ROW_MAJOR from the member's TType layout qualifier, guarded by isMatrix() so a
  scalar in a layout(row_major) block does not wrongly report 1.
* GL_UNIFORM_MATRIX_STRIDE is derived: glslang exposes no matrix stride, so it is computed from the
  std140 rule (each column/row vector rounded up to a vec4), which matches the std140 layout
  MobileGL's SPIR-V path emits. Evaluates to 16 for every GL 3.3 float matrix.

The -1-vs-0 distinction is handled explicitly: OFFSET / ARRAY_STRIDE / MATRIX_STRIDE / BLOCK_INDEX
return -1 for a default-block uniform (glslang gives arrayStride 0 there, so it is gated on block
membership), while ARRAY_STRIDE / MATRIX_STRIDE return 0 for a non-array / non-matrix member that IS
in a block. Errors: GL_INVALID_VALUE for uniformCount<0, any index >= active uniform count, or a
never-generated program name; GL_INVALID_OPERATION for a live shader name; GL_INVALID_ENUM for an
unaccepted pname (e.g. the GL 4.2 GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX). All validation runs before
any write, so params is untouched on error. There is no "not linked" error -- an unlinked program has
zero active uniforms, so any index raises GL_INVALID_VALUE.

Also fix GetActiveUniformArraySize, which returned glslang's TObjectReflection.size verbatim: that
field only carries the element count for a non-block array and reports 1 for a block array member,
so GL_UNIFORM_SIZE (and glGetActiveUniform's size out-param, and glGetProgramResourceiv's
GL_ARRAY_SIZE) wrongly reported 1 for an array inside a UBO. Take the count from the TType instead,
which is authoritative for both cases.

Covered by 3 ProgramTest cases (std140 block with scalar/array/mat4 + a default-block sampler, a
row_major variant, and the six error cases) that link real shaders and assert every pname value.
This commit is contained in:
2026-07-10 19:54:33 -04:00
parent d5e19cb7ba
commit 561d8992bc
5 changed files with 353 additions and 3 deletions
@@ -75,9 +75,17 @@ namespace MobileGL::MG_State::GLState {
return uniform.glDefineType;
}
// Number of active array elements (GL_UNIFORM_SIZE / GL_ARRAY_SIZE); 1 for a non-array.
// glslang's TObjectReflection.size only carries the element count for a NON-block array; for
// a block array member it reports 1, so take the count from the TType, which is authoritative
// for both. GL 3.3 core uniforms are always sized.
GLint GetActiveUniformArraySize(Uint index) const {
auto& uniform = m_program->getUniform(static_cast<Int>(index));
return uniform.size;
const auto& uniform = m_program->getUniform(static_cast<Int>(index));
const glslang::TType* type = uniform.getType();
if (type != nullptr && type->isSizedArray()) {
return type->getOuterArraySize();
}
return uniform.size < 1 ? 1 : uniform.size;
}
Int GetActiveUniformBlockIndex(Uint index) const {
@@ -85,6 +93,63 @@ namespace MobileGL::MG_State::GLState {
return uniform.index;
}
// GL_UNIFORM_OFFSET: byte offset within the owning named block. glslang already reports -1
// for a default-block uniform, which is exactly the spec value there.
GLint GetActiveUniformOffset(Uint index) const {
return m_program->getUniform(static_cast<Int>(index)).offset;
}
// GL_UNIFORM_ARRAY_STRIDE: byte stride of an array member in a named block; 0 for a non-array
// block member; -1 for a default-block uniform. glslang yields arrayStride==0 for the
// default-block case, so gate on block membership to return the spec-mandated -1.
GLint GetActiveUniformArrayStride(Uint index) const {
const auto& uniform = m_program->getUniform(static_cast<Int>(index));
return (uniform.index < 0) ? -1 : uniform.arrayStride;
}
// GL_UNIFORM_IS_ROW_MAJOR: 1 only for a row-major matrix in a named block, else 0. The
// isMatrix() guard is required -- glslang stamps a block-level layout(row_major) onto
// non-matrix members too, so a float/vec in a row_major block would otherwise report 1.
// For the glslang build here a block-level layout(row_major) is also resolved onto each
// matrix member's own qualifier (verified by GetActiveUniformsivRowMajorBlock), so the member
// check suffices; the getUniformBlock() fallback is defensive for a config that instead leaves
// an inheriting member's layoutMatrix == ElmNone.
GLint GetActiveUniformIsRowMajor(Uint index) const {
const auto& uniform = m_program->getUniform(static_cast<Int>(index));
if (uniform.index < 0) return 0;
const glslang::TType* type = uniform.getType();
if (type == nullptr || !type->isMatrix()) return 0;
glslang::TLayoutMatrix layoutMatrix = type->getQualifier().layoutMatrix;
if (layoutMatrix == glslang::ElmNone) {
layoutMatrix = m_program->getUniformBlock(uniform.index).getType()->getQualifier().layoutMatrix;
}
return (layoutMatrix == glslang::ElmRowMajor) ? 1 : 0;
}
// GL_UNIFORM_MATRIX_STRIDE: byte stride between columns (col-major) / rows (row-major) of a
// matrix in a named block; 0 for a non-matrix block member; -1 for a default-block uniform.
// glslang exposes no matrix stride, so it is derived from the std140 rule -- each column/row
// vector's base alignment rounded up to a vec4 (16 B). MobileGL's SPIR-V path lays every UBO
// out as std140 (packed/shared are coerced), so this matches the offsets glslang reports. For
// every GL 3.3 float matrix this evaluates to 16, independent of majorness.
GLint GetActiveUniformMatrixStride(Uint index) const {
const auto& uniform = m_program->getUniform(static_cast<Int>(index));
if (uniform.index < 0) return -1;
const glslang::TType* type = uniform.getType();
if (type == nullptr || !type->isMatrix()) return 0;
glslang::TLayoutMatrix layoutMatrix = type->getQualifier().layoutMatrix;
if (layoutMatrix == glslang::ElmNone) {
layoutMatrix = m_program->getUniformBlock(uniform.index).getType()->getQualifier().layoutMatrix;
}
const bool rowMajor = (layoutMatrix == glslang::ElmRowMajor);
const int strideVectorComponents = rowMajor ? type->getMatrixCols() : type->getMatrixRows();
constexpr int scalarSize = 4; // GL 3.3 core uniform matrices are float
const int vectorAlignment = (strideVectorComponents <= 1) ? scalarSize
: (strideVectorComponents == 2) ? 2 * scalarSize
: 4 * scalarSize;
return (vectorAlignment + 15) & ~15; // std140 round-up to a vec4
}
const glslang::TType* GetUniformTType(Uint location) const {
auto& uniform = m_program->getUniform(m_uniformIndexInTProgram[location]);
return uniform.getType();