[MG_Pipe, MG_Remote, MG_Backend] (Disaggregated): P5f fe - carry XFB capture snapshots across Begin

This commit is contained in:
2026-09-19 21:57:45 -04:00
parent 85f3104731
commit 07bf8d7ce0
8 changed files with 143 additions and 5 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
#include <Includes.h>
#include "MG_State/GLState/TextureState/TextureEnum.h"
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Pipe/MGPipeTypes.h>
#include <MG_Pipe/MGPipeHandles.h>
#endif
namespace MobileGL {
+88 -3
View File
@@ -1528,8 +1528,39 @@ namespace MobileGL::MG_Backend::DirectGLES {
// gl_NextBuffer is the shape that makes them differ: buffer 0 has stride 0
// and nothing bound, so target 0 describes buffer 1.
SizeT bufferIndex = 0;
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeHandle handle = MG_Pipe::kMGPipeNullHandle;
#endif
};
#if MOBILEGL_BUILD_DISAGGREGATED
struct XfbProgramSource {
SharedPtr<MG_State::GLState::ProgramObject> Frontend;
SharedPtr<const MG_State::GLState::ProgramArchive> Archive;
explicit operator Bool() const { return Frontend || Archive; }
const XfbProgramSource* operator->() const { return this; }
SizeT GetTransformFeedbackBufferCount() const {
return Archive ? Archive->Link.xfbStrides.size() : Frontend->GetTransformFeedbackBufferCount();
}
Uint32 GetTransformFeedbackStride(Uint32 index) const {
return Archive ? (index < Archive->Link.xfbStrides.size() ? Archive->Link.xfbStrides[index] : 0)
: Frontend->GetTransformFeedbackStride(index);
}
Bool NeedsScatteredTransformFeedbackCapture() const {
return Archive ? Archive->Link.xfbNeedsScatteredCapture : Frontend->NeedsScatteredTransformFeedbackCapture();
}
Uint32 GetTransformFeedbackPackedStride() const {
return Archive ? Archive->Link.xfbPackedStride : Frontend->GetTransformFeedbackPackedStride();
}
GLenum GetTransformFeedbackBufferMode() const {
return Archive ? Archive->Link.xfbBufferMode : Frontend->GetTransformFeedbackBufferMode();
}
const auto& GetTransformFeedbackVaryings() const {
return Archive ? Archive->Link.xfbVaryings : Frontend->GetTransformFeedbackVaryings();
}
};
#endif
// Per frontend transform feedback object. The default object (name 0) maps to
// the driver's default object (id 0) and is always present.
struct XfbObjectState {
@@ -1544,6 +1575,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// End scatters them into `targets`.
Bool scattered = false;
SharedPtr<MG_State::GLState::ProgramObject> scatterProgram;
#if MOBILEGL_BUILD_DISAGGREGATED
SharedPtr<const MG_State::GLState::ProgramArchive> scatterArchive;
#endif
SizeT scatterCapacityVertices = 0;
};
@@ -1620,14 +1654,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (g_GLESFuncs.glMapBufferRange != nullptr && g_GLESFuncs.glUnmapBuffer != nullptr) {
for (const auto& target : targets) {
if (!target.buffer) continue;
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport == MG_Config::TransportMode::Monolith)
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport == MG_Config::TransportMode::Monolith)
#endif
if (!target.buffer) continue;
#if MOBILEGL_BUILD_DISAGGREGATED
// P5c: under an active transport the frontend object is client
// memory (rule E), so the persistence question is the server
// resource's and the captured bytes go back as a writeback EVENT -
// WritebackFromBackend from the apply thread is the R1/R2 shape.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
const MG_Pipe::MGPipeHandle res = BufferImpl::HandleOfBuffer(target.buffer.get());
const MG_Pipe::MGPipeHandle res = target.handle;
auto* resource = BufferImpl::FindBufferResourceForHandle(res);
if (resource == nullptr || resource->persistentMapped) continue;
const SizeT size = target.end - target.start;
@@ -1732,7 +1772,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// occupies are written, so the holes gl_SkipComponents asks for keep whatever the
// application had put there - which is the whole point of the feature.
void ScatterCapturedRecords(XfbObjectState& xfb) {
#if MOBILEGL_BUILD_DISAGGREGATED
const XfbProgramSource program{xfb.scatterProgram, xfb.scatterArchive};
#else
const auto& program = xfb.scatterProgram;
#endif
if (!program || xfb.targets.empty()) return;
if (g_GLESFuncs.glMapBufferRange == nullptr || g_GLESFuncs.glUnmapBuffer == nullptr) return;
@@ -1769,6 +1813,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// whole range down once.
for (SizeT targetIndex = 0; targetIndex < xfb.targets.size(); ++targetIndex) {
const auto& target = xfb.targets[targetIndex];
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport == MG_Config::TransportMode::Monolith)
#endif
if (!target.buffer) continue;
// By BUFFER index, not by position in the compacted list - see XfbCaptureTarget.
const SizeT stride = program->GetTransformFeedbackStride(static_cast<Uint32>(target.bufferIndex));
@@ -1784,7 +1831,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Pipe::MGPipeHandle splitRes = MG_Pipe::kMGPipeNullHandle;
BufferImpl::GLESBufferResource* splitResource = nullptr;
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
splitRes = BufferImpl::HandleOfBuffer(target.buffer.get());
splitRes = target.handle;
splitResource = BufferImpl::FindBufferResourceForHandle(splitRes);
const Uint8* hostBytes = splitResource != nullptr ? splitResource->hostBytes : nullptr;
if (splitResource == nullptr || hostBytes == nullptr) {
@@ -1876,7 +1923,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
// not captured, and opening the span would also subject it to the capture
// primitive-mode rule the paused draw is exempt from.
if (!xfb.pending || xfb.paused) return;
#if MOBILEGL_BUILD_DISAGGREGATED
XfbProgramSource program;
const MG_Pipe::MGPStreamOutputBegin* span = nullptr;
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
const auto& state = MG_Pipe::MGPipeApplier();
const auto found = state.StreamOutputSpans.find(state.BoundStreamOutputLifetimeId);
if (found != state.StreamOutputSpans.end()) {
span = &found->second;
const auto* record = PipeShaderCsoRecordForHandle(span->CaptureProgram);
if (record) program.Archive = record->Archive;
}
} else {
program.Frontend = MGB_CTX->GetTransformFeedbackProgram();
}
#else
const auto& program = MGB_CTX->GetTransformFeedbackProgram();
#endif
if (!program) {
// The pending flag is deliberately NOT consumed here. It used to be cleared
// before this check, so a single draw that could not see the capture program
@@ -1896,6 +1959,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
// recording it here keeps End independent of the frontend capture state.
const SizeT bufferCount = program->GetTransformFeedbackBufferCount();
for (SizeT i = 0; i < bufferCount; ++i) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (!span || i >= 4) continue;
const auto& range = span->Targets[i];
if (MG_Pipe::MGPipeHandleIsNull(range.Res) || range.Size == 0) continue;
auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, range.Res);
if (!resource || resource->id == 0) continue;
xfb.targets.push_back({nullptr, resource->id, static_cast<SizeT>(range.Offset),
static_cast<SizeT>(range.Offset + range.Size), i, range.Res});
BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLuint>(i),
resource->id, static_cast<GLintptr>(range.Offset), static_cast<GLsizeiptr>(range.Size));
continue;
}
#endif
auto& point = MGB_CTX->GetBufferBindingPoint(BufferTarget::TransformFeedback,
static_cast<Uint>(i));
const auto& bufferObject = point.GetBoundObject();
@@ -1909,6 +1986,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
xfb.targets.push_back({bufferObject, backendResource->id, start, end, i});
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport == MG_Config::TransportMode::Monolith)
#endif
BufferImpl::SyncTransformFeedbackBindingPoints(bufferCount);
// A layout with holes or several interleaved buffers is not expressible on ES:
@@ -1930,7 +2010,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (capacityVertices == ~SizeT(0)) capacityVertices = 0;
if (BindScatterCaptureBuffer(program->GetTransformFeedbackPackedStride(), capacityVertices)) {
xfb.scattered = true;
#if MOBILEGL_BUILD_DISAGGREGATED
xfb.scatterProgram = program.Frontend;
xfb.scatterArchive = program.Archive;
#else
xfb.scatterProgram = program;
#endif
xfb.scatterCapacityVertices = capacityVertices;
} else {
// NO SPAN RATHER THAN A SPAN THAT WRITES SOMEWHERE ELSE. The ES program for a
+6
View File
@@ -2135,6 +2135,12 @@ namespace MobileGL::MG_Pipe {
}
Bool PipeInputs::HasOpenTransformFeedbackSpan(Uint64 lifetimeId) const {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
return lifetimeId != 0 && MGPipeApplier().StreamOutputSpans.count(lifetimeId) != 0;
}
#endif
MGP_STICKY_FORWARD_PULL(HasOpenTransformFeedbackSpan);
const auto* ctx = LiveContext();
return ctx != nullptr && ctx->HasOpenTransformFeedbackSpan(lifetimeId);
+5 -1
View File
@@ -1669,8 +1669,12 @@ namespace MobileGL::MG_Pipe {
struct MGPStreamOutputBegin {
Uint32 PrimitiveMode;
Uint32 Pad0;
// P5f: immutable span snapshot; no frontend pointers survive Begin.
MGPipeHandle CaptureProgram;
Uint64 LifetimeId;
MGPBufferRange Targets[4];
};
MGP_ASSERT_POD(MGPStreamOutputBegin, 8);
MGP_ASSERT_POD(MGPStreamOutputBegin, 120);
// end_stream_output carries the accounting the client owns; the scatter itself is a
// read-modify-write of the client's shadow and lives there (section 7.2.1).
+16
View File
@@ -1379,6 +1379,10 @@ namespace MobileGL::MG_Pipe {
g_applier.DrawProgram = kMGPipeNullHandle;
g_applier.DispatchProgram = kMGPipeNullHandle;
g_applier.BoundShaderCso = kMGPipeNullHandle;
#if MOBILEGL_BUILD_DISAGGREGATED
g_applier.BoundStreamOutputLifetimeId = 0;
g_applier.StreamOutputSpans.clear();
#endif
// P5e (sb, CONTRACT-P5E.md §5.6): the three binding-point windows are per-context
// WORKING state and go with the rest of it - a returning context has its own
// glBindBufferBase history and may not inherit the one this applier was left holding.
@@ -1431,6 +1435,10 @@ namespace MobileGL::MG_Pipe {
g_applier.DrawProgram = kMGPipeNullHandle;
g_applier.DispatchProgram = kMGPipeNullHandle;
g_applier.BoundShaderCso = kMGPipeNullHandle;
#if MOBILEGL_BUILD_DISAGGREGATED
g_applier.BoundStreamOutputLifetimeId = 0;
g_applier.StreamOutputSpans.clear();
#endif
// AND "THE WORKING HANDLES THEY COULD NAME" IS ALL OF THEM, NOT JUST THE THREE ABOVE.
// Every framebuffer record holds eleven MGPSurface::Res naming texture and renderbuffer
// records this function has just dropped - which is why the table itself goes above -
@@ -3206,6 +3214,10 @@ namespace MobileGL::MG_Pipe {
// bind_render_state's precedent for the same question.
if (MGPipeHandleIsNull(handle.Handle)) {
g_applier.BoundShaderCso = kMGPipeNullHandle;
#if MOBILEGL_BUILD_DISAGGREGATED
g_applier.BoundStreamOutputLifetimeId = 0;
g_applier.StreamOutputSpans.clear();
#endif
++g_applier.ProgramBindingSerial;
return;
}
@@ -3238,6 +3250,10 @@ namespace MobileGL::MG_Pipe {
Bool cleared = false;
if (g_applier.BoundShaderCso == handle.Handle) {
g_applier.BoundShaderCso = kMGPipeNullHandle;
#if MOBILEGL_BUILD_DISAGGREGATED
g_applier.BoundStreamOutputLifetimeId = 0;
g_applier.StreamOutputSpans.clear();
#endif
cleared = true;
}
if (g_applier.DrawProgram == handle.Handle) {
+2
View File
@@ -847,6 +847,8 @@ namespace MobileGL::MG_Pipe {
// copy_framebuffer_to_texture's destination texture (MGPCopyFromFramebuffer::Dst).
MGPipeHandle VerbCopyTexDst = kMGPipeNullHandle;
MGPipeHandle VerbStorageBlockProgram = kMGPipeNullHandle;
Uint64 BoundStreamOutputLifetimeId = 0;
UnorderedMap<Uint64, MGPStreamOutputBegin> StreamOutputSpans;
// generate_mipmap's texture (MGPMipPlan::Res).
MGPipeHandle VerbMipRes = kMGPipeNullHandle;
// The current indirect draw's command buffer and (for the *Count forms) parameter
+19
View File
@@ -62,6 +62,7 @@
#include <MG_Impl/Pipe/FramebufferEmit.h>
#include <MG_Impl/Pipe/PipeFill.h>
#include <MG_Impl/Pipe/TextureEmit.h>
#include <MG_Impl/Pipe/ProgramEmit.h>
#include <MG_Pipe/PipeMutation.h>
namespace MobileGL::MG_Remote::Client {
@@ -1412,6 +1413,24 @@ namespace MobileGL::MG_Remote::Client {
// The GL token verbatim (contract table 0's "GL enums on the wire"): the sink hands
// it to the backend slot that takes it, and nothing between here and there reads it.
record.PrimitiveMode = static_cast<Uint32>(primitiveMode);
auto& context = *MG_State::pGLContext;
record.LifetimeId = context.GetBoundTransformFeedbackLifetimeId();
const auto& program = context.GetTransformFeedbackProgram();
if (program) {
Uint64 bytes = 0;
record.CaptureProgram = MG_Pipe::MGPipeProgramEmitterInstance().AcquireShaderCso(*program, bytes);
}
static_assert(MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS == 4);
for (Uint i = 0; i < 4; ++i) {
const auto& point = context.GetBufferBindingPoint(BufferTarget::TransformFeedback, i);
const auto& buffer = point.GetBoundObject();
if (!buffer) continue;
const auto range = point.GetRange();
const SizeT start = std::min(range.start, buffer->GetSize());
const SizeT end = std::min(range.end, buffer->GetSize());
record.Targets[i] = {MG_Pipe::MGPipeBufferResources().Find(*buffer), start,
end > start ? end - start : 0};
}
session.EmitAndWait(MG_Pipe::MGPWireOp::BeginStreamOutput, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
@@ -955,6 +955,9 @@ namespace MobileGL::MG_Remote::Server {
// the buffer bindings are read through the pulls. So this record's effect is not
// visible until a DRAW crosses - which is why an XFB scenario whose draw is still class
// C moves its first blocker to that draw rather than rendering.
auto& state = MG_Pipe::MGPipeApplier();
state.BoundStreamOutputLifetimeId = begin.LifetimeId;
state.StreamOutputSpans[begin.LifetimeId] = begin;
table->GL.BeginTransformFeedback(static_cast<GLenum>(begin.PrimitiveMode));
++m_streamOutputSpans;
return true;
@@ -974,6 +977,8 @@ namespace MobileGL::MG_Remote::Server {
// server-side scatter is what will need them.
(void)accounting;
table->GL.EndTransformFeedback();
auto& state = MG_Pipe::MGPipeApplier();
state.StreamOutputSpans.erase(state.BoundStreamOutputLifetimeId);
++m_streamOutputSpans;
return true;
}
@@ -1010,6 +1015,7 @@ namespace MobileGL::MG_Remote::Server {
// lifetime of its own - it has no reader on this side today, and pretending otherwise
// by folding it into the key is exactly the "a GL name is never an identity" confusion
// the contract's GlName row is written against.
MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId = bind.LifetimeId;
table->GL.BindTransformFeedback(static_cast<GLuint>(bind.GlName));
++m_streamOutputBinds;
return true;