mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user