mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix, Test] (MG_State, MG_Impl, MG_Backend): a program pipeline's compute stage is dispatched on its own, and the graphics composite draws its stage programs' uniform values
This commit is contained in:
@@ -3010,8 +3010,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
// Single per-dispatch program resolve and texture-key capture, as in
|
// Single per-dispatch program resolve and texture-key capture, as in
|
||||||
// PrepareForDraw (nothing below can move either).
|
// PrepareForDraw (nothing below can move either). The DISPATCH accessor: with a
|
||||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
// pipeline bound this is its compute stage program, which is a whole program on its
|
||||||
|
// own - the graphics composite a draw builds carries no compute stage.
|
||||||
|
const auto& currentProgram = MG_State::pGLContext->GetProgramForDispatch();
|
||||||
const TextureImpl::DrawTextureSyncKeys textureKeys = TextureImpl::CaptureDrawTextureSyncKeys();
|
const TextureImpl::DrawTextureSyncKeys textureKeys = TextureImpl::CaptureDrawTextureSyncKeys();
|
||||||
|
|
||||||
BufferImpl::SyncComputeBuffers(includeDispatchIndirectBuffer);
|
BufferImpl::SyncComputeBuffers(includeDispatchIndirectBuffer);
|
||||||
|
|||||||
@@ -4582,7 +4582,6 @@ void main() {
|
|||||||
MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages");
|
MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages");
|
||||||
return VK_NULL_HANDLE;
|
return VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast path: skip the full pipeline resolution when the pipeline state is unchanged from the
|
// Fast path: skip the full pipeline resolution when the pipeline state is unchanged from the
|
||||||
// previous draw (the common intra-batch case). The key provably covers every
|
// previous draw (the common intra-batch case). The key provably covers every
|
||||||
// PipelineCreatePayload field: draw mode (topology + polygon-fill depth-bias gate), program
|
// PipelineCreatePayload field: draw mode (topology + polygon-fill depth-bias gate), program
|
||||||
@@ -6144,7 +6143,9 @@ void main() {
|
|||||||
void VulkanRenderer::DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
|
void VulkanRenderer::DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
|
||||||
m_textureManager->CollectGarbage();
|
m_textureManager->CollectGarbage();
|
||||||
auto& frame = m_frameContext.GetCurrent();
|
auto& frame = m_frameContext.GetCurrent();
|
||||||
const auto& program = *MG_State::pGLContext->GetProgramForDraw();
|
// The DISPATCH accessor: with a pipeline bound this is its compute stage program
|
||||||
|
// itself, never the graphics composite (which carries no compute stage at all).
|
||||||
|
const auto& program = *MG_State::pGLContext->GetProgramForDispatch();
|
||||||
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) {
|
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) {
|
||||||
MGLOG_E("DispatchCompute skipped: program=%u has no optimized SPIR-V",
|
MGLOG_E("DispatchCompute skipped: program=%u has no optimized SPIR-V",
|
||||||
program.GetExternalIndex());
|
program.GetExternalIndex());
|
||||||
@@ -6189,7 +6190,8 @@ void main() {
|
|||||||
void VulkanRenderer::DispatchComputeIndirect(GLintptr indirect) {
|
void VulkanRenderer::DispatchComputeIndirect(GLintptr indirect) {
|
||||||
m_textureManager->CollectGarbage();
|
m_textureManager->CollectGarbage();
|
||||||
auto& frame = m_frameContext.GetCurrent();
|
auto& frame = m_frameContext.GetCurrent();
|
||||||
const auto& program = *MG_State::pGLContext->GetProgramForDraw();
|
// See DispatchCompute: the dispatch accessor, not the draw one.
|
||||||
|
const auto& program = *MG_State::pGLContext->GetProgramForDispatch();
|
||||||
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) {
|
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) {
|
||||||
MGLOG_E("DispatchComputeIndirect skipped: program=%u has no optimized SPIR-V",
|
MGLOG_E("DispatchComputeIndirect skipped: program=%u has no optimized SPIR-V",
|
||||||
program.GetExternalIndex());
|
program.GetExternalIndex());
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
#include "../Getter/GL_Getter.h"
|
#include "../Getter/GL_Getter.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
|
static Bool ValidateProgramForExecution(const SharedPtr<MG_State::GLState::ProgramObject>& currentProgram,
|
||||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
const char* functionName) {
|
||||||
if (!currentProgram) {
|
if (!currentProgram) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
@@ -34,10 +34,17 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
|
||||||
|
return ValidateProgramForExecution(MG_State::pGLContext->GetProgramForDraw(), functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A dispatch resolves its program through the DISPATCH accessor: with a pipeline bound
|
||||||
|
// that is the pipeline's compute stage program, not the graphics composite a draw would
|
||||||
|
// build - which no longer contains a compute stage to find at all.
|
||||||
static Bool ValidateCurrentProgramForCompute(const char* functionName) {
|
static Bool ValidateCurrentProgramForCompute(const char* functionName) {
|
||||||
if (!ValidateCurrentProgramForExecution(functionName)) return false;
|
const auto& currentProgram = MG_State::pGLContext->GetProgramForDispatch();
|
||||||
|
if (!ValidateProgramForExecution(currentProgram, functionName)) return false;
|
||||||
|
|
||||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
|
||||||
if (currentProgram->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
if (currentProgram->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
|
|||||||
@@ -863,12 +863,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bytes a uniform actually occupies in the global UBO. It is the tight GL type size for
|
// Bytes a uniform actually occupies in the global UBO. It is the tight GL type size for
|
||||||
// everything except a float matrix, whose padded columns make it wider.
|
// everything except a float matrix, whose padded columns make it wider. The rule itself
|
||||||
|
// lives on ProgramObject, because the pipeline composite's uniform refresh needs the same
|
||||||
|
// one and two copies of a layout rule is one too many.
|
||||||
SizeT UniformStorageSpanInBytes(const glslang::TType* ttype, SizeT tightSize) {
|
SizeT UniformStorageSpanInBytes(const glslang::TType* ttype, SizeT tightSize) {
|
||||||
if (ttype != nullptr && ttype->isMatrix() && ttype->getBasicType() != glslang::EbtDouble) {
|
return MG_State::GLState::ProgramObject::UniformStorageSpanInBytes(ttype, tightSize);
|
||||||
return static_cast<SizeT>(ttype->getMatrixCols()) * 4 * sizeof(GLfloat);
|
|
||||||
}
|
|
||||||
return tightSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetUniform_State(GLuint program, GLint location, void* params) {
|
void GetUniform_State(GLuint program, GLint location, void* params) {
|
||||||
|
|||||||
@@ -176,17 +176,15 @@ void main() { gl_Position = i_position; }
|
|||||||
gl.EndFrame();
|
gl.EndFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
// glActiveShaderProgram picks which stage program glUniform* addresses.
|
// glActiveShaderProgram picks which stage program glUniform* addresses - and the draw has to
|
||||||
|
// see what was written there.
|
||||||
//
|
//
|
||||||
// DISABLED: a second, independent defect, left failing on purpose rather than deleted. The
|
// The second defect of the cluster, and the one the pixels expose most directly: uniform
|
||||||
// materialization fix above got the stages recorded and the pipeline drawing, but a uniform
|
// values live on the stage program (GetProgramForUniform returns the pipeline's active
|
||||||
// set through the active shader program does not reach the flattened composite: the draw
|
// program) while the draw reads the composite GetProgramForDraw builds out of the stage
|
||||||
// paints u_color's default rather than the value written. GetProgramForUniform() returns the
|
// programs' shaders. Two objects, two sets of uniform storage; before the composite was
|
||||||
// pipeline's active program, while GetProgramForDraw() builds a SEPARATE composite object out
|
// refreshed from its stage programs this painted u_color's zero default instead of green.
|
||||||
// of the stage programs' shaders - so uniform values live on one object and the draw reads
|
TEST_F(ProgramPipelineScenario, UniformsGoToTheActiveShaderProgram) {
|
||||||
// another. Enable this the moment the composite inherits (or aliases) its stage programs'
|
|
||||||
// uniform storage.
|
|
||||||
TEST_F(ProgramPipelineScenario, DISABLED_UniformsGoToTheActiveShaderProgram) {
|
|
||||||
if (!Ready()) return;
|
if (!Ready()) return;
|
||||||
|
|
||||||
static const char* kUniformFS = R"(#version 430 core
|
static const char* kUniformFS = R"(#version 430 core
|
||||||
@@ -236,14 +234,15 @@ void main() { o_color = u_color; }
|
|||||||
// The sso-compute-pipeline shape: compute and non-compute stages on ONE pipeline object, the
|
// The sso-compute-pipeline shape: compute and non-compute stages on ONE pipeline object, the
|
||||||
// compute stage writing the buffer the vertex stage then reads.
|
// compute stage writing the buffer the vertex stage then reads.
|
||||||
//
|
//
|
||||||
// DISABLED: the third defect in this cluster. Attaching a compute stage alongside graphics
|
// The third defect of the cluster: the flattening used to pull EVERY stage into one
|
||||||
// stages is now accepted, but the dispatch/draw pair still paints nothing and leaves an error
|
// composite, so a single program was asked to serve both glDispatchCompute and glDrawArrays.
|
||||||
// behind - GetProgramForDraw flattens EVERY stage of the pipeline into one composite, so the
|
// GL keeps them apart - a pipeline's compute stage is a whole program dispatched on its own
|
||||||
// compute stage and the graphics stages end up in a single program that can serve neither
|
// and never participates in a draw - which is why the accessors are split (GetProgramForDraw
|
||||||
// glDispatchCompute nor glDrawArrays correctly. GL keeps them separate: a pipeline's compute
|
// composites the graphics stages, GetProgramForDispatch hands back the compute stage
|
||||||
// stage is dispatched on its own and never participates in a draw. Enable this when the
|
// program). It is also the shape that killed the process on Adreno: the composite carried a
|
||||||
// flattening splits the compute stage out from the graphics ones.
|
// compute module into vkCreateGraphicsPipelines, and that driver SIGSEGVs rather than
|
||||||
TEST_F(ProgramPipelineScenario, DISABLED_ComputeAndGraphicsStagesShareOnePipeline) {
|
// returning an error.
|
||||||
|
TEST_F(ProgramPipelineScenario, ComputeAndGraphicsStagesShareOnePipeline) {
|
||||||
if (!Ready()) return;
|
if (!Ready()) return;
|
||||||
HeadlessGL& gl = Gl();
|
HeadlessGL& gl = Gl();
|
||||||
const int width = gl.Width();
|
const int width = gl.Width();
|
||||||
|
|||||||
@@ -369,6 +369,115 @@ namespace MobileGL::MG_State {
|
|||||||
return m_programState.GetCurrentProgram();
|
return m_programState.GetCurrentProgram();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copies every default-block uniform value `source` holds into the same-named uniform of
|
||||||
|
// `destination`, by name and by location.
|
||||||
|
//
|
||||||
|
// The composite a pipeline draws through is a DIFFERENT program object from the stage
|
||||||
|
// programs the application writes uniforms to - glUniform* addresses the pipeline's
|
||||||
|
// active program and glProgramUniform* addresses a named one, neither of which is the
|
||||||
|
// composite - so without this a pipeline draw reads the composite's zero defaults and
|
||||||
|
// paints them. Values are COPIED rather than aliased: the two programs' global UBOs are
|
||||||
|
// laid out independently (the composite merges several stages' uniforms into one block,
|
||||||
|
// so the same uniform sits at a different offset in each), and a copy also means the
|
||||||
|
// composite can outlive a stage program without ever pointing into freed storage.
|
||||||
|
//
|
||||||
|
// Location-by-location so that arrays are carried across whole, and via the padded
|
||||||
|
// storage span so a mat3's std140 column padding travels with it.
|
||||||
|
static void MirrorUniformValues(ProgramObject& source, ProgramObject& destination) {
|
||||||
|
if (!source.GetLinkStatus() || !destination.GetLinkStatus()) return;
|
||||||
|
const char* sourceUbo = static_cast<const char*>(source.GetUBOData());
|
||||||
|
char* destinationUbo = static_cast<char*>(destination.MapUBO());
|
||||||
|
const SizeT sourceUboSize = source.GetUBOSize();
|
||||||
|
const SizeT destinationUboSize = destination.GetUBOSize();
|
||||||
|
|
||||||
|
const Uint uniformCount = source.GetUniformCount();
|
||||||
|
for (Uint index = 0; index < uniformCount; ++index) {
|
||||||
|
const String& name = source.GetActiveUniformName(index);
|
||||||
|
if (name.empty()) continue;
|
||||||
|
const Int sourceBase = source.GetUniformLocation(name);
|
||||||
|
const Int destinationBase = destination.GetUniformLocation(name);
|
||||||
|
// A uniform the composite's own link dropped (or renamed) is simply not
|
||||||
|
// mirrored; the draw cannot read what does not exist.
|
||||||
|
if (sourceBase < 0 || destinationBase < 0) continue;
|
||||||
|
|
||||||
|
const GLint arraySize = source.GetActiveUniformArraySize(index);
|
||||||
|
const Int elements = arraySize > 0 ? static_cast<Int>(arraySize) : 1;
|
||||||
|
for (Int element = 0; element < elements; ++element) {
|
||||||
|
const Int sourceLocation = sourceBase + element;
|
||||||
|
const Int destinationLocation = destinationBase + element;
|
||||||
|
if (!source.IsValidUniformLocation(sourceLocation) ||
|
||||||
|
!destination.IsValidUniformLocation(destinationLocation)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Stop at the end of EITHER side's array rather than walking onto the
|
||||||
|
// neighbouring uniform of whichever program has the shorter one.
|
||||||
|
if (!source.UniformLocationsAliasSameUniform(sourceBase, sourceLocation) ||
|
||||||
|
!destination.UniformLocationsAliasSameUniform(destinationBase, destinationLocation)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Bool sourceOpaque = source.IsUniformOpaqueAtLocation(sourceLocation);
|
||||||
|
if (sourceOpaque != destination.IsUniformOpaqueAtLocation(destinationLocation)) break;
|
||||||
|
if (sourceOpaque) {
|
||||||
|
// A sampler/image unit is phase-A state, not UBO bytes. The setter
|
||||||
|
// itself is a no-op when the value already matches, so this does not
|
||||||
|
// churn the composite's backend state version.
|
||||||
|
destination.SetUniformSamplerOrImageUnitIndex(
|
||||||
|
destinationLocation, source.GetUniformSamplerOrImageUnitIndex(sourceLocation));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SizeT span = source.GetUniformStorageSpanInBytes(sourceLocation);
|
||||||
|
if (span == 0 || span != destination.GetUniformStorageSpanInBytes(destinationLocation)) continue;
|
||||||
|
const Uint sourceOffset = source.GetUniformOffset(sourceLocation);
|
||||||
|
const Uint destinationOffset = destination.GetUniformOffset(destinationLocation);
|
||||||
|
// Either side can legitimately lack backing storage: the optimizer deletes a
|
||||||
|
// uniform nothing reads, and a program whose SPIR-V phase settled cancelled
|
||||||
|
// has no shadow at all. Both report kInvalidUniformOffset / a null shadow.
|
||||||
|
if (sourceUbo == nullptr || destinationUbo == nullptr ||
|
||||||
|
sourceOffset == ProgramObject::kInvalidUniformOffset ||
|
||||||
|
destinationOffset == ProgramObject::kInvalidUniformOffset ||
|
||||||
|
sourceOffset + span > sourceUboSize || destinationOffset + span > destinationUboSize) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::memcmp(destinationUbo + destinationOffset, sourceUbo + sourceOffset, span) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Memcpy(destinationUbo + destinationOffset, sourceUbo + sourceOffset, span);
|
||||||
|
destination.MarkUBOContentDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
static void RefreshCompositeUniforms(ProgramPipelineObject& pipeline, const SharedPtr<ProgramObject>& composite) {
|
||||||
|
if (!composite) return;
|
||||||
|
const auto versions = pipeline.ComputeUniformMirrorVersions();
|
||||||
|
if (versions == pipeline.GetMirroredUniformVersions()) return;
|
||||||
|
|
||||||
|
// A program bound to two stages appears twice; mirroring it twice would be
|
||||||
|
// idempotent but is still work, and the second pass would have nothing to do.
|
||||||
|
Array<ProgramObject*, ProgramPipelineObject::kGraphicsStageCount> mirrored{};
|
||||||
|
SizeT mirroredCount = 0;
|
||||||
|
for (SizeT stage = 0; stage < ProgramPipelineObject::kGraphicsStageCount; ++stage) {
|
||||||
|
const auto& stageProgram = pipeline.GetStageProgram(static_cast<ShaderStage>(stage));
|
||||||
|
if (!stageProgram) continue;
|
||||||
|
Bool alreadyMirrored = false;
|
||||||
|
for (SizeT i = 0; i < mirroredCount; ++i) {
|
||||||
|
if (mirrored[i] == stageProgram.get()) {
|
||||||
|
alreadyMirrored = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (alreadyMirrored) continue;
|
||||||
|
mirrored[mirroredCount++] = stageProgram.get();
|
||||||
|
MirrorUniformValues(*stageProgram, *composite);
|
||||||
|
}
|
||||||
|
pipeline.SetMirroredUniformVersions(versions);
|
||||||
|
}
|
||||||
|
|
||||||
const SharedPtr<ProgramObject>& GLContext::GetProgramForDraw() {
|
const SharedPtr<ProgramObject>& GLContext::GetProgramForDraw() {
|
||||||
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
||||||
const auto& currentProgram = m_programState.GetCurrentProgram();
|
const auto& currentProgram = m_programState.GetCurrentProgram();
|
||||||
@@ -402,13 +511,16 @@ namespace MobileGL::MG_State {
|
|||||||
// that will never be produced again: every draw would miss the cache and rebuild
|
// that will never be produced again: every draw would miss the cache and rebuild
|
||||||
// (and relink) the composite. Join first, so the signature describes settled
|
// (and relink) the composite. Join first, so the signature describes settled
|
||||||
// programs. In steady state this is a null check per stage.
|
// programs. In steady state this is a null check per stage.
|
||||||
for (SizeT stage = 0; stage < static_cast<SizeT>(ShaderStage::ShaderStageCount); ++stage) {
|
for (SizeT stage = 0; stage < ProgramPipelineObject::kGraphicsStageCount; ++stage) {
|
||||||
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
||||||
if (stageProgram) stageProgram->JoinLinkAndSpirv();
|
if (stageProgram) stageProgram->JoinLinkAndSpirv();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto signature = pipeline->ComputeDrawProgramSignature();
|
const auto signature = pipeline->ComputeDrawProgramSignature();
|
||||||
if (const auto& cached = pipeline->GetCachedDrawProgram(signature)) return cached;
|
if (const auto& cached = pipeline->GetCachedDrawProgram(signature)) {
|
||||||
|
RefreshCompositeUniforms(*pipeline, cached);
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
// Everything downstream of here - the backends, the uniform plumbing, the draw
|
// Everything downstream of here - the backends, the uniform plumbing, the draw
|
||||||
// validation - is written against a single linked program, so the pipeline is
|
// validation - is written against a single linked program, so the pipeline is
|
||||||
@@ -420,8 +532,14 @@ namespace MobileGL::MG_State {
|
|||||||
// could otherwise be handed. Backend registries key on the object, not the name.
|
// could otherwise be handed. Backend registries key on the object, not the name.
|
||||||
auto composite = MakeShared<ProgramObject>(0u);
|
auto composite = MakeShared<ProgramObject>(0u);
|
||||||
|
|
||||||
|
// GRAPHICS stages only. A pipeline may carry a compute stage alongside them (GL
|
||||||
|
// 4.6 core 7.4 forbids linking compute WITH another stage into one program, not
|
||||||
|
// attaching a compute program to a pipeline that also has graphics ones), and that
|
||||||
|
// stage belongs to glDispatchCompute, not to this draw. Compositing it in produced
|
||||||
|
// a graphics program carrying a compute module, which Adreno 830 does not reject
|
||||||
|
// from vkCreateGraphicsPipelines - it SIGSEGVs inside it.
|
||||||
Bool anyStage = false;
|
Bool anyStage = false;
|
||||||
for (SizeT stage = 0; stage < static_cast<SizeT>(ShaderStage::ShaderStageCount); ++stage) {
|
for (SizeT stage = 0; stage < ProgramPipelineObject::kGraphicsStageCount; ++stage) {
|
||||||
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
||||||
if (!stageProgram) continue;
|
if (!stageProgram) continue;
|
||||||
for (const auto& shader : stageProgram->GetAttachedShaders()) {
|
for (const auto& shader : stageProgram->GetAttachedShaders()) {
|
||||||
@@ -440,7 +558,32 @@ namespace MobileGL::MG_State {
|
|||||||
// for the same reason: the backend is about to read its SPIR-V.
|
// for the same reason: the backend is about to read its SPIR-V.
|
||||||
composite->JoinLinkAndSpirv();
|
composite->JoinLinkAndSpirv();
|
||||||
pipeline->SetCachedDrawProgram(signature, Move(composite));
|
pipeline->SetCachedDrawProgram(signature, Move(composite));
|
||||||
return pipeline->GetCachedDrawProgram(signature);
|
const auto& cached = pipeline->GetCachedDrawProgram(signature);
|
||||||
|
RefreshCompositeUniforms(*pipeline, cached);
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SharedPtr<ProgramObject>& GLContext::GetProgramForDispatch() {
|
||||||
|
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
||||||
|
const auto& currentProgram = m_programState.GetCurrentProgram();
|
||||||
|
if (currentProgram) {
|
||||||
|
// Same join contract as GetProgramForDraw's glUseProgram half - see the note
|
||||||
|
// there. A dispatch reads the same non-artifact versions a draw does.
|
||||||
|
currentProgram->JoinLinkAndSpirv();
|
||||||
|
return currentProgram;
|
||||||
|
}
|
||||||
|
if (m_boundProgramPipeline == 0) return nullProgram;
|
||||||
|
const auto& pipeline = GetBoundProgramPipeline();
|
||||||
|
if (!pipeline) return nullProgram;
|
||||||
|
// No compositing and no cache: GL 4.6 core 7.4 makes a compute program exclusive of
|
||||||
|
// every other stage, so the pipeline's compute stage program IS the program to
|
||||||
|
// dispatch, uniforms and all. That also means glUniform* through the active program
|
||||||
|
// lands on the very object the dispatch reads - the composite's uniform refresh has
|
||||||
|
// no counterpart to do here.
|
||||||
|
const auto& computeProgram = pipeline->GetStageProgram(ShaderStage::Compute);
|
||||||
|
if (!computeProgram) return nullProgram;
|
||||||
|
computeProgram->JoinLinkAndSpirv();
|
||||||
|
return computeProgram;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SharedPtr<ProgramObject>& GLContext::GetProgramForUniform() {
|
const SharedPtr<ProgramObject>& GLContext::GetProgramForUniform() {
|
||||||
|
|||||||
@@ -163,9 +163,15 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
void UseProgram(Uint program);
|
void UseProgram(Uint program);
|
||||||
const SharedPtr<ProgramObject>& GetCurrentProgram();
|
const SharedPtr<ProgramObject>& GetCurrentProgram();
|
||||||
// What a draw or dispatch actually executes: the program in use, or - when
|
// What a DRAW executes: the program in use, or - when there is none - the bound
|
||||||
// there is none - the bound pipeline's stages composited into one program.
|
// pipeline's GRAPHICS stages composited into one program. A pipeline's compute
|
||||||
|
// stage is never part of that composite; ask GetProgramForDispatch for it.
|
||||||
const SharedPtr<ProgramObject>& GetProgramForDraw();
|
const SharedPtr<ProgramObject>& GetProgramForDraw();
|
||||||
|
// What a DISPATCH executes: the program in use, or - when there is none - the
|
||||||
|
// bound pipeline's compute stage program itself. GL's compute stage is a whole
|
||||||
|
// program on its own (GL 4.6 core 7.4: it may not be linked with any other
|
||||||
|
// stage), so there is nothing to composite and no composite to cache.
|
||||||
|
const SharedPtr<ProgramObject>& GetProgramForDispatch();
|
||||||
// What glUniform* addresses: the program in use, or the bound pipeline's
|
// What glUniform* addresses: the program in use, or the bound pipeline's
|
||||||
// active program (GL 4.6 core 7.6.1).
|
// active program (GL 4.6 core 7.6.1).
|
||||||
const SharedPtr<ProgramObject>& GetProgramForUniform();
|
const SharedPtr<ProgramObject>& GetProgramForUniform();
|
||||||
|
|||||||
@@ -326,6 +326,20 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
: kInvalidUniformOffset;
|
: kInvalidUniformOffset;
|
||||||
}
|
}
|
||||||
Uint GetUniformSizesInBytes(Uint location) const { return MG_Util::GetGLTypeSize(GetUniformType(location)); }
|
Uint GetUniformSizesInBytes(Uint location) const { return MG_Util::GetGLTypeSize(GetUniformType(location)); }
|
||||||
|
// Bytes a uniform actually occupies in the global UBO, which is not its GL type size:
|
||||||
|
// std140 pads each column of a float matrix out to a vec4, so a mat3 spans 48 bytes
|
||||||
|
// even though only 36 of them carry components. Anything reading or writing a whole
|
||||||
|
// uniform's storage - a bounds check, a copy between two programs' shadows - wants
|
||||||
|
// this rather than GetUniformSizesInBytes.
|
||||||
|
static SizeT UniformStorageSpanInBytes(const glslang::TType* type, SizeT tightSize) {
|
||||||
|
if (type != nullptr && type->isMatrix() && type->getBasicType() != glslang::EbtDouble) {
|
||||||
|
return static_cast<SizeT>(type->getMatrixCols()) * 4 * sizeof(Float);
|
||||||
|
}
|
||||||
|
return tightSize;
|
||||||
|
}
|
||||||
|
SizeT GetUniformStorageSpanInBytes(Uint location) const {
|
||||||
|
return UniformStorageSpanInBytes(GetUniformTType(location), GetUniformSizesInBytes(location));
|
||||||
|
}
|
||||||
|
|
||||||
Int GetAttributeLocation(const String& name) {
|
Int GetAttributeLocation(const String& name) {
|
||||||
const auto it = std::find(Artifacts().attribs.begin(), Artifacts().attribs.end(), name);
|
const auto it = std::find(Artifacts().attribs.begin(), Artifacts().attribs.end(), name);
|
||||||
|
|||||||
@@ -40,17 +40,31 @@ namespace MobileGL {
|
|||||||
|
|
||||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||||
|
|
||||||
|
// The stages a DRAW is built from: every stage but compute. GL 4.6 core 7.4
|
||||||
|
// makes the compute stage exclusive - a program object containing a compute
|
||||||
|
// shader may contain no other stage, and a pipeline's compute stage is
|
||||||
|
// dispatched on its own and never participates in a draw. So the compute stage
|
||||||
|
// is not merely irrelevant to the composite below, it must never enter it: a
|
||||||
|
// compute module handed to vkCreateGraphicsPipelines is a driver crash rather
|
||||||
|
// than an error return (Adreno 830 SIGSEGVs inside it).
|
||||||
|
static constexpr SizeT kGraphicsStageCount = static_cast<SizeT>(ShaderStage::Compute);
|
||||||
|
static_assert(static_cast<SizeT>(ShaderStage::Compute) + 1 ==
|
||||||
|
static_cast<SizeT>(ShaderStage::ShaderStageCount),
|
||||||
|
"ShaderStage must keep Compute last so the graphics stages are a prefix");
|
||||||
|
|
||||||
// A draw sees one program, but a pipeline holds one program per stage. The
|
// A draw sees one program, but a pipeline holds one program per stage. The
|
||||||
// stages are composited into a single hidden program object, rebuilt whenever
|
// GRAPHICS stages are composited into a single hidden program object, rebuilt
|
||||||
// the stage set - or any stage program's own link - changes. The signature is
|
// whenever the stage set - or any stage program's own link - changes. The
|
||||||
// what that "changes" means: a stage program's lifetime id pins the object and
|
// signature is what that "changes" means: a stage program's lifetime id pins the
|
||||||
// its backend state version pins the link generation.
|
// object and its backend state version pins the link generation. It covers
|
||||||
using DrawProgramSignature =
|
// exactly the stages the composite is built from, so attaching or relinking a
|
||||||
Array<Uint64, static_cast<SizeT>(ShaderStage::ShaderStageCount) * 2>;
|
// compute stage never invalidates a perfectly good graphics composite - and the
|
||||||
|
// compute stage, having no composite of its own, can never collide with it.
|
||||||
|
using DrawProgramSignature = Array<Uint64, kGraphicsStageCount * 2>;
|
||||||
|
|
||||||
DrawProgramSignature ComputeDrawProgramSignature() const {
|
DrawProgramSignature ComputeDrawProgramSignature() const {
|
||||||
DrawProgramSignature signature{};
|
DrawProgramSignature signature{};
|
||||||
for (SizeT stage = 0; stage < static_cast<SizeT>(ShaderStage::ShaderStageCount); ++stage) {
|
for (SizeT stage = 0; stage < kGraphicsStageCount; ++stage) {
|
||||||
const auto& program = m_stagePrograms[stage];
|
const auto& program = m_stagePrograms[stage];
|
||||||
if (!program) continue;
|
if (!program) continue;
|
||||||
signature[stage * 2] = program->GetLifetimeId();
|
signature[stage * 2] = program->GetLifetimeId();
|
||||||
@@ -59,6 +73,32 @@ namespace MobileGL {
|
|||||||
return signature;
|
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>;
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
const UniformMirrorVersions& GetMirroredUniformVersions() const { return m_mirroredUniformVersions; }
|
||||||
|
void SetMirroredUniformVersions(const UniformMirrorVersions& versions) {
|
||||||
|
m_mirroredUniformVersions = versions;
|
||||||
|
}
|
||||||
|
|
||||||
const SharedPtr<ProgramObject>& GetCachedDrawProgram(const DrawProgramSignature& signature) const {
|
const SharedPtr<ProgramObject>& GetCachedDrawProgram(const DrawProgramSignature& signature) const {
|
||||||
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
||||||
if (!m_drawProgram || m_drawProgramSignature != signature) return nullProgram;
|
if (!m_drawProgram || m_drawProgramSignature != signature) return nullProgram;
|
||||||
@@ -67,6 +107,8 @@ namespace MobileGL {
|
|||||||
void SetCachedDrawProgram(const DrawProgramSignature& signature, SharedPtr<ProgramObject> program) {
|
void SetCachedDrawProgram(const DrawProgramSignature& signature, SharedPtr<ProgramObject> program) {
|
||||||
m_drawProgramSignature = signature;
|
m_drawProgramSignature = signature;
|
||||||
m_drawProgram = Move(program);
|
m_drawProgram = Move(program);
|
||||||
|
// A rebuilt composite holds none of its stage programs' uniform values yet.
|
||||||
|
m_mirroredUniformVersions = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -74,6 +116,7 @@ namespace MobileGL {
|
|||||||
SharedPtr<ProgramObject> m_activeProgram;
|
SharedPtr<ProgramObject> m_activeProgram;
|
||||||
SharedPtr<ProgramObject> m_drawProgram;
|
SharedPtr<ProgramObject> m_drawProgram;
|
||||||
DrawProgramSignature m_drawProgramSignature{};
|
DrawProgramSignature m_drawProgramSignature{};
|
||||||
|
UniformMirrorVersions m_mirroredUniformVersions{};
|
||||||
String m_infoLog;
|
String m_infoLog;
|
||||||
const Uint m_externalIndex = 0;
|
const Uint m_externalIndex = 0;
|
||||||
Bool m_validateStatus = false;
|
Bool m_validateStatus = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user