mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix, Test] (MG_State): a pipeline draw uses the block bindings its stage programs were given, not the ones their shaders declared
This commit is contained in:
@@ -458,9 +458,46 @@ namespace MobileGL::MG_State {
|
||||
}
|
||||
}
|
||||
|
||||
// Brings the pipeline's composite up to date with the uniform values its stage programs
|
||||
// now hold. Runs on every draw through a pipeline, so the common case is the version
|
||||
// compare below and nothing else.
|
||||
// The other half of "the composite is a different program object": interface BLOCK
|
||||
// bindings. glUniformBlockBinding and glShaderStorageBlockBinding place a block on a
|
||||
// binding point, and they do it per program - so a pipeline whose blocks were placed
|
||||
// that way drew against the composite's own bindings, which come from the shader
|
||||
// declarations alone. A block declared without any layout(binding) therefore sat on
|
||||
// whatever the declaration implied while the application's buffers sat somewhere else,
|
||||
// and nothing anywhere raised an error: the draw simply read or wrote the wrong place.
|
||||
//
|
||||
// Both sides seed these from the same shader declarations at link, so mirroring a block
|
||||
// the application never rebound writes back the value the destination already holds and
|
||||
// the setters' equality checks make it free.
|
||||
static void MirrorBlockBindings(const ProgramObject& source, ProgramObject& destination) {
|
||||
// Storage blocks are keyed by GL name on both sides - the one coordinate the
|
||||
// frontend, SPIR-V and driver index spaces all agree on - so this is a direct
|
||||
// replay. Empty for the overwhelming majority of programs.
|
||||
for (const auto& [blockName, binding] : source.GetShaderStorageBlockBindingOverrides()) {
|
||||
if (binding < 0) continue;
|
||||
destination.SetShaderStorageBlockBinding(blockName, static_cast<Uint>(binding));
|
||||
}
|
||||
|
||||
// Uniform blocks are keyed by index, and the two programs number them
|
||||
// independently, so they are matched by name.
|
||||
const Int sourceBlockCount = source.GetActiveUniformBlocksCount();
|
||||
for (Int sourceIndex = 0; sourceIndex < sourceBlockCount; ++sourceIndex) {
|
||||
const Int binding = static_cast<Int>(source.GetUniformBlockBinding(static_cast<Uint>(sourceIndex)));
|
||||
// -1 is "no declared binding and never rebound" - there is nothing to carry,
|
||||
// and forwarding it would land as binding 0xFFFFFFFF.
|
||||
if (binding < 0) continue;
|
||||
const String& blockName = source.GetUniformBlockName(static_cast<Uint>(sourceIndex));
|
||||
if (blockName.empty()) continue;
|
||||
const Uint destinationIndex = destination.GetUniformBlockIndex(blockName.c_str());
|
||||
if (destinationIndex == 0xFFFFFFFFu) continue; // GL_INVALID_INDEX
|
||||
destination.SetUniformBlockBinding(destinationIndex, static_cast<Uint>(binding));
|
||||
}
|
||||
}
|
||||
|
||||
// Brings the pipeline's composite up to date with the per-program state its stage
|
||||
// programs hold and it does not: uniform values, and interface block bindings. Runs on
|
||||
// every draw through a pipeline, so the common case is the version compare below and
|
||||
// nothing else.
|
||||
static void RefreshCompositeUniforms(ProgramPipelineObject& pipeline, const SharedPtr<ProgramObject>& composite) {
|
||||
if (!composite) return;
|
||||
const auto versions = pipeline.ComputeUniformMirrorVersions();
|
||||
@@ -483,6 +520,7 @@ namespace MobileGL::MG_State {
|
||||
if (alreadyMirrored) continue;
|
||||
mirrored[mirroredCount++] = stageProgram.get();
|
||||
MirrorUniformValues(*stageProgram, *composite);
|
||||
MirrorBlockBindings(*stageProgram, *composite);
|
||||
}
|
||||
pipeline.SetMirroredUniformVersions(versions);
|
||||
}
|
||||
|
||||
@@ -613,13 +613,23 @@ namespace MobileGL::MG_State::GLState {
|
||||
return (ubo.stages & stageMask) != 0;
|
||||
}
|
||||
|
||||
// Set by glUniformBlockBinding
|
||||
// Bumped by both block-binding setters below. A program pipeline's flattened composite
|
||||
// is a different program object from the stage programs the application rebinds blocks
|
||||
// on, so it has to be told - and this is what tells it something is worth re-reading.
|
||||
// Separate from m_backendStateVersion because the storage-block setter deliberately
|
||||
// does not disturb that one (see SetShaderStorageBlockBinding).
|
||||
Uint32 GetBlockBindingVersion() const { return m_blockBindingVersion; }
|
||||
|
||||
// Set by glUniformBlockBinding. The vector is seeded at link with each block's DECLARED
|
||||
// binding (layout(binding=N), else -1), so an untouched program already reports what its
|
||||
// shaders asked for.
|
||||
void SetUniformBlockBinding(Uint index, Uint binding) {
|
||||
if (index >= Artifacts().uniformBlockBinding.size() || Artifacts().uniformBlockBinding[index] == static_cast<Int>(binding)) {
|
||||
return;
|
||||
}
|
||||
Artifacts().uniformBlockBinding[index] = static_cast<Int>(binding);
|
||||
++m_backendStateVersion;
|
||||
++m_blockBindingVersion;
|
||||
}
|
||||
|
||||
Uint GetUniformBlockBinding(Uint index) const { return Artifacts().uniformBlockBinding[index]; }
|
||||
@@ -631,6 +641,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
// means "never rebound", and the shader's declared binding still stands.
|
||||
void SetShaderStorageBlockBinding(const String& blockName, Uint binding) {
|
||||
Artifacts().shaderStorageBlockBinding[blockName] = static_cast<Int>(binding);
|
||||
// Deliberately NOT m_backendStateVersion: Espryt's entry point never forces a
|
||||
// program build off this, and bumping that version would start doing so. The
|
||||
// dedicated counter carries the news to the pipeline composite instead.
|
||||
++m_blockBindingVersion;
|
||||
}
|
||||
// -1 when the block has never been rebound. `blockName` is the interface-query
|
||||
// spelling; an arrayed block's elements ("B[0]", "B[1]") are separate GL resources
|
||||
@@ -1059,6 +1073,8 @@ namespace MobileGL::MG_State::GLState {
|
||||
// READ-side operation (the first gated getter is what pulls the result in), and the
|
||||
// publish has to bump these. Still GL-thread-only - a worker never touches them.
|
||||
mutable Uint32 m_backendStateVersion = 0;
|
||||
// Interface-block binding generation; see GetBlockBindingVersion.
|
||||
Uint32 m_blockBindingVersion = 0;
|
||||
|
||||
// Backend-owned content-hash memo (see GetBackendHashMemo): valid only while
|
||||
// m_backendStateVersion matches. Several slots, not one: a backend may resolve the same
|
||||
|
||||
@@ -94,24 +94,27 @@ namespace MobileGL {
|
||||
return signature;
|
||||
}
|
||||
|
||||
// Uniform values are written to the STAGE programs - glUniform* addresses the
|
||||
// pipeline's active program (GL 4.6 core 7.6.1) and glProgramUniform* addresses
|
||||
// a named one - while the draw reads the composite. Two different objects'
|
||||
// storage, so the composite is refreshed from its stage programs before each
|
||||
// draw that needs it. These are the per-stage versions "needs it" is measured
|
||||
// against: the stage program's uniform-shadow content version in the low half
|
||||
// and its backend state version (which the opaque/sampler-unit writes bump) in
|
||||
// the high half. All zero after a rebuild, because a fresh composite starts at
|
||||
// GL's zero defaults and so needs a full refresh.
|
||||
using UniformMirrorVersions = Array<Uint64, kGraphicsStageCount>;
|
||||
// Per-program state is written to the STAGE programs - glUniform* addresses the
|
||||
// pipeline's active program (GL 4.6 core 7.6.1), glProgramUniform* addresses a
|
||||
// named one, and the two block-binding calls address a named one - while the
|
||||
// draw reads the composite. Two different objects' state, so the composite is
|
||||
// refreshed from its stage programs before each draw that needs it. These are
|
||||
// the per-stage versions "needs it" is measured against. All zero after a
|
||||
// rebuild, because a fresh composite holds only what its shaders declared and
|
||||
// so needs a full refresh.
|
||||
using UniformMirrorVersions = Array<Uint64, kGraphicsStageCount * 2>;
|
||||
|
||||
UniformMirrorVersions ComputeUniformMirrorVersions() const {
|
||||
UniformMirrorVersions versions{};
|
||||
for (SizeT stage = 0; stage < kGraphicsStageCount; ++stage) {
|
||||
const auto& program = m_stagePrograms[stage];
|
||||
if (!program) continue;
|
||||
versions[stage] = (static_cast<Uint64>(program->GetBackendStateVersion()) << 32) |
|
||||
static_cast<Uint64>(program->GetUBOContentVersion());
|
||||
versions[stage * 2] = (static_cast<Uint64>(program->GetBackendStateVersion()) << 32) |
|
||||
static_cast<Uint64>(program->GetUBOContentVersion());
|
||||
// Its own slot rather than folded into the pair above: the storage-block
|
||||
// setter moves this and NOTHING else, so a rebinding would otherwise be
|
||||
// invisible to the refresh gate.
|
||||
versions[stage * 2 + 1] = program->GetBlockBindingVersion();
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user