[Refactor] (P13 W5 S4): the record verbs move from ServerVerbSink to MG_Record::RecordVerbSink, which ServerVerbSink now extends.

This commit is contained in:
2026-10-06 12:32:04 -04:00
parent f4be8c380f
commit fe5324c900
9 changed files with 1654 additions and 1510 deletions
+2
View File
@@ -697,6 +697,8 @@ if (MOBILEGL_BUILD_RECORD_ARM)
MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp
# P5 b1: the block-granularity persistent-map push the record arm makes mandatory (T2).
MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp
# The record verbs' consumer (ServerVerbSink under a transport is this plus the session's).
MobileGL/MG_Backend/Record/RecordVerbSink.cpp
)
endif()
File diff suppressed because it is too large Load Diff
+197
View File
@@ -0,0 +1,197 @@
// MobileGL - MobileGL/MG_Backend/Record/RecordVerbSink.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
// P13 W5: THE RECORD VERBS' CONSUMER. The verbs no MGPipeApply* entry point takes (clear, blit, the
// readbacks, the draws, dispatch, copies, image binds, XFB, mipmaps) end in the backend's own call
// through the private GlobalBackendFunctionsTable this sink holds. Under a transport the server's
// ServerVerbSink (MG_Remote/Server/PipeApplier.h) is this plus the session's verbs; the record
// arm's in-process port drives this class directly, which is what lets a library without a
// transport run the record arm.
#pragma once
#include <Includes.h>
#include <MG_Backend/BackendObject.h>
#include <MG_Backend/BackendObjects.h>
#include <MG_Pipe/MGPipe.h>
#include <MG_Pipe/PipeVerbSink.h>
namespace MobileGL::MG_Record {
class RecordVerbSink : public MG_Pipe::MGPipeVerbSink {
public:
// The server's private backend. Null until ServerLoop::CreateBackend has run, and a
// verb that arrives before then declines by name rather than dereferencing.
void SetBackend(MG_Backend::BackendObject* backend);
MG_Backend::BackendObject* Backend() const { return m_backend; }
void SetMaxReplyBytes(Uint64 bytes) { m_maxReplyBytes = bytes; }
Bool OnClear(const MG_Pipe::MGPClear& clear) override;
Bool OnBlit(const MG_Pipe::MGPBlit& blit) override;
Bool OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
MG_Pipe::MGPipeReplySink* replies) override;
Bool OnGetTextureImage(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
MG_Pipe::MGPipeReplySink* replies) override;
// P9 (CONTRACT-P9.md §1): the pack-buffer halves. Nothing is answered; the tight pixels
// land in record.Dst through the applier's buffer-write gate.
Bool OnReadPixelsToBuffer(const MG_Pipe::MGPReadbackToBuffer& record) override;
Bool OnGetTextureImageToBuffer(const MG_Pipe::MGPReadbackToBuffer& record) override;
Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
const MG_Pipe::MGHostSpan* userIndices,
const MG_Pipe::MGPDrawIndirect* indirect) override;
// ---- P5b (MG_Remote/CONTRACT-P5B.md): one override per row a migration package owns.
// At the contract commit EVERY BODY BELOW IS A STUB that dies
// Fatal{UnmigratedVerb, "<GL slot>"} by the slot's own name - the same line the client's
// class-C table raises and the census greps - so a client flipped ahead of its server
// half aborts by name rather than rendering nothing, and the census on this head is
// unchanged (the client refuses first). The owning package replaces the body.
//
// i1 OnLaunchGrid ("DispatchCompute"), OnMemoryBarrier, OnResourceCopyRegion
// ("CopyImageSubData"), OnBindShaderImage ("BindImageTexture"),
// OnSetStorageBlockBinding ("ShaderStorageBlockBinding")
// t2 LANDED. OnBeginStreamOutput / OnEndStreamOutput / OnPauseStreamOutput /
// OnResumeStreamOutput / OnBindStreamOutput / OnPatchParameter are real bodies
// now: the backend call the contract names, the null-slot DECLINE, a tally.
// f1 OnGenerateMipmap, OnCopyFramebufferToTexture ("CopyTexImage2D" /
// "CopyTexSubImage2D"), and OnClear's four non-Whole kinds (live already)
// d1 OnDrawVbo above: the indirect tail, the user-index span, NumDraws > 1 and the
// instanced arms - LIVE since d1 v1 (every shape the client's nineteen draw slots
// produce dispatches to the backend slot CONTRACT-P5B.md §2 d1 names)
Bool OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) override;
Bool OnMemoryBarrier(const MG_Pipe::MGPMemoryBarrier& barrier) override;
Bool OnResourceCopyRegion(const MG_Pipe::MGPCopyRegion& copy) override;
Bool OnBindShaderImage(const MG_Pipe::MGPImageBind& bind) override;
Bool OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding,
const char* name) override;
Bool OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) override;
Bool OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) override;
Bool OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
Bool OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
Bool OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) override;
Bool OnDeleteStreamOutput(const MG_Pipe::MGPStreamOutputBind& object) override;
Bool OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) override;
Bool OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) override;
Bool OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) override;
// Per-verb tallies. The lane asserts these moved, because "the scenario passed" on a
// split build is also what a scenario that ran entirely on the monolith path looks
// like (R-16: a probe may not arm against a stub).
Uint64 Clears() const { return m_clears; }
Uint64 Draws() const { return m_draws; }
Uint64 Readbacks() const { return m_readbacks; }
Uint64 Blits() const { return m_blits; }
Uint64 ReadbackBytes() const { return m_readbackBytes; }
// P9: pack-buffer readbacks that landed in their buffer (both ops), and the buffer-write
// records they took. Readbacks() above counts only the reply-slot form.
Uint64 BufferReadbacks() const { return m_bufferReadbacks; }
Uint64 BufferReadbackWrites() const { return m_bufferReadbackWrites; }
// ---- P5b package i1's tallies. R-16: a probe may not arm against a stub, and on a
// split build "the scenario passed" is also what a scenario that never left the
// monolith path looks like - so the lane asserts the number that only this sink can
// move. One per wire ROW, not per GL slot: launch_grid carries both dispatch entry
// points and memory_barrier both barrier ones, and the sink is where they separate.
Uint64 ImageBinds() const { return m_imageBinds; }
Uint64 Dispatches() const { return m_dispatches; }
Uint64 MemoryBarriers() const { return m_memoryBarriers; }
Uint64 ImageCopies() const { return m_imageCopies; }
Uint64 StorageBlockBindings() const { return m_storageBlockBindings; }
// ID-49's tight-size control reads this: the scratch a read_pixels grew to. It must equal
// the tight w*h*bpp extent of the read, never the client's DstSize - a scratch sized from
// DstSize is exactly the heap overflow codex 1 found, one field over.
Uint64 ReadbackScratchBytes() const { return static_cast<Uint64>(m_readbackScratch.size()); }
// ---- P5b d1: what the LAST draw_vbo record carried, as the sink saw it ---------------
//
// Recorded BEFORE the backend is consulted, so a process with no backend object (every
// unit case) can still assert the wire's fields rather than only that a draw "was
// declined": the record's head, its first range, its indirect block, and whether a
// user-index span rode with it and how many bytes it named. Nothing here outlives the
// call except these copies (rule C: the pointers the sink was handed are not kept).
struct LastDrawRecord {
MG_Pipe::MGPDrawInfo Info{};
MG_Pipe::MGPDrawRange FirstRange{};
MG_Pipe::MGPDrawIndirect Indirect{};
Uint64 UserIndexBytes = 0;
Bool HadUserIndices = false;
Bool HadIndirect = false;
};
const LastDrawRecord& LastDraw() const { return m_lastDraw; }
// draw_vbo records seen, applied or declined; Draws() above counts only the applied.
Uint64 DrawRecords() const { return m_drawRecords; }
// P5b t2's tallies, for the same reason the five above exist (R-16): under split "the
// scenario passed" is also what a scenario that ran entirely on the monolith path looks
// like, so a lane that wants to say the XFB spans CROSSED has to read a counter the
// server moved. Spans counts Begin and End together - they are one span and a lane that
// saw only one of them has a bug the two-counter version would have hidden behind a
// sum; controls counts Pause and Resume; binds and patch parameters count their own.
Uint64 StreamOutputSpans() const { return m_streamOutputSpans; }
Uint64 StreamOutputControls() const { return m_streamOutputControls; }
Uint64 StreamOutputBinds() const { return m_streamOutputBinds; }
Uint64 PatchParameters() const { return m_patchParameters; }
protected:
// A backend change: the subclass lets go of what it held on the old one.
virtual void OnBackendChanged() {}
// The wire's user-index span (a draw whose indices rode in a stage segment). The record
// arm's port never hands one over - its indices are in the client's own buffer - so only
// the wire's subclass, which can check a span against its segments, answers true.
virtual Bool CheckUserIndices(const MG_Pipe::MGPDrawInfo&, const MG_Pipe::MGPDrawRange*,
const MG_Pipe::MGHostSpan&) {
return false;
}
const MG_Backend::GlobalBackendFunctionsTable* Table(const char* verb) const;
// read_pixels' read, shared by the reply form and the pack-buffer form: the bound read
// framebuffer, NEUTRAL pack state (ID-49), `tight` bytes into m_readbackScratch. False
// when this server has no GL.ReadPixels - the caller owns what that means.
Bool ReadBoundFramebufferTight(const MG_Pipe::MGPReadbackInfo& info, Uint64 tight);
// get_texture_image's read, shared the same way: the whole level into `bytes`.
Bool ReadTextureImageTight(const MG_Pipe::MGPReadbackInfo& image, Vector<Uint8>& bytes);
// P9: `tight` holds Src's pixels, tightly packed; land them in record.Dst at the
// record's layout through MGPipeApplyResourceSubData, one write per contiguous run. The
// layout was checked before the read (PipeApplier.cpp ReadbackToBufferLayoutFault).
Bool LandReadbackInBuffer(const MG_Pipe::MGPReadbackToBuffer& record, Uint8* tight,
Uint64 bytesPerPixel);
MG_Backend::BackendObject* m_backend = nullptr;
Uint64 m_clears = 0;
Uint64 m_draws = 0;
Uint64 m_readbacks = 0;
Uint64 m_blits = 0;
Uint64 m_readbackBytes = 0;
Uint64 m_bufferReadbacks = 0;
Uint64 m_bufferReadbackWrites = 0;
Uint64 m_imageBinds = 0;
Uint64 m_dispatches = 0;
Uint64 m_memoryBarriers = 0;
Uint64 m_imageCopies = 0;
Uint64 m_storageBlockBindings = 0;
Uint64 m_streamOutputSpans = 0;
Uint64 m_streamOutputControls = 0;
Uint64 m_streamOutputBinds = 0;
Uint64 m_patchParameters = 0;
// ReadPixels' destination. The pixels go into the reply slot, but GLFunctionsTable::
// ReadPixels writes into a caller buffer, so one staging vector per session sits
// between them. Grown, never shrunk, and never handed out past the call.
Vector<Uint8> m_readbackScratch;
// Server-declared LinkTerms.maxReplyBytes, copied from the attached server link.
Uint64 m_maxReplyBytes = 0;
// P5b d1: the multi-draw arrays the glMultiDraw* slots take, rebuilt from the ranges
// per record (rule C: bounded by NumDraws, owned here, never handed out past the call),
// and the last-record witness above.
Vector<GLsizei> m_multiCounts;
Vector<GLint> m_multiFirsts;
Vector<const void*> m_multiOffsets;
Vector<GLint> m_multiBaseVertices;
LastDrawRecord m_lastDraw{};
Uint64 m_drawRecords = 0;
};
} // namespace MobileGL::MG_Record
+10
View File
@@ -180,6 +180,16 @@ namespace MobileGL::MG_Pipe {
bool MGPipeRecordLatched() { return g_recordLatchedHook != nullptr && g_recordLatchedHook(); }
void MGPipeProtocolFatal(const char* what, const char* detail) {
MGPipeRecordFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} %s", what,
detail != nullptr ? detail : "");
}
void MGPipeProtocolFatalAt(const char* what, Uint64 got, Uint64 expected) {
MGPipeRecordFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} got=%llu expected=%llu",
what, static_cast<unsigned long long>(got), static_cast<unsigned long long>(expected));
}
MGPipeRecordFailHook MGPipeRecordFailHookInstalled() { return g_recordFailHook; }
MGPipeRecordLatchHook MGPipeRecordLatchHookInstalled() { return g_recordLatchHook; }
MGPipeRecordLatchedHook MGPipeRecordLatchedHookInstalled() { return g_recordLatchedHook; }
+6
View File
@@ -41,6 +41,8 @@
#include <MG_Pipe/PipeFatalFamily.h>
#include <cstdint>
namespace MobileGL::MG_Pipe {
// THE FAMILIES THAT CROSS THIS BOUNDARY, AND ONLY THOSE. Not `MGFatalFamily` cast to an
@@ -124,6 +126,10 @@ namespace MobileGL::MG_Pipe {
MGPipeRecordLatch(MGFatalFamily family, const char* fmt, ...);
// Whether the calling session already latched (false with no hook).
bool MGPipeRecordLatched();
// The record arm's two ProtocolCorruption lines, worded exactly as the wire codec's
// WireProtocolFatal / WireProtocolFatalAt (so every census and death matcher reads them alike).
[[noreturn]] void MGPipeProtocolFatal(const char* what, const char* detail);
[[noreturn]] void MGPipeProtocolFatalAt(const char* what, ::std::uint64_t got, ::std::uint64_t expected);
#if MOBILEGL_BUILD_RECORD_ARM
// ---- THE LATCH TWIN: A LOST GPU DEVICE ENDS ITS SESSION, NOT THE PROCESS ----------------
+256
View File
@@ -0,0 +1,256 @@
// MobileGL - MobileGL/MG_Pipe/PipeVerbSink.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
// P13 W5: THE VERB SINK AND ITS REPLIES, WITHOUT THE WIRE. These were the wire codec's
// (MG_Remote/Wire/PipeWireCodec.h, which keeps their old names as aliases): the decoder hands each
// validated verb to a sink, and the sink answers through a reply sink. The record arm's in-process
// port does exactly the same in a library that has no wire, so the interfaces live here.
#pragma once
#include <Includes.h>
#include <MG_Pipe/MGPipe.h>
namespace MobileGL::MG_Pipe {
// One tail array. Two of the rows carry two (SetShaderBuffers, SetStreamOutputTargets),
// DrawVbo carries a conditional second one and P5e's SetProgramBindings carries three,
// which is why EncodeRecord's one-tail form could not stay the only one.
struct MGPipeVerbTail {
const void* Bytes = nullptr;
Uint64 Size = 0;
};
// Where a kReplySlot answer goes. Declared HERE and not in Server/ so the codec does not
// depend on the server session: the decoder's job ends at "produce the answer bytes".
//
// The slot is addressed seq % slots and the server writes the seq back into the slot
// header for self-check (table 0's slot header row). Status: 0 = OK, 1 = DECLINED,
// 2 = ERROR. DECLINED IS A REAL ANSWER, not a failure - it is how MapPersistent says
// nullptr (R-6) and how the four Bool acceptance entry points say false (R-5).
class MGPipeReplySink {
public:
virtual ~MGPipeReplySink() = default;
static constexpr Int32 kStatusOk = 0;
static constexpr Int32 kStatusDeclined = 1;
static constexpr Int32 kStatusError = 2;
virtual void PostReply(Uint64 seq, Int32 status, const void* bytes, Uint64 size) = 0;
};
// ---- the verb sink: the five rows with no MGPipeApply* to delegate to ---------------
//
// The decoder owns NO semantics, so every arm ends in an existing MGPipeApply* free
// function - except five, and they are exactly contract §7's class B: Clear (57), Blit
// (56), ReadPixels (58), DrawVbo (59) and Present (67). Those are GLFunctionsTable VERBS.
// MG_Pipe has no applier for any of them (the 37 MGPipeApply* entry points are the object
// and state families), so for these five P5 writes the first consumer as well as the first
// producer - and the consumer is the SERVER'S backend call, which is v1's, not the codec's.
//
// So the codec does what it can prove and stops there: it bounds-checks, cross-checks the
// tail, resolves the segments and hands over a DECODED, VALIDATED argument list. With no
// sink installed those five arms return false ("this build does not implement it"), which
// is the same answer the other unimplemented rows give.
//
// SetShaderBuffers (38) and SetStreamOutputTargets (39) also have no applier entry point,
// and they deliberately get NO sink method: both are off P5's reduced path (BRIEF §4's
// exclusion list), so inventing a consumer for them would be building a semantics nobody
// can test this phase. Their arms validate both tails - which is the part a later phase
// must not have to re-derive - and return false.
struct MGPipeQueryResultReply {
Uint64 Value = 0;
Uint32 Produced = 0;
Uint32 Reserved = 0;
};
static_assert(sizeof(MGPipeQueryResultReply) == 16);
class MGPipeVerbSink {
public:
virtual ~MGPipeVerbSink() = default;
// P11 B2 (CONTRACT-P11.md B2): map_persistent in a session that runs T0. True = the sink
// handled it and `status` is the answer (OK: the client's AHardwareBuffer is now the
// store; DECLINED: the store runs T2); false = not a T0 session, and the codec gives
// today's constant decline. `seq` is the record's, which is what the store's Offer names.
virtual Bool OnMapPersistent(const MGPHandleOnly&, Uint64 /*seq*/, Int32& /*status*/) {
return false;
}
virtual Bool OnFenceCreate(const MGPHandleOnly&) { return false; }
virtual Bool OnFenceDestroy(const MGPHandleOnly&) { return false; }
virtual Bool OnFenceStatus(const MGPHandleOnly&, Uint32&) { return false; }
virtual Bool OnFenceWait(const MGPFenceWait&, Uint32&) { return false; }
virtual Bool OnFenceWaitServer(const MGPFenceWait&) { return false; }
virtual Bool OnQueryCreate(const MGPQueryDesc&) { return false; }
virtual Bool OnQueryBegin(const MGPQueryDesc&) { return false; }
virtual Bool OnQueryEnd(const MGPQueryDesc&) { return false; }
virtual Bool OnQueryCounter(const MGPQueryDesc&) { return false; }
virtual Bool OnQueryAvailable(const MGPHandleOnly&, Uint32&) { return false; }
virtual Bool OnQueryResult(const MGPQueryResultRequest&, MGPipeQueryResultReply&) { return false; }
virtual Bool OnQueryDestroy(const MGPHandleOnly&) { return false; }
virtual Bool OnQueryTimestamp(const MGPTimestampRequest&, Int64&) { return false; }
virtual Bool OnDeleteStreamOutput(const MGPStreamOutputBind&) { return false; }
virtual Bool OnClear(const MGPClear& clear) {
(void)clear;
return false;
}
virtual Bool OnBlit(const MGPBlit& blit) {
(void)blit;
return false;
}
virtual Bool OnPresent(const MGPPresent& present) {
(void)present;
return false;
}
// The pixels go back in the reply slot (contract table 1 row 23: the destination is
// ALWAYS SEG_REPLY in P5, which is why MGPReadbackInfo gains no Seg field), so the
// sink is handed the seq and the sink it must answer into.
virtual Bool OnReadPixels(const MGPReadbackInfo& info, Uint64 seq, MGPipeReplySink* replies) {
(void)info;
(void)seq;
(void)replies;
return false;
}
virtual Bool OnGetTextureImage(const MGPReadbackInfo&, Uint64, MGPipeReplySink*) {
return false;
}
// P9 (CONTRACT-P9.md §1): the same two reads with a pack buffer as the destination. No
// seq and no sink - nothing is answered; the rows land in `Dst` on this side.
virtual Bool OnReadPixelsToBuffer(const MGPReadbackToBuffer&) { return false; }
virtual Bool OnGetTextureImageToBuffer(const MGPReadbackToBuffer&) { return false; }
// `ranges` is info.NumDraws entries. `userIndices` is null unless the record set
// kDrawHasUserIndices; `indirect` is null unless it set kDrawIsIndirect (P5b d1,
// CONTRACT-P5B.md). The layout refuses a record that sets both, so at most one of the
// two is non-null. The span is VALIDATED (all four R-2 arms, the segment-range one
// included) and names a SEG_STAGE run the client staged; the sink resolves it through
// MGPipeHostBytes and never holds the pointer past its return (rule C).
virtual Bool OnDrawVbo(const MGPDrawInfo& info, const MGPDrawRange* ranges,
const MGHostSpan* userIndices,
const MGPDrawIndirect* indirect) {
(void)info;
(void)ranges;
(void)userIndices;
(void)indirect;
return false;
}
// ---- P5b (MG_Remote/CONTRACT-P5B.md): the rows the four migration packages consume.
//
// Every one below is a GLFunctionsTable verb with NO MGPipeApply* entry point - the
// census's correction - so, exactly like the five above, the codec validates and hands
// over and the SERVER'S sink (Server/PipeApplier.cpp's ServerVerbSink) makes the
// backend call. The default bodies return false ("this build does not implement it");
// ServerVerbSink's stubs die Fatal{UnmigratedVerb, "<GL slot>"} by name until the owning
// package lands the real body, so a client that flips a slot ahead of its server half
// aborts with the same line the census greps rather than rendering nothing.
//
// i1 OnLaunchGrid, OnMemoryBarrier, OnResourceCopyRegion, OnBindShaderImage,
// OnSetStorageBlockBinding
// t2 OnBeginStreamOutput, OnEndStreamOutput, OnPauseStreamOutput,
// OnResumeStreamOutput, OnBindStreamOutput, OnPatchParameter
// f1 OnGenerateMipmap, OnCopyFramebufferToTexture (and OnClear's non-Whole kinds)
// d1 OnDrawVbo's indirect tail and user-index span (above)
virtual Bool OnLaunchGrid(const MGPGridInfo& grid) {
(void)grid;
return false;
}
virtual Bool OnMemoryBarrier(const MGPMemoryBarrier& barrier) {
(void)barrier;
return false;
}
virtual Bool OnResourceCopyRegion(const MGPCopyRegion& copy) {
(void)copy;
return false;
}
virtual Bool OnBindShaderImage(const MGPImageBind& bind) {
(void)bind;
return false;
}
// `name` is the NUL-terminated block name the decoder copied out of the record's
// SEG_STAGE blob; valid for the call only.
virtual Bool OnSetStorageBlockBinding(const MGPStorageBlockBinding& binding,
const char* name) {
(void)binding;
(void)name;
return false;
}
virtual Bool OnBeginStreamOutput(const MGPStreamOutputBegin& begin) {
(void)begin;
return false;
}
virtual Bool OnEndStreamOutput(const MGPXfbAccounting& accounting) {
(void)accounting;
return false;
}
virtual Bool OnPauseStreamOutput(const MGPStreamOutputControl& control) {
(void)control;
return false;
}
virtual Bool OnResumeStreamOutput(const MGPStreamOutputControl& control) {
(void)control;
return false;
}
virtual Bool OnBindStreamOutput(const MGPStreamOutputBind& bind) {
(void)bind;
return false;
}
virtual Bool OnPatchParameter(const MGPPatchParameter& patch) {
(void)patch;
return false;
}
virtual Bool OnGenerateMipmap(const MGPMipPlan& plan) {
(void)plan;
return false;
}
virtual Bool OnCopyFramebufferToTexture(const MGPCopyFromFramebuffer& copy) {
(void)copy;
return false;
}
// ---- P5c (MG_Remote/CONTRACT-P5C.md §5): the two control records, opcodes 77..78.
//
// Same hand-over shape as the P5b rows: the codec validates the record (a fixed-size
// POD, no blob, no tail) and the SERVER'S sink does the work - OnApplierReset runs the
// server's own MGPipeApplierReset() after asserting ContextSerial against the
// session's (§5.1: ASSERTED, never dispatched on, P5c has one context per session),
// and OnObjectDeath releases the kind's twin table by the handle the record carried
// (§5.2). The default bodies return false ("this build does not implement it"), so a
// unit decoder without a server declines by the same answer every other unimplemented
// row gives.
virtual Bool OnApplierReset(const MGPApplierReset& reset) {
(void)reset;
return false;
}
virtual Bool OnObjectDeath(const MGPHandleOnly& death) {
(void)death;
return false;
}
// ---- P14 S1 (docs/Disaggregated/design/11-state-ownership.md): bind_context, opcode 84.
//
// The applier_reset hand-over shape, one plane over: a fixed-size POD the bounds gate has
// already proved, no blob, no tail, no reply, so the codec validates nothing further and
// the SERVER'S sink does the work - the session's current context token becomes the
// record's. It is not an assertion like OnApplierReset's serial: the token is minted by
// the client's EGL layer and the server has no independent count of it, so an unknown
// token is the sink's own refusal to make and not a shape the codec can check.
virtual Bool OnBindContext(const MGPBindContext& bind) {
(void)bind;
return false;
}
// ---- shared images, opcode 85 (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md).
// `seq` is the record's reply-slot id, which an Import's descriptor names on the aux
// socket. True fills `reply`; false is a DECLINED answer.
virtual Bool OnSharedImage(const MGPSharedImageOp& op, Uint64 seq,
MGPSharedImageReply& reply) {
(void)op;
(void)seq;
(void)reply;
return false;
}
};
} // namespace MobileGL::MG_Pipe
File diff suppressed because it is too large Load Diff
+15 -153
View File
@@ -45,6 +45,7 @@
#include "../Transport/Ring.h"
#include "../Wire/PipeWireCodec.h"
#include <MG_Backend/Record/RecordVerbSink.h>
#include <deque>
#include <utility>
@@ -147,13 +148,12 @@ namespace MobileGL::MG_Remote::Server {
// a split process is the client's emit table (table 3) - calling THAT here would re-emit
// the record the server is in the middle of applying, which is an infinite loop that
// renders nothing and looks like a hang.
class ServerVerbSink final : public Wire::WireVerbSink {
class ServerVerbSink final : public MG_Record::RecordVerbSink {
public:
// The server's private backend. Null until ServerLoop::CreateBackend has run, and a
// verb that arrives before then declines by name rather than dereferencing.
void SetBackend(MG_Backend::BackendObject* backend);
MG_Backend::BackendObject* Backend() const { return m_backend; }
void SetMaxReplyBytes(Uint64 bytes) { m_maxReplyBytes = bytes; }
// P13 W5: the record verbs (clear, blit, readbacks, draws, dispatch, copies, images,
// XFB, mipmaps) are MG_Record::RecordVerbSink's - the record arm applies them in a
// library without a transport too. What stays here is the session's: fences and
// queries, present, the T0 map, the control records and shared images.
// P11 B2: T0's map_persistent - ServerSession::AdoptStoreT0 (it owns the aux socket).
Bool OnMapPersistent(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status) override;
@@ -181,54 +181,7 @@ namespace MobileGL::MG_Remote::Server {
Bool OnQueryDestroy(const MG_Pipe::MGPHandleOnly&) override;
Bool OnQueryTimestamp(const MG_Pipe::MGPTimestampRequest&, Int64&) override;
void ReleaseQueries();
Bool OnClear(const MG_Pipe::MGPClear& clear) override;
Bool OnBlit(const MG_Pipe::MGPBlit& blit) override;
Bool OnPresent(const MG_Pipe::MGPPresent& present) override;
Bool OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
Wire::ReplySink* replies) override;
Bool OnGetTextureImage(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
Wire::ReplySink* replies) override;
// P9 (CONTRACT-P9.md §1): the pack-buffer halves. Nothing is answered; the tight pixels
// land in record.Dst through the applier's buffer-write gate.
Bool OnReadPixelsToBuffer(const MG_Pipe::MGPReadbackToBuffer& record) override;
Bool OnGetTextureImageToBuffer(const MG_Pipe::MGPReadbackToBuffer& record) override;
Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
const MG_Pipe::MGHostSpan* userIndices,
const MG_Pipe::MGPDrawIndirect* indirect) override;
// ---- P5b (MG_Remote/CONTRACT-P5B.md): one override per row a migration package owns.
// At the contract commit EVERY BODY BELOW IS A STUB that dies
// Fatal{UnmigratedVerb, "<GL slot>"} by the slot's own name - the same line the client's
// class-C table raises and the census greps - so a client flipped ahead of its server
// half aborts by name rather than rendering nothing, and the census on this head is
// unchanged (the client refuses first). The owning package replaces the body.
//
// i1 OnLaunchGrid ("DispatchCompute"), OnMemoryBarrier, OnResourceCopyRegion
// ("CopyImageSubData"), OnBindShaderImage ("BindImageTexture"),
// OnSetStorageBlockBinding ("ShaderStorageBlockBinding")
// t2 LANDED. OnBeginStreamOutput / OnEndStreamOutput / OnPauseStreamOutput /
// OnResumeStreamOutput / OnBindStreamOutput / OnPatchParameter are real bodies
// now: the backend call the contract names, the null-slot DECLINE, a tally.
// f1 OnGenerateMipmap, OnCopyFramebufferToTexture ("CopyTexImage2D" /
// "CopyTexSubImage2D"), and OnClear's four non-Whole kinds (live already)
// d1 OnDrawVbo above: the indirect tail, the user-index span, NumDraws > 1 and the
// instanced arms - LIVE since d1 v1 (every shape the client's nineteen draw slots
// produce dispatches to the backend slot CONTRACT-P5B.md §2 d1 names)
Bool OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) override;
Bool OnMemoryBarrier(const MG_Pipe::MGPMemoryBarrier& barrier) override;
Bool OnResourceCopyRegion(const MG_Pipe::MGPCopyRegion& copy) override;
Bool OnBindShaderImage(const MG_Pipe::MGPImageBind& bind) override;
Bool OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding,
const char* name) override;
Bool OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) override;
Bool OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) override;
Bool OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
Bool OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
Bool OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) override;
Bool OnDeleteStreamOutput(const MG_Pipe::MGPStreamOutputBind& object) override;
Bool OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) override;
Bool OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) override;
Bool OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) override;
// ---- P5c ct (MG_Remote/CONTRACT-P5C.md §5): the two control records ----------------
//
@@ -272,78 +225,19 @@ namespace MobileGL::MG_Remote::Server {
// assert the sequence rather than only the count.
Uint64 ExpectedApplierResetSerial() const { return m_applierResetSerial; }
// Per-verb tallies. The lane asserts these moved, because "the scenario passed" on a
// split build is also what a scenario that ran entirely on the monolith path looks
// like (R-16: a probe may not arm against a stub).
Uint64 Clears() const { return m_clears; }
Uint64 Draws() const { return m_draws; }
Uint64 Readbacks() const { return m_readbacks; }
Uint64 Blits() const { return m_blits; }
// The session's per-verb tallies; the record verbs' are RecordVerbSink's.
Uint64 Presents() const { return m_presents; }
Uint64 LastPresentSerial() const { return m_lastPresentSerial; }
Uint64 ReadbackBytes() const { return m_readbackBytes; }
// P9: pack-buffer readbacks that landed in their buffer (both ops), and the buffer-write
// records they took. Readbacks() above counts only the reply-slot form.
Uint64 BufferReadbacks() const { return m_bufferReadbacks; }
Uint64 BufferReadbackWrites() const { return m_bufferReadbackWrites; }
// ---- P5b package i1's tallies. R-16: a probe may not arm against a stub, and on a
// split build "the scenario passed" is also what a scenario that never left the
// monolith path looks like - so the lane asserts the number that only this sink can
// move. One per wire ROW, not per GL slot: launch_grid carries both dispatch entry
// points and memory_barrier both barrier ones, and the sink is where they separate.
Uint64 ImageBinds() const { return m_imageBinds; }
Uint64 Dispatches() const { return m_dispatches; }
Uint64 MemoryBarriers() const { return m_memoryBarriers; }
Uint64 ImageCopies() const { return m_imageCopies; }
Uint64 StorageBlockBindings() const { return m_storageBlockBindings; }
// ID-49's tight-size control reads this: the scratch a read_pixels grew to. It must equal
// the tight w*h*bpp extent of the read, never the client's DstSize - a scratch sized from
// DstSize is exactly the heap overflow codex 1 found, one field over.
Uint64 ReadbackScratchBytes() const { return static_cast<Uint64>(m_readbackScratch.size()); }
// ---- P5b d1: what the LAST draw_vbo record carried, as the sink saw it ---------------
//
// Recorded BEFORE the backend is consulted, so a process with no backend object (every
// unit case) can still assert the wire's fields rather than only that a draw "was
// declined": the record's head, its first range, its indirect block, and whether a
// user-index span rode with it and how many bytes it named. Nothing here outlives the
// call except these copies (rule C: the pointers the sink was handed are not kept).
struct LastDrawRecord {
MG_Pipe::MGPDrawInfo Info{};
MG_Pipe::MGPDrawRange FirstRange{};
MG_Pipe::MGPDrawIndirect Indirect{};
Uint64 UserIndexBytes = 0;
Bool HadUserIndices = false;
Bool HadIndirect = false;
};
const LastDrawRecord& LastDraw() const { return m_lastDraw; }
// draw_vbo records seen, applied or declined; Draws() above counts only the applied.
Uint64 DrawRecords() const { return m_drawRecords; }
// P5b t2's tallies, for the same reason the five above exist (R-16): under split "the
// scenario passed" is also what a scenario that ran entirely on the monolith path looks
// like, so a lane that wants to say the XFB spans CROSSED has to read a counter the
// server moved. Spans counts Begin and End together - they are one span and a lane that
// saw only one of them has a bug the two-counter version would have hidden behind a
// sum; controls counts Pause and Resume; binds and patch parameters count their own.
Uint64 StreamOutputSpans() const { return m_streamOutputSpans; }
Uint64 StreamOutputControls() const { return m_streamOutputControls; }
Uint64 StreamOutputBinds() const { return m_streamOutputBinds; }
Uint64 PatchParameters() const { return m_patchParameters; }
private:
const MG_Backend::GlobalBackendFunctionsTable* Table(const char* verb) const;
// read_pixels' read, shared by the reply form and the pack-buffer form: the bound read
// framebuffer, NEUTRAL pack state (ID-49), `tight` bytes into m_readbackScratch. False
// when this server has no GL.ReadPixels - the caller owns what that means.
Bool ReadBoundFramebufferTight(const MG_Pipe::MGPReadbackInfo& info, Uint64 tight);
// get_texture_image's read, shared the same way: the whole level into `bytes`.
Bool ReadTextureImageTight(const MG_Pipe::MGPReadbackInfo& image, Vector<Uint8>& bytes);
// P9: `tight` holds Src's pixels, tightly packed; land them in record.Dst at the
// record's layout through MGPipeApplyResourceSubData, one write per contiguous run. The
// layout was checked before the read (PipeApplier.cpp ReadbackToBufferLayoutFault).
Bool LandReadbackInBuffer(const MG_Pipe::MGPReadbackToBuffer& record, Uint8* tight,
Uint64 bytesPerPixel);
// A backend change ends every fence and query this sink holds (they are the old
// backend's natives).
void OnBackendChanged() override;
// The wire's user-index span is checked against this session's segments.
Bool CheckUserIndices(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
const MG_Pipe::MGHostSpan& span) override {
return Wire::CheckDrawUserIndices(info, ranges, span);
}
struct FenceEntry {
Uint32 Gen = 0;
@@ -368,25 +262,8 @@ namespace MobileGL::MG_Remote::Server {
void EndNativeQuery(QueryEntry& entry);
Bool QueryGpuComplete(QueryEntry& entry, Bool wait);
UnorderedMap<Uint32, QueryEntry> m_queries;
MG_Backend::BackendObject* m_backend = nullptr;
Uint64 m_clears = 0;
Uint64 m_draws = 0;
Uint64 m_readbacks = 0;
Uint64 m_blits = 0;
Uint64 m_presents = 0;
Uint64 m_lastPresentSerial = 0;
Uint64 m_readbackBytes = 0;
Uint64 m_bufferReadbacks = 0;
Uint64 m_bufferReadbackWrites = 0;
Uint64 m_imageBinds = 0;
Uint64 m_dispatches = 0;
Uint64 m_memoryBarriers = 0;
Uint64 m_imageCopies = 0;
Uint64 m_storageBlockBindings = 0;
Uint64 m_streamOutputSpans = 0;
Uint64 m_streamOutputControls = 0;
Uint64 m_streamOutputBinds = 0;
Uint64 m_patchParameters = 0;
// P5c ct. m_applierResetSerial is BOTH the expected ContextSerial of the next record
// and the count of accepted resets: the two are one number because the serial is the
// session's own count of applier_reset records (CONTRACT-P5C.md §1: asserted, never
@@ -400,21 +277,6 @@ namespace MobileGL::MG_Remote::Server {
// the object the control plane's CreateContext/DestroyContext also own - one table, one
// owner, and the sink is only the ring's door into it.
Uint64 m_bindContexts = 0;
// ReadPixels' destination. The pixels go into the reply slot, but GLFunctionsTable::
// ReadPixels writes into a caller buffer, so one staging vector per session sits
// between them. Grown, never shrunk, and never handed out past the call.
Vector<Uint8> m_readbackScratch;
// Server-declared LinkTerms.maxReplyBytes, copied from the attached server link.
Uint64 m_maxReplyBytes = 0;
// P5b d1: the multi-draw arrays the glMultiDraw* slots take, rebuilt from the ranges
// per record (rule C: bounded by NumDraws, owned here, never handed out past the call),
// and the last-record witness above.
Vector<GLsizei> m_multiCounts;
Vector<GLint> m_multiFirsts;
Vector<const void*> m_multiOffsets;
Vector<GLint> m_multiBaseVertices;
LastDrawRecord m_lastDraw{};
Uint64 m_drawRecords = 0;
// The newest native fence (shared_image NativeFence) this session handed out, kept so a
// later fence with nothing left to export still answers with a descriptor: by then this
// one has signaled (the backend waited the work out), and a signaled sync_file stays so.
+8 -233
View File
@@ -42,6 +42,7 @@
#include <Includes.h>
#include <MG_Pipe/MGPipe.h>
#include <MG_Pipe/PipeVerbSink.h>
#include "../Transport/Ring.h"
#include "../Transport/Doorbell.h"
@@ -235,13 +236,8 @@ namespace MobileGL::MG_Remote::Wire {
// The catalogue's own spelling of an opcode, for a Fatal line. Out of range is "<opcode>".
const char* WireOpName(MG_Pipe::MGPWireOp op);
// One tail array. Two of the rows carry two (SetShaderBuffers, SetStreamOutputTargets),
// DrawVbo carries a conditional second one and P5e's SetProgramBindings carries three,
// which is why EncodeRecord's one-tail form could not stay the only one.
struct WireTail {
const void* Bytes = nullptr;
Uint64 Size = 0;
};
// P13 W5: the tail is MG_Pipe's (PipeVerbSink.h) - the record arm's in-process port speaks it too.
using WireTail = ::MobileGL::MG_Pipe::MGPipeVerbTail;
// ---- encoder -----------------------------------------------------------------------
//
@@ -473,232 +469,11 @@ namespace MobileGL::MG_Remote::Wire {
// ---- decoder -----------------------------------------------------------------------
// Where a kReplySlot answer goes. Declared HERE and not in Server/ so the codec does not
// depend on the server session: the decoder's job ends at "produce the answer bytes".
//
// The slot is addressed seq % slots and the server writes the seq back into the slot
// header for self-check (table 0's slot header row). Status: 0 = OK, 1 = DECLINED,
// 2 = ERROR. DECLINED IS A REAL ANSWER, not a failure - it is how MapPersistent says
// nullptr (R-6) and how the four Bool acceptance entry points say false (R-5).
class ReplySink {
public:
virtual ~ReplySink() = default;
static constexpr Int32 kStatusOk = 0;
static constexpr Int32 kStatusDeclined = 1;
static constexpr Int32 kStatusError = 2;
virtual void PostReply(Uint64 seq, Int32 status, const void* bytes, Uint64 size) = 0;
};
// ---- the verb sink: the five rows with no MGPipeApply* to delegate to ---------------
//
// The decoder owns NO semantics, so every arm ends in an existing MGPipeApply* free
// function - except five, and they are exactly contract §7's class B: Clear (57), Blit
// (56), ReadPixels (58), DrawVbo (59) and Present (67). Those are GLFunctionsTable VERBS.
// MG_Pipe has no applier for any of them (the 37 MGPipeApply* entry points are the object
// and state families), so for these five P5 writes the first consumer as well as the first
// producer - and the consumer is the SERVER'S backend call, which is v1's, not the codec's.
//
// So the codec does what it can prove and stops there: it bounds-checks, cross-checks the
// tail, resolves the segments and hands over a DECODED, VALIDATED argument list. With no
// sink installed those five arms return false ("this build does not implement it"), which
// is the same answer the other unimplemented rows give.
//
// SetShaderBuffers (38) and SetStreamOutputTargets (39) also have no applier entry point,
// and they deliberately get NO sink method: both are off P5's reduced path (BRIEF §4's
// exclusion list), so inventing a consumer for them would be building a semantics nobody
// can test this phase. Their arms validate both tails - which is the part a later phase
// must not have to re-derive - and return false.
struct QueryResultReply {
Uint64 Value = 0;
Uint32 Produced = 0;
Uint32 Reserved = 0;
};
static_assert(sizeof(QueryResultReply) == 16);
class WireVerbSink {
public:
virtual ~WireVerbSink() = default;
// P11 B2 (CONTRACT-P11.md B2): map_persistent in a session that runs T0. True = the sink
// handled it and `status` is the answer (OK: the client's AHardwareBuffer is now the
// store; DECLINED: the store runs T2); false = not a T0 session, and the codec gives
// today's constant decline. `seq` is the record's, which is what the store's Offer names.
virtual Bool OnMapPersistent(const MG_Pipe::MGPHandleOnly&, Uint64 /*seq*/, Int32& /*status*/) {
return false;
}
virtual Bool OnFenceCreate(const MG_Pipe::MGPHandleOnly&) { return false; }
virtual Bool OnFenceDestroy(const MG_Pipe::MGPHandleOnly&) { return false; }
virtual Bool OnFenceStatus(const MG_Pipe::MGPHandleOnly&, Uint32&) { return false; }
virtual Bool OnFenceWait(const MG_Pipe::MGPFenceWait&, Uint32&) { return false; }
virtual Bool OnFenceWaitServer(const MG_Pipe::MGPFenceWait&) { return false; }
virtual Bool OnQueryCreate(const MG_Pipe::MGPQueryDesc&) { return false; }
virtual Bool OnQueryBegin(const MG_Pipe::MGPQueryDesc&) { return false; }
virtual Bool OnQueryEnd(const MG_Pipe::MGPQueryDesc&) { return false; }
virtual Bool OnQueryCounter(const MG_Pipe::MGPQueryDesc&) { return false; }
virtual Bool OnQueryAvailable(const MG_Pipe::MGPHandleOnly&, Uint32&) { return false; }
virtual Bool OnQueryResult(const MG_Pipe::MGPQueryResultRequest&, QueryResultReply&) { return false; }
virtual Bool OnQueryDestroy(const MG_Pipe::MGPHandleOnly&) { return false; }
virtual Bool OnQueryTimestamp(const MG_Pipe::MGPTimestampRequest&, Int64&) { return false; }
virtual Bool OnDeleteStreamOutput(const MG_Pipe::MGPStreamOutputBind&) { return false; }
virtual Bool OnClear(const MG_Pipe::MGPClear& clear) {
(void)clear;
return false;
}
virtual Bool OnBlit(const MG_Pipe::MGPBlit& blit) {
(void)blit;
return false;
}
virtual Bool OnPresent(const MG_Pipe::MGPPresent& present) {
(void)present;
return false;
}
// The pixels go back in the reply slot (contract table 1 row 23: the destination is
// ALWAYS SEG_REPLY in P5, which is why MGPReadbackInfo gains no Seg field), so the
// sink is handed the seq and the sink it must answer into.
virtual Bool OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq, ReplySink* replies) {
(void)info;
(void)seq;
(void)replies;
return false;
}
virtual Bool OnGetTextureImage(const MG_Pipe::MGPReadbackInfo&, Uint64, ReplySink*) {
return false;
}
// P9 (CONTRACT-P9.md §1): the same two reads with a pack buffer as the destination. No
// seq and no sink - nothing is answered; the rows land in `Dst` on this side.
virtual Bool OnReadPixelsToBuffer(const MG_Pipe::MGPReadbackToBuffer&) { return false; }
virtual Bool OnGetTextureImageToBuffer(const MG_Pipe::MGPReadbackToBuffer&) { return false; }
// `ranges` is info.NumDraws entries. `userIndices` is null unless the record set
// kDrawHasUserIndices; `indirect` is null unless it set kDrawIsIndirect (P5b d1,
// CONTRACT-P5B.md). The layout refuses a record that sets both, so at most one of the
// two is non-null. The span is VALIDATED (all four R-2 arms, the segment-range one
// included) and names a SEG_STAGE run the client staged; the sink resolves it through
// MG_Pipe::MGPipeHostBytes and never holds the pointer past its return (rule C).
virtual Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
const MG_Pipe::MGHostSpan* userIndices,
const MG_Pipe::MGPDrawIndirect* indirect) {
(void)info;
(void)ranges;
(void)userIndices;
(void)indirect;
return false;
}
// ---- P5b (MG_Remote/CONTRACT-P5B.md): the rows the four migration packages consume.
//
// Every one below is a GLFunctionsTable verb with NO MGPipeApply* entry point - the
// census's correction - so, exactly like the five above, the codec validates and hands
// over and the SERVER'S sink (Server/PipeApplier.cpp's ServerVerbSink) makes the
// backend call. The default bodies return false ("this build does not implement it");
// ServerVerbSink's stubs die Fatal{UnmigratedVerb, "<GL slot>"} by name until the owning
// package lands the real body, so a client that flips a slot ahead of its server half
// aborts with the same line the census greps rather than rendering nothing.
//
// i1 OnLaunchGrid, OnMemoryBarrier, OnResourceCopyRegion, OnBindShaderImage,
// OnSetStorageBlockBinding
// t2 OnBeginStreamOutput, OnEndStreamOutput, OnPauseStreamOutput,
// OnResumeStreamOutput, OnBindStreamOutput, OnPatchParameter
// f1 OnGenerateMipmap, OnCopyFramebufferToTexture (and OnClear's non-Whole kinds)
// d1 OnDrawVbo's indirect tail and user-index span (above)
virtual Bool OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) {
(void)grid;
return false;
}
virtual Bool OnMemoryBarrier(const MG_Pipe::MGPMemoryBarrier& barrier) {
(void)barrier;
return false;
}
virtual Bool OnResourceCopyRegion(const MG_Pipe::MGPCopyRegion& copy) {
(void)copy;
return false;
}
virtual Bool OnBindShaderImage(const MG_Pipe::MGPImageBind& bind) {
(void)bind;
return false;
}
// `name` is the NUL-terminated block name the decoder copied out of the record's
// SEG_STAGE blob; valid for the call only.
virtual Bool OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding,
const char* name) {
(void)binding;
(void)name;
return false;
}
virtual Bool OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) {
(void)begin;
return false;
}
virtual Bool OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) {
(void)accounting;
return false;
}
virtual Bool OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) {
(void)control;
return false;
}
virtual Bool OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) {
(void)control;
return false;
}
virtual Bool OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) {
(void)bind;
return false;
}
virtual Bool OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) {
(void)patch;
return false;
}
virtual Bool OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) {
(void)plan;
return false;
}
virtual Bool OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) {
(void)copy;
return false;
}
// ---- P5c (MG_Remote/CONTRACT-P5C.md §5): the two control records, opcodes 77..78.
//
// Same hand-over shape as the P5b rows: the codec validates the record (a fixed-size
// POD, no blob, no tail) and the SERVER'S sink does the work - OnApplierReset runs the
// server's own MGPipeApplierReset() after asserting ContextSerial against the
// session's (§5.1: ASSERTED, never dispatched on, P5c has one context per session),
// and OnObjectDeath releases the kind's twin table by the handle the record carried
// (§5.2). The default bodies return false ("this build does not implement it"), so a
// unit decoder without a server declines by the same answer every other unimplemented
// row gives.
virtual Bool OnApplierReset(const MG_Pipe::MGPApplierReset& reset) {
(void)reset;
return false;
}
virtual Bool OnObjectDeath(const MG_Pipe::MGPHandleOnly& death) {
(void)death;
return false;
}
// ---- P14 S1 (docs/Disaggregated/design/11-state-ownership.md): bind_context, opcode 84.
//
// The applier_reset hand-over shape, one plane over: a fixed-size POD the bounds gate has
// already proved, no blob, no tail, no reply, so the codec validates nothing further and
// the SERVER'S sink does the work - the session's current context token becomes the
// record's. It is not an assertion like OnApplierReset's serial: the token is minted by
// the client's EGL layer and the server has no independent count of it, so an unknown
// token is the sink's own refusal to make and not a shape the codec can check.
virtual Bool OnBindContext(const MG_Pipe::MGPBindContext& bind) {
(void)bind;
return false;
}
// ---- shared images, opcode 85 (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md).
// `seq` is the record's reply-slot id, which an Import's descriptor names on the aux
// socket. True fills `reply`; false is a DECLINED answer.
virtual Bool OnSharedImage(const MG_Pipe::MGPSharedImageOp& op, Uint64 seq,
MG_Pipe::MGPSharedImageReply& reply) {
(void)op;
(void)seq;
(void)reply;
return false;
}
};
// P13 W5: the reply sink and the verb sink are MG_Pipe's (PipeVerbSink.h): the record arm
// applies the same verbs in a library without a transport, through the same interface.
using ReplySink = ::MobileGL::MG_Pipe::MGPipeReplySink;
using QueryResultReply = ::MobileGL::MG_Pipe::MGPipeQueryResultReply;
using WireVerbSink = ::MobileGL::MG_Pipe::MGPipeVerbSink;
// Not thread safe: one decoder on the apply thread, by construction.
class PipeWireDecoder {