50 KiB
P5 scout — the caps snapshot, the reply slot, and the reverse channel
Tree read: /home/swung/w7/pipe (WSL), branch feat/disaggregated @ a29807cc (P4a landed), read over
//wsl.localhost/Arch/.... Nothing was written, built, or run in that tree. Every line number below was
opened. Repo-relative paths are under MobileGL/ unless the path starts with docs/, tools/, scripts/
or ~/w7/.
Background read first: docs/Disaggregated/ARCHITECTURE.md §3.3, §4, §8, §10, §11, §13.1, §15.1;
docs/Disaggregated/ROADMAP.md:21 (the P5 row) and open questions 1-2 (:80-85);
~/w7/notes/p4a/INTEGRATOR-DECISIONS.md ID-1..57.
One-sentence summary of the whole subject. Of the five things P5's row names in my area, exactly one
has a live producer and a live consumer today (OnGpuWritten / OnBufferWriteback, DirectGLES-only);
MGPCaps, MGPReplySlot and the other eight callbacks are declared-and-asserted but have zero call
sites in the whole tree — P5 is their first implementation, not their migration.
PART 1 — MGPCaps: the startup snapshot
1.1 Where it is defined
| thing | file:line | notes |
|---|---|---|
enum MGPCapBit : Uint64 |
MG_Pipe/MGPipeTypes.h:108-124 |
9 bits: kCapNone, kCapViewportArray, kCapFloat64VertexAttrib, kCapResidentSubData, kCapCpuXfbPrimitiveAccounting, kCapTimerQuery, kCapOcclusionQuery, kCapXfbPrimitivesQuery, kCapNeedsHostIndexBytes, kCapNeedsHostUboBytes. (That is 8 real caps + kCapNone; kCapNeedsHostUboBytes is 1<<8.) |
struct MGPCaps |
MG_Pipe/MGPipeTypes.h:126-139 |
four members: DynamicBackendParameters Dynamic (:132), Uint64 CallMask (:133), MGPBlobRef FormatCapabilities (:137), MGPBlobRef RendererInfo (:138) |
| size assert | MGPipeTypes.h:140, :145-146 |
stated as a composition, not a literal: sizeof(MGPCaps) == sizeof(DynamicBackendParameters) + 8 + 24 + 24, because DynamicBackendParameters still carries SizeT members and is ABI-dependent until P0.5's fixed-width move (which has not happened — see 1.6/risk R1) |
| field list for G4/verify | MG_Pipe/PipeFields.def:41-42 (MGP_FIELDS_MGPCaps(F) = F(Dynamic) F(CallMask) F(FormatCapabilities) F(RendererInfo)), registered in the POD list at PipeFields.def:338 |
|
| catalogue row | MG_Pipe/PipeCalls.def:80 — X(GetCaps, MGPCaps, kScreen, kReplySlot), opcode 1 (the wire opcode is the 1-based position in MGP_CALL_LIST, PipeCalls.def:29) |
|
| generated table slot | MG_Pipe/generated/PipeTables.inc:18 — void (*GetCaps)(const MGPCaps* payload, MGPReplySlot* reply); |
|
| generated monolith thunk | MG_Pipe/generated/PipeThunks.inc:20-22 — gMGPipeScreen.GetCaps(payload, reply) |
|
| generated wire record | MG_Pipe/generated/PipeWire.inc:123-126 |
|
| generated verify | MG_Pipe/generated/PipeVerify.inc:53, :133, :340-341 |
DynamicBackendParameters itself is MG_Backend/BackendObject.h:316-549 (the struct ends at :549;
GpuVendorKind GpuVendor at :548 is the last member, enum class WindowBackend follows at :551).
That is ~90 scalars plus the six per-axis compute limits MaxComputeWorkGroupCount[3] /
MaxComputeWorkGroupSize[3] and one method pair (PerLayerFramebufferAttachmentBit /
SupportsPerLayerFramebufferAttachment) — POD-safe, methods are constexpr/const.
MGPipeTypes.h:28 records the deliberate coupling: MGPCaps embeds DynamicBackendParameters
by inclusion, so that a caps field added in BackendObject.h needs no second edit.
1.2 Who fills it, and when
Today nobody fills it. MGPCaps has no producer and no consumer anywhere:
gMGPipeScreen / gMGPipeContext are declared at MG_Pipe/MGPipe.h:137-138 and are read by exactly one
place in the tree, MG_Test/Pipe/PipeCatalogueTest.cpp:107 and :111 (which walk them as arrays of
function pointers to assert the table arity). No backend installs either table; the landed P3a/P4a wiring
goes through the free functions MGPipeApply* in MG_Pipe/PipeApply.h instead, and PipeApply.h
declares no MGPipeApplyGetCaps. (Full list of the 37 MGPipeApply* entry points that do exist:
PipeApply.h:740-1052; no caps, no read_pixels, no fence, no query.)
What does fill the underlying state, and when:
BackendObject::InitCapabilities()is pure virtual atBackendObject.h:573and is called lazily, from insideBackendObject::MakeEGLCurrentatMG_Backend/BackendObject.cpp:341-347, guarded bym_backendCapabilitiesInitialized(:341, set at:346). It is not called fromMG_Backend::Init().- The latch is cleared by
BackendObject::ResetEGLRuntimeState()(BackendObject.cpp:359, clear at:362), which runs on surface release/teardown from three sites (:429,:461,:467). SoInitCapabilitiescan run more than once per process — at most once per surface lifetime, exactly asARCHITECTURE.md§13.1 says ("surface 生命周期与首次MakeCurrent+InitCapabilities每 surface 至多一次"). - DirectGLES:
BackendObject_DirectGLES::InitCapabilities()atMG_Backend/DirectGLES/BackendObject_DirectGLES.cpp:853-874—FillInGLESCapabilities→SetGLESCapabilities→UpdateAdvertisedCapabilityExtensions→UpdateDynamicBackendParameters→PopulateFormatCapabilities→PrintFormatCapabilities. - DirectVulkan:
BackendObject_DirectVulkan::InitCapabilities()atMG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp:371-395—QueryVulkanCapabilities→UpdateDynamicBackendParameters→UpdateAdvertisedExtensions→MGB_CTX->InvalidateCompileEnv()(:390, guarded byMGB_CTX_LIVE) →PopulateFormatCapabilities.
The ordering trap P5 must design around (BackendObject_DirectGLES.cpp:780-792, verbatim in the
comment): LogBackendInfo() (MG_Backend/Init.cpp:15) reads
pActiveBackendObject->GetRendererInfo() at MG_Backend/Init.cpp:21 during MG_Backend::Init()
(Init.cpp:48, LogBackendInfo() called at :69) — i.e. before any ES/Vulkan context exists and
before InitCapabilities has run. Both backends therefore build a provisional extension list in
their static initializer and reconcile it at the end of InitCapabilities
(UpdateAdvertisedCapabilityExtensions, BackendObject_DirectGLES.cpp:793-799;
BackendObject_DirectVulkan::UpdateAdvertisedExtensions, :793). Under split, MG_Backend::Init() is
the single hook where BackendObject_Remote is installed (MG_Backend/Init.cpp:48-70), and
LogBackendInfo() will call GetRendererInfo() on it before the server has a surface. Two readings,
and the brief must pick one:
(a) BackendObject_Remote::GetRendererInfo() returns an empty/placeholder RendererInfo until the
first CapsSnapshot arrives, and P5 accepts one wrong log line at startup; or
(b) MG_Backend::Init() is restructured so LogBackendInfo() is deferred to first-MakeCurrent.
Evidence that would settle it: whether anything other than MGLOG_I reads GetRendererInfo()
before the first eglMakeCurrent. I checked: in non-test code the only pre-MakeCurrent reader is
LogBackendInfo (Init.cpp:21); every other reader is behind a GL entry point (list in 1.3), so (a)
is cheap. Guess: (a) is right, but I did not trace the FCL/launcher startup order.
1.3 Who reads caps on the client today — the exact enumeration
Counts are of non-test, non-MG_Backend sites (MG_Impl + MG_State + MG_Util + Init.cpp):
| accessor | sites | where |
|---|---|---|
GetDynamicParameters() |
40 | GLImpl/Getter/GL_Getter.cpp 19 · GLImpl/Texture/GL_Texture.cpp 6 · GLImpl/Framebuffer/GL_Framebuffer.cpp 4 · GLImpl/Program/GL_Program.cpp 4 · GLImpl/{Buffer,Framebuffer,Texture,VertexArray}/Validators.cpp 1 each · GLImpl/VertexArray/GL_VertexArray.cpp 1 · MG_Util/ShaderTranspiler/CompileEnv.cpp 1 · MG_Util/ShaderTranspiler/ShaderCompiler.cpp 1 |
GetRendererInfo() |
7 | GL_Getter.cpp:596, :654, :2400 · GL_Program.cpp:1424 · GL_Query.cpp:77 · GL_Texture.cpp:5903 · CompileEnv.cpp:124 |
GetFormatCapabilities() |
4 | GL_Framebuffer.cpp:127, :784 · GL_Texture.cpp:570, :6855 |
GetBackendType() |
3 | GL_Framebuffer.cpp:47 · GL_Texture.cpp:6536 · CompileEnv.cpp:122 |
GetBackendAPIVersionString() |
2 | GL_Getter.cpp 1 · MG_Util/SelfTest/DriverPost.cpp 1 (the POST probe — server-side under split, ARCHITECTURE.md open question 7) |
That is 56 client caps read points (the doc's "40 pActiveBackendObject-> 站点 + 89 caps 读点" in
ARCHITECTURE.md:116 counts differently — it counts field reads inside those sites, e.g. GL_Getter.cpp:867
binds one dynamicParameters reference and then reads many members. Do not use 40+89 as a work estimate;
40 is the number of GetDynamicParameters() call expressions, and it coincides with the doc's 40 by
accident of arithmetic. Guess: the doc's 40 means the pActiveBackendObject-> expressions and the 89
means individual field reads; I did not count the 89.)
Two hot call sites are worth naming because they bind a reference and read many fields at once:
GL_Getter.cpp:2400-2401 (glGetFloatv/glGetIntegerv scalar family; binds both rendererInfo
and dynamicParameters) and MG_Util/ShaderTranspiler/CompileEnv.cpp:120-124
(env->backend = GetBackendType(), env->params = GetDynamicParameters() — a whole-struct copy —
and env->advertisedExtensions = GetRendererInfo().RendererGLInfo.Extensions).
1.4 What MUST be in the snapshot (a split client cannot call the driver)
Everything below is client-consumed and device-answered. I list the carrier that already exists.
DynamicBackendParameters(whole struct, by value).MGPCaps::Dynamic(MGPipeTypes.h:132). Consumers: the 40 sites above. Non-negotiable —CompileEnv.cpp:123copies the whole struct into the compile env, so a partial snapshot is not an option.RendererInfo=RendererName,BackendName,Optional<String> ExtraVendor,GLInfo RendererGLInfo{TargetGLVersion, TargetGLSLVersion, Vector<GLExtension> Extensions, IsCompatibilityProfile},StaticBackendCapabilityT{AllowVSOnlyPrograms}(MG_Util/Types.h:317,:321,:328-334). Not POD (threeStrings + aVector) → travels as theMGPCaps::RendererInfoblob (MGPipeTypes.h:138). Consumers:glGetString(GL_Getter.cpp:596),glGetStringi(GL_EXTENSIONS)(GL_Getter.cpp:654),glGetIntegervscalar family (:2400),AllowVSOnlyProgramsat link (GL_Program.cpp:1424), timer-query gating inglGenQueries/glQueryCounter(GL_Query.cpp:77), anisotropy/GL_EXT_texture_filter_anisotropicinglTexParameter(GL_Texture.cpp:5903), and the compile env's advertised extension list (CompileEnv.cpp:124) — which feeds glslang phase A/B, which stays on the client (ARCHITECTURE.mdopen question 7).FormatCapabilityCache(BackendObject.h:93-98) =FullCaps+CaveatCaps(FormatCapabilityTable,BackendObject.h:88) +SampleCounts(FormatSampleCountTable,BackendObject.h:90— aVector<Int>per (target, format) pair, which is whyMGPipeTypes.h:134-136says it must be a blob). Dimensions:kFormatCapabilityTargetCount = TextureTargetCount + 1(BackendObject.h:81-83) ×kFormatCapabilityFormatCount = TextureInternalFormatCount(:84). Consumers:GL_Framebuffer.cpp:127(renderable-format check inCheckCompleteness's helper),GL_Framebuffer.cpp:784andGL_Texture.cpp:570(GL_SAMPLES/GL_NUM_SAMPLE_COUNTSanswers),GL_Texture.cpp:6855. Serialization shape is P5's to invent —MGPipeTypes.h:136says in as many words "Their serializers land with the transport (P5)".BackendType(3 sites). Already implied byHello.backendType(protocol.fbs:57-64) but the client reads it throughpActiveBackendObject->GetBackendType();BackendObject_Remotemust answer it locally.GL_Texture.cpp:6536uses it to pick theGetTextureImagepath,GL_Framebuffer.cpp:47to pick the distinct-depth-stencil path,CompileEnv.cpp:122to key the compile env.GetBackendAPIVersionString()(1 client site inGL_Getter.cpp). Already in the control-plane schema asCapsSnapshot.apiVersion(protocol.fbs:91).CallMask(MGPipeTypes.h:133) — replaces "is this table slot null" as the feature probe (ARCHITECTURE.md:114). The schema already reservesCapsSnapshot.tableSlotMask: ulong(protocol.fbs:94) with the comment "which GLFunctionsTable slots the peer registered". Note the mismatch:CallMaskis documented asMGPCapBitbits (8 of them), buttableSlotMaskis documented as table slots. Two readings: (i) they are the same field and the schema comment is stale; (ii) they are two different masks and both are needed (cap bits + registered-slot bitmap). Evidence that would settle it:GL_Drawing.cpp:283's live use of "slot non-null" as a capability probe, andARCHITECTURE.md:114's sentence "CallMask取代『槽位是否为 null』这个隐式能力探测" — which reads as (i). Guess: (i), and the brief should say so explicitly, because 71 catalogue rows do not fit in the 8 cap bits and someone will otherwise build both.- The six compute limits ride inside
Dynamic(BackendObject.h:381-392); the schema also carriesmaxComputeWorkGroupCount/Sizeas separate[int]arrays (protocol.fbs:92-93) — redundant with (1), andAdvertisedLimitsScenario.ComputeWorkGroupLimitsAreTheCapsBlocksAnswer(MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp:580-623, harnessMG_IntegrationTest/Harness/BackendCapsPeek.{h,cpp}) is the existing gate that pins the caps copy andglGetIntegeri_vto one number. Reuse it as P5's caps gate — it already runs on every lane. prefersCpuXfbPrimitiveAccounting(protocol.fbs:95) — the schema's spelling ofkCapCpuXfbPrimitiveAccounting; same redundancy question as (6).
Already present in the control plane (do not re-invent): protocol.fbs:86-96
table CapsSnapshot { dynamicParameters: [ubyte]; rendererInfo: [ubyte]; formatCaps: [ubyte]; extensions: [string]; apiVersion: string; maxComputeWorkGroupCount: [int]; maxComputeWorkGroupSize: [int]; tableSlotMask: ulong; prefersCpuXfbPrimitiveAccounting: bool; }, with the header comment at
protocol.fbs:83-85 ("The three blobs are byte-for-byte images of the corresponding POD structs; they are
versioned by structSize-first discipline, not by this schema"). It is a CtrlMsg union member
(protocol.fbs:220) — i.e. the snapshot travels on the CTRL socket, not in SEG_CMD, and the
GetCaps catalogue row (PipeCalls.def:80) is the in-process/monolith spelling of the same answer.
protocol.fbs:98-104 also already has table DefaultFramebufferInfo and SurfaceReply.defaultFb
(:142-148) — that is the OnSurfaceChanged payload's control-plane twin.
1.5 What a caps snapshot CANNOT answer
- Caps invalidation is an event, not a value.
GLContext::GetCompileEnv()(MG_State/GLState/Core.cpp:33-43) memoises the compile env and re-captures it when the raw pointerMG_Backend::pActiveBackendObject.get()changes (Core.cpp:34,m_compileEnvBackend). Under split that pointer is the client's single long-livedBackendObject_Remoteand never changes, so a server-sideInitCapabilitiesre-run (surface recreate,ResetEGLRuntimeState→ nextMakeEGLCurrent) would leave the client's compile env, its preprocess memos and its advertised-extension list stale with nothing saying so. This is exactly whatOnCapsInvalidatedis for (MG_Pipe/MGPipeCallbacks.h:45), and its two producers today areBackendObject_DirectVulkan.cpp:390and:788. DirectGLES has zero producers — it re-runsUpdateAdvertisedCapabilityExtensions+UpdateDynamicBackendParametersatBackendObject_DirectGLES.cpp:865-871and tells the frontend nothing, because in monolith the frontend reads the same object. Under split that silence becomes a bug. P5 either (a) ships the caps snapshot again on everyInitCapabilitiesand treats re-arrival as invalidation, or (b) adds a DirectGLESInvalidateCompileEnvcall to match DirectVulkan. (a) is the smaller diff and does not touchdev-shaped backend code — recommend (a). - Default-framebuffer geometry is not caps.
pDefaultFramebufferInfois written by the backends intoMG_Impl(DirectGLES.cpp:11467PublishDefaultFramebufferDepthStencilFormat;DirectVulkan/Renderer/SwapchainObject.cpp:276-295, which even callsTextureObject2D::AllocateStorageon a frontend object). It changes per surface/resize, so it isOnSurfaceChanged(MGPipeCallbacks.h:43, payloadMGPSurfaceInfoatMGPipeTypes.h:1321-1328) /SurfaceReply.defaultFb— never the snapshot. NotekMGPipeDefaultFramebuffer = {0,1}(MG_Pipe/MGPipeHandles.h:73) already exists to retire the fourpDefaultFramebufferInfo->defaultFBOidentity compares in DirectGLES (DirectGLES.cpp:2846,:3137,:4370,:4414). - Per-format sample counts have a second, non-caps source.
ClampSamplesToBackendSupport(BackendObject_DirectGLES.cpp:806-828) falls back toGetGLESFormatMaxSamples(g_GLESCapabilities, ...)when the probedSampleCountsvector is empty (:820-826). That fallback reads a backend-private capabilities struct. It is a backend-side function so it stays server-side, but if any client path ever needs the clamped answer it cannot get it fromMGPCapsalone. - Live GL state dressed as a "cap".
GL_Getter.cpp:531-545floors backend limits against frontend constants, andGL_Getter.cpp:267-269only consults the backend when the currently bound buffer target isShaderStorage. These are fine (the flooring is client-side arithmetic over snapshot values) — flagged only so a reviewer does not mistake them for driver round trips. GetBackendFunctions()(BackendObject.h:592) has zero client-side call sites outsideMG_Backend(verified). The client's copygBackendFunctionsTableis assigned once atMG_Backend/Init.cpp:44; under split it must be either a table of emitters or all-null withCallMaskanswering instead.
1.6 Risks for the brief
- R1 (real, blocks the wire).
MGPCaps's size assert is a composition precisely becauseDynamicBackendParameterscontainsSizeT(BackendObject.h:317,:321,:546) andGLenum(:445-446).ARCHITECTURE.mdsaid P0.5 would move the caps block intoMGPipeValueTypes.hwith fixed-width members; that has not happened (MGPipeTypes.h:141-144still says "until P0.5 moves…"). A byte-for-byte blob of a struct withSizeTmembers is not portable client↔server if the two ever differ in word size. Forinproc(P5) this is harmless; forspawn(P6) on the same machine it is also harmless. Recommend: P5 asserts same-sizeofin theHello/Welcomehandshake (protocol.fbs:57-75,abiMajor/abiMinor/buildFingerprint) and defers the fixed-width rewrite. - R2.
MGPCapsiskScreen+kReplySlotin the catalogue but the real snapshot is a CTRL-socketCapsSnapshotmessage. Two mechanisms for one answer; the brief should state which oneinprocuses (guess: the CTRL one, becauseInProcessTransportmust run the same G3 codec as spawn and the handshake is the same code path).
PART 2 — kReplySlot and MGPReplySlot
2.1 The flag and the type
kReplySlot = 1u << 4(MG_Pipe/MGPipe.h:50), documented as "Answers into anMGPReplySlot; never blocks." The legend in the catalogue header isPipeCalls.def:18.struct MGPReplySlot { Uint64 Id; },MGP_ASSERT_POD(MGPReplySlot, 8)—MG_Pipe/MGPipeTypes.h:76-81. The comment at:76-77: "Where an asynchronous answer lands. Every server query in this catalogue is async-with-handle; none of them blocks (section 4.4.6, 'the total rule')." Field listPipeFields.def:32-33.- It is 8 bytes and one opaque
Id— there is no status, no size, no segment. Everything about where the bytes are is P5's to define. The obvious home isSEG_REPLY(server-owned, 8 MiB, 4 KiB slots —ARCHITECTURE.md§11.1;SegmentKind.Reply = 3atprotocol.fbs:40;Welcome.replyPoolatprotocol.fbs:73).
2.2 The ten calls that carry it
All from PipeCalls.def; opcode = 1-based position in MGP_CALL_LIST starting at :80.
| op | call | payload | class | flags | line |
|---|---|---|---|---|---|
| 1 | GetCaps |
MGPCaps |
kScreen |
kReplySlot |
:80 |
| 5 | MapPersistent |
MGPHandleOnly |
kScreen |
kReplySlot|kOptional |
:84 |
| 8 | FenceStatus |
MGPHandleOnly |
kScreen |
kReplySlot |
:87 |
| 9 | FenceWait |
MGPFenceWait |
kScreen |
kReplySlot |
:88 |
| 15 | QueryAvailable |
MGPHandleOnly |
kCtxQuery |
kReplySlot |
:94 |
| 16 | QueryResult |
MGPQueryResultRequest |
kCtxQuery |
kReplySlot |
:95 |
| 55 | ResourceReadback |
MGPReadback |
kCtxObject |
kReplySlot |
:138 |
| 58 | GetTextureImage |
MGPReadbackInfo |
kCtxObject |
kReplySlot |
:141 |
| 61 | ReadPixels |
MGPReadbackInfo |
kCtxVerb |
kReplySlot |
:145 |
| 69 | QueryTimestamp |
MGPTimestampRequest |
kCtxQuery |
kReplySlot |
:160 |
(I derived the opcodes by position; the brief should re-derive rather than trust me on the exact numbers — the set of ten rows is what matters and is exact.)
Generated artefacts for each: PipeTables.inc:18,22,25,26,37,38,75,78,81,92;
PipeThunks.inc:20,36,48,52,72,76,224,236,248,292.
2.3 How the answer gets back in monolith today — it does not use the reply slot at all
MGPReplySlot has zero producers and zero consumers outside the generated tables and the field-list
macro. The two reply-slot calls that are actually implemented today return their answers as ordinary
C++ return values from the free-function applier:
void* MGPipeApplyMapPersistent(const MGPHandleOnly& handle, Uint64 size, const void* seedBytes);—PipeApply.h:917, definedPipeApply.cpp:1893-1921. Returns the coherent host pointer, ornullptrfor a DECLINE, which is a real answer (PipeApply.cpp:1913-1919, and the same sentence atPipeApply.h:914-916: "a real answer and the reason the call iskOptionalas well askReplySlot"). Client caller:MGPipeEmitMapPersistent,MG_Impl/Pipe/PipeFill.cpp:772-789(returns the pointer straight up the stack).void MGPipeApplyResourceReadback(const MGPReadback& record);—PipeApply.h:908. Returns nothing; the bytes come back through the reverse channel instead, viaOnBufferWriteback(PipeApply.h:905-907, and the producer atMG_Backend/DirectGLES/Managers.cpp:2135-2139). The client emitterMGPipeEmitResourceReadback(PipeFill.cpp:754-768) says so at:764-766.
The other eight reply-slot calls (GetCaps, FenceStatus, FenceWait, QueryAvailable,
QueryResult, GetTextureImage, ReadPixels, QueryTimestamp) have no applier at all — they are
still served by MG_Backend::gBackendFunctionsTable.GL.* directly from MG_Impl. Example:
ReadPixels_Backend at MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp:3093-3096 is
MGP_FILL(ReadPixels); gBackendFunctionsTable.GL.ReadPixels(...) — a synchronous call with an out
pointer into the application's memory.
Consequence for the brief. P5 is the first phase that has to give MGPReplySlot a meaning, and it
has to answer three questions that nothing in the tree answers today:
- Who allocates the
Id? The slot pool is server-owned (SegmentKind.Reply), but the record carrying the request is client-produced. Most likely the client mints a monotonicIdand the server writes intoSEG_REPLY[Id % slots]; nothing in the tree says so. Guess. - How does the client know the answer landed?
ARCHITECTURE.md§11.7 namesEvReadbackDone,EvQueryResult,EvFenceSignaledasSEG_EVENTrecords — i.e. the reply slot carries the payload and the event ring carries the completion. No code exists for either. MapPersistent's decline must remain expressible: the answer is a pointer ornullptr, andnullptrmeans "declined", not "failed" (PipeApply.cpp:1913-1916). A reply-slot encoding that cannot distinguish "not yet" from "declined" will hangMGPipeEmitMapPersistent, which returns its value synchronously intoBufferObjectstorage definition.MapPersistentis the one reply-slot call whose client caller genuinely cannot proceed without the answer, which is whyARCHITECTURE.md§13.1 listsMapPersistentunder "不可避免" (T1 tier, once per storage definition) and whyMOBILEGL_IPC_ADOPT_TIER=2(decline) is the permanent correct fallback (§12).
Also note: kNeedsAck is a different mechanism from kReplySlot. Only ResourceRespecify
carries kNeedsAck (PipeCalls.def:82), and only when the per-record predicate
MGPipeResourceRespecifyNeedsAck(desc) says so — MG_Pipe/MGPipeTypes.h:680, pinned by
MG_Test/Pipe/PipeCatalogueTest.cpp:552-585
(ResourceRespecifyAcksOnlyImmutableStorage). ARCHITECTURE.md:298 says in as many words: "monolith
下 ack 是 ((void)0)… P5 把门铃接到这个谓词上", and P4a narrowed the predicate to buffer targets
(D-A2). So P5 owns: the doorbell on that predicate, and the reply-slot mechanism, and they are separate.
PART 3 — Blocking read_pixels
3.1 The current path, end to end
glReadPixels (MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp:3099)
└─ ReadPixels_State(...) :2924-3091 pure frontend validation
└─ ReadPixels_Backend(...) :3093-3096
├─ MGP_FILL(ReadPixels) :3094 fills the kReadback class (18 fields)
└─ gBackendFunctionsTable.GL.ReadPixels :3095 -> the backend, synchronous
glReadnPixels is the same two calls with a size bound (GL_Framebuffer.cpp:3135-3152; validation at
:3143, backend at :3151).
The DirectGLES implementation is DirectGLES.cpp:10851-11000:
:10858-10859picks native vs. converted readback;:10926-10929depth/stencil special paths;:10936-10937MGB_CTX->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject()— the PACK PBO, read from the context inside the backend;:10952ScopedPixelPackBuffer packBufferBinding(packBufferId)— the driver binding is scoped so it returns to 0 on every exit;:10955glReadPixels;:10982-10997— the blocking half. When a PBO was used, the backend immediatelyglMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, size, GL_MAP_READ_BIT)(:10985-10986),pixelPackBufferObject->WritebackFromBackend({pboMappedPtr, size}, 0)(:10989),BufferImpl::BumpBufferMutationEpoch()(:10991),glUnmapBuffer(:10993).
There are five MGB_CTX->GetBufferBindingSlot(BufferTarget::PixelPack) reads in DirectGLES:
:9407 (inside StoreReadbackRowsToClient, DirectGLES.cpp:9397 — the per-scanline writeback loop,
:9418), :10389 (ReadPixelsViaFormatConversion, :10371), :10626
(GetTexImageViaShadowConversion, :10604), :10936 (ReadPixels), :11362 (GetTextureImage).
DirectVulkan has its own at Renderer/VulkanRenderer.cpp:10988-11004 (ReadDepthStencilPixels, also a
per-row WritebackFromBackend loop at :11000). DirectGLES Utils.cpp:2343 is a sixth per-row
writeback.
MGPReadbackInfo (MGPipeTypes.h:1197-1205, 64 B) is the payload already minted for this:
MGPipeHandle Res — :1198 says "null for read_pixels: the bound read surface answers" — plus
MGPBox Box, Format, Type, Target, Level, DstOffset, DstSize.
3.2 What the split version must block on
It must block on exactly one thing: the pixels, and only when the destination is client memory.
ARCHITECTURE.md §13.1 puts it in the "不可避免" list in those words: "glReadPixels → 客户内存(像素进
SEG_REPLY,逐行写回循环留在 server 内按操作级批成一段)".
Concretely, the blocking version is:
- publish the command stream up to and including the
ReadPixelsrecord (doorbell —ARCHITECTURE.md§11.6 listskNeedsAckand the polling entry points as explicit doorbell points; a blocking readback is the same shape); - wait on the reply slot /
EvReadbackDone; - copy the pixels out of
SEG_REPLYinto the application's pointer.
Two things it must NOT block on, both stated in ARCHITECTURE.md §13.1's zero-round-trip list:
glReadPixelsinto a PACK PBO must NOT block. It becomes fire-and-forget: emit the record, and the client marks the PBO GPU-written itself ("glReadPixels→ pack PBO(fire-and-forget + client 侧MarkGpuWritten,严格优于 monolith 的无条件停等)"). This is the only place in P5 where the split path is strictly better than monolith — todayDirectGLES.cpp:10982-10997stalls onglMapBufferRange(READ)for every PBO readback, and the whole point of a PBO is not to. The client-side conservativeMarkGpuWrittenP5's row names is exactly this: the existing client callbackMGPipeClientOnGpuWritten(MG_Impl/Pipe/ResourceTracker.h:579-603) already setsm_gpuWritePending+m_hasDefinedContentviaBufferObject::MarkGpuWritten()(MG_State/GLState/BufferState/BufferObject.cpp:364), and the client just has to call it itself at the emission point instead of waiting for the server to announce. The contract note atManagers.cpp:2533-2538is the one to obey: both flags, or the nextResourceRespecifycarriesHasDefinedContent = 0and the shader-written contents are dropped silently.- The per-scanline writeback loop must NOT become per-scanline IPC.
MGPipeCallbacks.h:32'sOnBufferWritebackcomment andARCHITECTURE.md§8.1 both say it: "按操作级批处理(今天两处逐行循环绝不能 变成每扫描线一次 IPC)". The two loops areDirectGLES.cpp:9397-9424(StoreReadbackRowsToClient, writeback at:9418) andUtils.cpp:2337-2351; DirectVulkan's third isVulkanRenderer.cpp:10996-11004. Under split those loops stay inside the server and produce oneSEG_REPLYregion. - It must also not block on
glGetError(always local), onglFinish/glFlush(no-ops), or on the framebuffer-completeness check —ReadPixels_State(GL_Framebuffer.cpp:2924-3091) is 100% frontend and answers locally, includingCheckCompleteness()(:2963).
3.3 The seam that will bite
MGP_FILL(ReadPixels) maps to fill class kReadback (MG_Pipe/FillPoints.def:115), whose field set is
FillPoints.def:293-313 — 18 fields, and the first three are
GetPixelStoreParameters, GetBufferBindingSlot, GetFramebufferBindingSlot. GetBufferBindingSlot
returns BindingSlot<BufferObject>& (MG_Backend/MGPipe/PipeInputs.h:485-492), i.e. a pointer into
client memory — PipeFill.cpp:98-105 fills it as dst.m_bufferBindingSlot[target] = &ctx.GetBufferBindingSlot(target). That is fine in inproc (one address space) and impossible in
spawn. Its declared coverage owner is SetIndirectBuffers (MG_Pipe/Coverage.def:70,
generated/PipeCoverage.inc:40), and Coverage.def:184 notes it is polymorphic over a target set P3a
only partly covers.
So: P5's inproc milestone can ship with the backend still reading the PACK PBO out of PipeInputs;
P6's spawn cannot. Two readings of the P5 charter: (i) P5 only has to make inproc work, so the PBO
binding can stay a PipeInputs pointer read and MGPReadbackInfo.Res/DstOffset/DstSize stay unused;
(ii) P5 should already route the PBO destination through MGPReadbackInfo.Res + DstOffset because
MGPReadbackInfo:1198 was minted for it and P6 is only 5 days later. Evidence that would settle it:
whether DirectGLES.Split.ClearThenReadPixels (the P5 gate) uses a PBO — it does not (see 3.4), so
(i) passes the gate. Recommendation: (ii) anyway, because the P5 exit gate explicitly includes
"未迁移字段读 = Fatal{UnmigratedPipeInput}", and under MOBILEGL_BUILD_DISAGGREGATED the poison is
armed (MG_Backend/MGPipe/PipeInputs.h:21-26) — a split build that reads a client pointer through
PipeInputs is precisely what that gate exists to catch, and leaving it for P6 means P5's own gate is
only half-armed.
3.4 The P5 gate targets, as they exist today
DirectGLES.Split.ClearThenReadPixels — the scenario file exists:
MG_IntegrationTest/Scenarios/ClearThenReadPixelsScenario.cpp, five cases at :80, :143, :191,
:241, :308. All five read the default framebuffer into client memory (no PBO). It is registered
in MG_IntegrationTest/CMakeLists.txt:76; the TEST_PREFIX mechanism that makes lane names
(DirectGLES., DirectGLES.AsyncOn., DirectGLES.HandleRecycle.Handles., …) is at
CMakeLists.txt:739, :868, :1157 etc. — P5 adds a DirectGLES.Split. prefixed lane the same way.
DirectGLES.Split.Triangle — there is no Triangle scenario in the tree. Full scenario list (85
files) checked; nothing matching Triangle. P5 must write it.
PersistentCoherentMapScenario — does not exist either. The nearest existing relatives are
StorageBufferRegrowScenario.cpp (:255,
NStorageDefinitionsCostNMapPersistentRoundtripsNotOnePerDraw) and LargeArenaAdoptionScenario.cpp
(:450, AnAdoptionCostsExactlyOneMapPersistentRoundtrip) — the two map-persistent-roundtrips gates
(ARCHITECTURE.md §12). P5 must write PersistentCoherentMapScenario from the §12 recipe (map
PERSISTENT|WRITE|COHERENT, write, no other GL call, draw, readback-verify).
MOBILEGL_TRANSPORT — no parsing exists. grep over Config.h, MG_Util/ finds no Transport
and no MOBILEGL_IPC_*; the MOBILEGL_PIPE_* knobs are Config.h:320-415. ARCHITECTURE.md §15.1
requires MG_Config::Transport to exist and to be force-set to Monolith in mobilegl_server_main.
add_trace_replay_test is tools/trace_replay/CMakeLists.txt:311, with
add_trace_replay_test_for_backends at :384-387. There is no SPLIT argument and no
TRACE_TRANSPORT; P5 adds both.
PART 4 — MGPipeCallbacks: ten reverse callbacks + one forward terminator
4.1 The table
MG_Pipe/MGPipeCallbacks.h:27-52. kMGPipeCallbackCount = 10 at :56, asserted at :57-58 ("an
eleventh cannot be added without touching the transport's reverse-channel record table"), pinned by
MG_Test/Pipe/PipeCatalogueTest.cpp:684. The single global instance gMGPipeCallbacks is
null-initialised at MGPipeCallbacks.h:62.
| # | callback | decl | producers today | consumers today | needed by P5's reduced path? |
|---|---|---|---|---|---|
| 1 | OnGlError(code) |
:29 |
none (the 6 sites still call MGB_CTX->RecordError, forwarded) |
none | no for Clear/Triangle/ReadPixels; yes if the gate asserts glGetError()==0 |
| 2 | OnGpuWritten(res, rangeCount, ranges[]) |
:31 |
MG_Backend/DirectGLES/Managers.cpp:2516-2539 (MarkBufferGpuWritten, push arm only) |
MGPipeClientOnGpuWritten, MG_Impl/Pipe/ResourceTracker.h:579-603 |
yes — and P5 narrows it (client-side conservative set) |
| 3 | OnBufferWriteback(res, offset, bytes) |
:32 |
Managers.cpp:2135-2139 (Ops_H_Readback) |
MGPipeClientOnBufferWriteback, ResourceTracker.h:553-572 |
yes for resource_readback; not for the two gate scenarios |
| 4 | OnTextureWriteback(res, box, bytes) |
:33 |
none | none | no (CPU-fallback mip generation only) |
| 5 | OnTexturePullRequest(res, target, firstLevel, levelCount, pullSerial) |
:35-36 |
none — the site is only named, Managers.cpp:5334 raises MGPipeUnmigratedEmulation("texture-remint-pull") |
none | no — see Part 5 |
| 6 | OnMipLevelsGenerated(res, base, count) |
:39 |
none (DirectGLES.cpp:8051 raises MGPipeUnmigratedEmulation("generate-mipmap-storage") instead) |
none | no |
| 7 | OnSurfaceChanged(info) |
:43 |
none — the layering inversion is still live: DirectGLES.cpp:11467 and SwapchainObject.cpp:276-295 write MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo directly |
none | yes — nothing renders without a default FB |
| 8 | OnCapsInvalidated() |
:45 |
none — BackendObject_DirectVulkan.cpp:390, :788 call MGB_CTX->InvalidateCompileEnv() instead |
none | probably yes on DirectVulkan; see 1.5(1) |
| 9 | OnLog(level, text) |
:47 |
none | none | yes if the server is a second process (P6); for inproc the logger is shared, so no |
| 10 | OnXfbScatterReady(scratch, packedStride, vertices) |
:50 |
none | none | no |
| — | ResourceSubDataComplete (forward terminator, not in this struct) |
PipeCalls.def:136, payload MGPSubDataComplete MGPipeTypes.h:1135-1140 (24 B) |
none — PipeCatalogueTest.cpp:953 says in as many words that the terminator is a later phase's and "P4a must not build half a terminator" |
none | no |
So: 2 of 10 wired, 8 stubs. Installation is MGPipeInstallClientResourceCallbacks(),
ResourceTracker.h:605-613, which installs #2 and #3 only if the entry is still null — the comment
at :605-607 states the ownership rule: "the backend installs the rest of the table and these two
answer for it." Under split that rule inverts (the server has no client pointers at all), and the brief
should say so explicitly.
4.2 What P5's reduced path actually needs — and the ordering rule
ARCHITECTURE.md §8.2 is the one non-negotiable: the reverse channel needs the same ordering guarantee
as the forward one. The concrete instance is spelled out at Managers.cpp:2120-2136: writeback →
unmap → serial stamp → epoch bump strictly last ("The bump must NEVER move earlier"). The
mirror-image note on the client side is ResourceTracker.h:550-552.
For P5's milestone ("first inproc IPC frame, reduced path") the minimum is #2, #3, #7, plus #1 if
the gate checks glGetError, plus #8 on the DirectVulkan lane. #4, #5, #6, #10 and the terminator
are P7/P9 (texture and XFB families). #9 is P6.
4.3 The group the split will break first — PipeInputs's "forwarded to the live context"
MG_Backend/MGPipe/PipeInputs.h:558 opens a group of six accessors + RecordError that are not
snapshot fields: they forward to MG_State::pGLContext through
MG_Impl/Pipe/PipeFill.cpp's LiveContext() (PipeFill.cpp:313,
GLContext* LiveContext() { return MG_State::pGLContext.get(); }). In the server process
LiveContext() is null, and every one of them then silently degrades:
| accessor | decl | impl | backend callers | split answer |
|---|---|---|---|---|
GetBufferBindingPointCount(target) |
PipeInputs.h:569 |
PipeFill.cpp:1553-1557 |
DirectGLES.cpp:579 (SyncAtomicCounterBuffers), UniformManager.cpp:1190, :2014 |
server-side binding tables (P7/P9) |
GetProgramObject(index) |
:570 |
PipeFill.cpp:1559-1562 |
DirectGLES.cpp:5810, :9203, DirectVulkan.cpp:274 |
handle tables |
GetTextureObject(index) |
:571 |
PipeFill.cpp:1564-1566 |
VkTextureManager.cpp:810 |
handle tables |
HasOpenTransformFeedbackSpan(lifetimeId) |
:572 |
PipeFill.cpp:1569-1572 |
VulkanRenderer.cpp:11527 |
handle tables |
InvalidateCompileEnv() |
:573 |
PipeFill.cpp:1574-1576 |
BackendObject_DirectVulkan.cpp:390, :788 |
→ OnCapsInvalidated (#8) |
ValidateProgramName(index) |
:574 |
PipeFill.cpp:1578-1581 |
DirectGLES.cpp:5805, :9202, DirectVulkan.cpp:271 |
handle tables |
RecordError(code, info) |
:577 |
PipeFill.cpp:1583-1590 |
Managers.cpp:12812, DirectGLES.cpp:8123, DirectVulkan.cpp:717, VulkanRenderer.cpp:1339, :1343, :1399 |
→ OnGlError (#1) |
PipeInputs.h:563-568 states that this group deliberately carries no MGP_INPUT_CHECK, i.e. no
Fatal{UnmigratedPipeInput} — "a forward is a live call, not a stored value, and
InvalidateCompileEnv is reached from backend initialisation before any verb has filled, where a check
would be Fatal{...@<none>} on every start." This is the hole in P5's exit gate. The gate says "a
read of an unmigrated field = Fatal{UnmigratedPipeInput}", but these seven are the very reads a split
build most needs to catch and they are exempt by construction. RecordError degrades to
MGLOG_E_ONCE + drop (PipeFill.cpp:1585-1588); the other six degrade to null/false/zero silently.
Recommendation for the brief: P5 should add a split-only arm to those seven — under
MOBILEGL_BUILD_DISAGGREGATED with no live context, Fatal{UnmigratedPipeInput} (or the callback where
one exists), not a silent null. Cheap (7 sites), and it is the difference between P5's gate being armed
and being decorative. The precedent for a split-only Fatal arm is MGPipeUnmigratedEmulation
(PipeApply.h:1068, monolith body PipeApply.cpp:2820 = (void)name;).
Good news for the reduced path: of the seven, a Clear + Triangle + ReadPixels sequence on DirectGLES
hits none. SyncAtomicCounterBuffers (DirectGLES.cpp:574-620) only runs when a program declares
atomic counters; MarkShaderStorageBuffersGpuWritten (DirectGLES.cpp:562-573) uses
GetTouchedBufferBindingPointCount / GetBufferBindingPoint, which are migrated fields, not
forwarded ones. ValidateProgramName/GetProgramObject are on glGetProgramBinary-shaped paths
(DirectGLES.cpp:5805, :9202), not on draw.
4.4 The Fatal{UnmigratedPipeInput} machinery P5 inherits
- Armed by
MOBILEGL_PIPE_POISON,PipeInputs.h:21-26— andMOBILEGL_BUILD_DISAGGREGATEDis one of the three arming conditions, so a split build has the poison on by construction. - The check macro
MGP_INPUT_CHECKisPipeInputs.h:44-53; the abort isMGPipeInputPoisonFatalForVerb(PipeInputs.h:31),MGLOG_F+std::abort(), live at every log level (:29-31). - Existing gates that assert the message shape:
MG_IntegrationTest/Scenarios/PoisonOmissionScenario.cpp(prefix at:88, pair assertion at:341) andPipeVerifyArmingScenario.cpp:78; unit sideMG_Test/Pipe/PipeInputsTest.cpp:93,:292-296. - The knob that injects an omission is
MOBILEGL_PIPE_POISON_OMIT=<Verb>:<FieldName>(Config.h:370).
PART 5 — D-B6, the one new stall class (server-initiated texture remint pull)
5.1 The design, summarised
ARCHITECTURE.md §8.4 (docs/Disaggregated/ARCHITECTURE.md:303-312). The server keeps no texels
(§10.1). Three things make it ask for a level it was already sent:
RequireImageBindableStorage's re-dirty, a whole-format re-creation, and a texture-view source remint.
Four mitigations run at once:
- Prevention (the main one): the client stamps
everImageBoundand carriesMGPResourceDesc::ImageBindableHinton everyResourceCreate/ResourceRespecify, so image-bindable storage is allocated up front. - The pull is async: the server emits
OnTexturePullRequestand marks the twin not-ready; the client re-sends on its next publish. What blocks ismgl-srv-apply, never the app thread. - Bounded retention, default off:
MOBILEGL_PIPE_TEXEL_RETAIN_MBdefault 0 (Config.h:397) —MipmapStorageholds a full CPU shadow per level, so a pull can always be served; the cache buys latency, not correctness. - Explicit terminator:
ResourceSubDataComplete(res, target, firstLevel, levelCount, pullSerial), may carry zero regions (a level whose content came only from rendering, from a copyCanMirrorCopyImageShadowrefused, or from GPU-side mip generation — the client has no bytes). Zero regions means the server proceeds with allocated-but-empty storage and logsMGLOG_W. Without the terminator the apply thread parks forever.
Gate: TextureRemintPullScenario (must include an unservable case, and must be red before the
terminator lands). Pull counts are published per trace case.
5.2 What is actually in the tree
- The site is named, not built:
MG_Backend/DirectGLES/Managers.cpp:5275-5345. The comment at:5275-5284says it exactly: "P4a supplies mitigation 1 — the prevention half… and NAMES the site. Mitigations 2-4 (the async pull, the bounded retention and theResourceSubDataCompleteterminator) andTextureRemintPullScenarioare P9's, and P4a must not build half a terminator." - The marker is
MG_Pipe::MGPipeUnmigratedEmulation("texture-remint-pull")atManagers.cpp:5334, raised once per transition and only where a level is actually replayed (review N-4,Managers.cpp:5289-5297). Monolith body is a no-op (PipeApply.cpp:2820). - The counter is
PipeStats::CallClass::TextureRemintPulls(MG_Util/Metrics/PipeStats.h:166, documented:161-166), incremented atManagers.cpp:5339only whenhadBackendStorage; name"tex-remint-pulls"(PipeStats.cpp:182), summary tokentrp=(PipeStats.cpp:459). Test-side peek:MG_IntegrationTest/Harness/P4aFinalFixPeek.{h,cpp}(P4aFinalFixPeek.h:35,.cpp:50-55), used byScenarios/P4aFinalFixScenario.cpp:518,:579-582,:602. - There are three sibling unmigrated-emulation markers P5 will meet on the same path:
"generate-mipmap-storage"(DirectGLES.cpp:8051),"generate-mipmap-cpu-fallback"(:8702),"copy-image-shadow-mirror"(:8997),"get-tex-image-shadow"(:10623) — four, in fact. All four are no-ops today and all four become split-only Fatals or callbacks."get-tex-image-shadow"is the one that touches P5's readback subject.
5.3 The measured rate, and the answer for P5
ROADMAP.md:85 (open question 2) is answered, by P4a:
P4a 已答:可以忽略,保留 LRU 维持默认 0。 … 在 79 例 retrace × 两后端、
MOBILEGL_PIPE_STATS_PERIOD=60、 push 构建(8c458cd5)下:780 个统计窗口里总共 2 次,分布在 2 个用例 (iris-photon-v1.3b-in-world、iris-derivative-main-d24.4.14-in-world,都只在 DirectGLES 侧,各 1 次), 其余 77 例恒 0。
Corroborated in ~/w7/notes/p4a/INTEGRATOR-DECISIONS.md:770 (the final-fix round: "tex-remint-pulls
= ROADMAP:85 的数字") and :726 (the review finding that made the counter real —
before it, the hint was always 0 and the number was unmeasurable). ImageBindableHint got its first real
producers in P4a (glBindImageTexture and sampler-view resolution each set a bit), so 2 is the residue
after prevention is already working.
Can P5's reduced path avoid it? Yes, and it does so by construction, three times over:
- The path is entered only from
RequireImageBindableStorage— i.e. only a texture that getsglBindImageTexture'd after its storage was already minted non-image-bindable. P5's two gate scenarios (ClearThenReadPixels, a newTriangle) use no image loads/stores at all. - Both fixtures where it fired are Iris shader-pack in-world traces; the P5 SSIM gate is OpenRA,
not Iris (
ROADMAP.md:21). - Even when it fires, monolith behaviour is unchanged —
Managers.cpp:5286-5288: "In monolith the code below keeps running exactly as it does today: the Fatal is a split-only arm."
Recommendation: P5 should (a) leave mitigations 2-4 to P9, exactly as Managers.cpp:5281-5284 and
PipeCatalogueTest.cpp:953 instruct, and (b) make the split-only arm of MGPipeUnmigratedEmulation
a Fatal so that if a P5 trace does reach it, the phase finds out loudly instead of rendering a wrong
mip. That single edit (PipeApply.cpp:2820 gains a #if MOBILEGL_BUILD_DISAGGREGATED arm) is also what
half-arms the P5 exit gate "a read of an unmigrated field = Fatal{UnmigratedPipeInput}" for the
emulation family, complementing the PipeInputs half in 4.3.
PART 6 — Consolidated list of things P5 must invent (nothing exists today)
| item | evidence it does not exist |
|---|---|
MGPCaps producer and consumer |
zero call sites; gMGPipeScreen read only by PipeCatalogueTest.cpp:107 |
BackendObject_Remote |
grep BackendObject_Remote over the tree: 0 hits outside ROADMAP.md:21 |
MG_Remote/Client/* (emitter tables) |
MG_Remote/ contains only Protocol/ (3 files) and Transport/ (12 files) |
MG_Remote/Server/* (PipeApplier, ServerLoop, PipeObjectTables, IndexHostMirror) |
same |
MGPReplySlot semantics |
zero producers/consumers |
MG_Config::Transport / MOBILEGL_TRANSPORT / MOBILEGL_IPC_* |
zero hits in Config.h, MG_Util/ |
SEG_REPLY / SEG_EVENT runtime code |
named in ShmSegment.h:9, Ring.h:80 (eventRingFull), protocol.fbs:40-41; no implementation |
DirectGLES.Split.Triangle |
no Triangle* scenario in the 85-file scenario list |
PersistentCoherentMapScenario |
not in the scenario list |
add_trace_replay_test(... SPLIT) / -DTRACE_TRANSPORT= |
tools/trace_replay/CMakeLists.txt:311, :384-387 have neither |
| a round-trip counter | PipeStats::CallClass (PipeStats.h:100-168) has MapPersistentRoundtrips and TextureRemintPulls but no general round-trip class. Any new class must be inside the #if MOBILEGL_PIPE_PUSH block (PipeStats.h:118-167) — G1 forbids resizing the pull build's counter arrays. |
| peak-RSS recording for both roles | no harness; tools/device_bench/bench.sh records frame times (ARCHITECTURE.md §13.2-④) |
Appendix — three claims I could not verify, flagged as guesses
- Opcode numbers in Part 2's table are derived by counting
MGP_CALL_LISTpositions, not read off a generated artefact. The set of tenkReplySlotrows and theirPipeCalls.defline numbers are exact; the opcodes are a guess and should be re-derived fromMG_Pipe/generated/PipeWire.incbefore anyone pins a wire value. CallMaskvsCapsSnapshot.tableSlotMask— argued in 1.4(6); my reading is that they are one field and the schema comment is stale, but I did not find a line that settles it.- Startup ordering of
LogBackendInfo()relative to the firsteglMakeCurrentin the real launcher — argued in 1.2; I verified it in library code only, not in FCL.