[Refactor] (ProgramState): answer the GL query surface from an owned reflection snapshot, not the live TProgram

This commit is contained in:
Swung0x48
2026-08-20 11:43:42 -04:00
parent 93f1106ba4
commit 8329ab4264
6 changed files with 377 additions and 126 deletions
+3 -4
View File
@@ -4670,12 +4670,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& name = stateProgramObject.GetUniformName(loc); const auto& name = stateProgramObject.GetUniformName(loc);
if (name.empty()) continue; if (name.empty()) continue;
if (!IsImageUniformType(stateProgramObject.GetUniformType(loc))) continue; if (!IsImageUniformType(stateProgramObject.GetUniformType(loc))) continue;
const glslang::TType* type = stateProgramObject.GetUniformTType(loc); const auto& type = stateProgramObject.GetUniformTypeFacts(loc);
if (type == nullptr) continue; if (type.hasFormat) {
if (type->getQualifier().hasFormat()) {
// Declared, and therefore left exactly as written - but a non-core spelling // Declared, and therefore left exactly as written - but a non-core spelling
// still needs the extension directive to survive the ES compiler. // still needs the extension directive to survive the ES compiler.
if (!IsCoreEsslLayoutFormat(type->getQualifier().getFormat())) { if (!IsCoreEsslLayoutFormat(static_cast<glslang::TLayoutFormat>(type.layoutFormat))) {
inputs.needsExtendedImageFormats = true; inputs.needsExtendedImageFormats = true;
} }
continue; continue;
+20 -17
View File
@@ -21,6 +21,9 @@
#include <MG_Backend/BackendObjects.h> #include <MG_Backend/BackendObjects.h>
namespace MobileGL::MG_Impl::GLImpl { namespace MobileGL::MG_Impl::GLImpl {
// The flattened uniform type these helpers used to take as a raw glslang::TType*
// pointing into the TProgram's pool allocator. See ProgramObject::TypeFacts.
using TypeFactsRef = const MG_State::GLState::ProgramObject::TypeFacts&;
static GLint BoolToGLInt(bool value) { static GLint BoolToGLInt(bool value) {
return value ? GL_TRUE : GL_FALSE; return value ? GL_TRUE : GL_FALSE;
} }
@@ -223,14 +226,14 @@ namespace MobileGL::MG_Impl::GLImpl {
return false; return false;
} }
GLint GetOpaqueUniformUnitLimit(const glslang::TType* type) { GLint GetOpaqueUniformUnitLimit(const TypeFactsRef type) {
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters(); const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
if (type && type->isImage()) return dynamicParameters.MaxImageUnits; if (type.isImage) return dynamicParameters.MaxImageUnits;
if (type && type->isTexture()) return dynamicParameters.MaxCombinedTextureImageUnits; if (type.isTexture) return dynamicParameters.MaxCombinedTextureImageUnits;
return 0; return 0;
} }
bool ValidateOpaqueUniformUnit(const char* functionName, const glslang::TType* type, GLint unit) { bool ValidateOpaqueUniformUnit(const char* functionName, const TypeFactsRef type, GLint unit) {
const GLint limit = GetOpaqueUniformUnitLimit(type); const GLint limit = GetOpaqueUniformUnitLimit(type);
if (unit < 0 || unit >= limit) { if (unit < 0 || unit >= limit) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
@@ -856,10 +859,10 @@ namespace MobileGL::MG_Impl::GLImpl {
// demotion makes a dmat4 a mat4 in the shader and a mat4-shaped slot here - but because it // demotion makes a dmat4 a mat4 in the shader and a mat4-shaped slot here - but because it
// is ROUTED differently: the caller's component-by-component EbtDouble branch has to widen // is ROUTED differently: the caller's component-by-component EbtDouble branch has to widen
// each float back to the queried type, and it undoes the same padding itself. // each float back to the queried type, and it undoes the same padding itself.
Bool TryGatherFloatMatrixColumns(const glslang::TType* ttype, const char* pBase, void* params) { Bool TryGatherFloatMatrixColumns(const TypeFactsRef ttype, const char* pBase, void* params) {
if (ttype == nullptr || !ttype->isMatrix() || ttype->getBasicType() == glslang::EbtDouble) return false; if (!ttype.isMatrix || ttype.isDouble) return false;
const Int columns = ttype->getMatrixCols(); const Int columns = ttype.matrixCols;
const Int rows = ttype->getMatrixRows(); const Int rows = ttype.matrixRows;
for (Int column = 0; column < columns; ++column) { for (Int column = 0; column < columns; ++column) {
Memcpy(static_cast<char*>(params) + static_cast<SizeT>(column) * rows * sizeof(GLfloat), Memcpy(static_cast<char*>(params) + static_cast<SizeT>(column) * rows * sizeof(GLfloat),
pBase + static_cast<SizeT>(column) * 4 * sizeof(GLfloat), rows * sizeof(GLfloat)); pBase + static_cast<SizeT>(column) * 4 * sizeof(GLfloat), rows * sizeof(GLfloat));
@@ -871,7 +874,7 @@ namespace MobileGL::MG_Impl::GLImpl {
// everything except a float matrix, whose padded columns make it wider. The rule itself // everything except a float matrix, whose padded columns make it wider. The rule itself
// lives on ProgramObject, because the pipeline composite's uniform refresh needs the same // lives on ProgramObject, because the pipeline composite's uniform refresh needs the same
// one and two copies of a layout rule is one too many. // one and two copies of a layout rule is one too many.
SizeT UniformStorageSpanInBytes(const glslang::TType* ttype, SizeT tightSize) { SizeT UniformStorageSpanInBytes(const TypeFactsRef ttype, SizeT tightSize) {
return MG_State::GLState::ProgramObject::UniformStorageSpanInBytes(ttype, tightSize); return MG_State::GLState::ProgramObject::UniformStorageSpanInBytes(ttype, tightSize);
} }
@@ -904,7 +907,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto offset = programObject->GetUniformOffset(location); auto offset = programObject->GetUniformOffset(location);
auto size = programObject->GetUniformSizesInBytes(location); auto size = programObject->GetUniformSizesInBytes(location);
char* pUBO = (char*)programObject->MapUBO(); char* pUBO = (char*)programObject->MapUBO();
auto* ttype = programObject->GetUniformTType(location); const auto& ttype = programObject->GetUniformTypeFacts(location);
const SizeT span = UniformStorageSpanInBytes(ttype, size); const SizeT span = UniformStorageSpanInBytes(ttype, size);
if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset || if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset ||
offset + span > programObject->GetUBOSize()) { offset + span > programObject->GetUBOSize()) {
@@ -958,7 +961,7 @@ namespace MobileGL::MG_Impl::GLImpl {
auto offset = programObject->GetUniformOffset(location); auto offset = programObject->GetUniformOffset(location);
auto size = programObject->GetUniformSizesInBytes(location); auto size = programObject->GetUniformSizesInBytes(location);
char* pUBO = static_cast<char*>(programObject->MapUBO()); char* pUBO = static_cast<char*>(programObject->MapUBO());
auto* ttype = programObject->GetUniformTType(location); const auto& ttype = programObject->GetUniformTypeFacts(location);
const SizeT span = UniformStorageSpanInBytes(ttype, size); const SizeT span = UniformStorageSpanInBytes(ttype, size);
if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset || if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset ||
offset + span > programObject->GetUBOSize()) { offset + span > programObject->GetUBOSize()) {
@@ -981,10 +984,10 @@ namespace MobileGL::MG_Impl::GLImpl {
// conversion rules (7.6: round to nearest for the integer queries) apply; the value // conversion rules (7.6: round to nearest for the integer queries) apply; the value
// widens back to the queried type, having lost precision at the glUniform*d that // widens back to the queried type, having lost precision at the glUniform*d that
// stored it and not here. // stored it and not here.
if (ttype->getBasicType() == glslang::EbtDouble) { if (ttype.isDouble) {
const Int columns = ttype->isMatrix() ? ttype->getMatrixCols() : 1; const Int columns = ttype.isMatrix ? ttype.matrixCols : 1;
const Int rows = ttype->isMatrix() ? ttype->getMatrixRows() const Int rows = ttype.isMatrix ? ttype.matrixRows
: (ttype->isVector() ? ttype->getVectorSize() : 1); : (ttype.isVector ? ttype.vectorSize : 1);
// std140 gives every matrix column its own 16-byte slot; a non-matrix is one // std140 gives every matrix column its own 16-byte slot; a non-matrix is one
// tightly packed run and never reaches the stride at all. // tightly packed run and never reaches the stride at all.
const SizeT columnStride = 4 * sizeof(GLfloat); const SizeT columnStride = 4 * sizeof(GLfloat);
@@ -1191,8 +1194,8 @@ namespace MobileGL::MG_Impl::GLImpl {
Memcpy(pUBO + offset + byteOffsetInsideUniform, value, writeSize); Memcpy(pUBO + offset + byteOffsetInsideUniform, value, writeSize);
programObject.MarkUBOContentDirty(); programObject.MarkUBOContentDirty();
} else { } else {
auto* ttype = programObject.GetUniformTType(location); const auto& ttype = programObject.GetUniformTypeFacts(location);
if (!ttype->isTexture() && !ttype->isImage()) return; if (!ttype.isTexture && !ttype.isImage) return;
if constexpr (!std::is_same_v<std::remove_cv_t<T>, GLint> || ItemCount != 1) { if constexpr (!std::is_same_v<std::remove_cv_t<T>, GLint> || ItemCount != 1) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, ErrorCode::InvalidOperation,
@@ -36,6 +36,69 @@ namespace {
return std::min(backendLimit, capacity); return std::min(backendLimit, capacity);
} }
// Everything the post-link query surface ever asks a glslang::TType, flattened into a
// POD. The list is closed and was audited call site by call site: nothing after the link
// walks a struct, a type name or the AST, so there is no recursion to mirror.
//
// Why it has to be flattened at all: TObjectReflection::type points into the TProgram's
// OWN TPoolAllocator (reflection.cpp clones each TType into it), so every one of these
// pointers dangles the moment the TProgram is released - and releasing it is exactly what
// lets a link be served from the L1 translation memo without a parse.
static MobileGL::MG_State::GLState::ProgramObject::TypeFacts MakeTypeFacts(const glslang::TType* type) {
MobileGL::MG_State::GLState::ProgramObject::TypeFacts facts;
if (type == nullptr) return facts;
facts.isArray = type->isArray();
facts.isMatrix = type->isMatrix();
facts.isVector = type->isVector();
facts.isOpaque = type->isOpaque();
facts.isTexture = type->isTexture();
facts.isImage = type->isImage();
facts.isDouble = type->getBasicType() == glslang::EbtDouble;
facts.isVoid = type->getBasicType() == glslang::EbtVoid;
facts.basicType = static_cast<MobileGL::Int>(type->getBasicType());
// Stored RAW, exactly as glslang reports them (0 for a non-matrix, 1 for a scalar),
// because the callers already gate on isMatrix()/isVector() themselves.
facts.vectorSize = type->getVectorSize();
facts.matrixCols = type->getMatrixCols();
facts.matrixRows = type->getMatrixRows();
const glslang::TQualifier& qualifier = type->getQualifier();
facts.isBuffer = qualifier.storage == glslang::EvqBuffer;
facts.isPatch = qualifier.patch;
facts.hasIndex = qualifier.hasIndex();
facts.layoutIndex = static_cast<MobileGL::Int>(qualifier.layoutIndex);
facts.hasFormat = qualifier.hasFormat();
facts.layoutFormat = static_cast<MobileGL::Uint>(qualifier.getFormat());
facts.layoutMatrix = static_cast<MobileGL::Int>(qualifier.layoutMatrix);
return facts;
}
// One glslang::TObjectReflection, flattened. Shared by uniforms, blocks, pipe inputs and
// pipe outputs, because glslang reflects all four as TObjectReflection.
static MobileGL::MG_State::GLState::ProgramObject::ResourceReflection MakeResourceReflection(
const glslang::TObjectReflection& object) {
MobileGL::MG_State::GLState::ProgramObject::ResourceReflection record;
record.name = object.name;
record.glDefineType = object.glDefineType;
record.offset = object.offset;
record.size = object.size;
record.index = object.index;
record.counterIndex = object.counterIndex;
record.arrayStride = object.arrayStride;
record.topLevelArraySize = object.topLevelArraySize;
record.topLevelArrayStride = object.topLevelArrayStride;
record.binding = object.getBinding();
record.location = object.layoutLocation();
record.stages = static_cast<MobileGL::Uint32>(object.stages);
record.type = MakeTypeFacts(object.getType());
// GL_UNIFORM_SIZE / GL_ARRAY_SIZE, resolved here so no caller needs the TType:
// TObjectReflection::size carries the element count only for a NON-block array, so
// the sized-array outer count wins whenever it exists.
const glslang::TType* type = object.getType();
record.arraySize = (type != nullptr && type->isSizedArray()) ? type->getOuterArraySize()
: (object.size < 1 ? 1 : object.size);
return record;
}
static MobileGL::String StripArrayElementSuffix(const MobileGL::String& name) { static MobileGL::String StripArrayElementSuffix(const MobileGL::String& name) {
const MobileGL::SizeT bracket = name.find('['); const MobileGL::SizeT bracket = name.find('[');
return bracket == MobileGL::String::npos ? name : name.substr(0, bracket); return bracket == MobileGL::String::npos ? name : name.substr(0, bracket);
@@ -497,6 +560,15 @@ namespace MobileGL::MG_State::GLState {
spirvHandoff.reflection.uniformIndexInTProgram = artifacts.uniformIndexInTProgram; spirvHandoff.reflection.uniformIndexInTProgram = artifacts.uniformIndexInTProgram;
spirvHandoff.reflection.tProgramUniformIndexToGl = artifacts.tProgramUniformIndexToGl; spirvHandoff.reflection.tProgramUniformIndexToGl = artifacts.tProgramUniformIndexToGl;
spirvHandoff.reflection.maxUniformLocation = artifacts.maxUniformLocation; spirvHandoff.reflection.maxUniformLocation = artifacts.maxUniformLocation;
// The owned reflection mirror, and the block index space its global-UBO test needs.
// BuildGlobalUboRouting reads BOTH - per-uniform array size, opaqueness, GL type and
// matrix shape, plus "is this a member of a GL-visible block". Leaving them out of the
// handoff is not a compile error, it is a SILENT one: every array collapses to a
// single element and every element past the first falls through to the fallback tail
// allocator (ProgramTest.NestedStructArrayUniformElementWrites catches exactly that).
spirvHandoff.reflection.uniformReflection = artifacts.uniformReflection;
spirvHandoff.reflection.blockReflection = artifacts.blockReflection;
spirvHandoff.reflection.tProgramBlockIndexToGl = artifacts.tProgramBlockIndexToGl;
spirvHandoff.spirvCacheKey = BuildSpirvCacheKey(env); spirvHandoff.spirvCacheKey = BuildSpirvCacheKey(env);
spirvHandoff.ready = true; spirvHandoff.ready = true;
MGLOG_D("ProgramObject %u: phase A done, %zu module(s) handed to the SPIR-V job", in.externalIndex, MGLOG_D("ProgramObject %u: phase A done, %zu module(s) handed to the SPIR-V job", in.externalIndex,
@@ -1032,9 +1104,78 @@ namespace MobileGL::MG_State::GLState {
MGLOG_D("ProgramObject %u: Reflection - UBO[%d] name='%s' size=%u binding=%d", in.externalIndex, i, MGLOG_D("ProgramObject %u: Reflection - UBO[%d] name='%s' size=%u binding=%d", in.externalIndex, i,
ubo.name.c_str(), ubo.size, ubo.getBinding()); ubo.name.c_str(), ubo.size, ubo.getBinding());
} }
SnapshotGlslangReflection();
return true; return true;
} }
// The last thing DoReflection does, and the thing that lets everything after it stop
// caring that a glslang::TProgram ever existed: copy every reflection record the GL query
// surface reads into LinkArtifacts' own owned tables.
//
// Indexed by TPROGRAM index throughout - the same space glUniformIndexToTProgram,
// tProgramUniformIndexToGl and uniformIndexInTProgram already speak - so the accessors
// that used to call program->getUniform(i) index uniformReflection[i] and are otherwise
// unchanged.
void ProgramLinkTask::SnapshotGlslangReflection() {
glslang::TProgram& program = *artifacts.program;
// Blocks FIRST: a uniform's effective layoutMatrix is resolved against its owning
// block below, which needs the block records to already exist.
const Int blockCount = program.getNumUniformBlocks();
artifacts.blockReflection.clear();
artifacts.blockReflection.reserve(static_cast<SizeT>(blockCount));
for (Int i = 0; i < blockCount; ++i) {
artifacts.blockReflection.push_back(MakeResourceReflection(program.getUniformBlock(i)));
}
const Int uniformCount = program.getNumUniformVariables();
artifacts.uniformReflection.clear();
artifacts.uniformReflection.reserve(static_cast<SizeT>(uniformCount));
artifacts.uniformIndexByName.clear();
artifacts.uniformIndexByName.reserve(static_cast<SizeT>(uniformCount));
for (Int i = 0; i < uniformCount; ++i) {
ProgramObject::UniformReflection record = MakeResourceReflection(program.getUniform(i));
// A block-level layout(row_major)/(column_major) that the member did not inherit
// in its own qualifier. Resolved once HERE rather than at every GL_UNIFORM_* query,
// which is what the getUniformBlock() fallback in the old accessors was doing.
if (record.type.layoutMatrix == static_cast<Int>(glslang::ElmNone) && record.index >= 0 &&
record.index < static_cast<Int>(artifacts.blockReflection.size())) {
record.type.layoutMatrix = artifacts.blockReflection[record.index].type.layoutMatrix;
}
// Keyed on the REFLECTED name and on uniforms only. That is deliberate and is the
// filtered semantics the old code hand-rolled: glslang's TReflection::nameToIndex
// also holds block and function entries, which is exactly why every
// getUniformIndex() call site re-checked getUniform(idx).name == name afterwards.
// First writer wins, so a duplicated name resolves the way a forward scan would.
artifacts.uniformIndexByName.emplace(record.name, i);
artifacts.uniformReflection.push_back(Move(record));
}
const Int pipeInputCount = program.getNumPipeInputs();
artifacts.pipeInputReflection.clear();
artifacts.pipeInputReflection.reserve(static_cast<SizeT>(pipeInputCount));
for (Int i = 0; i < pipeInputCount; ++i) {
artifacts.pipeInputReflection.push_back(MakeResourceReflection(program.getPipeInput(i)));
}
const Int pipeOutputCount = program.getNumPipeOutputs();
artifacts.pipeOutputReflection.clear();
artifacts.pipeOutputReflection.reserve(static_cast<SizeT>(pipeOutputCount));
for (Int i = 0; i < pipeOutputCount; ++i) {
artifacts.pipeOutputReflection.push_back(MakeResourceReflection(program.getPipeOutput(i)));
}
artifacts.atomicCounterCount = program.getNumAtomicCounters();
for (Uint dim = 0; dim < 3u; ++dim) {
artifacts.computeLocalSize[dim] = program.getLocalSize(static_cast<Int>(dim));
}
MGLOG_D("ProgramObject %u: Reflection - snapshot: %zu uniform(s), %zu block(s), %zu input(s), "
"%zu output(s)",
in.externalIndex, artifacts.uniformReflection.size(), artifacts.blockReflection.size(),
artifacts.pipeInputReflection.size(), artifacts.pipeOutputReflection.size());
}
Bool ProgramLinkTask::ValidateFragmentOutputLocations() { Bool ProgramLinkTask::ValidateFragmentOutputLocations() {
if (!artifacts.program) return false; if (!artifacts.program) return false;
// The pipe-output list is the output interface of the program's LAST stage. Only a // The pipe-output list is the output interface of the program's LAST stage. Only a
@@ -165,6 +165,9 @@ namespace MobileGL::MG_State::GLState {
MG_Util::ShaderTranspiler::TranslationCacheKey BuildSpirvCacheKey( MG_Util::ShaderTranspiler::TranslationCacheKey BuildSpirvCacheKey(
const MG_Util::ShaderTranspiler::CompileEnv& env) const; const MG_Util::ShaderTranspiler::CompileEnv& env) const;
Bool DoReflection(const MG_Util::ShaderTranspiler::CompileEnv& env); Bool DoReflection(const MG_Util::ShaderTranspiler::CompileEnv& env);
// Copies every reflection record the GL query surface reads out of the glslang
// TProgram into LinkArtifacts own owned tables. Runs at the tail of DoReflection.
void SnapshotGlslangReflection();
Bool ValidateFragmentOutputLocations(); Bool ValidateFragmentOutputLocations();
Bool ResolveTransformFeedbackVaryings(); Bool ResolveTransformFeedbackVaryings();
void ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate); void ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate);
@@ -24,6 +24,68 @@ namespace MobileGL::MG_State::GLState {
class ProgramObject { class ProgramObject {
public: public:
// Everything the query surface ever asked a glslang::TType, flattened. Twenty
// predicates, no recursion: nothing post-link ever walks a struct, a type name or the
// AST, so a POD covers the whole surface exactly.
struct TypeFacts {
Bool isArray = false;
Bool isMatrix = false;
Bool isVector = false;
Bool isOpaque = false;
Bool isTexture = false;
Bool isImage = false;
Bool isDouble = false; // getBasicType() == EbtDouble
Bool isVoid = false; // getBasicType() == EbtVoid (hidden block members)
Bool isBuffer = false; // getQualifier().storage == EvqBuffer
Bool isPatch = false; // getQualifier().patch
Bool hasIndex = false; // getQualifier().hasIndex()
Bool hasFormat = false; // getQualifier().hasFormat()
Int vectorSize = 0;
Int matrixCols = 0;
Int matrixRows = 0;
Int layoutIndex = 0; // getQualifier().layoutIndex
Uint layoutFormat = 0; // getQualifier().getFormat()
// glslang::TLayoutMatrix, widened. For a uniform this is already RESOLVED against
// the owning block's qualifier, so the getUniformBlock() fallback the old
// accessors carried is gone.
Int layoutMatrix = 0;
// glslang::TBasicType, widened - ApplyUniformInitialValues and the typed
// glGetUniform* paths compare against a handful of enumerators.
Int basicType = 0;
};
// One glslang::TObjectReflection, flattened. Used for uniforms, blocks, pipe inputs
// and pipe outputs alike, because glslang reflects all four as TObjectReflection.
struct ResourceReflection {
String name;
GLenum glDefineType = 0;
Int offset = -1;
// TObjectReflection::size, RAW. For a uniform prefer `arraySize` below, which is
// the resolved GL_UNIFORM_SIZE answer.
Int size = 0;
// TObjectReflection::index - for a uniform, the TPROGRAM block index owning it
// (-1 for a default-block one; translate with GlBlockIndexFromTProgram).
Int index = -1;
Int counterIndex = -1;
Int arrayStride = 0;
Int topLevelArraySize = 0;
Int topLevelArrayStride = 0;
Int binding = -1;
Int location = -1; // layoutLocation()
// EShLanguageMask of the stages that reference it; 0 means "declared but read by
// nobody", which is what the dead-default-block-uniform filter tests.
Uint32 stages = 0;
// GL_UNIFORM_SIZE / GL_ARRAY_SIZE, already resolved through the
// isSizedArray()/getOuterArraySize()/size fallback.
GLint arraySize = 1;
TypeFacts type;
};
using UniformReflection = ResourceReflection;
using BlockReflection = ResourceReflection;
using PipeInputReflection = ResourceReflection;
using PipeOutputReflection = ResourceReflection;
ProgramObject(Uint externalIndex) : m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()) {} ProgramObject(Uint externalIndex) : m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()) {}
// Cancel-not-join, exactly like ~ShaderObject: the link job owns its inputs, so an // Cancel-not-join, exactly like ~ShaderObject: the link job owns its inputs, so an
// in-flight link whose program just went away is safe to abandon where it stands. // in-flight link whose program just went away is safe to abandon where it stands.
@@ -134,8 +196,7 @@ namespace MobileGL::MG_State::GLState {
const Int index = Artifacts().uniformIndexInTProgram[base]; const Int index = Artifacts().uniformIndexInTProgram[base];
// "[k]" only addresses arrays ("scalar[0]" is not a uniform name), and only // "[k]" only addresses arrays ("scalar[0]" is not a uniform name), and only
// in-range elements. // in-range elements.
const glslang::TType* type = Artifacts().program->getUniform(index).getType(); if (!UniformAt(index).type.isArray) return -1;
if (type == nullptr || !type->isArray()) return -1;
if (static_cast<GLint>(element) >= GetUniformArraySizeByTIndex(index)) return -1; if (static_cast<GLint>(element) >= GetUniformArraySizeByTIndex(index)) return -1;
const Int location = base + (Int)element; const Int location = base + (Int)element;
if (!UniformLocationsAliasSameUniform(base, location)) return -1; if (!UniformLocationsAliasSameUniform(base, location)) return -1;
@@ -175,44 +236,35 @@ namespace MobileGL::MG_State::GLState {
} }
Int GetActiveUniformIndex(const String& name) const { Int GetActiveUniformIndex(const String& name) const {
const Int tProgramCount = static_cast<Int>(Artifacts().tProgramUniformIndexToGl.size()); // uniformIndexByName is keyed by the REFLECTED name, so a lookup that hits is
const Int uniformIndex = Artifacts().program->getUniformIndex(name.c_str()); // already the exact-match the old code re-verified with a string compare after
if (uniformIndex >= 0 && uniformIndex < tProgramCount && // glslang's getUniformIndex(); a lookup that misses needs no bounds check.
Artifacts().program->getUniform(uniformIndex).name == name) { const auto& byName = Artifacts().uniformIndexByName;
return GlUniformIndexFromTProgram(uniformIndex); if (const auto direct = byName.find(name); direct != byName.end()) {
return GlUniformIndexFromTProgram(direct->second);
} }
// Reflection stores an array uniform under "arr[0]"; accept the bare "arr" // 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 // spelling too. The reverse ("arr[0]" against a bare "arr" entry) is kept for
// robustness against non-suffixed reflection entries. // robustness against non-suffixed reflection entries.
if (!name.empty() && name.back() != ']') { if (!name.empty() && name.back() != ']') {
const String suffixedName = name + "[0]"; const auto suffixed = byName.find(name + "[0]");
const Int suffixedIndex = Artifacts().program->getUniformIndex(suffixedName.c_str()); return suffixed != byName.end() ? GlUniformIndexFromTProgram(suffixed->second) : -1;
if (suffixedIndex >= 0 && suffixedIndex < tProgramCount &&
Artifacts().program->getUniform(suffixedIndex).name == suffixedName) {
return GlUniformIndexFromTProgram(suffixedIndex);
}
return -1;
} }
if (name.length() <= 3 || name.compare(name.length() - 3, 3, "[0]") != 0) 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 auto base = byName.find(name.substr(0, name.length() - 3));
const Int baseIndex = Artifacts().program->getUniformIndex(baseName.c_str()); return base != byName.end() ? GlUniformIndexFromTProgram(base->second) : -1;
if (baseIndex < 0 || baseIndex >= tProgramCount) return -1;
return Artifacts().program->getUniform(baseIndex).name == baseName ? GlUniformIndexFromTProgram(baseIndex)
: -1;
} }
Bool IsValidUniformLocation(Int location) const { return IsValidUniformLocation(Artifacts(), location); } Bool IsValidUniformLocation(Int location) const { return IsValidUniformLocation(Artifacts(), location); }
GLenum GetUniformType(Uint location) const { GLenum GetUniformType(Uint location) const {
auto& uniform = Artifacts().program->getUniform(Artifacts().uniformIndexInTProgram[location]); return UniformAt(Artifacts().uniformIndexInTProgram[location]).glDefineType;
return uniform.glDefineType;
} }
GLenum GetActiveUniformType(Uint index) const { GLenum GetActiveUniformType(Uint index) const {
auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index)); return UniformAt(TProgramUniformIndex(index)).glDefineType;
return uniform.glDefineType;
} }
// Number of active array elements (GL_UNIFORM_SIZE / GL_ARRAY_SIZE); 1 for a non-array. // Number of active array elements (GL_UNIFORM_SIZE / GL_ARRAY_SIZE); 1 for a non-array.
@@ -229,16 +281,15 @@ namespace MobileGL::MG_State::GLState {
} }
Int GetActiveUniformBlockIndex(Uint index) const { Int GetActiveUniformBlockIndex(Uint index) const {
auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index));
// Members of the synthesized global UBO are default-block uniforms to GL: -1. // Members of the synthesized global UBO are default-block uniforms to GL: -1.
return GlBlockIndexFromTProgram(uniform.index); return GlBlockIndexFromTProgram(UniformAt(TProgramUniformIndex(index)).index);
} }
// GL_UNIFORM_OFFSET: byte offset within the owning named block; -1 for a default-block // GL_UNIFORM_OFFSET: byte offset within the owning named block; -1 for a default-block
// uniform. The relaxed parse gives global-UBO members real byte offsets, but GL must keep // uniform. The relaxed parse gives global-UBO members real byte offsets, but GL must keep
// seeing them as default-block uniforms, so gate on the GL-visible block index. // seeing them as default-block uniforms, so gate on the GL-visible block index.
GLint GetActiveUniformOffset(Uint index) const { GLint GetActiveUniformOffset(Uint index) const {
const auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index)); const auto& uniform = UniformAt(TProgramUniformIndex(index));
if (GlBlockIndexFromTProgram(uniform.index) < 0) return -1; if (GlBlockIndexFromTProgram(uniform.index) < 0) return -1;
return uniform.offset; return uniform.offset;
} }
@@ -252,13 +303,12 @@ namespace MobileGL::MG_State::GLState {
// generated SPIR-V lay the array out with std140 16-byte-rounded strides. MobileGL's UBO // 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. // layout is always std140, where every array element stride rounds up to a vec4.
GLint GetActiveUniformArrayStride(Uint index) const { GLint GetActiveUniformArrayStride(Uint index) const {
const auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index)); const auto& uniform = UniformAt(TProgramUniformIndex(index));
if (GlBlockIndexFromTProgram(uniform.index) < 0) return -1; if (GlBlockIndexFromTProgram(uniform.index) < 0) return -1;
const glslang::TType* type = uniform.getType(); if (!uniform.type.isArray) return 0;
if (type == nullptr || !type->isArray()) return 0; if (uniform.type.isMatrix) {
if (type->isMatrix()) {
const bool rowMajor = GetActiveUniformIsRowMajor(index) != 0; const bool rowMajor = GetActiveUniformIsRowMajor(index) != 0;
const int vectors = rowMajor ? type->getMatrixRows() : type->getMatrixCols(); const int vectors = rowMajor ? uniform.type.matrixRows : uniform.type.matrixCols;
return GetActiveUniformMatrixStride(index) * vectors; return GetActiveUniformMatrixStride(index) * vectors;
} }
return 16; // scalars and vectors: std140 rounds the element stride up to a vec4 return 16; // scalars and vectors: std140 rounds the element stride up to a vec4
@@ -272,15 +322,12 @@ namespace MobileGL::MG_State::GLState {
// check suffices; the getUniformBlock() fallback is defensive for a config that instead leaves // check suffices; the getUniformBlock() fallback is defensive for a config that instead leaves
// an inheriting member's layoutMatrix == ElmNone. // an inheriting member's layoutMatrix == ElmNone.
GLint GetActiveUniformIsRowMajor(Uint index) const { GLint GetActiveUniformIsRowMajor(Uint index) const {
const auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index)); const auto& uniform = UniformAt(TProgramUniformIndex(index));
if (GlBlockIndexFromTProgram(uniform.index) < 0) return 0; if (GlBlockIndexFromTProgram(uniform.index) < 0) return 0;
const glslang::TType* type = uniform.getType(); if (!uniform.type.isMatrix) return 0;
if (type == nullptr || !type->isMatrix()) return 0; // layoutMatrix is already resolved against the owning block's qualifier at
glslang::TLayoutMatrix layoutMatrix = type->getQualifier().layoutMatrix; // snapshot time, so the getUniformBlock() fallback this used to carry is gone.
if (layoutMatrix == glslang::ElmNone) { return (uniform.type.layoutMatrix == static_cast<Int>(glslang::ElmRowMajor)) ? 1 : 0;
layoutMatrix = Artifacts().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 // GL_UNIFORM_MATRIX_STRIDE: byte stride between columns (col-major) / rows (row-major) of a
@@ -290,16 +337,11 @@ namespace MobileGL::MG_State::GLState {
// out as std140 (packed/shared are coerced), so this matches the offsets glslang reports. For // 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. // every GL 3.3 float matrix this evaluates to 16, independent of majorness.
GLint GetActiveUniformMatrixStride(Uint index) const { GLint GetActiveUniformMatrixStride(Uint index) const {
const auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index)); const auto& uniform = UniformAt(TProgramUniformIndex(index));
if (GlBlockIndexFromTProgram(uniform.index) < 0) return -1; if (GlBlockIndexFromTProgram(uniform.index) < 0) return -1;
const glslang::TType* type = uniform.getType(); if (!uniform.type.isMatrix) return 0;
if (type == nullptr || !type->isMatrix()) return 0; const bool rowMajor = (uniform.type.layoutMatrix == static_cast<Int>(glslang::ElmRowMajor));
glslang::TLayoutMatrix layoutMatrix = type->getQualifier().layoutMatrix; const int strideVectorComponents = rowMajor ? uniform.type.matrixCols : uniform.type.matrixRows;
if (layoutMatrix == glslang::ElmNone) {
layoutMatrix = Artifacts().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 constexpr int scalarSize = 4; // GL 3.3 core uniform matrices are float
const int vectorAlignment = (strideVectorComponents <= 1) ? scalarSize const int vectorAlignment = (strideVectorComponents <= 1) ? scalarSize
: (strideVectorComponents == 2) ? 2 * scalarSize : (strideVectorComponents == 2) ? 2 * scalarSize
@@ -307,21 +349,39 @@ namespace MobileGL::MG_State::GLState {
return (vectorAlignment + 15) & ~15; // std140 round-up to a vec4 return (vectorAlignment + 15) & ~15; // std140 round-up to a vec4
} }
const glslang::TType* GetUniformTType(Uint location) const { // The flattened type of the uniform at `location`. This is what replaced
auto& uniform = Artifacts().program->getUniform(Artifacts().uniformIndexInTProgram[location]); // GetUniformTType(): the same information, owned by the program instead of by a
return uniform.getType(); // glslang pool, so it stays valid for a link served from the L1 translation memo.
const TypeFacts& GetUniformTypeFacts(Uint location) const {
return UniformAt(Artifacts().uniformIndexInTProgram[location]).type;
} }
Bool IsUniformOpaqueAtLocation(Uint location) const { return GetUniformTType(location)->isOpaque(); } // Replaces GetUniformTType(), which used to hand a raw glslang::TType* - into a
// pool the program no longer necessarily owns - out to the DirectGLES image-format
// bake. These are the only three things any caller ever read off it.
Bool UniformHasDeclaredImageFormat(Uint location) const {
return UniformAt(Artifacts().uniformIndexInTProgram[location]).type.hasFormat;
}
Uint GetUniformDeclaredImageFormat(Uint location) const {
return UniformAt(Artifacts().uniformIndexInTProgram[location]).type.layoutFormat;
}
// Matrix column count, 0 for a non-matrix. The global-UBO fallback allocator sizes a
// matrix slot from it.
Int GetUniformMatrixColumns(Uint location) const {
const auto& uniform = UniformAt(Artifacts().uniformIndexInTProgram[location]);
return uniform.type.isMatrix ? uniform.type.matrixCols : 0;
}
Bool IsUniformOpaqueAtLocation(Uint location) const {
return UniformAt(Artifacts().uniformIndexInTProgram[location]).type.isOpaque;
}
const String& GetUniformName(Uint location) const { const String& GetUniformName(Uint location) const {
auto& uniform = Artifacts().program->getUniform(Artifacts().uniformIndexInTProgram[location]); return UniformAt(Artifacts().uniformIndexInTProgram[location]).name;
return uniform.name;
} }
const String& GetActiveUniformName(Uint index) const { const String& GetActiveUniformName(Uint index) const {
auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index)); return UniformAt(TProgramUniformIndex(index)).name;
return uniform.name;
} }
// Sentinel for a uniform location without global-UBO backing storage (should not // Sentinel for a uniform location without global-UBO backing storage (should not
// survive linking: GenerateBinary falls back to tail-allocated scratch storage). // survive linking: GenerateBinary falls back to tail-allocated scratch storage).
@@ -355,17 +415,17 @@ namespace MobileGL::MG_State::GLState {
// would, half its GL type size, and a `dmat4` is padded like any other matrix. Anything // would, half its GL type size, and a `dmat4` is padded like any other matrix. Anything
// reading or writing a whole uniform's storage - a bounds check, a copy between two // reading or writing a whole uniform's storage - a bounds check, a copy between two
// programs' shadows - wants this rather than GetUniformSizesInBytes. // programs' shadows - wants this rather than GetUniformSizesInBytes.
static SizeT UniformStorageSpanInBytes(const glslang::TType* type, SizeT tightSize) { static SizeT UniformStorageSpanInBytes(const TypeFacts& type, SizeT tightSize) {
if (type != nullptr && type->isMatrix()) { if (type.isMatrix) {
return static_cast<SizeT>(type->getMatrixCols()) * 4 * sizeof(Float); return static_cast<SizeT>(type.matrixCols) * 4 * sizeof(Float);
} }
if (type != nullptr && type->getBasicType() == glslang::EbtDouble) { if (type.isDouble) {
return tightSize / 2; return tightSize / 2;
} }
return tightSize; return tightSize;
} }
SizeT GetUniformStorageSpanInBytes(Uint location) const { SizeT GetUniformStorageSpanInBytes(Uint location) const {
return UniformStorageSpanInBytes(GetUniformTType(location), GetUniformSizesInBytes(location)); return UniformStorageSpanInBytes(GetUniformTypeFacts(location), GetUniformSizesInBytes(location));
} }
// ---- "written since link": the per-location dirty set the pipeline composite mirrors from ---- // ---- "written since link": the per-location dirty set the pipeline composite mirrors from ----
@@ -476,14 +536,14 @@ namespace MobileGL::MG_State::GLState {
return mask; return mask;
} }
Uint32 GetActiveFragmentOutputLocationMask() const { Uint32 GetActiveFragmentOutputLocationMask() const {
if (!Artifacts().program) { if (Artifacts().pipeOutputReflection.empty()) {
return 0; return 0;
} }
Uint32 mask = 0; Uint32 mask = 0;
const Int outputCount = Artifacts().program->getNumPipeOutputs(); const Int outputCount = static_cast<Int>(Artifacts().pipeOutputReflection.size());
for (Int index = 0; index < outputCount; ++index) { for (Int index = 0; index < outputCount; ++index) {
const Int location = static_cast<Int>(Artifacts().program->getPipeOutput(index).layoutLocation()); const Int location = Artifacts().pipeOutputReflection[index].location;
if (location >= 0 && location < 32) { if (location >= 0 && location < 32) {
mask |= (1u << location); mask |= (1u << location);
} }
@@ -491,38 +551,34 @@ namespace MobileGL::MG_State::GLState {
return mask; return mask;
} }
Int GetActiveFragmentOutputCount() const { Int GetActiveFragmentOutputCount() const {
return Artifacts().program ? Artifacts().program->getNumPipeOutputs() : 0; return static_cast<Int>(Artifacts().pipeOutputReflection.size());
} }
const String& GetActiveFragmentOutputName(Uint index) const { const String& GetActiveFragmentOutputName(Uint index) const {
MOBILEGL_ASSERT(Artifacts().program != nullptr, "ProgramObject::GetActiveFragmentOutputName: program is null"); MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().pipeOutputReflection.size()),
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().program->getNumPipeOutputs()),
"ProgramObject::GetActiveFragmentOutputName: index=%u out of range", index); "ProgramObject::GetActiveFragmentOutputName: index=%u out of range", index);
return Artifacts().program->getPipeOutput(static_cast<Int>(index)).name; return Artifacts().pipeOutputReflection[index].name;
} }
Int GetFragmentOutputLocation(Uint index) const { Int GetFragmentOutputLocation(Uint index) const {
MOBILEGL_ASSERT(Artifacts().program != nullptr, "ProgramObject::GetFragmentOutputLocation: program is null"); MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().pipeOutputReflection.size()),
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().program->getNumPipeOutputs()),
"ProgramObject::GetFragmentOutputLocation: index=%u out of range", "ProgramObject::GetFragmentOutputLocation: index=%u out of range",
index); index);
return static_cast<Int>(Artifacts().program->getPipeOutput(static_cast<Int>(index)).layoutLocation()); return Artifacts().pipeOutputReflection[index].location;
} }
GLint GetActiveFragmentOutputArraySize(Uint index) const { GLint GetActiveFragmentOutputArraySize(Uint index) const {
MOBILEGL_ASSERT(Artifacts().program != nullptr, "ProgramObject::GetActiveFragmentOutputArraySize: program is null"); MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().pipeOutputReflection.size()),
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().program->getNumPipeOutputs()),
"ProgramObject::GetActiveFragmentOutputArraySize: index=%u out of range", index); "ProgramObject::GetActiveFragmentOutputArraySize: index=%u out of range", index);
return Artifacts().program->getPipeOutput(static_cast<Int>(index)).size; return Artifacts().pipeOutputReflection[index].size;
} }
GLenum GetFragmentOutputType(Uint index) const { GLenum GetFragmentOutputType(Uint index) const {
MOBILEGL_ASSERT(Artifacts().program != nullptr, "ProgramObject::GetFragmentOutputType: program is null"); MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().pipeOutputReflection.size()),
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().program->getNumPipeOutputs()),
"ProgramObject::GetFragmentOutputType: index=%u out of range", "ProgramObject::GetFragmentOutputType: index=%u out of range",
index); index);
return Artifacts().program->getPipeOutput(static_cast<Int>(index)).glDefineType; return Artifacts().pipeOutputReflection[index].glDefineType;
} }
GLenum GetAttribType(Uint index) const { return Artifacts().attribTypes[index]; } GLenum GetAttribType(Uint index) const { return Artifacts().attribTypes[index]; }
const String& GetAttribName(Uint index) const { return Artifacts().attribs[index]; } const String& GetAttribName(Uint index) const { return Artifacts().attribs[index]; }
GLenum GetActiveAttribType(Uint index) const { return Artifacts().program->getPipeInput(static_cast<Int>(index)).glDefineType; } GLenum GetActiveAttribType(Uint index) const { return Artifacts().pipeInputReflection[index].glDefineType; }
GLint GetActiveAttribArraySize(Uint index) const { return Artifacts().program->getPipeInput(static_cast<Int>(index)).size; } GLint GetActiveAttribArraySize(Uint index) const { return Artifacts().pipeInputReflection[index].size; }
// The Vulkan-semantics parse reflects the vertex builtins under their SPIR-V names; // The Vulkan-semantics parse reflects the vertex builtins under their SPIR-V names;
// GL must keep reporting the GL spellings (glGetActiveAttrib and the program-input // GL must keep reporting the GL spellings (glGetActiveAttrib and the program-input
// resource queries enumerate builtins). // resource queries enumerate builtins).
@@ -534,7 +590,7 @@ namespace MobileGL::MG_State::GLState {
return name; return name;
} }
const String& GetActiveAttribName(Uint index) const { const String& GetActiveAttribName(Uint index) const {
return NormalizeBuiltinPipeInputName(Artifacts().program->getPipeInput(static_cast<Int>(index)).name); return NormalizeBuiltinPipeInputName(Artifacts().pipeInputReflection[index].name);
} }
// PHASE B, all three (see EnsureSpirvJoined): the shadow buffer's layout is decided // PHASE B, all three (see EnsureSpirvJoined): the shadow buffer's layout is decided
// by the OPTIMIZED SPIR-V, so it does not exist until the SPIR-V job has settled - and // by the OPTIMIZED SPIR-V, so it does not exist until the SPIR-V job has settled - and
@@ -627,7 +683,7 @@ namespace MobileGL::MG_State::GLState {
// which means the change is only honoured by regenerating the program. That // which means the change is only honoured by regenerating the program. That
// regeneration is gated on link-shaped versions, so without a counter that moves // regeneration is gated on link-shaped versions, so without a counter that moves
// here the new unit would never reach the driver. // here the new unit would never reach the driver.
if (const glslang::TType* type = GetUniformTType(location); type != nullptr && type->isImage()) { if (GetUniformTypeFacts(location).isImage) {
++m_imageUnitVersion; ++m_imageUnitVersion;
} }
} }
@@ -703,19 +759,16 @@ namespace MobileGL::MG_State::GLState {
// this after a failed glGetAttribLocation, and reached it as soon as the CopyTexImage2D // this after a failed glGetAttribLocation, and reached it as soon as the CopyTexImage2D
// throw ahead of it stopped killing the run first. // throw ahead of it stopped killing the run first.
Int GetActiveAtomicCounterCount() const { Int GetActiveAtomicCounterCount() const {
const auto& program = Artifacts().program; return Artifacts().atomicCounterCount;
return program ? program->getNumAtomicCounters() : 0;
} }
Int GetActiveAttributesCount() const { Int GetActiveAttributesCount() const {
const auto& program = Artifacts().program; return static_cast<Int>(Artifacts().pipeInputReflection.size());
return program ? program->getNumPipeInputs() : 0;
} }
// GL-visible uniform blocks only: the synthesized MGL_GLOBAL_UBO the relaxed parse // GL-visible uniform blocks only: the synthesized MGL_GLOBAL_UBO the relaxed parse
// materializes for default-block uniforms is filtered out by DoReflection. // materializes for default-block uniforms is filtered out by DoReflection.
Int GetActiveUniformBlocksCount() const { return static_cast<Int>(Artifacts().glBlockIndexToTProgram.size()); } Int GetActiveUniformBlocksCount() const { return static_cast<Int>(Artifacts().glBlockIndexToTProgram.size()); }
GLuint GetComputeLocalSize(Uint dim) const { GLuint GetComputeLocalSize(Uint dim) const {
const auto& program = Artifacts().program; return dim < 3u ? Artifacts().computeLocalSize[dim] : 0u;
return program ? program->getLocalSize(static_cast<Int>(dim)) : 0;
} }
Int GetActiveAttributesMaxLength() const { return Artifacts().attribInNameMaxLength; } Int GetActiveAttributesMaxLength() const { return Artifacts().attribInNameMaxLength; }
Int GetActiveUniformBlocksMaxNameLength() const { return Artifacts().uniformBlockNameMaxLength; } Int GetActiveUniformBlocksMaxNameLength() const { return Artifacts().uniformBlockNameMaxLength; }
@@ -739,11 +792,11 @@ namespace MobileGL::MG_State::GLState {
// (like a std140 struct) occupies a vec4-rounded size, and that is what the // (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 // 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). // than the block (a block ending in ivec3 reported 12 while the driver needs 16).
return (Artifacts().program->getUniformBlock(Artifacts().glBlockIndexToTProgram[index]).size + 15u) & ~15u; return (static_cast<Uint>(BlockAt(Artifacts().glBlockIndexToTProgram[index]).size) + 15u) & ~15u;
} }
const String& GetUniformBlockName(Uint index) const { const String& GetUniformBlockName(Uint index) const {
auto& ubo = Artifacts().program->getUniformBlock(Artifacts().glBlockIndexToTProgram[index]); const auto& ubo = BlockAt(Artifacts().glBlockIndexToTProgram[index]);
return ubo.name; return ubo.name;
} }
@@ -774,7 +827,7 @@ namespace MobileGL::MG_State::GLState {
} }
Bool IsUniformBlockReferencedByStage(Uint index, EShLanguage stage) const { Bool IsUniformBlockReferencedByStage(Uint index, EShLanguage stage) const {
const auto& ubo = Artifacts().program->getUniformBlock(Artifacts().glBlockIndexToTProgram[index]); const auto& ubo = BlockAt(Artifacts().glBlockIndexToTProgram[index]);
const auto stageMask = static_cast<EShLanguageMask>(1 << stage); const auto stageMask = static_cast<EShLanguageMask>(1 << stage);
return (ubo.stages & stageMask) != 0; return (ubo.stages & stageMask) != 0;
} }
@@ -909,9 +962,46 @@ namespace MobileGL::MG_State::GLState {
// what makes "every read of link output joins the pending link" a property the // what makes "every read of link output joins the pending link" a property the
// compiler checks rather than a review item - a new reader cannot spell the field // compiler checks rather than a review item - a new reader cannot spell the field
// without going through the gate. // without going through the gate.
// ---- the owned mirror of glslang's reflection ----
//
// WHY THIS EXISTS. Every GL query about a linked program used to be answered by
// asking the live glslang::TProgram - program->getUniform(i).getType()->isMatrix()
// and friends. That made the TProgram part of the program's PERMANENT state, which
// in turn made the whole front end (parse + link) unskippable: the L1 shader
// translation memo could hand back the SPIR-V but the reflection still had to be
// rebuilt from a freshly parsed AST.
//
// These three tables are a snapshot of everything the query surface ever reads off
// the TProgram, in PLAIN OWNED VALUES - no TType*, no TString, nothing pointing into
// a glslang pool. Taken once at the tail of DoReflection (SnapshotGlslangReflection),
// they are copyable, immutable after the link, and safe to memoize and share between
// ProgramObjects and threads. Once they are filled, `program` is dead weight to
// everything except DoReflection itself.
//
// INDEXED BY TPROGRAM INDEX, deliberately: that is the space uniformIndexInTProgram,
// glUniformIndexToTProgram and tProgramUniformIndexToGl already speak, so every
// accessor that used to call program->getUniform(i) indexes uniformReflection[i]
// instead, unchanged in every other respect.
struct LinkArtifacts { struct LinkArtifacts {
// Live only between LinkProgram() and the end of DoReflection. Everything after
// that reads the owned mirror below; a link served from the L1 memo never
// constructs one at all, so this is null for such a program and MUST NOT be
// dereferenced outside DoReflection.
SharedPtr<glslang::TProgram> program; SharedPtr<glslang::TProgram> program;
// The owned reflection snapshot. Indexed by TProgram index; see the structs above.
Vector<UniformReflection> uniformReflection;
Vector<BlockReflection> blockReflection;
Vector<PipeInputReflection> pipeInputReflection;
Vector<PipeOutputReflection> pipeOutputReflection;
// Program-level scalars glslang answers off the linked intermediates.
Int atomicCounterCount = 0;
Array<GLuint, 3> computeLocalSize{};
// Replaces program->getUniformIndex(name). Maps the reflected name to its
// TProgram uniform index.
UnorderedMap<String, Int> uniformIndexByName;
// Attributes (Vertex in) // Attributes (Vertex in)
Vector<String> attribs; Vector<String> attribs;
Vector<GLenum> attribTypes; Vector<GLenum> attribTypes;
@@ -1057,12 +1147,24 @@ namespace MobileGL::MG_State::GLState {
// for both. GL 3.3 core uniforms are always sized. Takes a TProgram uniform index (the space // for both. GL 3.3 core uniforms are always sized. Takes a TProgram uniform index (the space
// the artifacts' uniformIndexInTProgram stores). // the artifacts' uniformIndexInTProgram stores).
static GLint GetUniformArraySizeByTIndex(const LinkArtifacts& artifacts, Int tIndex) { static GLint GetUniformArraySizeByTIndex(const LinkArtifacts& artifacts, Int tIndex) {
const auto& uniform = artifacts.program->getUniform(tIndex); return UniformAtIn(artifacts, tIndex).arraySize;
const glslang::TType* type = uniform.getType();
if (type != nullptr && type->isSizedArray()) {
return type->getOuterArraySize();
} }
return uniform.size < 1 ? 1 : uniform.size;
// Bounds-checked mirror lookup. Out of range yields a default-constructed entry
// rather than UB, which is the same shape the phase-B getters use: a program whose
// reflection is missing must stay answerable, not crash the query surface.
static const UniformReflection& UniformAtIn(const LinkArtifacts& artifacts, Int tIndex) {
static const UniformReflection kEmpty;
if (tIndex < 0 || static_cast<SizeT>(tIndex) >= artifacts.uniformReflection.size()) return kEmpty;
return artifacts.uniformReflection[tIndex];
}
const UniformReflection& UniformAt(Int tIndex) const { return UniformAtIn(Artifacts(), tIndex); }
const BlockReflection& BlockAt(Int tBlockIndex) const {
static const BlockReflection kEmpty;
if (tBlockIndex < 0 || static_cast<SizeT>(tBlockIndex) >= Artifacts().blockReflection.size()) {
return kEmpty;
}
return Artifacts().blockReflection[tBlockIndex];
} }
// Blocks until a pending link has published its artifacts. Public because a few call // Blocks until a pending link has published its artifacts. Public because a few call
@@ -329,22 +329,25 @@ namespace MobileGL::MG_State::GLState {
for (Uint location = 0; location <= reflection.maxUniformLocation; ++location) { for (Uint location = 0; location <= reflection.maxUniformLocation; ++location) {
if (artifacts.uniformOffsets[location] != ProgramObject::kInvalidUniformOffset) continue; if (artifacts.uniformOffsets[location] != ProgramObject::kInvalidUniformOffset) continue;
if (!ProgramObject::IsValidUniformLocation(reflection, static_cast<Int>(location))) continue; if (!ProgramObject::IsValidUniformLocation(reflection, static_cast<Int>(location))) continue;
const auto& uniform = reflection.program->getUniform(reflection.uniformIndexInTProgram[location]); const auto& uniform =
const glslang::TType* type = uniform.getType(); ProgramObject::UniformAtIn(reflection, reflection.uniformIndexInTProgram[location]);
if (type != nullptr && type->isOpaque()) continue; if (uniform.type.isOpaque) continue;
if (uniform.index >= 0 && uniform.index < reflection.program->getNumUniformBlocks() && // Member of a named uniform block: not settable through glUniform*, so it needs
std::strstr(reflection.program->getUniformBlock(uniform.index).name.c_str(), // no global-UBO shadow storage. tProgramBlockIndexToGl[i] >= 0 means block i is
MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) == nullptr) { // GL-visible, i.e. NOT the synthesized MGL_GLOBAL_UBO - which is exactly what the
// Member of a named uniform block: not settable through glUniform*, so it // strstr(GLOBAL_UBO_NAME) test this replaced was asking, without needing the
// needs no global-UBO shadow storage. // TProgram to spell the block name.
if (uniform.index >= 0 &&
uniform.index < static_cast<Int>(reflection.tProgramBlockIndexToGl.size()) &&
reflection.tProgramBlockIndexToGl[uniform.index] >= 0) {
continue; continue;
} }
// std140-style slot: the matrix upload paths write column vectors at // std140-style slot: the matrix upload paths write column vectors at
// 16-byte strides, so a matrix slot must cover cols * 16 bytes. // 16-byte strides, so a matrix slot must cover cols * 16 bytes.
SizeT slotSize = MG_Util::GetGLTypeSize(uniform.glDefineType); SizeT slotSize = MG_Util::GetGLTypeSize(uniform.glDefineType);
if (type != nullptr && type->isMatrix()) { if (uniform.type.isMatrix) {
slotSize = static_cast<SizeT>(type->getMatrixCols()) * 16u; slotSize = static_cast<SizeT>(uniform.type.matrixCols) * 16u;
} }
slotSize = (slotSize + 15u) & ~static_cast<SizeT>(15u); slotSize = (slotSize + 15u) & ~static_cast<SizeT>(15u);
const SizeT slotOffset = (artifacts.globalUboScratch.size() + 15u) & ~static_cast<SizeT>(15u); const SizeT slotOffset = (artifacts.globalUboScratch.size() + 15u) & ~static_cast<SizeT>(15u);