diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 552251d3..13a6d626 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -10,7 +10,7 @@ #include #include "MG_State/GLState/TextureState/TextureEnum.h" #if MOBILEGL_BUILD_DISAGGREGATED -#include +#include #endif namespace MobileGL { diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 1c3a26c6..e2c5db4a 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -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 Frontend; + SharedPtr 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 scatterProgram; +#if MOBILEGL_BUILD_DISAGGREGATED + SharedPtr 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(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(range.Offset), + static_cast(range.Offset + range.Size), i, range.Res}); + BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(i), + resource->id, static_cast(range.Offset), static_cast(range.Size)); + continue; + } +#endif auto& point = MGB_CTX->GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(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 diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 3769e5f6..53d99b40 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -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); diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index bfc8fd15..7f015d37 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -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). diff --git a/MobileGL/MG_Pipe/PipeApply.cpp b/MobileGL/MG_Pipe/PipeApply.cpp index c10c8153..86fef2a5 100644 --- a/MobileGL/MG_Pipe/PipeApply.cpp +++ b/MobileGL/MG_Pipe/PipeApply.cpp @@ -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) { diff --git a/MobileGL/MG_Pipe/PipeApply.h b/MobileGL/MG_Pipe/PipeApply.h index 7eebcd72..8e5ce01b 100644 --- a/MobileGL/MG_Pipe/PipeApply.h +++ b/MobileGL/MG_Pipe/PipeApply.h @@ -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 StreamOutputSpans; // generate_mipmap's texture (MGPMipPlan::Res). MGPipeHandle VerbMipRes = kMGPipeNullHandle; // The current indirect draw's command buffer and (for the *Count forms) parameter diff --git a/MobileGL/MG_Remote/Client/EmitTables.cpp b/MobileGL/MG_Remote/Client/EmitTables.cpp index f422cee8..633cd335 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.cpp +++ b/MobileGL/MG_Remote/Client/EmitTables.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include 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(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); } diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index 6e63780b..7f58bc33 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -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(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(bind.GlName)); ++m_streamOutputBinds; return true;