mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Refactor] (ProgramState): answer the GL query surface from an owned reflection snapshot, not the live TProgram
This commit is contained in:
@@ -4670,12 +4670,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const auto& name = stateProgramObject.GetUniformName(loc);
|
||||
if (name.empty()) continue;
|
||||
if (!IsImageUniformType(stateProgramObject.GetUniformType(loc))) continue;
|
||||
const glslang::TType* type = stateProgramObject.GetUniformTType(loc);
|
||||
if (type == nullptr) continue;
|
||||
if (type->getQualifier().hasFormat()) {
|
||||
const auto& type = stateProgramObject.GetUniformTypeFacts(loc);
|
||||
if (type.hasFormat) {
|
||||
// Declared, and therefore left exactly as written - but a non-core spelling
|
||||
// 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;
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
|
||||
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) {
|
||||
return value ? GL_TRUE : GL_FALSE;
|
||||
}
|
||||
@@ -223,14 +226,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
GLint GetOpaqueUniformUnitLimit(const glslang::TType* type) {
|
||||
GLint GetOpaqueUniformUnitLimit(const TypeFactsRef type) {
|
||||
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
||||
if (type && type->isImage()) return dynamicParameters.MaxImageUnits;
|
||||
if (type && type->isTexture()) return dynamicParameters.MaxCombinedTextureImageUnits;
|
||||
if (type.isImage) return dynamicParameters.MaxImageUnits;
|
||||
if (type.isTexture) return dynamicParameters.MaxCombinedTextureImageUnits;
|
||||
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);
|
||||
if (unit < 0 || unit >= limit) {
|
||||
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
|
||||
// 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.
|
||||
Bool TryGatherFloatMatrixColumns(const glslang::TType* ttype, const char* pBase, void* params) {
|
||||
if (ttype == nullptr || !ttype->isMatrix() || ttype->getBasicType() == glslang::EbtDouble) return false;
|
||||
const Int columns = ttype->getMatrixCols();
|
||||
const Int rows = ttype->getMatrixRows();
|
||||
Bool TryGatherFloatMatrixColumns(const TypeFactsRef ttype, const char* pBase, void* params) {
|
||||
if (!ttype.isMatrix || ttype.isDouble) return false;
|
||||
const Int columns = ttype.matrixCols;
|
||||
const Int rows = ttype.matrixRows;
|
||||
for (Int column = 0; column < columns; ++column) {
|
||||
Memcpy(static_cast<char*>(params) + static_cast<SizeT>(column) * 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
|
||||
// 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.
|
||||
SizeT UniformStorageSpanInBytes(const glslang::TType* ttype, SizeT tightSize) {
|
||||
SizeT UniformStorageSpanInBytes(const TypeFactsRef ttype, SizeT tightSize) {
|
||||
return MG_State::GLState::ProgramObject::UniformStorageSpanInBytes(ttype, tightSize);
|
||||
}
|
||||
|
||||
@@ -904,7 +907,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
auto offset = programObject->GetUniformOffset(location);
|
||||
auto size = programObject->GetUniformSizesInBytes(location);
|
||||
char* pUBO = (char*)programObject->MapUBO();
|
||||
auto* ttype = programObject->GetUniformTType(location);
|
||||
const auto& ttype = programObject->GetUniformTypeFacts(location);
|
||||
const SizeT span = UniformStorageSpanInBytes(ttype, size);
|
||||
if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset ||
|
||||
offset + span > programObject->GetUBOSize()) {
|
||||
@@ -958,7 +961,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
auto offset = programObject->GetUniformOffset(location);
|
||||
auto size = programObject->GetUniformSizesInBytes(location);
|
||||
char* pUBO = static_cast<char*>(programObject->MapUBO());
|
||||
auto* ttype = programObject->GetUniformTType(location);
|
||||
const auto& ttype = programObject->GetUniformTypeFacts(location);
|
||||
const SizeT span = UniformStorageSpanInBytes(ttype, size);
|
||||
if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset ||
|
||||
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
|
||||
// widens back to the queried type, having lost precision at the glUniform*d that
|
||||
// stored it and not here.
|
||||
if (ttype->getBasicType() == glslang::EbtDouble) {
|
||||
const Int columns = ttype->isMatrix() ? ttype->getMatrixCols() : 1;
|
||||
const Int rows = ttype->isMatrix() ? ttype->getMatrixRows()
|
||||
: (ttype->isVector() ? ttype->getVectorSize() : 1);
|
||||
if (ttype.isDouble) {
|
||||
const Int columns = ttype.isMatrix ? ttype.matrixCols : 1;
|
||||
const Int rows = ttype.isMatrix ? ttype.matrixRows
|
||||
: (ttype.isVector ? ttype.vectorSize : 1);
|
||||
// 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.
|
||||
const SizeT columnStride = 4 * sizeof(GLfloat);
|
||||
@@ -1191,8 +1194,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
Memcpy(pUBO + offset + byteOffsetInsideUniform, value, writeSize);
|
||||
programObject.MarkUBOContentDirty();
|
||||
} else {
|
||||
auto* ttype = programObject.GetUniformTType(location);
|
||||
if (!ttype->isTexture() && !ttype->isImage()) return;
|
||||
const auto& ttype = programObject.GetUniformTypeFacts(location);
|
||||
if (!ttype.isTexture && !ttype.isImage) return;
|
||||
if constexpr (!std::is_same_v<std::remove_cv_t<T>, GLint> || ItemCount != 1) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
|
||||
@@ -36,6 +36,69 @@ namespace {
|
||||
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) {
|
||||
const MobileGL::SizeT bracket = name.find('[');
|
||||
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.tProgramUniformIndexToGl = artifacts.tProgramUniformIndexToGl;
|
||||
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.ready = true;
|
||||
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,
|
||||
ubo.name.c_str(), ubo.size, ubo.getBinding());
|
||||
}
|
||||
|
||||
SnapshotGlslangReflection();
|
||||
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() {
|
||||
if (!artifacts.program) return false;
|
||||
// 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(
|
||||
const MG_Util::ShaderTranspiler::CompileEnv& env) const;
|
||||
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 ResolveTransformFeedbackVaryings();
|
||||
void ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate);
|
||||
|
||||
@@ -24,6 +24,68 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
class ProgramObject {
|
||||
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()) {}
|
||||
// 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.
|
||||
@@ -134,8 +196,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
const Int index = Artifacts().uniformIndexInTProgram[base];
|
||||
// "[k]" only addresses arrays ("scalar[0]" is not a uniform name), and only
|
||||
// in-range elements.
|
||||
const glslang::TType* type = Artifacts().program->getUniform(index).getType();
|
||||
if (type == nullptr || !type->isArray()) return -1;
|
||||
if (!UniformAt(index).type.isArray) return -1;
|
||||
if (static_cast<GLint>(element) >= GetUniformArraySizeByTIndex(index)) return -1;
|
||||
const Int location = base + (Int)element;
|
||||
if (!UniformLocationsAliasSameUniform(base, location)) return -1;
|
||||
@@ -175,44 +236,35 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
Int GetActiveUniformIndex(const String& name) const {
|
||||
const Int tProgramCount = static_cast<Int>(Artifacts().tProgramUniformIndexToGl.size());
|
||||
const Int uniformIndex = Artifacts().program->getUniformIndex(name.c_str());
|
||||
if (uniformIndex >= 0 && uniformIndex < tProgramCount &&
|
||||
Artifacts().program->getUniform(uniformIndex).name == name) {
|
||||
return GlUniformIndexFromTProgram(uniformIndex);
|
||||
// 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
|
||||
// glslang's getUniformIndex(); a lookup that misses needs no bounds check.
|
||||
const auto& byName = Artifacts().uniformIndexByName;
|
||||
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"
|
||||
// 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 = Artifacts().program->getUniformIndex(suffixedName.c_str());
|
||||
if (suffixedIndex >= 0 && suffixedIndex < tProgramCount &&
|
||||
Artifacts().program->getUniform(suffixedIndex).name == suffixedName) {
|
||||
return GlUniformIndexFromTProgram(suffixedIndex);
|
||||
}
|
||||
return -1;
|
||||
const auto suffixed = byName.find(name + "[0]");
|
||||
return suffixed != byName.end() ? GlUniformIndexFromTProgram(suffixed->second) : -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 = Artifacts().program->getUniformIndex(baseName.c_str());
|
||||
if (baseIndex < 0 || baseIndex >= tProgramCount) return -1;
|
||||
return Artifacts().program->getUniform(baseIndex).name == baseName ? GlUniformIndexFromTProgram(baseIndex)
|
||||
: -1;
|
||||
const auto base = byName.find(name.substr(0, name.length() - 3));
|
||||
return base != byName.end() ? GlUniformIndexFromTProgram(base->second) : -1;
|
||||
}
|
||||
|
||||
Bool IsValidUniformLocation(Int location) const { return IsValidUniformLocation(Artifacts(), location); }
|
||||
|
||||
GLenum GetUniformType(Uint location) const {
|
||||
auto& uniform = Artifacts().program->getUniform(Artifacts().uniformIndexInTProgram[location]);
|
||||
return uniform.glDefineType;
|
||||
return UniformAt(Artifacts().uniformIndexInTProgram[location]).glDefineType;
|
||||
}
|
||||
|
||||
GLenum GetActiveUniformType(Uint index) const {
|
||||
auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index));
|
||||
return uniform.glDefineType;
|
||||
return UniformAt(TProgramUniformIndex(index)).glDefineType;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index));
|
||||
// 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
|
||||
// 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.
|
||||
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;
|
||||
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
|
||||
// layout is always std140, where every array element stride rounds up to a vec4.
|
||||
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;
|
||||
const glslang::TType* type = uniform.getType();
|
||||
if (type == nullptr || !type->isArray()) return 0;
|
||||
if (type->isMatrix()) {
|
||||
if (!uniform.type.isArray) return 0;
|
||||
if (uniform.type.isMatrix) {
|
||||
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 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
|
||||
// an inheriting member's layoutMatrix == ElmNone.
|
||||
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;
|
||||
const glslang::TType* type = uniform.getType();
|
||||
if (type == nullptr || !type->isMatrix()) return 0;
|
||||
glslang::TLayoutMatrix layoutMatrix = type->getQualifier().layoutMatrix;
|
||||
if (layoutMatrix == glslang::ElmNone) {
|
||||
layoutMatrix = Artifacts().program->getUniformBlock(uniform.index).getType()->getQualifier().layoutMatrix;
|
||||
}
|
||||
return (layoutMatrix == glslang::ElmRowMajor) ? 1 : 0;
|
||||
if (!uniform.type.isMatrix) return 0;
|
||||
// layoutMatrix is already resolved against the owning block's qualifier at
|
||||
// snapshot time, so the getUniformBlock() fallback this used to carry is gone.
|
||||
return (uniform.type.layoutMatrix == static_cast<Int>(glslang::ElmRowMajor)) ? 1 : 0;
|
||||
}
|
||||
|
||||
// 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
|
||||
// every GL 3.3 float matrix this evaluates to 16, independent of majorness.
|
||||
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;
|
||||
const glslang::TType* type = uniform.getType();
|
||||
if (type == nullptr || !type->isMatrix()) return 0;
|
||||
glslang::TLayoutMatrix layoutMatrix = type->getQualifier().layoutMatrix;
|
||||
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();
|
||||
if (!uniform.type.isMatrix) return 0;
|
||||
const bool rowMajor = (uniform.type.layoutMatrix == static_cast<Int>(glslang::ElmRowMajor));
|
||||
const int strideVectorComponents = rowMajor ? uniform.type.matrixCols : uniform.type.matrixRows;
|
||||
constexpr int scalarSize = 4; // GL 3.3 core uniform matrices are float
|
||||
const int vectorAlignment = (strideVectorComponents <= 1) ? scalarSize
|
||||
: (strideVectorComponents == 2) ? 2 * scalarSize
|
||||
@@ -307,21 +349,39 @@ namespace MobileGL::MG_State::GLState {
|
||||
return (vectorAlignment + 15) & ~15; // std140 round-up to a vec4
|
||||
}
|
||||
|
||||
const glslang::TType* GetUniformTType(Uint location) const {
|
||||
auto& uniform = Artifacts().program->getUniform(Artifacts().uniformIndexInTProgram[location]);
|
||||
return uniform.getType();
|
||||
// The flattened type of the uniform at `location`. This is what replaced
|
||||
// GetUniformTType(): the same information, owned by the program instead of by a
|
||||
// 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 {
|
||||
auto& uniform = Artifacts().program->getUniform(Artifacts().uniformIndexInTProgram[location]);
|
||||
return uniform.name;
|
||||
return UniformAt(Artifacts().uniformIndexInTProgram[location]).name;
|
||||
}
|
||||
|
||||
const String& GetActiveUniformName(Uint index) const {
|
||||
auto& uniform = Artifacts().program->getUniform(TProgramUniformIndex(index));
|
||||
return uniform.name;
|
||||
return UniformAt(TProgramUniformIndex(index)).name;
|
||||
}
|
||||
// Sentinel for a uniform location without global-UBO backing storage (should not
|
||||
// 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
|
||||
// reading or writing a whole uniform's storage - a bounds check, a copy between two
|
||||
// programs' shadows - wants this rather than GetUniformSizesInBytes.
|
||||
static SizeT UniformStorageSpanInBytes(const glslang::TType* type, SizeT tightSize) {
|
||||
if (type != nullptr && type->isMatrix()) {
|
||||
return static_cast<SizeT>(type->getMatrixCols()) * 4 * sizeof(Float);
|
||||
static SizeT UniformStorageSpanInBytes(const TypeFacts& type, SizeT tightSize) {
|
||||
if (type.isMatrix) {
|
||||
return static_cast<SizeT>(type.matrixCols) * 4 * sizeof(Float);
|
||||
}
|
||||
if (type != nullptr && type->getBasicType() == glslang::EbtDouble) {
|
||||
if (type.isDouble) {
|
||||
return tightSize / 2;
|
||||
}
|
||||
return tightSize;
|
||||
}
|
||||
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 ----
|
||||
@@ -476,14 +536,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
return mask;
|
||||
}
|
||||
Uint32 GetActiveFragmentOutputLocationMask() const {
|
||||
if (!Artifacts().program) {
|
||||
if (Artifacts().pipeOutputReflection.empty()) {
|
||||
return 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) {
|
||||
const Int location = static_cast<Int>(Artifacts().program->getPipeOutput(index).layoutLocation());
|
||||
const Int location = Artifacts().pipeOutputReflection[index].location;
|
||||
if (location >= 0 && location < 32) {
|
||||
mask |= (1u << location);
|
||||
}
|
||||
@@ -491,38 +551,34 @@ namespace MobileGL::MG_State::GLState {
|
||||
return mask;
|
||||
}
|
||||
Int GetActiveFragmentOutputCount() const {
|
||||
return Artifacts().program ? Artifacts().program->getNumPipeOutputs() : 0;
|
||||
return static_cast<Int>(Artifacts().pipeOutputReflection.size());
|
||||
}
|
||||
const String& GetActiveFragmentOutputName(Uint index) const {
|
||||
MOBILEGL_ASSERT(Artifacts().program != nullptr, "ProgramObject::GetActiveFragmentOutputName: program is null");
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().program->getNumPipeOutputs()),
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().pipeOutputReflection.size()),
|
||||
"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 {
|
||||
MOBILEGL_ASSERT(Artifacts().program != nullptr, "ProgramObject::GetFragmentOutputLocation: program is null");
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().program->getNumPipeOutputs()),
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().pipeOutputReflection.size()),
|
||||
"ProgramObject::GetFragmentOutputLocation: index=%u out of range",
|
||||
index);
|
||||
return static_cast<Int>(Artifacts().program->getPipeOutput(static_cast<Int>(index)).layoutLocation());
|
||||
return Artifacts().pipeOutputReflection[index].location;
|
||||
}
|
||||
GLint GetActiveFragmentOutputArraySize(Uint index) const {
|
||||
MOBILEGL_ASSERT(Artifacts().program != nullptr, "ProgramObject::GetActiveFragmentOutputArraySize: program is null");
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().program->getNumPipeOutputs()),
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().pipeOutputReflection.size()),
|
||||
"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 {
|
||||
MOBILEGL_ASSERT(Artifacts().program != nullptr, "ProgramObject::GetFragmentOutputType: program is null");
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().program->getNumPipeOutputs()),
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(Artifacts().pipeOutputReflection.size()),
|
||||
"ProgramObject::GetFragmentOutputType: index=%u out of range",
|
||||
index);
|
||||
return Artifacts().program->getPipeOutput(static_cast<Int>(index)).glDefineType;
|
||||
return Artifacts().pipeOutputReflection[index].glDefineType;
|
||||
}
|
||||
GLenum GetAttribType(Uint index) const { return Artifacts().attribTypes[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; }
|
||||
GLint GetActiveAttribArraySize(Uint index) const { return Artifacts().program->getPipeInput(static_cast<Int>(index)).size; }
|
||||
GLenum GetActiveAttribType(Uint index) const { return Artifacts().pipeInputReflection[index].glDefineType; }
|
||||
GLint GetActiveAttribArraySize(Uint index) const { return Artifacts().pipeInputReflection[index].size; }
|
||||
// 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
|
||||
// resource queries enumerate builtins).
|
||||
@@ -534,7 +590,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
return name;
|
||||
}
|
||||
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
|
||||
// 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
|
||||
// regeneration is gated on link-shaped versions, so without a counter that moves
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -703,19 +759,16 @@ namespace MobileGL::MG_State::GLState {
|
||||
// this after a failed glGetAttribLocation, and reached it as soon as the CopyTexImage2D
|
||||
// throw ahead of it stopped killing the run first.
|
||||
Int GetActiveAtomicCounterCount() const {
|
||||
const auto& program = Artifacts().program;
|
||||
return program ? program->getNumAtomicCounters() : 0;
|
||||
return Artifacts().atomicCounterCount;
|
||||
}
|
||||
Int GetActiveAttributesCount() const {
|
||||
const auto& program = Artifacts().program;
|
||||
return program ? program->getNumPipeInputs() : 0;
|
||||
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.
|
||||
Int GetActiveUniformBlocksCount() const { return static_cast<Int>(Artifacts().glBlockIndexToTProgram.size()); }
|
||||
GLuint GetComputeLocalSize(Uint dim) const {
|
||||
const auto& program = Artifacts().program;
|
||||
return program ? program->getLocalSize(static_cast<Int>(dim)) : 0;
|
||||
return dim < 3u ? Artifacts().computeLocalSize[dim] : 0u;
|
||||
}
|
||||
Int GetActiveAttributesMaxLength() const { return Artifacts().attribInNameMaxLength; }
|
||||
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
|
||||
// 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 (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 {
|
||||
auto& ubo = Artifacts().program->getUniformBlock(Artifacts().glBlockIndexToTProgram[index]);
|
||||
const auto& ubo = BlockAt(Artifacts().glBlockIndexToTProgram[index]);
|
||||
return ubo.name;
|
||||
}
|
||||
|
||||
@@ -774,7 +827,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
// compiler checks rather than a review item - a new reader cannot spell the field
|
||||
// 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 {
|
||||
// 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;
|
||||
|
||||
// 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)
|
||||
Vector<String> attribs;
|
||||
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
|
||||
// the artifacts' uniformIndexInTProgram stores).
|
||||
static GLint GetUniformArraySizeByTIndex(const LinkArtifacts& artifacts, Int tIndex) {
|
||||
const auto& uniform = artifacts.program->getUniform(tIndex);
|
||||
const glslang::TType* type = uniform.getType();
|
||||
if (type != nullptr && type->isSizedArray()) {
|
||||
return type->getOuterArraySize();
|
||||
return UniformAtIn(artifacts, tIndex).arraySize;
|
||||
}
|
||||
|
||||
// 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 uniform.size < 1 ? 1 : uniform.size;
|
||||
return Artifacts().blockReflection[tBlockIndex];
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (artifacts.uniformOffsets[location] != ProgramObject::kInvalidUniformOffset) continue;
|
||||
if (!ProgramObject::IsValidUniformLocation(reflection, static_cast<Int>(location))) continue;
|
||||
const auto& uniform = reflection.program->getUniform(reflection.uniformIndexInTProgram[location]);
|
||||
const glslang::TType* type = uniform.getType();
|
||||
if (type != nullptr && type->isOpaque()) continue;
|
||||
if (uniform.index >= 0 && uniform.index < reflection.program->getNumUniformBlocks() &&
|
||||
std::strstr(reflection.program->getUniformBlock(uniform.index).name.c_str(),
|
||||
MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) == nullptr) {
|
||||
// Member of a named uniform block: not settable through glUniform*, so it
|
||||
// needs no global-UBO shadow storage.
|
||||
const auto& uniform =
|
||||
ProgramObject::UniformAtIn(reflection, reflection.uniformIndexInTProgram[location]);
|
||||
if (uniform.type.isOpaque) continue;
|
||||
// Member of a named uniform block: not settable through glUniform*, so it needs
|
||||
// no global-UBO shadow storage. tProgramBlockIndexToGl[i] >= 0 means block i is
|
||||
// GL-visible, i.e. NOT the synthesized MGL_GLOBAL_UBO - which is exactly what the
|
||||
// strstr(GLOBAL_UBO_NAME) test this replaced was asking, without needing the
|
||||
// TProgram to spell the block name.
|
||||
if (uniform.index >= 0 &&
|
||||
uniform.index < static_cast<Int>(reflection.tProgramBlockIndexToGl.size()) &&
|
||||
reflection.tProgramBlockIndexToGl[uniform.index] >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// std140-style slot: the matrix upload paths write column vectors at
|
||||
// 16-byte strides, so a matrix slot must cover cols * 16 bytes.
|
||||
SizeT slotSize = MG_Util::GetGLTypeSize(uniform.glDefineType);
|
||||
if (type != nullptr && type->isMatrix()) {
|
||||
slotSize = static_cast<SizeT>(type->getMatrixCols()) * 16u;
|
||||
if (uniform.type.isMatrix) {
|
||||
slotSize = static_cast<SizeT>(uniform.type.matrixCols) * 16u;
|
||||
}
|
||||
slotSize = (slotSize + 15u) & ~static_cast<SizeT>(15u);
|
||||
const SizeT slotOffset = (artifacts.globalUboScratch.size() + 15u) & ~static_cast<SizeT>(15u);
|
||||
|
||||
Reference in New Issue
Block a user