mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[MG_Pipe, MG_Remote, MG_Backend] (Disaggregated): P5f fe - carry XFB capture snapshots across Begin
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user