[Perf] (MG_State, MG_Util): compile shaders with a single relaxed parse

glCompileShader used to parse every source twice: once under the GL client
(reflection only) and once under the relaxed Vulkan client (SPIR-V + the
plain-uniform global UBO), with GenerateBinary re-preprocessing, re-parsing
and re-linking every attached shader on every glLinkProgram. The GL-client
pass is gone: Compile() performs the one link-compatible relaxed parse and
the linked TProgram serves reflection and codegen both. Measured on the BSL
shaderpack compile phase: Espryt 2.80s -> 2.14s, Magma 3.78s -> 3.07s.

What the relaxed parse cannot provide is restored explicitly:
- explicit layout(location/binding) qualifiers on default-block uniforms and
  samplers are extracted lexically at Compile() (the relaxed parse strips
  them) and merged per link with cross-stage conflict checks;
- uniforms the relaxed parse sweeps into MGL_GLOBAL_UBO but no stage reads
  are filtered from the GL reflection surface through GL<->TProgram index
  translation maps (dead uniforms stay inactive, the synthesized block stays
  hidden, builtins reflect under their GL spellings);
- SPIR-V is generated BEFORE buildReflection touches the program (its
  live-variable analysis perturbs GlslangToSpv output - generated modules
  stay bit-identical to the old pipeline's), while the glUniform*-to-scratch
  routing tables are built strictly AFTER reflection, whose results size and
  key them;
- a TShader feeds exactly one link (mapIO mutates the intermediate); relinks
  and multi-program attachments re-parse the stored preprocessed source.

Validated: DirectGLES retrace suite green (two pre-existing local-driver
failures unchanged old vs new), KHR-GL30 877/878 on Espryt/NVIDIA (the one
failure pre-exists this change), unit tests green, per-module SPIR-V hashes
identical across a full DirectVulkan replay.
This commit is contained in:
BZLZHH
2026-08-08 01:25:54 -04:00
parent 81bcbd6c14
commit 0d0527192a
7 changed files with 983 additions and 128 deletions
@@ -155,6 +155,11 @@ namespace MobileGL::MG_State::GLState {
m_program.reset();
m_generatedSpirv.clear();
m_uniformLocations.clear();
m_glUniformIndexToTProgram.clear();
m_tProgramUniformIndexToGl.clear();
m_glBlockIndexToTProgram.clear();
m_tProgramBlockIndexToGl.clear();
m_linkedExplicitUniformLocations.clear();
m_uniformIndexInTProgram.clear();
m_uniformSamplerOrImageUnitIndex.clear();
m_explicitOpaqueUniformBindings.clear();
@@ -564,13 +569,45 @@ namespace MobileGL::MG_State::GLState {
MGLOG_E("ProgramObject %u: Link failed - %s", m_externalIndex, m_infoLog.c_str());
return;
}
shaders[i] = m_shaders[i]->GetCompiledShader();
String reparseLog;
shaders[i] = m_shaders[i]->TakeShaderForLink(reparseLog);
if (!shaders[i]) {
// Only reachable when the consume-once re-parse of an already-compiled
// source fails, which no valid state transition produces.
m_infoLog = std::format("Internal error: re-parsing an attached {} for linking failed:\n{}",
MG_Util::ConvertGLEnumToString(shaderTypes[i]), reparseLog);
MGLOG_E("ProgramObject %u: Link failed - %s", m_externalIndex, m_infoLog.c_str());
return;
}
MGLOG_D("ProgramObject %u: shader[%zu] compiled shader ptr %p, src len %zu", m_externalIndex, i,
shaders[i].get(), m_shaders[i]->GetShaderSource().length());
MGLOG_D("ProgramObject %u: shader[%zu] source:\n%s", m_externalIndex, i,
m_shaders[i]->GetShaderSource().c_str());
}
// Merge the shaders' lexically extracted explicit uniform locations. The same
// uniform declared in several stages must agree on its location (config-A glslang
// enforced this at mapIO; the relaxed parse no longer sees the qualifiers).
for (const auto& shader : m_shaders) {
for (const auto& [name, location] : shader->GetExplicitUniformLocations()) {
const auto [it, inserted] = m_linkedExplicitUniformLocations.emplace(name, location);
if (!inserted && it->second != location) {
m_infoLog = std::format(
"Uniform '{}' is declared with conflicting explicit locations ({} and {}) "
"across stages.",
name, it->second, location);
MGLOG_E("ProgramObject %u: Link failed - %s", m_externalIndex, m_infoLog.c_str());
return;
}
}
// Sampler/image layout(binding = N) initial units, likewise invisible to the
// relaxed parse. Stage order matches the old per-stage mapIO capture, so a
// name declared in several stages keeps the last stage's binding as before.
for (const auto& [name, binding] : shader->GetExplicitOpaqueBindings()) {
m_explicitOpaqueUniformBindings[name] = binding;
}
}
MG_Util::ShaderTranspiler::ProgramAttrib attrib{.shaders = Move(shaders),
.explicitVertexInLocations = m_explicitAttribLocations,
.explicitFragmentOutLocations = m_explicitFragDataLocation,
@@ -606,8 +643,27 @@ namespace MobileGL::MG_State::GLState {
}
}
// SPIR-V must be generated BEFORE buildReflection touches m_program:
// reflection's live-variable analysis mutates the intermediates in ways that
// change subsequent GlslangToSpv output (observed: catastrophic uniform
// misbinding on DirectVulkan for UBO-heavy content). The old two-link pipeline
// never ran buildReflection on the SPIR-V-producing program; this order keeps
// that property with the single link. The glUniform*-to-scratch routing
// tables, in contrast, are sized and keyed by reflection results, so they are
// built strictly AFTER DoReflection. (Everything else on the reflection
// surface - locations, sampler units, block bindings/sizes - was measured
// identical in either order.)
MGLOG_D("ProgramObject %u: Starting SPIR-V generation", m_externalIndex);
GenerateSpirv();
MGLOG_D("ProgramObject %u: Starting reflection", m_externalIndex);
DoReflection();
if (!DoReflection()) {
MGLOG_E("ProgramObject %u: Link failed during reflection: %s", m_externalIndex, m_infoLog.c_str());
return;
}
MGLOG_D("ProgramObject %u: Building global-UBO routing tables", m_externalIndex);
BuildGlobalUboRouting();
MGLOG_D("ProgramObject %u: Reflection done (linkStatus=%d)", m_externalIndex, (int)m_linkStatus);
if (!ValidateFragmentOutputLocations()) {
return;
@@ -618,9 +674,6 @@ namespace MobileGL::MG_State::GLState {
m_infoLog.c_str());
return;
}
MGLOG_D("ProgramObject %u: Starting binary generation", m_externalIndex);
GenerateBinary();
MGLOG_D("ProgramObject %u: Binary generation finished (generatedSpirv size=%zu)", m_externalIndex,
m_generatedSpirv.size());
}
@@ -642,12 +695,12 @@ namespace MobileGL::MG_State::GLState {
return m_shaders;
}
void ProgramObject::DoReflection() {
Bool ProgramObject::DoReflection() {
if (!m_program) {
MGLOG_E("ProgramObject %u: DoReflection called but m_program is null", m_externalIndex);
m_linkStatus = false;
m_infoLog = "DoReflection failed: no program.";
return;
return false;
}
MGLOG_D("ProgramObject %u: DoReflection - building reflection", m_externalIndex);
@@ -666,26 +719,124 @@ namespace MobileGL::MG_State::GLState {
m_linkStatus = false;
m_infoLog = "Build reflection failed.";
MGLOG_E("ProgramObject %u: DoReflection - buildReflection() returned false", m_externalIndex);
return;
return false;
}
// ---------- GL-facing index spaces (relaxed-parse cleanup) ----------
// Blocks first: global-UBO membership drives the uniform filter below. The
// synthesized MGL_GLOBAL_UBO is a transpiler artifact - its members are GL
// default-block uniforms and the block itself must stay invisible to GL (it
// did not exist in the GL-client parse this replaces).
const Int tProgramBlockCount = m_program->getNumUniformBlocks();
m_tProgramBlockIndexToGl.assign(tProgramBlockCount, -1);
m_glBlockIndexToTProgram.clear();
for (Int i = 0; i < tProgramBlockCount; i++) {
const auto& ubo = m_program->getUniformBlock(i);
if (std::strstr(ubo.name.c_str(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) != nullptr) {
continue;
}
m_tProgramBlockIndexToGl[i] = static_cast<Int>(m_glBlockIndexToTProgram.size());
m_glBlockIndexToTProgram.push_back(i);
}
// ------------ Uniforms (GL Plain) ----------------
// Allocate uniform locations
m_activeUniformCount = m_program->getNumUniformVariables();
// The relaxed parse sweeps every DECLARED default-block uniform into
// MGL_GLOBAL_UBO whether or not any stage reads it. GL requires a
// declared-but-unreferenced default-block uniform to be inactive (absent from
// glGetActiveUniform, glGetUniformLocation == -1): filter global-UBO members no
// stage references. Named-block members keep GL's every-declared-member-is-active
// semantics, exactly as before.
const Int tProgramUniformCount = m_program->getNumUniformVariables();
m_tProgramUniformIndexToGl.assign(tProgramUniformCount, -1);
m_glUniformIndexToTProgram.clear();
const auto isGlobalUboMember = [this](const glslang::TObjectReflection& uniform) {
return uniform.index >= 0 && uniform.index < static_cast<Int>(m_tProgramBlockIndexToGl.size()) &&
m_tProgramBlockIndexToGl[uniform.index] < 0;
};
for (Int i = 0; i < tProgramUniformCount; i++) {
const auto& uniform = m_program->getUniform(i);
if (isGlobalUboMember(uniform) && uniform.stages == 0) {
MGLOG_D("ProgramObject %u: Reflection - dead default-block uniform '%s' filtered from the GL "
"surface",
m_externalIndex, uniform.name.c_str());
continue;
}
m_tProgramUniformIndexToGl[i] = static_cast<Int>(m_glUniformIndexToTProgram.size());
m_glUniformIndexToTProgram.push_back(i);
}
m_activeUniformCount = static_cast<Uint>(m_glUniformIndexToTProgram.size());
MGLOG_D("ProgramObject %u: Reflection - active uniform count = %d (of %d reflected)", m_externalIndex,
m_activeUniformCount, tProgramUniformCount);
// Effective explicit location per TProgram uniform, from two sources:
// - the lexical side-channel for default-block uniforms - the relaxed parse
// dropped their layout(location = N) qualifiers when collecting them into
// MGL_GLOBAL_UBO, so reflection cannot provide them ("source-explicit");
// - glslang's layoutLocation() for opaque uniforms, where the qualifier
// survives the relaxed parse (and mapIO auto-assigns the rest).
constexpr Uint kNoLocation = glslang::TQualifier::layoutLocationEnd;
Vector<Uint> effectiveLocation(tProgramUniformCount, kNoLocation);
Vector<Bool> locationIsSourceExplicit(tProgramUniformCount, false);
UnorderedMap<String, Uint> structExplicitCursor; // declared root -> next member location
const auto findExplicitLocation = [this](const String& reflectedName) -> const Int* {
auto it = m_linkedExplicitUniformLocations.find(reflectedName);
if (it == m_linkedExplicitUniformLocations.end() && reflectedName.length() > 3 &&
reflectedName.compare(reflectedName.length() - 3, 3, "[0]") == 0) {
it = m_linkedExplicitUniformLocations.find(reflectedName.substr(0, reflectedName.length() - 3));
}
return it != m_linkedExplicitUniformLocations.end() ? &it->second : nullptr;
};
for (const Int i : m_glUniformIndexToTProgram) {
const auto& uniform = m_program->getUniform(i);
const glslang::TType* type = uniform.getType();
const Bool inNamedBlock = uniform.index >= 0 && !isGlobalUboMember(uniform);
if (inNamedBlock) continue; // block members never take glUniform locations
if (const Int* explicitLocation = findExplicitLocation(uniform.name)) {
effectiveLocation[i] = static_cast<Uint>(*explicitLocation);
locationIsSourceExplicit[i] = true;
} else if (!m_linkedExplicitUniformLocations.empty() &&
uniform.name.find('.') != String::npos) {
// A struct uniform's explicit location spreads consecutively over its
// flattened members ("s.a", "s[1].b", ...) in reflection order.
const SizeT cut = uniform.name.find_first_of(".[");
const auto rootIt = m_linkedExplicitUniformLocations.find(uniform.name.substr(0, cut));
if (rootIt != m_linkedExplicitUniformLocations.end()) {
auto [cursor, inserted] =
structExplicitCursor.emplace(rootIt->first, static_cast<Uint>(rootIt->second));
(void)inserted;
effectiveLocation[i] = cursor->second;
locationIsSourceExplicit[i] = true;
cursor->second += static_cast<Uint>(GetUniformLocationSpan(uniform));
}
}
if (effectiveLocation[i] == kNoLocation && type != nullptr && type->isOpaque()) {
effectiveLocation[i] = uniform.layoutLocation();
}
if (locationIsSourceExplicit[i] &&
effectiveLocation[i] + static_cast<Uint>(GetUniformLocationSpan(uniform)) > kNoLocation) {
// Config A rejected out-of-range explicit locations at parse; keep them
// from growing the location table unboundedly.
m_infoLog = std::format("Uniform '{}' explicit location {} is out of range.", uniform.name,
effectiveLocation[i]);
ResetLinkArtifacts();
return false;
}
}
Int requiredUniformLocations = 0;
MGLOG_D("ProgramObject %u: Reflection - active uniform count = %d", m_externalIndex, m_activeUniformCount);
for (int i = 0; i < m_activeUniformCount; i++) {
for (const Int i : m_glUniformIndexToTProgram) {
auto& uniform = m_program->getUniform(i);
auto location = uniform.layoutLocation();
const Uint location = effectiveLocation[i];
const Int locationSpan = GetUniformLocationSpan(uniform);
requiredUniformLocations += locationSpan;
if (location != glslang::TQualifier::layoutLocationEnd) {
if (location != kNoLocation) {
m_maxUniformLocation = std::max(m_maxUniformLocation, location + locationSpan - 1);
}
m_uniformNameMaxLength = std::max(m_uniformNameMaxLength, (Int)uniform.name.length());
m_uniformLocations[uniform.name] = location;
MGLOG_D("ProgramObject %u: Reflection - uniform[%d] name='%s' layoutLocation=%d", m_externalIndex, i,
uniform.name.c_str(), location);
MGLOG_D("ProgramObject %u: Reflection - uniform[%d] name='%s' effectiveLocation=%d", m_externalIndex,
i, uniform.name.c_str(), location);
}
MGLOG_D("ProgramObject %u: Reflection - computed m_maxUniformLocation=%u m_uniformNameMaxLength=%d",
@@ -706,21 +857,62 @@ namespace MobileGL::MG_State::GLState {
Vector<int> unallocatedUniformIndex;
// Populate vector with already allocated location
for (int i = 0; i < m_activeUniformCount; i++) {
// Pass 1: source-explicit locations. These are API contract
// (ARB_explicit_uniform_location), and an overlap between distinct uniforms is a
// link error - config A's mapIO rejected it ("Uniform location overlaps across
// stages"); the relaxed parse dropped the qualifiers, so it is enforced here.
for (const Int i : m_glUniformIndexToTProgram) {
auto& uniform = m_program->getUniform(i);
auto location = uniform.layoutLocation();
if (m_uniformLocations[uniform.name] == glslang::TQualifier::layoutLocationEnd) {
if (!locationIsSourceExplicit[i] || effectiveLocation[i] == kNoLocation) continue;
const Uint location = effectiveLocation[i];
const Int locationSpan = GetUniformLocationSpan(uniform);
for (Int element = 0; element < locationSpan; ++element) {
const Int existing = m_uniformIndexInTProgram[location + element];
if (existing != glslang::TQualifier::layoutLocationEnd && existing != i) {
m_infoLog =
std::format("Uniform location overlap: '{}' and '{}' both occupy location {}.",
m_program->getUniform(existing).name, uniform.name, location + element);
ResetLinkArtifacts();
return false;
}
m_uniformIndexInTProgram[location + element] = i;
}
MGLOG_D("ProgramObject %u: Reflection - assigned explicit-location uniform '%s' to locations "
"%u..%u (indexInTProgram=%d)",
m_externalIndex, uniform.name.c_str(), location, location + locationSpan - 1, i);
}
// Pass 2: glslang-assigned locations (opaque uniforms under the relaxed parse).
// Implementation-chosen, so on a collision with an explicit location the uniform
// is demoted to the first-fit pass below instead of failing the link.
for (const Int i : m_glUniformIndexToTProgram) {
auto& uniform = m_program->getUniform(i);
if (locationIsSourceExplicit[i]) continue;
const Uint location = effectiveLocation[i];
if (location == kNoLocation) {
unallocatedUniformIndex.emplace_back(i);
MGLOG_D("ProgramObject %u: Reflection - uniform '%s' is unallocated, will assign later",
m_externalIndex, uniform.name.c_str());
continue; // will allocate unallocated uniforms later
}
const Int locationSpan = GetUniformLocationSpan(uniform);
Bool spanIsFree = location + locationSpan - 1 <= m_maxUniformLocation;
for (Int element = 0; spanIsFree && element < locationSpan; ++element) {
spanIsFree =
m_uniformIndexInTProgram[location + element] == glslang::TQualifier::layoutLocationEnd;
}
if (!spanIsFree) {
m_uniformLocations[uniform.name] = kNoLocation;
unallocatedUniformIndex.emplace_back(i);
MGLOG_D("ProgramObject %u: Reflection - uniform '%s' auto location %u collides with an "
"explicit location, demoting to first-fit",
m_externalIndex, uniform.name.c_str(), location);
continue;
}
for (Int element = 0; element < locationSpan; ++element) {
m_uniformIndexInTProgram[location + element] = i;
}
MGLOG_D("ProgramObject %u: Reflection - assigned uniform '%s' to locations %d..%d "
MGLOG_D("ProgramObject %u: Reflection - assigned uniform '%s' to locations %u..%u "
"(indexInTProgram=%d)",
m_externalIndex, uniform.name.c_str(), location, location + locationSpan - 1, i);
}
@@ -772,7 +964,7 @@ namespace MobileGL::MG_State::GLState {
}
}
for (int i = 0; i < m_activeUniformCount; i++) {
for (const Int i : m_glUniformIndexToTProgram) {
auto& uniform = m_program->getUniform(i);
const auto locationIt = m_uniformLocations.find(uniform.name);
if (locationIt == m_uniformLocations.end()) {
@@ -842,7 +1034,10 @@ namespace MobileGL::MG_State::GLState {
for (int i = 0; i < inCount; ++i) {
auto& inVar = m_program->getPipeInput(i);
Int location = (Int)inVar.layoutLocation();
m_attribInNameMaxLength = std::max(m_attribInNameMaxLength, (Int)inVar.name.length());
// Builtins reflect under their SPIR-V names here; GL_ACTIVE_ATTRIBUTE_MAX_LENGTH
// must measure the GL spelling glGetActiveAttrib will report.
m_attribInNameMaxLength =
std::max(m_attribInNameMaxLength, (Int)NormalizeBuiltinPipeInputName(inVar.name).length());
if (location >= 0 && location < (int)m_attribs.size()) {
const Int locationSpan = GetVertexInputLocationSpan(inVar.glDefineType);
@@ -868,11 +1063,12 @@ namespace MobileGL::MG_State::GLState {
}
// ---------- UBO ----------
Int uboCount = m_program->getNumUniformBlocks();
// GL-visible blocks only (MGL_GLOBAL_UBO was filtered out above).
const Int uboCount = GetActiveUniformBlocksCount();
MGLOG_D("ProgramObject %u: Reflection - uniform block count (UBO) = %d", m_externalIndex, uboCount);
m_uniformBlockBinding.resize(uboCount, -1);
for (int i = 0; i < uboCount; i++) {
auto& ubo = m_program->getUniformBlock(i);
for (Int i = 0; i < uboCount; i++) {
auto& ubo = m_program->getUniformBlock(m_glBlockIndexToTProgram[i]);
m_uniformBlockNameMaxLength = std::max(m_uniformBlockNameMaxLength, (Int)ubo.name.length());
m_uniformBlockIndexByName[ubo.name] = i;
// if there's binding defined in shader as layout(binding = ...),
@@ -881,82 +1077,56 @@ namespace MobileGL::MG_State::GLState {
MGLOG_D("ProgramObject %u: Reflection - UBO[%d] name='%s' size=%u binding=%d", m_externalIndex, i,
ubo.name.c_str(), ubo.size, ubo.getBinding());
}
return true;
}
void ProgramObject::GenerateBinary() {
void ProgramObject::GenerateSpirv() {
/* As we passed first stage compilation/linking,
* we'll assume all the operations here should
* pass. We may be able to employ some optimizations
* here without the burden of error reporting.
*/
using namespace MG_Util::ShaderTranspiler;
MGLOG_D("ProgramObject %u: GenerateBinary - start", m_externalIndex);
Vector<SharedPtr<glslang::TShader>> shaders(m_shaders.size());
MGLOG_D("ProgramObject %u: GenerateSpirv - start", m_externalIndex);
// The shaders were parsed once, in the link-compatible (relaxed Vulkan-rules)
// configuration, and m_program linked those parses - so m_program IS the
// program the backends consume. Generate SPIR-V straight from its
// intermediates; the full re-parse + re-link that used to live here (one
// glslang pass per shader per link) is gone.
Vector<GLenum> shaderTypes(m_shaders.size());
// 1. Compile shaders
for (SizeT i = 0; i < m_shaders.size(); i++) {
auto shaderStage = m_shaders[i]->GetShaderStage();
auto shaderType = MG_Util::ConvertShaderStageToGLEnum(shaderStage);
String compileSource = m_shaders[i]->GetShaderSource();
PreprocessShaderSource(shaderStage, compileSource);
shaderTypes[i] = shaderType;
ShaderAttrib attrib{.shaderType = shaderType,
.sourceStr = compileSource,
.flags = 0}; // Will need patched glslang to work
MGLOG_D("ProgramObject %u: GenerateBinary - compiling shader[%zu] type %u", m_externalIndex, i, shaderType);
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
MGLOG_E("ProgramObject %u: GenerateBinary - CompileShader failed for shader[%zu], aborting "
"binary generation",
m_externalIndex, i);
MGLOG_E("ProgramObject %u: GenerateBinary - CompileShader return code %d, log:\n%s", m_externalIndex,
res.error().errc, res.error().log.c_str());
MGLOG_E("ProgramObject %u: GenerateBinary - last compiled shader src: \n%s", m_externalIndex,
compileSource.c_str());
}
MOBILEGL_ASSERT(res, "CompileShader failed during binary generation");
shaders[i] = res.value();
MGLOG_D("ProgramObject %u: GenerateBinary - compiled shader[%zu] -> TShader ptr %p", m_externalIndex, i,
shaders[i].get());
shaderTypes[i] = MG_Util::ConvertShaderStageToGLEnum(m_shaders[i]->GetShaderStage());
}
// 2. Do actual linking
ProgramAttrib attrib{.shaders = Move(shaders),
.explicitVertexInLocations = m_explicitAttribLocations,
.explicitFragmentOutLocations = m_explicitFragDataLocation,
.explicitFragmentOutIndices = m_explicitFragDataIndex,
.explicitOpaqueUniformBindings = &m_explicitOpaqueUniformBindings};
MGLOG_D("ProgramObject %u: GenerateBinary - linking program for binary", m_externalIndex);
auto programResult = ShaderCompiler::LinkProgram(attrib);
if (!programResult) {
MGLOG_E("ProgramObject %u: GenerateBinary - LinkProgram failed during binary generation", m_externalIndex);
}
MOBILEGL_ASSERT(programResult, "LinkProgram failed during binary generation");
auto& program = programResult.value();
MGLOG_D("ProgramObject %u: GenerateBinary - got linked program object", m_externalIndex);
ProgramBinaryAttrib binaryAttrib{
.shaderTypes = shaderTypes,
.program = *program,
.program = *m_program,
};
MGLOG_D("ProgramObject %u: GenerateBinary - requesting SPIR-V binary from program", m_externalIndex);
MGLOG_D("ProgramObject %u: GenerateSpirv - requesting SPIR-V binary from program", m_externalIndex);
auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
if (!binaryResult) {
MGLOG_E("ProgramObject %u: GenerateBinary - GetSpirvBinaryFromProgram failed", m_externalIndex);
MGLOG_E("ProgramObject %u: GenerateSpirv - GetSpirvBinaryFromProgram failed", m_externalIndex);
}
MOBILEGL_ASSERT(binaryResult, "GetSpirvBinaryFromProgram failed");
m_generatedSpirv = Move(binaryResult.value());
MGLOG_D("ProgramObject %u: GenerateBinary - generated %zu SPIR-V modules", m_externalIndex,
MGLOG_D("ProgramObject %u: GenerateSpirv - generated %zu SPIR-V modules", m_externalIndex,
m_generatedSpirv.size());
// 3. Linked SPIR-V generated, sanitize and optimize it
// Linked SPIR-V generated, sanitize and optimize it
for (auto& spv : m_generatedSpirv) {
auto success = ShaderCompiler::SanitizeAndOptimizeBinary(spv, spv);
MOBILEGL_ASSERT(success, "SanitizeBinary failed");
}
}
void ProgramObject::BuildGlobalUboRouting() {
using namespace MG_Util::ShaderTranspiler;
Vector<GLenum> shaderTypes(m_shaders.size());
for (SizeT i = 0; i < m_shaders.size(); i++) {
shaderTypes[i] = MG_Util::ConvertShaderStageToGLEnum(m_shaders[i]->GetShaderStage());
}
// 4. Do reflection (find global UBO etc.)
m_uniformSizesInBytes.clear();
m_uniformOffsets.clear();
m_globalUboScratch.clear();
@@ -969,13 +1139,13 @@ namespace MobileGL::MG_State::GLState {
auto& spv = m_generatedSpirv[i];
auto shaderType = shaderTypes[i];
MGLOG_D("ProgramObject %u: GenerateBinary - parsing SPIR-V meta data for module %zu "
MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - parsing SPIR-V meta data for module %zu "
"(shaderType=%u, wordCount=%zu)",
m_externalIndex, i, shaderType, spv.size());
SpvcSession session(spv, SessionUsageBit::Reflection);
auto result = session.ParseMetaData();
if (result < 0) {
MGLOG_D("ProgramObject %u: GenerateBinary - SpvcSession::ParseMetaData failed for module %zu, "
MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - SpvcSession::ParseMetaData failed for module %zu, "
"err = %d%s",
m_externalIndex, i, result,
(result == SPVC_ERROR_INVALID_SPIRV ? ". Probably no global UBO?" : ""));
@@ -983,7 +1153,7 @@ namespace MobileGL::MG_State::GLState {
} else {
auto& meta = session.GetMetadata();
auto size = meta.globalUboSize;
MGLOG_D("ProgramObject %u: GenerateBinary - SPIR-V meta: uboSize=%zu plainUniformCount=%zu "
MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - SPIR-V meta: uboSize=%zu plainUniformCount=%zu "
"plainUniformOffsets=%zu",
m_externalIndex, meta.globalUboSize, meta.plainUniformMemberSizesInBytes.size(),
meta.plainUniformOffsetsInUBO.size());
@@ -1002,7 +1172,7 @@ namespace MobileGL::MG_State::GLState {
locationIt = m_uniformLocations.find(name + "[0]");
}
if (locationIt == m_uniformLocations.end()) {
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u but not found in "
MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - uniform '%s' offset=%u but not found in "
"m_uniformLocations",
m_externalIndex, name.c_str(), offset);
continue;
@@ -1013,7 +1183,7 @@ namespace MobileGL::MG_State::GLState {
}
const Int uniformIndex = m_uniformIndexInTProgram[baseLocation];
const GLint arraySize = GetActiveUniformArraySize(uniformIndex);
const GLint arraySize = GetUniformArraySizeByTIndex(uniformIndex);
SizeT memberSize = 0;
const auto sizeIt = meta.plainUniformMemberSizesInBytes.find(name);
if (sizeIt != meta.plainUniformMemberSizesInBytes.end()) {
@@ -1037,12 +1207,12 @@ namespace MobileGL::MG_State::GLState {
const SizeT consumed = static_cast<SizeT>(element) * arrayStride;
m_uniformSizesInBytes[location] = memberSize > consumed ? memberSize - consumed : 0;
}
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u stride=%u size=%zu assigned "
MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - uniform '%s' offset=%u stride=%u size=%zu assigned "
"to locations %u..%u",
m_externalIndex, name.c_str(), offset, arrayStride, memberSize, baseLocation,
baseLocation + static_cast<Uint>(elementCount) - 1);
}
MGLOG_D("ProgramObject %u: GenerateBinary - finished parsing module %zu metadata",
MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - finished parsing module %zu metadata",
m_externalIndex, i);
}
}
@@ -1079,7 +1249,7 @@ namespace MobileGL::MG_State::GLState {
m_globalUboScratch.resize(slotOffset + slotSize, 0);
m_uniformOffsets[location] = static_cast<Uint>(slotOffset);
m_uniformSizesInBytes[location] = slotSize;
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' location %u has no UBO backing in the "
MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - uniform '%s' location %u has no UBO backing in the "
"generated SPIR-V (optimized out?); allocated %zu fallback bytes at scratch offset %zu",
m_externalIndex, uniform.name.c_str(), location, slotSize, slotOffset);
}
@@ -92,7 +92,7 @@ namespace MobileGL::MG_State::GLState {
// in-range elements.
const glslang::TType* type = m_program->getUniform(index).getType();
if (type == nullptr || !type->isArray()) return -1;
if (static_cast<GLint>(element) >= GetActiveUniformArraySize(index)) return -1;
if (static_cast<GLint>(element) >= GetUniformArraySizeByTIndex(index)) return -1;
const Int location = base + (Int)element;
if (!UniformLocationsAliasSameUniform(base, location)) return -1;
return location;
@@ -104,11 +104,30 @@ namespace MobileGL::MG_State::GLState {
return m_uniformIndexInTProgram[a] == m_uniformIndexInTProgram[b];
}
// ---- GL index <-> glslang TProgram index translation ----
// The single relaxed parse enumerates artifacts GL must not see: every declared
// default-block uniform (even dead ones) as a member of the synthesized
// MGL_GLOBAL_UBO, and that block itself. DoReflection builds filtered GL-facing
// index spaces; every public "index"-taking getter translates through them, so
// GL and backend consumers keep seeing exactly the pre-P0a surface.
Int TProgramUniformIndex(Uint glIndex) const {
return m_glUniformIndexToTProgram[glIndex];
}
Int GlUniformIndexFromTProgram(Int tIndex) const {
if (tIndex < 0 || tIndex >= static_cast<Int>(m_tProgramUniformIndexToGl.size())) return -1;
return m_tProgramUniformIndexToGl[tIndex];
}
Int GlBlockIndexFromTProgram(Int tBlockIndex) const {
if (tBlockIndex < 0 || tBlockIndex >= static_cast<Int>(m_tProgramBlockIndexToGl.size())) return -1;
return m_tProgramBlockIndexToGl[tBlockIndex];
}
Int GetActiveUniformIndex(const String& name) const {
const Int tProgramCount = static_cast<Int>(m_tProgramUniformIndexToGl.size());
const Int uniformIndex = m_program->getUniformIndex(name.c_str());
if (uniformIndex >= 0 && uniformIndex < m_activeUniformCount &&
if (uniformIndex >= 0 && uniformIndex < tProgramCount &&
m_program->getUniform(uniformIndex).name == name) {
return uniformIndex;
return GlUniformIndexFromTProgram(uniformIndex);
}
// Reflection stores an array uniform under "arr[0]"; accept the bare "arr"
@@ -117,9 +136,9 @@ namespace MobileGL::MG_State::GLState {
if (!name.empty() && name.back() != ']') {
const String suffixedName = name + "[0]";
const Int suffixedIndex = m_program->getUniformIndex(suffixedName.c_str());
if (suffixedIndex >= 0 && suffixedIndex < m_activeUniformCount &&
if (suffixedIndex >= 0 && suffixedIndex < tProgramCount &&
m_program->getUniform(suffixedIndex).name == suffixedName) {
return suffixedIndex;
return GlUniformIndexFromTProgram(suffixedIndex);
}
return -1;
}
@@ -127,8 +146,9 @@ namespace MobileGL::MG_State::GLState {
if (name.length() <= 3 || name.compare(name.length() - 3, 3, "[0]") != 0) return -1;
const String baseName = name.substr(0, name.length() - 3);
const Int baseIndex = m_program->getUniformIndex(baseName.c_str());
if (baseIndex < 0 || baseIndex >= m_activeUniformCount) return -1;
return m_program->getUniform(baseIndex).name == baseName ? baseIndex : -1;
if (baseIndex < 0 || baseIndex >= tProgramCount) return -1;
return m_program->getUniform(baseIndex).name == baseName ? GlUniformIndexFromTProgram(baseIndex)
: -1;
}
Bool IsValidUniformLocation(Int location) const {
@@ -136,7 +156,8 @@ namespace MobileGL::MG_State::GLState {
if (static_cast<SizeT>(location) >= m_uniformIndexInTProgram.size()) return false;
const Int uniformIndexInProgram = m_uniformIndexInTProgram[location];
return uniformIndexInProgram != glslang::TQualifier::layoutLocationEnd &&
uniformIndexInProgram >= 0 && uniformIndexInProgram < m_activeUniformCount;
uniformIndexInProgram >= 0 &&
uniformIndexInProgram < static_cast<Int>(m_tProgramUniformIndexToGl.size());
}
GLenum GetUniformType(Uint location) const {
@@ -145,16 +166,17 @@ namespace MobileGL::MG_State::GLState {
}
GLenum GetActiveUniformType(Uint index) const {
auto& uniform = m_program->getUniform(static_cast<Int>(index));
auto& uniform = m_program->getUniform(TProgramUniformIndex(index));
return uniform.glDefineType;
}
// Number of active array elements (GL_UNIFORM_SIZE / GL_ARRAY_SIZE); 1 for a non-array.
// glslang's TObjectReflection.size only carries the element count for a NON-block array; for
// a block array member it reports 1, so take the count from the TType, which is authoritative
// for both. GL 3.3 core uniforms are always sized.
GLint GetActiveUniformArraySize(Uint index) const {
const auto& uniform = m_program->getUniform(static_cast<Int>(index));
// for both. GL 3.3 core uniforms are always sized. Takes a TProgram uniform index (the space
// m_uniformIndexInTProgram stores).
GLint GetUniformArraySizeByTIndex(Int tIndex) const {
const auto& uniform = m_program->getUniform(tIndex);
const glslang::TType* type = uniform.getType();
if (type != nullptr && type->isSizedArray()) {
return type->getOuterArraySize();
@@ -162,15 +184,23 @@ namespace MobileGL::MG_State::GLState {
return uniform.size < 1 ? 1 : uniform.size;
}
Int GetActiveUniformBlockIndex(Uint index) const {
auto& uniform = m_program->getUniform(static_cast<Int>(index));
return uniform.index;
GLint GetActiveUniformArraySize(Uint index) const {
return GetUniformArraySizeByTIndex(TProgramUniformIndex(index));
}
// GL_UNIFORM_OFFSET: byte offset within the owning named block. glslang already reports -1
// for a default-block uniform, which is exactly the spec value there.
Int GetActiveUniformBlockIndex(Uint index) const {
auto& uniform = m_program->getUniform(TProgramUniformIndex(index));
// Members of the synthesized global UBO are default-block uniforms to GL: -1.
return GlBlockIndexFromTProgram(uniform.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 {
return m_program->getUniform(static_cast<Int>(index)).offset;
const auto& uniform = m_program->getUniform(TProgramUniformIndex(index));
if (GlBlockIndexFromTProgram(uniform.index) < 0) return -1;
return uniform.offset;
}
// GL_UNIFORM_ARRAY_STRIDE: byte stride of an array member in a named block; 0 for a non-array
@@ -182,8 +212,8 @@ 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 = m_program->getUniform(static_cast<Int>(index));
if (uniform.index < 0) return -1;
const auto& uniform = m_program->getUniform(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()) {
@@ -202,8 +232,8 @@ 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 = m_program->getUniform(static_cast<Int>(index));
if (uniform.index < 0) return 0;
const auto& uniform = m_program->getUniform(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;
@@ -220,8 +250,8 @@ 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 = m_program->getUniform(static_cast<Int>(index));
if (uniform.index < 0) return -1;
const auto& uniform = m_program->getUniform(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;
@@ -250,7 +280,7 @@ namespace MobileGL::MG_State::GLState {
}
const String& GetActiveUniformName(Uint index) const {
auto& uniform = m_program->getUniform(static_cast<Int>(index));
auto& uniform = m_program->getUniform(TProgramUniformIndex(index));
return uniform.name;
}
// Sentinel for a uniform location without global-UBO backing storage (should not
@@ -321,7 +351,19 @@ namespace MobileGL::MG_State::GLState {
const String& GetAttribName(Uint index) const { return m_attribs[index]; }
GLenum GetActiveAttribType(Uint index) const { return m_program->getPipeInput(static_cast<Int>(index)).glDefineType; }
GLint GetActiveAttribArraySize(Uint index) const { return m_program->getPipeInput(static_cast<Int>(index)).size; }
const String& GetActiveAttribName(Uint index) const { return m_program->getPipeInput(static_cast<Int>(index)).name; }
// 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).
static const String& NormalizeBuiltinPipeInputName(const String& name) {
static const String kGlVertexId = "gl_VertexID";
static const String kGlInstanceId = "gl_InstanceID";
if (name == "gl_VertexIndex") return kGlVertexId;
if (name == "gl_InstanceIndex") return kGlInstanceId;
return name;
}
const String& GetActiveAttribName(Uint index) const {
return NormalizeBuiltinPipeInputName(m_program->getPipeInput(static_cast<Int>(index)).name);
}
void* MapUBO() { return m_globalUboScratch.data(); }
const void* GetUBOData() const { return m_globalUboScratch.data(); }
Uint GetUBOSize() const { return static_cast<Uint>(m_globalUboScratch.size()); }
@@ -403,7 +445,9 @@ namespace MobileGL::MG_State::GLState {
Bool GetValidateStatus() const { return m_validateStatus; }
Int GetActiveAtomicCounterCount() const { return m_program->getNumAtomicCounters(); }
Int GetActiveAttributesCount() const { return m_program->getNumPipeInputs(); }
Int GetActiveUniformBlocksCount() const { return m_program->getNumUniformBlocks(); }
// 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>(m_glBlockIndexToTProgram.size()); }
GLuint GetComputeLocalSize(Uint dim) const { return m_program->getLocalSize(static_cast<Int>(dim)); }
Int GetActiveAttributesMaxLength() const { return m_attribInNameMaxLength; }
Int GetActiveUniformBlocksMaxNameLength() const { return m_uniformBlockNameMaxLength; }
@@ -427,11 +471,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 (m_program->getUniformBlock((Int)index).size + 15u) & ~15u;
return (m_program->getUniformBlock(m_glBlockIndexToTProgram[index]).size + 15u) & ~15u;
}
const String& GetUniformBlockName(Uint index) const {
auto& ubo = m_program->getUniformBlock((Int)index);
auto& ubo = m_program->getUniformBlock(m_glBlockIndexToTProgram[index]);
return ubo.name;
}
@@ -462,7 +506,7 @@ namespace MobileGL::MG_State::GLState {
}
Bool IsUniformBlockReferencedByStage(Uint index, EShLanguage stage) const {
const auto& ubo = m_program->getUniformBlock((Int)index);
const auto& ubo = m_program->getUniformBlock(m_glBlockIndexToTProgram[index]);
const auto stageMask = static_cast<EShLanguageMask>(1 << stage);
return (ubo.stages & stageMask) != 0;
}
@@ -546,13 +590,23 @@ namespace MobileGL::MG_State::GLState {
private:
void ResetLinkArtifacts();
void DoReflection();
// Builds the GL-facing reflection surface from the linked TProgram. Returns false
// (with m_infoLog set and link artifacts reset) when reflection itself fails or an
// explicit-uniform-location conflict makes the link invalid.
Bool DoReflection();
// Resolves the requested transform feedback varyings against the linked
// vertex stage; fails the link (GL semantics) on unknown or duplicate
// names or exceeded capture limits.
Bool ResolveTransformFeedbackVaryings();
void ResolveGsTriangleStripCapture(const glslang::TIntermediate* captureIntermediate);
void GenerateBinary();
// The former GenerateBinary, split around DoReflection's data dependencies:
// SPIR-V must be generated BEFORE buildReflection touches m_program (its
// live-variable analysis mutates the intermediates enough to change
// GlslangToSpv output), while the glUniform*-to-global-UBO routing tables are
// sized and keyed by reflection results (m_maxUniformLocation,
// m_uniformLocations) and so must run AFTER it.
void GenerateSpirv();
void BuildGlobalUboRouting();
void WaitUntilGenerationCompleted() const;
void AddDefaultFragmentShaderIfMissing();
Bool ValidateFragmentOutputLocations();
@@ -583,6 +637,18 @@ namespace MobileGL::MG_State::GLState {
Int m_maxFragmentOutputColorNumber = 8;
// Uniforms
// GL-facing index spaces (see the translation helpers above): GL active-uniform
// index <-> glslang TProgram uniform index, GL uniform-block index <-> TProgram
// block index. -1 marks a TProgram entry GL does not expose (dead default-block
// uniforms swept into MGL_GLOBAL_UBO by the relaxed parse, and that block itself).
Vector<Int> m_glUniformIndexToTProgram;
Vector<Int> m_tProgramUniformIndexToGl;
Vector<Int> m_glBlockIndexToTProgram;
Vector<Int> m_tProgramBlockIndexToGl;
// Per-link merged snapshot of the attached shaders' lexically extracted
// layout(location = N) default-block uniform qualifiers (the relaxed parse drops
// them from reflection; the DoReflection assigner restores them from here).
UnorderedMap<String, Int> m_linkedExplicitUniformLocations;
UnorderedMap<String, Uint> m_uniformLocations;
// Ordered by location,
// aka. m_uniformIndexInTProgram[loc] == "uniform index of TProgram at location `loc`"
@@ -142,28 +142,33 @@ namespace {
namespace MobileGL::MG_State::GLState {
void ShaderObject::SetShaderSource(const String& source) {
m_source = source;
m_shader.reset();
m_compileStatus = false;
m_infoLog.clear();
InvalidateCompiledState();
}
void ShaderObject::SetShaderSource(String&& source) {
m_source = Move(source);
InvalidateCompiledState();
}
void ShaderObject::InvalidateCompiledState() {
m_shader.reset();
m_preprocessedSource.clear();
m_explicitUniformLocations.clear();
m_explicitOpaqueBindings.clear();
m_shaderConsumedByLink = false;
m_compileStatus = false;
m_infoLog.clear();
}
void ShaderObject::Compile() {
using namespace MG_Util::ShaderTranspiler;
InvalidateCompiledState();
String compileSource = m_source;
MG_Util::ShaderTranspiler::PreprocessShaderSource(m_stage, compileSource);
if (m_stage == ShaderStage::Compute) {
const std::optional<String> localSizeError = ValidateComputeLocalSizeLimits(compileSource);
if (localSizeError) {
m_compileStatus = false;
m_shader.reset();
m_infoLog = *localSizeError;
return;
}
@@ -172,27 +177,32 @@ namespace MobileGL::MG_State::GLState {
const std::optional<String> reservedError =
MG_Util::ShaderTranspiler::FindReservedIdentifierViolation(compileSource);
if (reservedError) {
m_compileStatus = false;
m_shader.reset();
m_infoLog = *reservedError;
return;
}
// Compile for OpenGL here, so that we can do validation and link
// like a real OpenGL driver at linking stage
// Will compile for other backends later.
// Single parse, in the link-compatible configuration (Vulkan-client env with
// relaxed rules): the TShader stored here is what glLinkProgram links and what
// the backends' SPIR-V is generated from - there is no second, GL-client parse
// anymore. The GL frontend semantics the relaxed parse cannot provide are
// restored on top: explicit default-block uniform locations through the lexical
// side-channel below, dead-uniform/global-UBO filtering in
// ProgramObject::DoReflection.
m_explicitUniformLocations = ExtractExplicitUniformLocations(compileSource);
m_explicitOpaqueBindings = ExtractExplicitOpaqueBindings(compileSource);
ShaderAttrib attrib{.shaderType = MG_Util::ConvertShaderStageToGLEnum(m_stage),
.sourceStr = compileSource,
.flags = ShaderCompileBits::CompileForOpenGL};
.flags = 0};
auto result = ShaderCompiler::CompileShader(attrib);
if (result) {
m_compileStatus = true;
m_shader = result.value();
m_preprocessedSource = Move(compileSource);
m_infoLog.clear();
} else {
m_compileStatus = false;
m_shader.reset();
m_explicitUniformLocations.clear();
m_explicitOpaqueBindings.clear();
m_infoLog = result.error().log;
MGLOG_D("ShaderObject::Compile: Shader %d compilation failed.\nSource:\n%s\nInfoLog:\n%s\nSetting "
"m_compileStatus = false as a result.",
@@ -200,6 +210,31 @@ namespace MobileGL::MG_State::GLState {
}
}
SharedPtr<glslang::TShader> ShaderObject::TakeShaderForLink(String& outReparseLog) {
if (m_shader && !m_shaderConsumedByLink) {
m_shaderConsumedByLink = true;
return m_shader;
}
// The stored parse already fed a link, whose mapIO mutated its intermediate.
// Re-parse the preprocessed source through the identical configuration; this
// costs one glslang parse, which is exactly what GenerateBinary used to spend
// here on EVERY link rather than only on reuse.
using namespace MG_Util::ShaderTranspiler;
ShaderAttrib attrib{.shaderType = MG_Util::ConvertShaderStageToGLEnum(m_stage),
.sourceStr = m_preprocessedSource,
.flags = 0};
auto result = ShaderCompiler::CompileShader(attrib);
if (!result) {
// Should be unreachable: the same source parsed successfully at Compile().
outReparseLog = result.error().log;
MGLOG_E("ShaderObject::TakeShaderForLink: re-parse of shader %d failed:\n%s", m_externalIndex,
outReparseLog.c_str());
return nullptr;
}
return result.value();
}
void ShaderObject::MarkAsDeleted() {
m_deleteStatus = true;
}
@@ -31,21 +31,49 @@ namespace MobileGL {
void Compile();
void MarkAsDeleted();
// Hands out a link-consumable TShader. glslang's mapIO mutates the TShader's
// aliased intermediate, so the parse stored by Compile() may feed exactly one
// link; every later link (relink, or the same shader attached to a second
// program) gets a fresh parse of the stored preprocessed source through the
// byte-identical CompileShader path (including the legacy-460 retry). Only
// callable while GetCompileStatus() is true. Returns null only if that
// re-parse fails - outReparseLog then carries its diagnostics.
SharedPtr<glslang::TShader> TakeShaderForLink(String& outReparseLog);
Uint GetExternalIndex() const { return m_externalIndex; }
ShaderStage GetShaderStage() const { return m_stage; }
const String& GetShaderSource() const { return m_source; }
const SharedPtr<glslang::TShader>& GetCompiledShader() const { return m_shader; }
const String& GetInfoLog() const { return m_infoLog; }
const UnorderedMap<String, Uint>& GetUniformLocations() const { return m_uniforms; }
// Explicit layout(location = N) qualifiers on this shader's default-block
// uniforms, captured lexically at Compile() because the relaxed parse drops
// them from reflection (see ExtractExplicitUniformLocations).
const UnorderedMap<String, Int>& GetExplicitUniformLocations() const {
return m_explicitUniformLocations;
}
// Explicit layout(binding = N) on sampler/image uniforms - their initial
// texture/image units - captured lexically for the same reason (see
// ExtractExplicitOpaqueBindings).
const UnorderedMap<String, Uint>& GetExplicitOpaqueBindings() const { return m_explicitOpaqueBindings; }
Bool GetCompileStatus() const { return m_compileStatus; }
Bool GetDeleteStatus() const { return m_deleteStatus; }
private:
void InvalidateCompiledState();
const Uint m_externalIndex = 0;
const ShaderStage m_stage;
String m_source;
// The source Compile() actually parsed (after PreprocessShaderSource), kept
// for TakeShaderForLink's re-parse so a later link never depends on the
// preprocessor being deterministic across backend-state changes.
String m_preprocessedSource;
SharedPtr<glslang::TShader> m_shader;
UnorderedMap<String, Uint> m_uniforms;
UnorderedMap<String, Int> m_explicitUniformLocations;
UnorderedMap<String, Uint> m_explicitOpaqueBindings;
Bool m_shaderConsumedByLink = false;
String m_infoLog;
Bool m_deleteStatus = false;