Two things land together: the plan and contract draft P6 needs before it starts, and the
working notes that until now existed on exactly one machine.
1. P6's plan and contract draft (neither started; not one line of code)
docs/Disaggregated/P6-SPAWN-PLAN.md the package plan
docs/Disaggregated/P6-CONTRACT-DRAFT.md the contract draft (moves to
MG_Remote/CONTRACT-P6.md when c6 lands)
The three findings that mattered, all written into the documents:
a. P6 is SMALLER than its roadmap cell suggests. The transport primitives are already in
the tree and already tested: SocketDoorbell including hangup detection and the two
lost-wakeup fences (Doorbell.cpp:211-360, four cases in FdPassingTest), SCM_RIGHTS fd
passing, ShmSegment::Adopt, and the SurfaceOp / WindowKind schema already defined in
protocol.fbs. inproc uses a real second shm mapping rather than an alias, and
SessionRings.h:22-28 says that was done so today is not the first time it runs. P6 is
assembly plus a control plane plus a process, not "write a socket transport".
b. The evidence for "P6 is only a transport swap" has EXPIRED. It was bought by P5c, whose
audit found 59 direct memory accesses in a tree that already looked clean. P5e changed
the tree again. So the first package, a6, is a read-only audit that writes no code, and
every contract row that depends on it is marked PENDING a6 rather than decided in
advance.
c. P6 does not get a game running on the phone. ANativeWindow* is meaningless in the child,
and protocol.fbs:186-196's own comment says Android transfers the window out of band -
that band is P12. P6 lands pbuffer / surfaceless / offscreen, which is what HeadlessGL,
the split-equivalent lane and trace replay need, and which is exactly what P6's own exit
gate measures. A real window reaching the server is a named refusal. This gets its own
section because "P6 is done" would otherwise read as "the game runs now".
The contract adds rule G: a role may not name a process-local handle. It extends P5c's
rule E (a role may not name the other role's memory) from memory to identity - EGLDisplay /
EGLSurface / EGLContext / ANativeWindow* / fd number / pid / mapped address either cross as
a token this contract defines or they do not cross, and a descriptor crosses only through
SCM_RIGHTS, where the kernel renumbers it.
What P5e left for P6 gets its own section. The latch semantics are already designed
(ARCHITECTURE.md:504) and are copied unchanged. What is new is that after run-ahead the
client no longer waits while the peer process can DIE: the client holds records that were
published and will never be applied, and Android's freezer suspends the child without
closing anything. So slow and dead must be distinguishable WITHOUT a timeout threshold.
That is P6's only new design; everything else is carriage. It shares a predicate with E1's
outstanding re-specification (ID-122), and the two should be designed together.
Two gates carried over from P5e's lessons: G2 requires integration-spawn's test-name set to
match integration-split's name for name, because a differing set means the lane is quietly
running less; and the arm-proof gate requires every entry to record the child pid and
transport=spawn in its OWN private log - ID-124 established that this kind of false green
really happens, and a spawn lane that actually ran monolith would pass every other gate.
S2's shape must be settled before it is written, and the document says why: E1 is the
cautionary case.
2. The working notes move into the repository (docs/Disaggregated/notes/, 415 .md files)
ROADMAP / CURRENT_STAGE_PROGRESS / MEASUREMENTS cite ruling numbers, audit line numbers and
the provenance of gate figures from ~/w7/notes/, a directory that exists on one machine and
is under no version control. A record cited hundreds of times that lives only locally is not
a record, so it moves in whole, with its directory structure intact.
Only .md moves. Scripts, run logs, census and gate result JSON, patches, APKs and nm dumps
stay where they are: they are evidence DATA rather than documents - large, regenerated per
run, and mostly carrying absolute paths and device serials that mean something only on the
machine that produced them. The mapping and this split are written in
docs/Disaggregated/notes/README.md.
Two exceptions are recorded there too: P6's BRIEF has been promoted to a first-class
document and keeps no copy under notes/, so the two cannot drift apart; and recovered/ holds
material salvaged from failed workflow runs, which may duplicate the like-named phase
directories and is kept because a few of those were the only complete version at the time.
CI's citation lint takes docs/Disaggregated/*.md only (non-recursive), and notes/ is
deliberately outside it: those notes' file:line citations were written against their own
code heads, and resolving them against today's HEAD would fail wholesale - which is not an
error, it is their correct state as a historical record. To check one, use the revision it
declares (--rev).
3. Inconsistencies fixed in passing
- README.md's status line still said P5e was in progress while the stage table already said
closed.
- CURRENT_STAGE_PROGRESS's next-step item 1 still listed the exit gate as pending, though
(a) and (b) were run in the previous commit; rewritten to closed plus the one item that
actually remains (E1).
- ROADMAP's P6 row still had the pre-phase plan in three of its cells; it now carries the
package order, the scope boundary and the two new gates.
Checked with check_doc_citations.py: 125 citations across docs/Disaggregated/*.md, 0 problems.
58 KiB
P1 specification, extracted from the MGPipe design docs
Scout report. Worktree: C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg (branch feat/disaggregated). Read-only; nothing was edited.
Docs read in full: docs/Disaggregated/README.md (57 lines), ARCHITECTURE.md (601 lines), ROADMAP.md (90 lines), MEASUREMENTS.md (96 lines). Every code claim below carries a path + line that I actually opened in this tree.
Quoting convention: > blocks are verbatim from the doc (Chinese as written). Everything else is my summary or my own measurement.
0. Where P1 sits
docs/Disaggregated/README.md:3:> 状态:**P0、P0.5 已落地**(feat/disaggregated@5d99ee43,基线dev@50fb1343)。下一步 P1 → P2,第 43 天 GO/NO-GO。见ROADMAP.md。- Track: monolith lane.
ROADMAP.md:9—> 两条跑道:**monolith 跑道** P0 → P0.5 → P1 → P2 → P3a → P4a → P3b/P4b → P7 → P8 → P13,每段可独立交付、可随时中止且 monolith 严格好于起点;**IPC 跑道** P5 → P6 → P9 → P10 → P11 → P12。 - Milestone:
ROADMAP.md:38—> - **第 25 天(P1 出口)**:verify harness 逐 draw 逐字段证明"推送等价于拉取"。零产品风险,**不是** GO/NO-GO。 - Cumulative day count:
ROADMAP.md:32— P0 9 → P0.5 15 → P1 25 → P2 43 … - Sunk-cost framing:
ROADMAP.md:58—> 真正只为 MGPipe 押上的是 P1 + P2 ≈ 28–39 天,NO-GO 分支下仍留下上述产物。 - Under NO-GO, P1's output is explicitly not rolled back (
ROADMAP.md:57):> **不回滚**:P0/P0.5/P1/P2 的产物(句柄基建与重键、两个头文件抽取、计数器、verify harness、渲染状态 CSO)全是自洽的 monolith 交付物,留在dev``.
1. The P1 row of the phase table (verbatim), ROADMAP.md:17
Columns are: 阶段 | 天 | 落地什么 | 验收门 | 依赖.
阶段: **P1** PipeInputs 替换与 verify harness
天: 10–13
落地什么 (deliverables), verbatim:
MG_Backend/MGPipe/PipeInputs.h(Espryt 32 / Magma 55 访问器);sed293 处 + 58 行非箭头清单逐条转换(显式交付物);逐 verb 类填充点(G5 表,~93 个边界站点);逐 verb 世代 poison;G4 影子比对器 + 第三种 CI 模式;20 处SyncPersistentMappedRange+ 6 处SyncGpuWrites的逐站点归属表
Broken out — six deliverables:
MG_Backend/MGPipe/PipeInputs.h, Espryt 32 accessors / Magma 55 accessors.- The mechanical
sedof 293 arrow sites plus a per-line conversion of the 58-line non-arrow list — the doc calls this an explicit deliverable (i.e. the list itself is a produced artifact, not a side effect). - Per-verb-class fill points: the G5 table, ~93 boundary sites.
- Per-verb generation poison.
- G4 shadow comparator + a third CI mode.
- A per-site ownership table for the 20
SyncPersistentMappedRangeand 6SyncGpuWritessites.
验收门 (acceptance gate), verbatim:
pull 构建
nm --defined-only不变、.text差异逐行归因(空守卫/三元重写推迟到 P2);40 trace + 全部集成测试在MOBILEGL_PIPE_VERIFY=1下零分歧;故意损坏一个快照字段能让 verify 变红;故意在glGenerateMipmap的填充表漏一个字段能在那条 verb 上触发 poison Fatal
Four gate clauses:
- On the pull build,
nm --defined-onlyunchanged;.textdifferences attributable line by line. Empty-guard / ternary rewrites are deferred to P2 — do not do them in P1, they would break this gate's attributability. - 40 traces + all integration tests, run under
MOBILEGL_PIPE_VERIFY=1, zero divergence. - Negative control A: deliberately corrupting one snapshot field must turn verify red.
- Negative control B: deliberately omitting one field from
glGenerateMipmap's fill table must raise the poison Fatal on that verb (not on a later draw). This is what makes the poison a per-verb generation rather than a bitmap.
依赖: P0.5. And from the P0.5 row (ROADMAP.md:16, last cell): > P0;**P1 与 P7 的硬前置** — P0.5 is a hard prerequisite for P1 and P7.
Every other P1 mention in the docs
| Where | Verbatim / content |
|---|---|
ARCHITECTURE.md:172 |
> 完整性由 scripts/gen_pipe_dirty_surface.py保证:枚举MG_Impl/GLImpl里每个 mutator → 必须 bump 的聚合世代,CI 重生成 +git diff --exit-code,未映射即失败(P1 起成为门)。 Also gives the measured surface: 926 mutator calls over 73 distinct mutators; 92 of them (7 mutators, mostly RecordError) sit at "即时发布点"; the other 834 are published by the following verb. > 映射表是 73 条目的问题。 |
ARCHITECTURE.md:323 |
Section title: ### 9.2 strangler 脚手架:PipeInputs + 逐 verb 填充 + poison 世代(P1) — §2 below. |
ARCHITECTURE.md:503 (gate 13.2-③) |
> 逐名功能基线是"P1 出口的重构后 monolith"(P1 出口先用 verify 证明等价于 81b17c0b);81b17c0b 只作性能锚点。 P1's exit becomes the by-name functional baseline for every later phase. |
ARCHITECTURE.md:553 |
Build layout: MobileGL/MG_Backend/MGPipe/ PipeInputs.h + MGPipeImpl_DirectGLES/DirectVulkan.cpp [P1+] — i.e. P1 creates that directory with three files: the header plus one impl TU per backend. (The directory does not exist yet: MobileGL/MG_Backend/ currently holds only BackendObject.cpp, BackendObject.h, BackendObjects.h, Init.cpp, DirectGLES/, DirectVulkan/.) |
ARCHITECTURE.md:568 |
CI: > gen_pipe_dirty_surface.py --summary(信息性,P1 成门) |
ARCHITECTURE.md:583 (附 A) |
> MOBILEGL_PIPE_VERIFY| OFF | 计划(P1;构建期开关,编译进SnapshotFromGLContext() 与 G4 比对器,P13 后保留) — note CMake-level switch, distinct from the runtime env var of the same name. |
ROADMAP.md:46 (GO/NO-GO) |
> - [ ] P1 交付的逐 draw 逐字段语义等价证明(40 trace + 全部集成测试零分歧) |
ROADMAP.md:18 |
P2's 依赖 cell is P1. |
In-tree code comments that name P1 (these are contracts the P1 implementer inherits):
MobileGL/MG_Pipe/generated/PipeFilled.inc:26/scripts/gen_pipe.py:462:// P0 is the skeleton: the enum, the tables and the assertion helper exist, PipeInputs // itself lands in P1.MobileGL/MG_Pipe/generated/PipeFilled.inc:161/scripts/gen_pipe.py:479:// true has to be argued for in P1 when the fillers land: a sticky field is a field // the poison cannot protect.MobileGL/MG_Pipe/PipeFields.def:16:// here makes the comparator blind to it; that gap closes in P1, when the verify harness // goes live and the comparator's coverage is itself asserted.scripts/gen_pipe.py:165andMobileGL/MG_Pipe/generated/PipeVerify.inc:236: the memcmp fallback types (RenderStateParameters,PixelStoreParameters,DynamicBackendParameters,MGHostSpan, listed atscripts/gen_pipe.py:167-172)// P1 gives them field lists of their own, at which point this branch stops being reachable from any payload.MobileGL/MG_Pipe/MGPipeTypes.h:32:// needs fixed-width members before it can move (a type change, not a move): P1/P7.— aboutDynamicBackendParametersinsideMGPCaps, which is what still keeps purity gate A offMGPipeTypes.h.MobileGL/MG_Pipe/Coverage.def:36-40:GetBufferBindingSlotis polymorphic overBufferTarget;/* inventory carries the target argument (P1) */— its rows must split acrossset_vertex_buffers/set_index_buffer/set_indirect_buffers/set_shader_buffersin P1.scripts/gen_pipe_dirty_surface.py:21-22:P0 is the skeleton: it reports. P1 adds the mapping file and CI regenerates it withgit diff --exit-codeand zero unmapped mutators, the same shape as gen_pipe.py's G6.scripts/gen_pipe_dirty_surface.py:193-194:dirty-surface: every mutator above is UNMAPPED - the aggregate-generation mapping file lands in P1, and this report is what it has to cover.scripts/check_include_closure.py:15and:86describe the P0.5 closure gate that P1 must not regress.
2. The strangler scaffold (ARCHITECTURE §9.2, lines 323–351)
2.1 PipeInputs + MGB_CTX — verbatim code block (ARCHITECTURE.md:325-340)
// MG_Backend/MGPipe/PipeInputs.h —— 按 memo 键组织,不按读点组织(~20 KB,字段集全迁移期稳定) struct PipeInputs { const RenderStateParameters& GetRenderStateParameters() const; // 阶段 A:类型与后端今天读到的完全一致 // … 每个后端真正用到的 GLContext 方法一个访问器(Espryt 32 / Magma 55) #if MOBILEGL_DEBUG || MOBILEGL_BUILD_DISAGGREGATED Uint64 m_filledGen[kFieldCount]; // 逐字段"上次填充的 verb 序号" Uint64 m_currentVerbSerial; #endif }; #if MOBILEGL_PIPE_PUSH # define MGB_CTX (&::MobileGL::MG_Pipe::gPipeInputs) #else # define MGB_CTX (::MG_State::pGLContext) #endif
Load-bearing details in that block:
- Organized by memo key, not by read point. That is why the field set is small (61) and stable across the whole migration.
MobileGL/MG_Pipe/generated/PipeFilled.inc:15-17repeats this:// PipeInputs is organized by MEMO KEY, not by read point, which is why the field set is // small and stable across the whole migration). - Size budget:
~20 KB. - Phase A rule: the accessor return types are exactly what the backends read today (
const RenderStateParameters&, etc.), so thesedis type-neutral. - The poison members are compiled only under
MOBILEGL_DEBUG || MOBILEGL_BUILD_DISAGGREGATED. MGB_CTXis a compile-time macro switched onMOBILEGL_PIPE_PUSH. Note the asymmetry with the runtime knob:MOBILEGL_PIPE_PUSHis also a runtime per-subsystem bitmask (MobileGL/Config.h:320-326,Uint64 PipePush = 0;atConfig.h:326, parsed atMobileGL/ConfigLoader.cpp:245). The docs use the same name for both; the P1 implementer must decide/record how the macro arm and the runtime bitmask relate (see §7, Risk R3).gPipeInputsis a single global instance inMobileGL::MG_Pipe(matchinggMGPipeScreen/gMGPipeContextatMobileGL/MG_Pipe/MGPipe.h:72-73).
2.2 The three-phase table (ARCHITECTURE.md:342-347) — verbatim
阶段 改什么 证明 A 别名 机械 sed:MG_State::pGLContext->→MGB_CTX->(293 处)+ 手工转换 58 行非箭头用法(~34 处MOBILEGL_ASSERT删除、7 处空守卫、3 处三元、.get()裸指针捕获与decltype别名、14 处!= nullptr、1 处注释);逐 verb 类填充点填gPipeInputsnm --defined-only不变;.text差异可逐行归因(空守卫/三元的重写推迟到 P2)B 推送 tracker 填 gPipeInputs,填充器按MOBILEGL_PIPE_PUSH位图逐字段让位MOBILEGL_PIPE_VERIFY=1:tracker 再填一份快照版,G4 比对器逐字段每 draw 比一次C 句柄化 SharedPtr<前端对象>字段 →MGPipeHandle+ POD 描述符;memo 重键;写回变回调全套门(§13)
P1 is phase A plus the scaffolding for B (the ROADMAP P1 row's verify harness). Phase B's per-field yielding by the MOBILEGL_PIPE_PUSH bitmap and phase C's handle-ification belong to P2+ (P2 lands the tracker + the first push subsystems; P3a/P4a land Track H).
The 58 non-arrow lines break down (per the doc) into: ~34 MOBILEGL_ASSERT deletions, 7 empty guards, 3 ternaries, .get() raw-pointer captures + decltype aliases, 14 != nullptr, 1 comment. The empty-guard and ternary rewrites are explicitly deferred to P2 so the .text diff stays attributable.
2.3 Fill points: the G5 table and the ~93 boundary sites (ARCHITECTURE.md:348) — verbatim
- 填充点逐 verb 类,不只
PrepareForDraw/SetupDraw两处:G5 从PipeCalls.def生成"每个kCtxVerb/kCtxObject调用可能读哪些字段"的表,在MG_Impl的 ~93 个边界站点生成 validate/fill 调用。
Cross-references:
- G2 (
ARCHITECTURE.md:157):> monolith 直调 thunkMGP_(),MG_Impl的约 93 个gBackendFunctionsTable.GL.*站点逐名改到它上面— the same ~93 sites are where the fill calls go. - G5 (
ARCHITECTURE.md:160):>PipeFilled.inc\|PipeInputs字段 id(61 个)与逐 verb 世代 poison - The eight validate entry points (
ARCHITECTURE.md:153, §5.1):> 八个 validate 入口,由PipeCalls.def的kCtxVerb/kCtxObject条目生成:ValidateForDraw(20 个 draw 入口)、ValidateForDispatch、ValidateForClear、ValidateForBlitOrCopy、ValidateForTextureOp(GenerateMipmap / CopyTex* / BindImageTexture)、ValidateForReadback、ValidateForXfbSpan、ValidateForQuery。八个而不是四个,因为MG_Impl用到的 70 个表项里只有约 22 个是 draw/dispatch,其余 ~48 个(clear、blit、copy、回读、barrier、XFB 跨度、query/sync)很多自己就读pGLContext。- P1 relevance: the tracker itself lands in P2, but the fill points land in P1 at these same eight shapes.
glGenerateMipmap— the P1 negative-control verb — is underValidateForTextureOp.
- P1 relevance: the tracker itself lands in P2, but the fill points land in P1 at these same eight shapes.
- Class counts (
ARCHITECTURE.md:171-172region, §3.2 table atARCHITECTURE.md:165-172):kCtxObject= 9 calls,kCtxVerb= 13 calls. Those 22 rows are the ones G5 has to enumerate fields for. ARCHITECTURE.md:155(§5.1, still binding on where a push happens):> **只有今天就在 GL 调用时刻分发的资源 op 在 GL 调用时刻推送**——即BufferBackendOps的七个 hook。纹理 subdata 不在此列(§6)。
Measured in this tree (my count, for the mechanical work estimate):
grep -rc "gBackendFunctionsTable" MobileGL/MG_Impl/→ 91 lines across 8 files:GLImpl/Drawing/GL_Drawing.cpp37,GLImpl/Framebuffer/GL_Framebuffer.cpp11,GLImpl/Getter/GL_Getter.cpp3,GLImpl/Program/GL_Program.cpp1,GLImpl/Program/ProgramInterface.h1,GLImpl/Query/GL_Query.cpp22,GLImpl/Sync/GL_Sync.cpp6,GLImpl/Texture/GL_Texture.cpp10. (Doc says "~93"; 91 lines here — a line may hold more than one site.)
2.4 Per-verb generation poison (ARCHITECTURE.md:349) — verbatim
- poison 是逐 verb 世代不是位图:每次 verb 递增
m_currentVerbSerial,字段被填时记下序号,读取时断言相等(跨 verb 有效的字段显式标 sticky)。位图看不见"上一个 draw 填过、紧随的glTexSubImage读到陈旧值"。debug 与 disaggregated 构建里读一个当前 verb 未填的字段是Fatal{UnmigratedPipeInput, "GetStencilState@DrawVbo"}。纯度门 grep 的是pGLContext不是pGLContext->。
So, precisely:
- What is poisoned: every one of the 61
PipeInputsfields, individually. - When:
m_currentVerbSerialis bumped once per verb; a fill stamps the field with the current serial; a read assertsstamp == currentVerbSerialunless the field is marked sticky. - Where it is live:
MOBILEGL_DEBUG || MOBILEGL_BUILD_DISAGGREGATEDbuilds only. - The Fatal:
Fatal{UnmigratedPipeInput, "<FieldName>@<Verb>"}— the message names the field and the verb. - Last sentence is a separate rule: purity gate C greps for
pGLContext, notpGLContext->— so anMGB_CTXalias that still spellspGLContextin the#elsearm insideMG_Backendwill trip gate C. (Gate C:ARCHITECTURE.md:502—> **C 门未声明**——grep -c 'pGLContext' MG_Backend/== 0.) TheMGB_CTXmacro definition therefore cannot live underMG_Backend/at P13; in P1 gate C is not yet green anyway (it turns green with the pull path's retirement,ARCHITECTURE.md:369).
The skeleton already in the tree — P1 fills it in, it does not invent it. MobileGL/MG_Pipe/generated/PipeFilled.inc (308 lines, generated by scripts/gen_pipe.py from Coverage.def + PipeCalls.def, included from MGPipe.h:86):
PipeFilled.inc:19-26states the poison contract verbatim in code:// The poison is a per-verb GENERATION, not a bit. A bitmap cannot see the dangerous case: // a field filled by the previous DRAW and then read by the glTexSubImage that follows is // stale, and its bit is already set. So every verb bumps CurrentVerbSerial, filling a // field stamps it with that serial, and reading a non-sticky field whose stamp is older is // Fatal{UnmigratedPipeInput} (section 6.2.2).PipeFilled.inc:28-91:enum class MGPipeInputField : Uint16 { … kFieldCount }— the 61 field ids, in this order:GetActiveTextureUnit, GetBlendColor, GetBlendEquationIndexed, GetBlendFuncIndexed, GetBoundTransformFeedbackName, GetBoundVertexArray, GetBufferBindingSlot, GetBufferBindingPoint, GetBufferBindingPointCount, GetTouchedBufferBindingPointCount, GetClampReadColor, GetClearColor, GetClearDepth, GetClearStencil, GetColorMaskIndexed, GetCullFaceMode, GetCurrentVertexAttribute, GetDepthFunc, GetDepthMask, GetDepthRangeIndexed, GetFramebufferBindingSlot, GetImageTextureBinding, GetLineWidth, GetLogicOp, GetMaxTouchedTextureUnit, GetMinSampleShadingValue, GetPatchDefaultInnerLevel, GetPatchDefaultOuterLevel, GetPatchVertices, GetPipelineStateVersion, GetPixelStoreParameters, GetPolygonModeFront, GetPolygonOffsetFactor, GetPolygonOffsetUnits, GetPrimitiveRestartIndex, GetProgramForDispatch, GetProgramForDraw, GetProgramObject, GetProvokingVertexMode, GetRenderStateParameters, GetRenderStateParametersVersion, GetSamplingResolutionGeneration, GetScissorBox, GetStencilState, GetTextureBindGeneration, GetTextureContextId, GetTextureObject, GetTextureUnitObject, GetTransformFeedbackCapturedVertices, GetTransformFeedbackGeneration, GetTransformFeedbackPausedPrimitiveCounter, GetTransformFeedbackProgram, GetViewport, GetViewportIndexed, IsCapabilityEnabled, IsCapabilityEnabledIndexed, IsTransformFeedbackActive, IsTransformFeedbackPaused, InvalidateCompileEnv, ValidateProgramName, RecordErrorPipeFilled.inc:93-94:static_assert(kMGPipeInputFieldCount == 61, "the PipeInputs field set moved");PipeFilled.inc:96-155:kMGPipeInputFieldNames[].PipeFilled.inc:158-225:kMGPipeInputFieldSticky[]— every entry is currentlyfalse, and eachtrueP1 introduces has to be argued for (PipeFilled.inc:160-162).PipeFilled.inc:227-291:kMGPipeInputFieldFilledBy[]— the call that must have filled each field by the time a verb reads it; names come fromCoverage.defsoPipeCoverage.incandPipeFilled.inccannot disagree (PipeFilled.inc:227-228). The last three entries are pseudo-calls:"kClientResolved"(×2:InvalidateCompileEnv,ValidateProgramName) and"kReverseChannel"(RecordError).PipeFilled.inc:293-296:struct MGPipeFilledState { Uint64 CurrentVerbSerial; Uint64 FilledGen[kMGPipeInputFieldCount]; };PipeFilled.inc:298-302:[[noreturn]] inline void MGPipeInputPoisonFatal(MGPipeInputField field, const char* verb)→MGLOG_F("MGPipe: Fatal{UnmigratedPipeInput, \"%s@%s\"}", …); std::abort();PipeFilled.inc:304-308:MGPipeInputFieldIsFresh(state, field)→ sticky fields pass whenFilledGen != 0; non-sticky requireFilledGen == CurrentVerbSerial.
So P1's poison work is: instantiate MGPipeFilledState inside PipeInputs (the doc's m_filledGen[kFieldCount] / m_currentVerbSerial), stamp it from the G5-generated fill points, check it in each of the 61 accessors, and argue each sticky = true.
The source of truth behind the field list is MobileGL/MG_Pipe/Coverage.def:29-97 (MGP_COVERAGE_ACCESSOR_LIST) — one X(Accessor, PipeCall) row per field, which is what generates both kMGPipeInputFieldFilledBy and the coverage table.
2.5 G4 shadow comparator / MOBILEGL_PIPE_VERIFY
The definitive statement is gate 13.2-②, ARCHITECTURE.md:501 — verbatim:
- 语义影子比对
MOBILEGL_PIPE_VERIFY=1——决定性的一条:两套状态模型活在同一地址空间,tracker 再用SnapshotFromGLContext()填一份PipeInputs,G4 比对器逐字段、每 draw 比对,打印第一个分歧字段与 draw 序号。抓 tracker 忘推的字段、dirty 位触发得太少(危险方向)、两条路径变换不一致的值。第三种 CI 模式,40 个 trace + 全部集成测试,~5–10× 慢,永不出货。逐字段而非memcmp(padding 会 false-DIFFER)。保留模式:消费即清的组(纹理 dirty rect)发射后无法重算,verify 时 tracker 保留清除前的集合并比对发射出去的(UnionBox, RegionCount, Regions[])。活过 P13。
Decomposed:
- What is compared: field by field, the pushed
PipeInputsagainst a secondPipeInputsfilled bySnapshotFromGLContext()— both models live in the same address space. - When: once per draw.
- Output on failure: the first differing field name + the draw serial.
- What it is designed to catch: (a) fields the tracker forgot to push; (b) a dirty bit that fires too rarely — the dangerous direction, which no purity gate can see; (c) values that the two paths transform inconsistently.
- "Zero divergence" means: across the 40 trace fixtures and the entire integration suite, the comparator never reports a differing field. That is exactly the ROADMAP gate and exactly GO/NO-GO item 1.
- "The third CI mode": a build/run configuration alongside the existing ones, running the 40 traces + all integration tests, ~5–10× slower, never shipped.
- Field-wise, never memcmp — padding would false-DIFFER.
- Retention mode (保留模式): for consume-and-clear groups (texture dirty rects) that cannot be recomputed after emission, the tracker under verify keeps the pre-clear set and compares the emitted
(UnionBox, RegionCount, Regions[]). (P1 only needs the mechanism to the extent texture ops are in scope; the shape gold-standardTextureUploadShapeScenariois a P3b/P4b gate,ROADMAP.md:23.) - Survives P13:
ARCHITECTURE.md:369—> P13:删SnapshotFromGLContext()的非 verify 分支、MGB_CTX、MOBILEGL_PIPE_PUSH、MOBILEGL_PIPE_LEGACY_MEMOS;**保留MOBILEGL_PIPE_VERIFY连同它需要的SnapshotFromGLContext()与MG_Stateinclude**(D-B5,verify 构建永不出货);三道纯度门在非 verify 构建上转绿。So P1 must buildSnapshotFromGLContext()as a permanent verify-only facility, not a throwaway. - CMake vs env:
ARCHITECTURE.md:583listsMOBILEGL_PIPE_VERIFYas a CMake option (OFF,计划(P1;构建期开关,编译进 SnapshotFromGLContext() 与 G4 比对器,P13 后保留));ARCHITECTURE.md:589lists the runtime envMOBILEGL_PIPE_VERIFYdefault0,逐 draw 逐字段影子比对. Both exist; P1 must wire both. Runtime side already landed:MobileGL/Config.h:327-332(Bool PipeVerify = false;atConfig.h:332) andMobileGL/ConfigLoader.cpp:246. - G4 in the catalogue table (
ARCHITECTURE.md:159):> G4 \|PipeVerify.inc\|MOBILEGL_PIPE_VERIFY的逐字段比对器(字段表来自PipeFields.def;浮点按位比较,NaN patch level 不会误报)
Already in the tree: MobileGL/MG_Pipe/generated/PipeVerify.inc (573 lines, included at MGPipe.h:83).
PipeVerify.inc:9-23restates the contract, including// Floating-point fields are compared by BITS, so a NaN patch level - which // glPatchParameterfv accepts and ComputePipelineStateHash already hashes bitwise - equals // itself instead of tripping every draw.PipeVerify.inc:30-…declares oneMGPipeVerify(const X&, const X&, const char** outField)per payload (≈63 payloads;MEASUREMENTS.md:82says63 个 verify payload).PipeVerify.inc:222-239: the dispatch template — float → bitwise memcmp; scalar/enum →==; else==if available; else the memcmp fallback, reachable today only fromRenderStateParameters,PixelStoreParameters,DynamicBackendParametersandMGHostSpan.PipeVerify.inc:233-237:// P0.5 moved the first two into MGPipeValueTypes.h; P1 gives them field lists of their own, at which point this branch stops being reachable from any payload.→ P1 deliverable (implied but explicit in code): addMGP_FIELDS_*lists for those value structs toMobileGL/MG_Pipe/PipeFields.def(238 lines, format shown atPipeFields.def:21-39) and remove them fromMEMCMP_FALLBACK_TYPES(scripts/gen_pipe.py:167-172).PipeFields.def:9-17states the zero-false-positive requirement:// MOBILEGL_PIPE_VERIFY has to have ZERO false positives and a padding byte is exactly what makes a memcmp of RenderStateParameters false-DIFFER.
2.6 Track V / Track H (ARCHITECTURE.md:353-357, §9.3) — verbatim
- Track V(值类型:
GetRenderStateParameters、GetPixelStoreParameters、capability 位、stencil/colormask/depthmask/scissor/patch/attrib 默认值、Magma ~22 个标量 getter……约 B 类读点的 55%):机械。- Track H(对象类型:167 个
SharedPtr<MG_State…>点):真活。- 读点分类实测(静态):A 探测变化 ~35(12%)、B 翻译输入 ~216(74%)、C 瞬时参数 ~4、D 身份/缓存键 ~48(与 B 重叠)、E 数据字节 3、写 8。74% 是 B 类——"bump 一个版本让 server 自己拉"行不通,值本身必须过去。
Glossary (README.md:52): > **Track V / Track H**:值类读点的迁移(整块 POD 过线)/ 对象类读点的迁移(SharedPtr<前端对象> → 句柄)。
P1 does neither migration — P1 makes both addressable: every read point, V or H, goes through a PipeInputs accessor. Track V's bulk moves in P2 via the residual value block; Track H's first slice moves in P2 (Espryt 0b + Magma subsystem 4) and the waves in P3a/P4a.
2.7 The residual value block (ARCHITECTURE.md:359-360, §9.4) — verbatim
Track V 的 55% 不需要逐字段接口条目就能跑起来,所以 P2 发一个显式临时调用
SetResidualValueState(MGPBlobRef),payloadResidualValueBlock{RenderStateParameters, PixelStoreParameters, CapabilityBits, patch 三字段}。三条纪律:退役是编译错误(MGL_RESIDUAL_BLOCK_SIZE只降不升,MobileGL/MG_Pipe/MGPipeTypes.h:535,P13 变成static_assert(sizeof == 0));布局逐成员offsetof断言且 split 下逐字段序列化(异质 POD 并集的 padding 差异 monolith verify 看不见);只在 P2..P13 存在,MOBILEGL_PIPE_STATS单独计一类字节(ResidualValueBlock,P0 已占位)。
It is a P2 deliverable, not P1 — but P1 constrains it: the PipeInputs accessors for GetRenderStateParameters / GetPixelStoreParameters / capability bits / the three patch fields are the ones the residual block will feed in P2, so their P1 signatures must be the whole-struct-by-const-ref shape the block can satisfy.
Verified in tree: MobileGL/MG_Pipe/MGPipeTypes.h:534 #define MGL_RESIDUAL_BLOCK_SIZE 1248 with the ratchet comment at MGPipeTypes.h:525-533 (// This number only ever goes DOWN … P13 replaces it with static_assert(sizeof(ResidualValueBlock) == 0)), the static_assert at MGPipeTypes.h:535-537, static_assert(std::is_trivially_copyable_v<ResidualValueBlock>) at MGPipeTypes.h:525(region), and struct MGPResidualValueState { Uint32 Version; Uint32 Pad0; MGPBlobRef Blob; } at MGPipeTypes.h:539-543. Size 1248 (of which RenderStateParameters is 1168) is confirmed at MEASUREMENTS.md:86.
2.8 SnapshotFromGLContext()
Mentions (there is no other definition in the docs; the function does not yet exist in the tree — grep -rn SnapshotFromGLContext over the source returns nothing outside docs/):
ARCHITECTURE.md:501(gate 13.2-②): the tracker uses it to fill a secondPipeInputsfor the comparator.ARCHITECTURE.md:367(§9.6):>MOBILEGL_PIPE_PUSH子系统位图(含一位关闭 CSO 内容寻址,负面对照)在阶段 B 是真正的旧-vs-新 A/B;阶段 C 之后不是——位清零时SnapshotFromGLContext()仍要合成句柄,后端仍跑重键后的 memo 代码,一个重键 bug 两臂都在。ARCHITECTURE.md:369(P13): delete its non-verify branch, keep the verify branch plus theMG_Stateinclude it needs.ROADMAP.md:30(P13 row):删SnapshotFromGLContext()非 verify 分支、MGB_CTX、MOBILEGL_PIPE_PUSH、MOBILEGL_PIPE_LEGACY_MEMOS;保留MOBILEGL_PIPE_VERIFY``.
So SnapshotFromGLContext() has two branches from birth: a non-verify branch (the pull path — what phase A's MGB_CTX #else arm effectively is) and a verify branch. P1 authors both.
2.9 The SyncPersistentMappedRange / SyncGpuWrites per-site ownership table
The rule the table has to encode is the stale-index discipline, ARCHITECTURE.md:233-238 (§5.7) — verbatim:
陈旧索引纪律是逐站点表,不是一条笼统规则(client 侧扫描/解析之前要做的 reconcile 必须逐字复现 monolith 的集合):
client 侧动作 必须做的 reconcile client 顶点数组范围计算 + 暂存 无(应用内存,无 GPU 写者) 最大索引扫描(EBO 源) SyncPersistentMappedRange()+SyncGpuWrites()最大索引扫描(client 指针源) 无 *IndirectCount计数解析只 SyncPersistentMappedRange(),不加SyncGpuWrites()——monolith 今天就只做这一个,加了会给 Create/Flywheel 的每 batch 平白加一次 publish-and-waitserver 侧 restart 重写 / multi-draw 展平 server 从镜像读;GPU 写者可见性由 OnGpuWritten收窄集在 server 本地判定
And ARCHITECTURE.md:294 (§8.1, last sentence): > 20 处 SyncPersistentMappedRange+ 6 处SyncGpuWrites按 §5.7 逐站点归属,其中至少一处消费者搬不走:Magma 的ResolveUniformBufferPayload把具名 UBO 打进自己的 UBO ring →SetShaderBuffers 的 host payload(D-B8)。
Also relevant to the same table: ARCHITECTURE.md:239 — > 前两条 client reconcile 的形态:publish → 等 appliedSeq → 排空事件 → 再碰 shadow。门:ClientArrayAfterComputeWriteScenario(去掉等待必须看到几何缺失);create-indirectfixture 上roundtrips-per-frame 必须读零(P8)。
The 20 + 6 sites, enumerated from this tree (I grepped MobileGL/MG_Backend/; comment-only hits excluded and listed separately). The doc's counts match exactly.
SyncPersistentMappedRange() — 20 call sites:
| # | Site | Backend |
|---|---|---|
| 1 | MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:263 drawBuffer-> |
Espryt |
| 2 | MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:4468 indexBuffer-> |
Espryt |
| 3 | MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:4722 drawBuffer-> |
Espryt |
| 4 | MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:4723 parameterBuffer-> |
Espryt |
| 5 | MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:4824 drawBuffer-> |
Espryt |
| 6 | MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:4825 parameterBuffer-> |
Espryt |
| 7 | MobileGL/MG_Backend/DirectGLES/Managers.cpp:1569 bufferObject-> |
Espryt |
| 8 | MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp:510 indexBuffer-> |
Espryt |
| 9 | MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp:280 drawBuffer-> |
Magma |
| 10 | MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp:471 parameterBuffer-> |
Magma |
| 11 | MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp:795 indexBufferShared-> |
Magma |
| 12 | MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp:2023 bufferObject-> |
Magma (the immovable consumer: ResolveUniformBufferPayload / D-B8) |
| 13 | MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp:628 bufferObject-> |
Magma |
| 14 | MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp:679 bufferObject-> |
Magma |
| 15 | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:3434 indexBufferShared-> |
Magma |
| 16 | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:3513 bufferObject-> |
Magma |
| 17 | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:3828 sourceBufferShared-> |
Magma |
| 18 | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:7137 indirectBuffer-> |
Magma |
| 19 | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:12132 drawBuffer-> |
Magma |
| 20 | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:12133 parameterBuffer-> |
Magma |
Comment-only mentions to keep in view while writing the table (they carry the reasoning): DirectGLES/Managers.cpp:1461 (// A live non-zero-copy map may owe a per-draw SyncPersistentMappedRange push), DirectGLES/Managers.cpp:2681, DirectVulkan/Renderer/VulkanRenderer.h:1214 (// SyncPersistentMappedRange is the push-down. A map taken AFTER the record …).
SyncGpuWrites() — 6 call sites:
| # | Site | Backend |
|---|---|---|
| 1 | MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:4469 indexBuffer-> (pairs with #2 above) |
Espryt |
| 2 | MobileGL/MG_Backend/DirectGLES/Managers.cpp:2643 bufferObject-> |
Espryt |
| 3 | MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp:511 indexBuffer-> (pairs with #8 above) |
Espryt |
| 4 | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:3433 indexBufferShared-> (pairs with #15) |
Magma |
| 5 | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:3827 sourceBufferShared-> (pairs with #17) |
Magma |
| 6 | MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:4161 indexBufferShared-> |
Magma |
Comment-only: DirectGLES/DirectGLES.cpp:459, DirectGLES/DirectGLES.cpp:509.
Note the pattern the table has to preserve: the EBO/max-index sites pair the two calls (rows 1/3/4/5 of the SyncGpuWrites list sit immediately after a SyncPersistentMappedRange on the same object), whereas the *IndirectCount parameter-buffer sites (DirectGLES.cpp:4722-4723, :4824-4825, DirectVulkan.cpp:471, VulkanRenderer.cpp:12132-12133) deliberately do not call SyncGpuWrites — exactly the row > *IndirectCount计数解析 \| **只**SyncPersistentMappedRange()`` in the §5.7 table, and the subject of open question 15 (§6 below).
2.10 The five-part verification gates (ARCHITECTURE §13.2, ARCHITECTURE.md:498-506)
Preamble, ARCHITECTURE.md:499: > "改前改后 nm --defined-only与.text size 完全相等"的门在本方案里按构造死亡(不存在能让旧字节回来的配置);替换是:
① Interface purity, three gates (ARCHITECTURE.md:500) — verbatim:
- 接口纯度三道门(只跑非 verify 构建):A 门 include 图——disaggregated 配置编译
MG_Backend时把MG_State/GLState从 include 搜索路径移除(nm --undefined-only对"只 include 不调用"是瞎的,而RenderState.h → FramebufferObject.h → TextureObject.h正是这种耦合),依赖 P0.5;B 门符号——nm --undefined-only libMobileGLServer.so | grep -E 'MG_State::GLState::|glslang'为空;C 门未声明——grep -c 'pGLContext' MG_Backend/== 0。外加 debug 断言"每个后端 memo 键都是{slot, gen},永不是裸前端指针",由HandleRecycleScenario支撑(重键前必须在至少一个后端上是红的)。
All three turn green only in P13 (ARCHITECTURE.md:369). P1 must not make them further from green: in particular gate C greps the bare token pGLContext, so the MGB_CTX #else arm must be defined outside MG_Backend/.
② — the verify comparator, quoted in full in §2.5 above (ARCHITECTURE.md:501). This is P1's gate.
③ Behavioural A/B (ARCHITECTURE.md:503) — verbatim:
- 行为 A/B:40 个 trace 在
{monolith-pull, monolith-push, split}下 SSIM ≥ 0.99(默认阈值);ctest -L integration-gpu在DirectGLES.与DirectGLES.Pipe./DirectGLES.Split.(DirectVulkan 同)之间逐名相同;单元测试全绿;CTS 逐后端 conformance 在 0.5 pp 内(行 = GL 版本/扩展,列 = 状态计数,rate = Pass/(Pass+Fail),NS 不进分母)。TextureUploadShapeScenario把逐纹理逐帧的上传形状(box vs N region、作业数)录金标比对——+6 ms 悬崖由形状相等把关,SSIM 对它完全不敏感。逐名功能基线是"P1 出口的重构后 monolith"(P1 出口先用 verify 证明等价于81b17c0b);81b17c0b只作性能锚点。
④ Monolith performance non-regression (ARCHITECTURE.md:504) — verbatim:
- monolith 性能不回归:两台设备 reboot-clean、同热窗口、配对 A/B,
tools/bench.sh+ trace replay--benchmark逐帧 JSON;指标是逐线程 CPU 时间,p50 与 p99;绝对阈值——tracker 每 draw 的 ns 公布并设上限(真实拉取基线只有每 draw 6.5–9.3 次 accessor,相对噪声阈值会平凡通过);Blaze3D blend-toggle 微基准单列;关掉 CSO 内容寻址的负面对照。
⑤ Coverage + poison + handle discipline (ARCHITECTURE.md:505) — verbatim:
- 覆盖 + poison + 句柄纪律:G6 重生成 0 UNMAPPED;
gen_pipe_dirty_surface.py重生成 0 未映射 mutator;逐 verb 世代 poison;G7 setter 一致性测试;ResidualValueBlock的offsetof断言与 P13 的sizeof == 0。
Of ⑤, P1 owns: the poison, and the gen_pipe_dirty_surface.py mapping-with-zero-unmapped gate (ARCHITECTURE.md:172, ARCHITECTURE.md:568, scripts/gen_pipe_dirty_surface.py:21-22) — see risk R2.
Two surviving byte-level equalities (ARCHITECTURE.md:506) — verbatim: > MOBILEGL_BUILD_DISAGGREGATED=OFF时nm --defined-only libMobileGL.so | grep MG_Remote 为空且链接行不增加库;nm -D libMobileGL.so | grep mobilegl_server_main在 RelWithDebInfo 里命中。符号与.text 漂移每阶段作为信息性指标发布。
3. What P2 expects P1 to have left in place
P2 row, ROADMAP.md:18, deliverables verbatim:
MG_Impl/Pipe/Tracker(dirty 位、5 个聚合世代、抑制器骨架);gen_pipe_dirty_surface.py首轮映射成门;MGPipeRenderStateSpans+ G7 setter 一致性测试;CsoCache(64 项,键 = pipeline 子集);create/bind_render_state+set_dynamic_state(EsprytSyncRenderState一行不动;MagmaComputePipelineStateHash/GetOrCreatePipeline/ApplyDynamicDrawStateTail改从 CSO 与动态 payload 取);set_pixel_pack_state、set_patch_state、set_vertex_attrib_defaults;set_residual_value_state+ResidualValueBlock绊线;第一片 Track H:Espryt 0b(SlotAllocator+ 6 个 registry → slot 数组 + 删TwinLookupMemo×3/OwnerEquals/g_fbSlotCache/GC)与 Magma 子系统 4(VertexInputStateFactory/VaoDrawMemo重键,删前端 VAO 里的后端裸指针);MOBILEGL_PIPE_LEGACY_MEMOS;补FramebufferSrgb/DepthClamp存储
P2's acceptance gate, verbatim:
集成 × 2 后端 × {pull, push} 逐名相同;40 trace push 下 SSIM ≥ 0.99 双后端;verify 零分歧;
HandleRecycleScenario绿且重键前红;G7 测试绿且拿掉一个字段能红;两台设备配对逐线程 CPU p50/p99 不差且 tracker 绝对 ns 在上限内;Blaze3D blend-toggle 微基准;CSO 内容寻址关闭的负面对照
Concretely, P1 must not paint P2 into a corner on:
- A
{pull, push}A/B arm that is real. P2 compares integration × 2 backends ×{pull, push}by name. That requires theMOBILEGL_PIPE_PUSHbitmap to genuinely select per subsystem, per field (phase B:> 填充器按MOBILEGL_PIPE_PUSH位图逐字段让位,ARCHITECTURE.md:345). P1's accessors and fill points must be structured so a field-granular yield is possible, not just a whole-build switch. Note the honesty caveat P1 inherits (ARCHITECTURE.md:367): after phase C the bitmap stops being a true A/B, which is why P2 also introduces the compile-timeMOBILEGL_PIPE_LEGACY_MEMOS(default ON) arm. SyncRenderStateuntouched.ARCHITECTURE.md:348-region §5.3,ARCHITECTURE.md:347:> **Espryt 的SyncRenderState(693 行)拿到的仍是const RenderStateParameters&,单Uint16早退、三段 memcmp 一行不动**. GO/NO-GO item 2 (ROADMAP.md:47) restates it:> 两个后端上都已推送的渲染状态,SyncRenderState693 行一行未动. P1'sPipeInputs::GetRenderStateParameters()must therefore returnconst RenderStateParameters&— a by-value or restructured return would force aSyncRenderStateedit and break GO/NO-GO item 2. The doc's phase-A comment says exactly this:// 阶段 A:类型与后端今天读到的完全一致(ARCHITECTURE.md:328).- Server-side working
RenderStateParameters.ARCHITECTURE.md:347:> server 侧:每 context 一份 workingRenderStateParameters(1168 B),bind与set_dynamic_state各把自己的 chunk 散射进去。P1'sPipeInputsis where that working copy will live in P2. Size it for that (~20 KB,ARCHITECTURE.md:326). - The five aggregate generations are new frontend fields P2 adds (
ARCHITECTURE.md:164-169, §5.2 table):VertexArrayState::m_anyVaoAttributeGeneration,FramebufferState::m_anyAttachmentGeneration,TextureState::m_anyTextureContentGeneration+m_anyTextureParamsGeneration,BufferState::m_anyBufferChangeGeneration— all marked 新增, landing "on existing bump points (~20 lines)" (ARCHITECTURE.md:170). P1 must not restructure those mutators in ways that scatter the bump points. - The dirty-surface mapping file. P1 creates it (73 entries); P2 turns the first mapping into a gate (
ROADMAP.md:18) — see risk R2 for the phase disagreement. - Residual value block shape. See §2.7:
GetRenderStateParameters,GetPixelStoreParameters, capability bits and the three patch fields must be reachable as whole-struct values. - CSO cache and content addressing.
ARCHITECTURE.md:63(§2.3): the client-side content-addressed CSO maps with LRU caps (render-state 64 / vertex-elements 1024 / sampler 256 / sampler-view 4096 / shader tied toProgramObjectlifetime). P2 also needs the negative control bit insideMOBILEGL_PIPE_PUSHthat disables CSO content addressing (Config.h:320-325,ARCHITECTURE.md:367,ROADMAP.md:52), so P1's use of the bitmap must leave that bit free and meaningful. - Track H's first slice needs handles to be a
PipeInputsfield type. Phase C (ARCHITECTURE.md:346) turnsSharedPtr<frontend object>fields intoMGPipeHandle+ POD descriptors. Handles already exist (MobileGL/MG_Pipe/MGPipeHandles.h; composite-shader band atMGPipeHandles.h:88-90:kMGPipeShaderCsoSlotLimit = 1u << 20,kMGPipeShaderCsoCompositeSlotBase = limit - (limit >> 4), predicateMGPipeIsCompositeShaderSlotat:92-94). P1's object-typed accessors should be shaped so the return type can be swapped without re-touching all 167 read points a second time. - The eight validate entry points are the frame P2's tracker hangs on (
ARCHITECTURE.md:153). P1's fill points should be those same eight shapes, generated, not hand-placed ad hoc. - G7 chunk table is frozen in P2, not P1 (
ARCHITECTURE.md:161:带offsetof的 chunk 表与 setter 一致性测试在 P2), andCoverage.def:72-74warnsGetProvokingVertexModeis not inComputePipelineStateHashtoday/* recorded here so the G7 chunk table has to answer for it before it freezes */. P1 must keep that accessor visible in the field list (it is, field #39).
4. Commit / CI discipline (通用纪律) and the GO/NO-GO items P1 delivers
4.1 通用纪律, ROADMAP.md:7 — verbatim (applies to every commit)
默认 ALL target 必须完整构建;禁止提交热路径插桩(CI grep 门);每个门必须能因它存在的理由变红;Windows 机器不是正确性门;设备对比走 reboot-clean + 同热窗口配对 A/B,CPU 定频按项目协议;每阶段出口跑一次五部分门;每阶段性能判据是逐线程 CPU 时间。
Enforcement in the tree (CI job pipe-gates, .github/workflows/test.yml:835-887, deliberately independent of build-linux per test.yml:838-839):
test.yml:850-853—python3 scripts/gen_pipe.pythengit diff --exit-code -- MobileGL/MG_Pipe/generated.test.yml:862-869— stdio-instrumentation grep gate overMobileGL/MG_BackendandMobileGL/MG_State:fprintf(stderr|stdout,printf(,puts(,std::cout|cerr.test.yml:857-861says the gatestarts with no exceptions, and any addition to it needs a reason in the pull request rather than a quiet whitelist entry.test.yml:871-874—python3 scripts/gen_pipe_dirty_surface.py --summary, with the comment# Informational: … It becomes a gate in P1, when the mapping file exists to diff against.test.yml:876-887—check_doc_citations.py, currently|| true(warning only),# It becomes --strict when the documents settle.P1 edits these docs, so keepfile:linecitations correct — the lint checks exactly thefile:linereferences indocs/Disaggregated/*.md.- Separate job
include-graph-check(test.yml:332-349):python3 scripts/check_include_closure.py --mode both --compiler clang++-20 --self-test --require-all.test.yml:330-331restates the discipline in English:The script's own --self-test is always on: a negative control that stopped tripping fails the job, because a gate that cannot go red is not a gate (ROADMAP.md:7).
Also binding on P1 commits (from repo memory / project convention, consistent with the docs): single-line [Type] (Scope): description messages; no Co-Authored-By.
CTS turnaround (ROADMAP.md:34) — verbatim: > **CTS 周转单独计价**:gl44to46约 56,271 例。逐阶段只跑该阶段可能影响的具名块(P4apacked_pixels、P3b/P4b texture_/shader_image_、P9 transform_feedback*);完整 caselist 只在五个架构边界(P0.5、P3a、P4a、P3b/P4b、P13)与每次合并 dev 之前跑,放 CI 不放关键路径。若周转仍主导排期,加宽估时而不是削弱门。 — P1 is not one of the five full-caselist boundaries, but a merge to dev still triggers a full run.
4.2 The day-43 GO/NO-GO list, ROADMAP.md:44-52 — verbatim, P1's contribution marked
手上必须有:
- P1 交付的逐 draw 逐字段语义等价证明(40 trace + 全部集成测试零分歧) ← P1 delivers this one
- 两个后端上都已推送的渲染状态,
SyncRenderState693 行一行未动- 两片 Track H 的实测单位成本(Espryt 0b、Magma 子系统 4)
- 两台设备(Adreno 830
35d0befa、Mali3B159D009VZ00000)reboot-clean 配对的逐线程 CPU 时间增量,p50 与 p99- tracker 每 draw 的绝对 ns(上限从设备基线定:稳态每 draw 6.5–9.3 次 accessor + memo 探测,见
MEASUREMENTS.md)- Blaze3D blend-toggle 微基准(enable/draw/disable/draw,MC batch 速率)
- 负面对照:关掉 CSO 内容寻址重跑,把"推送更慢"与"CSO 设计更慢"分开
Verdict criteria, ROADMAP.md:56 — verbatim: > - **继续**:两台设备 p50 与 p99 逐线程 CPU 增量都不为负;tracker 绝对 ns 在上限内;Track H 单位成本不超出估计的 50%。按两条跑道推进。
The per-draw per-field semantic equivalence proof = gate 13.2-② run to zero divergence over 40 traces + the whole integration suite, with both negative controls (corrupt a snapshot field → red; drop a field from glGenerateMipmap's fill table → poison Fatal on that verb). It is P1's single most important artifact and it is the first line of the GO/NO-GO list.
The absolute-ns ceiling P1's work is later measured against comes from MEASUREMENTS.md:50-60 (the four-trace table) and MEASUREMENTS.md:64: > **真机稳态动态 accessor 成本是每 draw 6.5–9.3 次**(预测区间 10–25 的下沿;llvmpipe 的 15.5/20.7 是 memo 冷的)。推送要打败的是 ~8 次 accessor + memo 探测,不是 124/169 的静态调用点数。GO/NO-GO 的 tracker 绝对 ns 上限从这里定。 The measured per-draw accessor counts: Espryt 9.28 / Magma 8.56 on minecraft-1.21.4-in-world (MEASUREMENTS.md:52-53); Espryt 8.44 / Magma 6.53 on improved-transparency-minecraft-26.3 (MEASUREMENTS.md:56-57). MEASUREMENTS.md:60 warns accessor-calls is a lower bound (static counting at ~10 hot entry points; site list is a contract at MobileGL/MG_Util/Metrics/PipeStats.cpp:16-100).
5. Open questions and caveats that touch P1
From ROADMAP.md:70-90 ("仍然开放的问题"), the ones that bear on P1:
- #1 (
ROADMAP.md:74) —> **client 侧 dirty 走查的真实每 draw CPU 代价。** 拉取基线已实测为每 draw 6.5–9.3 次 accessor + memo 探测;推送要在这个数字下净减少。P2 的头号数字,逐线程 CPU + 绝对 ns,两台设备。P1's accessor design determines whether this is measurable at all; P1 should keep the accessor call path cheap enough that the P2 number is not dominated by P1 scaffolding. - #5 (
ROADMAP.md:78) —> **FramebufferSrgb/DepthClamp的拍板。** 事实已清(无存储、glEnable静默吞掉、六个读点恒 false、41 个 fixture 无一开启);建议在 chunk 表冻结前补真存储并把FramebufferSrgb划进 pipeline 半边。由计划所有者拍板,**拍板前不冻结 chunk 表**。The sixFramebufferSrgbread points are insideMG_Backendand are read points P1'ssedwill touch; today they consume a compile-timefalse(MEASUREMENTS.md:84). P1 must alias them without silently changing them, and must not freeze the chunk table. - #7 (
ROADMAP.md:80) —> **MG_Util的切割缝。** server 需要 SPIRV-Cross pass 流水线、ESSL 转译缓存、格式处理器、POST 探针;client 需要 glslang phase A/B 与反射层。P0.5 解决了ProgramObject.h一处,MG_Util内部是否有干净的 Transpile-vs-Reflect 缝未审计。Unaudited; may surface as an unexpected include when P1 touches backend TUs. - #14 (
ROADMAP.md:87) —> **推送模型改变哪些按拉取模式调过的缓存命中率。** 幸存者容量在 P13 重调。 - #15 (
ROADMAP.md:88) —> **monolith 的*IndirectCount不调SyncGpuWrites()是不是潜在缺口**(compute 写的 indirect buffer)。独立dev问题,拆分不得借机顺手修。Directly aimed at P1's per-site ownership table: the*IndirectCountrows must record today's behaviour verbatim, bug included. Do not "fix" it in P1. - #17 (
ROADMAP.md:89-90) —> **create-indirect fixture 在 Adreno 830 上的失败**是dev@81b17c0b就有的(基线 APK 复现),不是本分支造成;它是 P3a/P8 验收清单里的用例,需要先在dev上修。P1's "40 trace" gate inherits this:MEASUREMENTS.md:96—minecraft-1.21.1-neoforge-create-indirect-in-worldfails on both devices (Adreno: Espryt black frame after ~4.5 min, MagmaVK_ERROR_DEVICE_LOSTon texture upload submit; Mali: SSIM 0.85 / 0.45). Expect it to be excluded or waived when running the 40-trace verify sweep.
Other caveats in the docs that touch P1:
ARCHITECTURE.md:3—> 落地状态以feat/disaggregated@458ccde1为准whileREADME.md:3says P0.5 landed at5d99ee43;ROADMAP.md:3still saysP0 已落地(feat/disaggregated@458ccde1). The docs' own commit anchors are inconsistent; the ROADMAP table cells are the current truth.ARCHITECTURE.md:172measured surface:> 926 次 mutator 调用落在 73 个不同 mutator 上vsMEASUREMENTS.md:81which says73 个不同 mutator(RecordError一项就占 836 次)and92 次(36 个即时发布点、7 个 mutator …).ARCHITECTURE.md:172says 92 calls / 7 mutators without the 36. Consistent enough; useMEASUREMENTS.md:81for the full shape. Scanner limits are stated atscripts/gen_pipe_dirty_surface.py:196-198: it matches braced bodies textually, a mutator inside a lambda is attributed to the enclosing function, and a mutation published through a helper reads as deferred.ARCHITECTURE.md:33(§1.3) — theInit.cpphook branch lands in P5, not P1:> (分支在 P5 落地;P0 的Init.cpp尚未含它。)MobileGL/MG_Pipe/Coverage.def:16-17says UNMAPPED rows areallowed in P0 and merely counted, ZERO from P5 onward, while gate 13.2-⑤ (ARCHITECTURE.md:505) saysG6 重生成 0 UNMAPPEDas a standing gate andMEASUREMENTS.md:82reports 0 UNMAPPED already. No action for P1 beyond keeping it at zero whenGetBufferBindingSlotis split by target (Coverage.def:36-40).MEASUREMENTS.md:92-95harness traps that will bite a P1 device run: the trace app never reachesMobileGL::DestroyImplsoMOBILEGL_PIPE_STATS_FILEJSON never lands on device (only the periodicMGPipe stats:line inmobilegl.log);run_android_retrace_local.pyrmtrees one shared result root per tree, so two devices must run serially from one tree;--envvalues containing/data/...needMSYS_NO_PATHCONV=1.
6. Risks / discrepancies I found (read these before starting)
R1 — the "293 sites" figure does not match this tree. ROADMAP.md:17 and ARCHITECTURE.md:344 both say sed 293 sites. Measured here: grep -rno "MG_State::pGLContext->" MobileGL/MG_Backend/ → 277 occurrences (274 distinct lines), distributed: DirectGLES/DirectGLES.cpp 91, DirectGLES/Managers.cpp 15, DirectGLES/MultiDraw.cpp 5, DirectGLES/Utils.cpp 2, DirectVulkan/BackendObject_DirectVulkan.cpp 2, DirectVulkan/DirectVulkan.cpp 12, DirectVulkan/Renderer/UniformManager.cpp 14, DirectVulkan/Renderer/VkClearManager.cpp 1, DirectVulkan/Renderer/VkRenderPassManager.cpp 3, DirectVulkan/Renderer/VkTextureManager.cpp 2, DirectVulkan/Renderer/VulkanRenderer.cpp 127. The 58 non-arrow lines matches exactly (DirectGLES/DirectGLES.cpp 1, DirectGLES/Managers.cpp 8, DirectVulkan/BackendObject_DirectVulkan.cpp 2, DirectVulkan/DirectVulkan.cpp 35, DirectVulkan/Renderer/UniformManager.cpp 9, DirectVulkan/Renderer/VertexInputStateFactory.h 1, DirectVulkan/Renderer/VulkanRenderer.cpp 2). Likely the 293 was counted at a different commit or with a different spelling. Re-count and update ROADMAP.md:17 / ARCHITECTURE.md:344 as part of P1, since these are cited numbers under the doc-citation lint.
R2 — the dirty-surface mapping gate is assigned to two different phases. ARCHITECTURE.md:172 (P1 起成为门), ARCHITECTURE.md:568 (信息性,P1 成门), scripts/gen_pipe_dirty_surface.py:21-22 and .github/workflows/test.yml:872-873 all say P1. ROADMAP.md:18 puts gen_pipe_dirty_surface.py 首轮映射成门 in the P2 deliverables and the P1 row does not mention it. Resolve before planning P1's day budget — 73 mapping entries is real work and the P1 row's 10–13 days does not obviously include it.
R3 — MOBILEGL_PIPE_PUSH is simultaneously a preprocessor condition and a runtime Uint64 bitmask. ARCHITECTURE.md:334 uses #if MOBILEGL_PIPE_PUSH to switch MGB_CTX; MobileGL/Config.h:326 defines Uint64 PipePush = 0; and MobileGL/ConfigLoader.cpp:245 reads it from the environment; ARCHITECTURE.md:345 says the fillers yield per field according to the bitmap. These cannot both be the same symbol. P1 must pick the spelling (e.g. a CMake MOBILEGL_PIPE_PUSH selecting the alias arm, with MG_Config::Features.PipePush selecting subsystems at run time) and write it down; leaving it ambiguous is how P2's {pull, push} by-name A/B silently degenerates into a single arm.
R4 — MGB_CTX's #else arm spells pGLContext. Purity gate C greps the bare token under MG_Backend/ (ARCHITECTURE.md:502, and ARCHITECTURE.md:349's last sentence says so explicitly). Define the macro outside MG_Backend/ (e.g. in MG_Pipe/) so the pull arm's spelling does not live where the gate looks. Gate C is not required green until P13, but placing it wrong makes P13 a second migration.
R5 — sticky fields are unaudited. All 61 entries of kMGPipeInputFieldSticky[] are false today (PipeFilled.inc:163-225). Any true P1 introduces is a field the poison stops protecting (PipeFilled.inc:160-162). Candidates that will look sticky and probably are not: GetTextureContextId, GetTextureBindGeneration, GetSamplingResolutionGeneration, GetTransformFeedbackGeneration, GetPipelineStateVersion, GetRenderStateParametersVersion. Each true needs a written argument in the commit.
R6 — MGPipeTypes.h still includes MG_Backend/BackendObject.h. MobileGL/MG_Pipe/MGPipeTypes.h:25-34 documents this as P0.5 debt: MGPCaps embeds DynamicBackendParameters, so purity gate A asserts MGPipeValueTypes.h instead of MGPipeTypes.h, and // The caps block needs fixed-width members before it can move (a type change, not a move): P1/P7. (MGPipeTypes.h:32). P1 may be expected to do the fixed-width conversion; it is not in the ROADMAP P1 row. Confirm scope with the plan owner rather than discovering it when gate A is re-tightened.
R7 — the four memcmp-fallback types. scripts/gen_pipe.py:167-172 (MEMCMP_FALLBACK_TYPES = {RenderStateParameters, PixelStoreParameters, DynamicBackendParameters, MGHostSpan}) and PipeVerify.inc:232-237 both say P1 gives them field lists. Not listed in the ROADMAP P1 row either, but it is a precondition of "zero false positives" for the P1 verify gate — RenderStateParameters is precisely the struct documented to false-DIFFER under memcmp (PipeFields.def:11-13). Treat it as part of deliverable 5.
R8 — verify's cost. 5–10× slowdown over 40 traces + the whole integration suite, on two contended devices, with the serial-run constraint from MEASUREMENTS.md:93. Budget device time; the P1 row's 10–13 days does not price CTS turnaround (ROADMAP.md:34).
7. Quick reference: files P1 touches or creates
| Path | Status | Role in P1 |
|---|---|---|
MobileGL/MG_Backend/MGPipe/PipeInputs.h |
create | The 61-field struct, Espryt 32 / Magma 55 accessors, poison state (ARCHITECTURE.md:325-340, :553) |
MobileGL/MG_Backend/MGPipe/MGPipeImpl_DirectGLES.cpp |
create | ARCHITECTURE.md:553 |
MobileGL/MG_Backend/MGPipe/MGPipeImpl_DirectVulkan.cpp |
create | ARCHITECTURE.md:553 |
MobileGL/MG_Backend/DirectGLES/*, DirectVulkan/* |
edit | 277 arrow sites + 58 non-arrow lines (R1) |
MobileGL/MG_Impl/GLImpl/** |
edit | ~93 (measured 91-line) boundary sites get generated validate/fill calls |
MobileGL/MG_Pipe/PipeFields.def |
edit | field lists for the four memcmp-fallback types (R7) |
MobileGL/MG_Pipe/Coverage.def |
edit | split GetBufferBindingSlot by BufferTarget (Coverage.def:36-40) |
MobileGL/MG_Pipe/generated/*.inc |
regenerate + commit | CI diffs them (test.yml:850-853) |
scripts/gen_pipe.py |
edit | G5 fill-table emission, drop fallback types |
scripts/gen_pipe_dirty_surface.py + new mapping file |
edit / create | 73-entry mapping; gate (R2) |
.github/workflows/test.yml |
edit | third CI mode (MOBILEGL_PIPE_VERIFY); promote dirty-surface to a gate |
CMakeLists.txt |
edit | MOBILEGL_PIPE_VERIFY build option (ARCHITECTURE.md:583) |
MobileGL/Config.h / ConfigLoader.cpp |
already landed | runtime PipePush / PipeVerify (Config.h:320-332, ConfigLoader.cpp:245-246) |
docs/Disaggregated/*.md |
edit | keep file:line citations valid (check_doc_citations.py, test.yml:876-887) |