mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix] (MG_Util/ShaderTranspiler, MG_State, MG_Impl/GLImpl, MG_Backend/DirectGLES): GL CTS uniform_block - coerce packed/shared block layouts to std140 at source preprocess (glslang rejects them when targeting SPIR-V; std140 is the only UBO layout the pipeline emits), GL-style block reflection (array "[0]" names, per-element struct-array expansion, unused members and declared-but-unread blocks stay active), vec4-padded GL_UNIFORM_BLOCK_DATA_SIZE, std140 array strides for struct-nested arrays (glslang reflects tight strides there), arrayed-block instances share the first instance member set, glDeleteShader-flagged names stay usable while attached, and backend ESSL emits against highp default precision so relaxed block members match across stages (KHR-GL33.shaders.uniform_block on llvmpipe: 659 Fail -> 828/828 Pass)
This commit is contained in:
@@ -279,6 +279,10 @@ namespace MobileGL::MG_State {
|
||||
return m_programState.MarkShaderObjectForDeletion(index);
|
||||
}
|
||||
|
||||
void GLContext::ReleaseShaderNameIfOrphaned(const Uint index) {
|
||||
return m_programState.ReleaseShaderNameIfOrphaned(index);
|
||||
}
|
||||
|
||||
Bool GLContext::ValidateProgramName(const Uint index) const {
|
||||
return m_programState.ValidateProgramObject(index);
|
||||
}
|
||||
|
||||
@@ -116,6 +116,9 @@ namespace MobileGL {
|
||||
Uint CreateShader(ShaderStage stage);
|
||||
void MarkProgramForDeletion(Uint index);
|
||||
void MarkShaderForDeletion(Uint index);
|
||||
// Frees a deletion-flagged shader's name once it lost its last GL-visible
|
||||
// attachment (call after glDetachShader).
|
||||
void ReleaseShaderNameIfOrphaned(Uint index);
|
||||
Bool ValidateProgramName(Uint index) const;
|
||||
Bool ValidateShaderName(Uint index) const;
|
||||
const SharedPtr<ProgramObject>& GetProgramObject(Uint index);
|
||||
|
||||
@@ -360,7 +360,18 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
MGLOG_D("ProgramObject %u: DoReflection - building reflection", m_externalIndex);
|
||||
if (!m_program->buildReflection()) {
|
||||
// GL-style reflection naming (GL CTS uniform_block relies on all four):
|
||||
// - BasicArraySuffix: an array uniform is reported as "arr[0]" per the GL spec.
|
||||
// - StrictArraySuffix: named-block struct arrays expand per element ("s[0].a",
|
||||
// "s[1].a", ...) following ARB_program_interface_query rules. Default-block
|
||||
// (loose) uniforms already expand per element without this option.
|
||||
// - AllBlockVariables: every member of an active named block is active even when
|
||||
// no shader statement reads it (ES 3.0/GL 3.3 named-block semantics).
|
||||
// - SharedStd140UBO: a DECLARED uniform block is active even when no member is
|
||||
// ever read (reflected from the linker objects). PreprocessShaderSource coerces
|
||||
// every block to std140, so this covers all of them.
|
||||
if (!m_program->buildReflection(EShReflectionStrictArraySuffix | EShReflectionBasicArraySuffix |
|
||||
EShReflectionAllBlockVariables | EShReflectionSharedStd140UBO)) {
|
||||
m_linkStatus = false;
|
||||
m_infoLog = "Build reflection failed.";
|
||||
MGLOG_E("ProgramObject %u: DoReflection - buildReflection() returned false", m_externalIndex);
|
||||
@@ -483,7 +494,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto explicitBinding = m_explicitOpaqueUniformBindings.find(uniform.name);
|
||||
// Reflection names an array "texs[0]" while the layout(binding = N) map from the IO
|
||||
// resolver is keyed by the declared name ("texs"); look up both spellings.
|
||||
auto explicitBinding = m_explicitOpaqueUniformBindings.find(uniform.name);
|
||||
if (explicitBinding == m_explicitOpaqueUniformBindings.end() && uniform.name.length() > 3 &&
|
||||
uniform.name.compare(uniform.name.length() - 3, 3, "[0]") == 0) {
|
||||
explicitBinding =
|
||||
m_explicitOpaqueUniformBindings.find(uniform.name.substr(0, uniform.name.length() - 3));
|
||||
}
|
||||
const int initialUnit =
|
||||
explicitBinding != m_explicitOpaqueUniformBindings.end() ? static_cast<int>(explicitBinding->second) : 0;
|
||||
const Int locationSpan = GetUniformLocationSpan(uniform);
|
||||
@@ -685,7 +703,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
m_globalUboScratch.resize(size);
|
||||
}
|
||||
for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) {
|
||||
const auto locationIt = m_uniformLocations.find(name);
|
||||
// SPIRV-Reflect leaf names never carry a "[0]" suffix; frontend
|
||||
// reflection keys arrays as "arr[0]" (GL naming), so retry with the
|
||||
// suffix before declaring the uniform unbacked.
|
||||
auto locationIt = m_uniformLocations.find(name);
|
||||
if (locationIt == m_uniformLocations.end()) {
|
||||
locationIt = m_uniformLocations.find(name + "[0]");
|
||||
}
|
||||
if (locationIt == m_uniformLocations.end()) {
|
||||
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u but not found in "
|
||||
"m_uniformLocations",
|
||||
|
||||
@@ -18,6 +18,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
public:
|
||||
ProgramObject(Uint externalIndex) : m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()) {}
|
||||
bool ShaderIsAttached(const SharedPtr<ShaderObject>& shader);
|
||||
// GL-visible attachment: in the attach list and not pending detach (glDetachShader
|
||||
// defers the actual removal to the next link).
|
||||
Bool ShaderIsAttachedGLVisible(const SharedPtr<ShaderObject>& shader) const {
|
||||
const auto matches = [&shader](const SharedPtr<ShaderObject>& s) { return s.get() == shader.get(); };
|
||||
if (std::none_of(m_shaders.begin(), m_shaders.end(), matches)) return false;
|
||||
return std::none_of(m_detachedShaders.begin(), m_detachedShaders.end(), matches);
|
||||
}
|
||||
bool AttachShader(const SharedPtr<ShaderObject>& shader);
|
||||
SizeT DetachShader(const SharedPtr<ShaderObject>& shader);
|
||||
SizeT RemoveShader(const SharedPtr<ShaderObject>& shader);
|
||||
@@ -45,10 +52,16 @@ namespace MobileGL::MG_State::GLState {
|
||||
const auto it = m_uniformLocations.find(name);
|
||||
if (it != m_uniformLocations.end()) return (Int)it->second;
|
||||
|
||||
// "arr[k]" resolves to the location of element k: glslang reflection stores
|
||||
// arrays under their base name (no "[0]" suffix), and DoReflection reserves
|
||||
// one location per array element, so element k lives at base + k.
|
||||
if (name.length() < 4 || name.back() != ']') return -1;
|
||||
// Reflection stores GL-style names: an array uniform is keyed "arr[0]" (its base
|
||||
// location). A bare "arr" query resolves to that entry; an "arr[k]" query resolves
|
||||
// to base + k because DoReflection reserves one location per array element.
|
||||
if (name.empty()) return -1;
|
||||
if (name.back() != ']') {
|
||||
const auto suffixedIt = m_uniformLocations.find(name + "[0]");
|
||||
if (suffixedIt != m_uniformLocations.end()) return (Int)suffixedIt->second;
|
||||
return -1;
|
||||
}
|
||||
if (name.length() < 4) return -1;
|
||||
const SizeT bracket = name.rfind('[');
|
||||
// Require at least one digit between the brackets.
|
||||
if (bracket == String::npos || bracket + 1 >= name.length() - 1) return -1;
|
||||
@@ -58,8 +71,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
element = element * 10 + static_cast<Uint>(name[i] - '0');
|
||||
if (element > 0x0FFFFFFFu) return -1;
|
||||
}
|
||||
const auto baseIt = m_uniformLocations.find(name.substr(0, bracket));
|
||||
if (baseIt == m_uniformLocations.end()) return -1;
|
||||
auto baseIt = m_uniformLocations.find(name.substr(0, bracket) + "[0]");
|
||||
if (baseIt == m_uniformLocations.end()) {
|
||||
// Legacy key without the "[0]" suffix (defensive; reflection normally
|
||||
// stores the suffixed form for arrays).
|
||||
baseIt = m_uniformLocations.find(name.substr(0, bracket));
|
||||
if (baseIt == m_uniformLocations.end()) return -1;
|
||||
}
|
||||
const Int base = (Int)baseIt->second;
|
||||
if (!IsValidUniformLocation(base)) return -1;
|
||||
const Int index = m_uniformIndexInTProgram[base];
|
||||
@@ -86,6 +104,19 @@ namespace MobileGL::MG_State::GLState {
|
||||
return uniformIndex;
|
||||
}
|
||||
|
||||
// Reflection stores an array uniform under "arr[0]"; accept the bare "arr"
|
||||
// spelling too. The reverse ("arr[0]" against a bare "arr" entry) is kept for
|
||||
// robustness against non-suffixed reflection entries.
|
||||
if (!name.empty() && name.back() != ']') {
|
||||
const String suffixedName = name + "[0]";
|
||||
const Int suffixedIndex = m_program->getUniformIndex(suffixedName.c_str());
|
||||
if (suffixedIndex >= 0 && suffixedIndex < m_activeUniformCount &&
|
||||
m_program->getUniform(suffixedIndex).name == suffixedName) {
|
||||
return suffixedIndex;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (name.length() <= 3 || name.compare(name.length() - 3, 3, "[0]") != 0) return -1;
|
||||
const String baseName = name.substr(0, name.length() - 3);
|
||||
const Int baseIndex = m_program->getUniformIndex(baseName.c_str());
|
||||
@@ -136,11 +167,24 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
// 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.
|
||||
// block member; -1 for a default-block uniform (glslang yields arrayStride==0 there, so gate
|
||||
// on block membership for the spec-mandated -1). The stride itself is derived from the type
|
||||
// instead of glslang's reflected arrayStride: for an array nested inside a struct member,
|
||||
// glslang computes that field against the enclosing STRUCT's (unset) packing and reports a
|
||||
// tight std430-like stride (ivec2 a[7] -> 8), even though its own member offsets and the
|
||||
// generated SPIR-V lay the array out with std140 16-byte-rounded strides. MobileGL's UBO
|
||||
// layout is always std140, where every array element stride rounds up to a vec4.
|
||||
GLint GetActiveUniformArrayStride(Uint index) const {
|
||||
const auto& uniform = m_program->getUniform(static_cast<Int>(index));
|
||||
return (uniform.index < 0) ? -1 : uniform.arrayStride;
|
||||
if (uniform.index < 0) return -1;
|
||||
const glslang::TType* type = uniform.getType();
|
||||
if (type == nullptr || !type->isArray()) return 0;
|
||||
if (type->isMatrix()) {
|
||||
const bool rowMajor = GetActiveUniformIsRowMajor(index) != 0;
|
||||
const int vectors = rowMajor ? type->getMatrixRows() : type->getMatrixCols();
|
||||
return GetActiveUniformMatrixStride(index) * vectors;
|
||||
}
|
||||
return 16; // scalars and vectors: std140 rounds the element stride up to a vec4
|
||||
}
|
||||
|
||||
// GL_UNIFORM_IS_ROW_MAJOR: 1 only for a row-major matrix in a named block, else 0. The
|
||||
@@ -327,6 +371,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
Uint GetUniformBlockIndex(const char* name) const {
|
||||
auto it = m_uniformBlockIndexByName.find(name);
|
||||
if (it != m_uniformBlockIndexByName.end()) return it->second;
|
||||
// Instances of an arrayed block are reflected as "Block[0]".."Block[N-1]";
|
||||
// a bare "Block" query resolves to the first instance per GL semantics.
|
||||
const String suffixedName = String(name) + "[0]";
|
||||
it = m_uniformBlockIndexByName.find(suffixedName);
|
||||
if (it != m_uniformBlockIndexByName.end()) return it->second;
|
||||
return 0xFFFFFFFFu; // GL_INVALID_INDEX
|
||||
}
|
||||
Bool IsActiveUniformBlock(Uint index) const {
|
||||
@@ -335,7 +384,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
Uint GetUBOSizeAt(Uint index) const {
|
||||
if (!IsActiveUniformBlock(index)) return 0;
|
||||
return m_program->getUniformBlock((Int)index).size;
|
||||
// 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
|
||||
// than the block (a block ending in ivec3 reported 12 while the driver needs 16).
|
||||
return (m_program->getUniformBlock((Int)index).size + 15u) & ~15u;
|
||||
}
|
||||
|
||||
const String& GetUniformBlockName(Uint index) const {
|
||||
@@ -343,8 +396,30 @@ namespace MobileGL::MG_State::GLState {
|
||||
return ubo.name;
|
||||
}
|
||||
|
||||
// Uniform entries that belong to an arrayed uniform block are reflected once, against
|
||||
// the first instance ("Block[0]"); per GL semantics every other instance shares that
|
||||
// member set. Maps any instance's block index to the index owning the member entries.
|
||||
Uint GetUniformBlockMemberOwnerIndex(Uint index) const {
|
||||
const String& name = GetUniformBlockName(index);
|
||||
if (name.empty() || name.back() != ']') return index;
|
||||
const SizeT bracket = name.rfind('[');
|
||||
if (bracket == String::npos) return index;
|
||||
const auto it = m_uniformBlockIndexByName.find(name.substr(0, bracket) + "[0]");
|
||||
if (it != m_uniformBlockIndexByName.end()) return it->second;
|
||||
return index;
|
||||
}
|
||||
|
||||
// GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS: derived from the same active-uniform scan that
|
||||
// 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).
|
||||
Int GetUniformBlockActiveUniformCount(Uint index) const {
|
||||
return m_program->getUniformBlock((Int)index).numMembers;
|
||||
const Int ownerIndex = static_cast<Int>(GetUniformBlockMemberOwnerIndex(index));
|
||||
Int count = 0;
|
||||
for (Uint uniformIndex = 0; uniformIndex < m_activeUniformCount; ++uniformIndex) {
|
||||
if (GetActiveUniformBlockIndex(uniformIndex) == ownerIndex) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Bool IsUniformBlockReferencedByStage(Uint index, EShLanguage stage) const {
|
||||
|
||||
@@ -29,9 +29,18 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (!CheckIndexAvail(program, m_programObjects)) return; // FIXME: add error reporting here
|
||||
auto& programObject = m_programObjects[program];
|
||||
if (programObject != nullptr) {
|
||||
// Snapshot the attachments: deleting the program is a detach point for shaders
|
||||
// that were flagged with glDeleteShader while still attached.
|
||||
const Vector<SharedPtr<ShaderObject>> attachedShaders = programObject->GetAttachedShaders();
|
||||
programObject->MarkAsDeleted();
|
||||
programObject.reset();
|
||||
m_programIndexGenerator.Delete(program);
|
||||
for (const auto& shader : attachedShaders) {
|
||||
const Uint shaderName = shader->GetExternalIndex();
|
||||
if (CheckIndexAvail(shaderName, m_shaderObjects) && m_shaderObjects[shaderName] == shader) {
|
||||
ReleaseShaderNameIfOrphaned(shaderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,12 +75,35 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (!CheckIndexAvail(shader, m_shaderObjects)) return;
|
||||
auto& shaderObject = m_shaderObjects[shader];
|
||||
if (shaderObject != nullptr) {
|
||||
m_shaderObjects[shader]->MarkAsDeleted();
|
||||
m_shaderObjects[shader].reset();
|
||||
m_shaderIndexGenerator.Delete(shader);
|
||||
// glDeleteShader on an attached shader only FLAGS it; the name stays valid (and
|
||||
// glShaderSource/glCompileShader keep working on it) until the shader is detached
|
||||
// from every program. The GL CTS compiles shaders through exactly this
|
||||
// create-attach-delete-source-compile sequence (uniform_block.common.name_matching).
|
||||
shaderObject->MarkAsDeleted();
|
||||
ReleaseShaderNameIfOrphaned(shader);
|
||||
}
|
||||
}
|
||||
|
||||
Bool ProgramState::ShaderHasGLVisibleAttachment(const SharedPtr<ShaderObject>& shaderObject) const {
|
||||
for (const auto& programObject : m_programObjects) {
|
||||
if (programObject != nullptr && programObject->ShaderIsAttachedGLVisible(shaderObject)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// A program deleted while current vacates its table slot but stays alive as the
|
||||
// current program; its attachments still count.
|
||||
return m_currentProgram != nullptr && m_currentProgram->ShaderIsAttachedGLVisible(shaderObject);
|
||||
}
|
||||
|
||||
void ProgramState::ReleaseShaderNameIfOrphaned(Uint shader) {
|
||||
if (!CheckIndexAvail(shader, m_shaderObjects)) return;
|
||||
auto& shaderObject = m_shaderObjects[shader];
|
||||
if (shaderObject == nullptr || !shaderObject->GetDeleteStatus()) return;
|
||||
if (ShaderHasGLVisibleAttachment(shaderObject)) return;
|
||||
shaderObject.reset();
|
||||
m_shaderIndexGenerator.Delete(shader);
|
||||
}
|
||||
|
||||
Bool ProgramState::ValidateShaderObject(Uint shader) const {
|
||||
return CheckIndexAvail(shader, m_shaderObjects) && m_shaderObjects[shader] != nullptr;
|
||||
}
|
||||
|
||||
@@ -26,11 +26,16 @@ namespace MobileGL::MG_State::GLState {
|
||||
Uint CreateShader(ShaderStage stage);
|
||||
const SharedPtr<ShaderObject>& GetShaderObject(Uint shader);
|
||||
void MarkShaderObjectForDeletion(Uint shader);
|
||||
// Frees a deletion-flagged shader's name once no program holds a GL-visible
|
||||
// attachment to it (the deferred half of glDeleteShader-while-attached).
|
||||
void ReleaseShaderNameIfOrphaned(Uint shader);
|
||||
Bool ValidateShaderObject(Uint shader) const;
|
||||
|
||||
const SharedPtr<ProgramObject>& GetCurrentProgram() const { return m_currentProgram; }
|
||||
|
||||
private:
|
||||
Bool ShaderHasGLVisibleAttachment(const SharedPtr<ShaderObject>& shaderObject) const;
|
||||
|
||||
template <typename T>
|
||||
static Bool CheckIndexAvail(const SizeT idx, const Vector<T>& vec) {
|
||||
return idx < vec.size();
|
||||
|
||||
Reference in New Issue
Block a user