Files
MobileGL/docs/Disaggregated/ARCHITECTURE.md
T
swung0x48 43b5cfa94e [Docs] (Disaggregated): the four index documents keep only summaries, and every phase's detail lives in its own notes/<phase>/README.md
- ROADMAP, MEASUREMENTS and ARCHITECTURE are rewritten as summaries (333 KB of index text -> 84 KB).
  ROADMAP's phase table is one line per phase plus a link; MEASUREMENTS keeps headline numbers under
  its old §1-§13 numbering (plus §14-§16 for P6.5, P7 and P12); ARCHITECTURE keeps every decided rule
  under the same §1-§17 / appendix numbering, so the `ARCHITECTURE.md §N` references in code still
  land, and §17 becomes an index of the phase landing shapes.
- CURRENT_STAGE_PROGRESS now describes the current stage (the P12 on-screen-server subset) and absorbs
  CURRENT_STAGE_STATUS, which is removed. The P7 status tables, the P6 closing snapshot and the P6.5
  first-wave snapshot move to notes/p7, notes/p6 and notes/p65.
- Each phase gets notes/<phase>/README.md: a short summary, then the verbatim material that left
  the index documents (its ROADMAP row, its MEASUREMENTS section with subsection numbers kept, its
  ARCHITECTURE §17 part, milestones) and a file index. P8-P11 and P13 get their planned scope;
  the full open-question list moves to notes/OPEN-QUESTIONS.md.
- The phase reports that sat at the top level move into their phase folders with unchanged names:
  P5D-INPROC-PERFORMANCE, P5E-RUNAHEAD, P5F-WIRE-COMPLETENESS, P6-SPAWN-PLAN, P6-CONTRACT-DRAFT,
  P6-ENDSTATE-REVIEW. Their relative links, CONTRACT-P6.md's links to them and one tool docstring
  are updated; notes/README.md maps every old path and section number to its new place.
- 71 files under notes/recovered/ that were byte-identical copies of phase-folder files are removed.
- Corrections made while condensing: the server process reads Transport as Spawn (CONTRACT-P6 §3.1
  amended the old "child forces monolith" rule); both backends now publish kCapRunAheadApply;
  MOBILEGL_IPC_RESPAWN is a named refusal and MOBILEGL_IPC_IDLE_EXIT_S is not parsed; the Redmi
  pin row is in tools/device_bench/pin_device.sh.

check_doc_citations over docs/Disaggregated/*.md: 0 problems. No relative link is newly broken.
2026-09-24 22:21:09 +00:00

46 KiB
Raw Blame History

MGPipe 设计与架构

只写已决定的设计,每条附一句理由。章节编号稳定(代码注释按 ARCHITECTURE.md §N 引用它们)。阶段号与状态见 ROADMAP.md,实测见 MEASUREMENTS.md;各阶段的落地细节、偏差与当时的论证在 notes/<阶段>/README.md(§17 是索引)。wire 契约原文在 MobileGL/MG_Remote/CONTRACT-*.md,与本文冲突时以更新的契约为准。

1. 边界

1.1 一句话

MG_Backend 本来就是一台贴着目标 API 的状态机(Espryt = DirectGLES,Magma = DirectVulkan),缺的是一份"我被告知了什么"的显式声明。MGPipe 就是这份声明:前端在每条 verb 之前把变化推过去,后端不再拉 MG_State::pGLContext。接口从两个后端自己维护的关键结构反推(SetupDrawSnapshot、DrawTextureSyncKeys、ResolvedVertexBindings、g_syncedRenderStateParameters、BufferBackendOps …);gallium 是词汇的目的地,不是推导前提。

1.2 两张函数指针表

MGPipeScreen(share-group 作用域:caps、resource、persistent map、fence)与 MGPipeContext(其余全部),由 PipeCalls.def 生成。

  • 函数指针 struct 而非虚基类:null 项已表示"未实现,前端回退",MG_Test 整表替换即可 mock。
  • 两张表从第一天分开,事后拆会给记录重新编号;v1 只有一个 screen、一个 context、一条 flow。
  • EGL 生命周期与 caps 面留在 pActiveBackendObject 的虚函数上(罕见路径,走控制面)。

1.3 三种形态,一份后端

形态 表里装的是什么 用途
monolith(默认) 后端自己的函数,回调直调 MG_State 出货
inproc 发射器 → 同进程第二个线程上的 applier CI 主验收;也就是 monolith 的渲染线程
spawn 发射器 → 数据面 → 另一个进程的 applier → 同一批后端函数 两进程形态;控制面可以是 fork / unix: / tcp://(§11.9),server 可在另一台机器上

唯一 hook 点是 MG_Backend::Init() 里一个 #if MOBILEGL_BUILD_DISAGGREGATED 分支:MG_Config::Transport != Monolith 时装 MG_Remote::BackendObject_Remote。MG_Impl 的边界调用点零 #ifdef。

2. 对象模型

2.1 句柄 = {slot, gen}(MG_Pipe/MGPipeHandles.h)

  • 8 字节 POD;client 铸造,server 永不返回句柄 → 整份目录零创建 round trip(对 gallium 的偏离 D1)。
  • slot 按 kind 稠密分配(free list + 高水位),server 对象表是数组;gen 只在 slot 复用时 ++。
  • kind:Buffer, Texture, Renderbuffer, Framebuffer, Xfb, RenderStateCso, VertexElementsCso, SamplerCso, SamplerViewCso, ShaderCso, Fence, Query, Context。保留:{0,0} = null;Framebuffer {0,1} = 默认帧缓冲;ShaderCso 高段给 program pipeline 合成体。
  • GL name 只作诊断(GlNameForDiag),永不做身份、memo 键或 content hash。

2.2 两种世代,严格分开

拥有者 回答 过线
MGPipeHandle::Gen client "还是同一个 GL 对象吗?" 是
MGGen(后端纪元,如 g_bufferMutationEpoch) server "我自己是否重铸了驱动对象?" 永不(只以纹理拉取请求的形式出现,§8.4)

client 的回绕 Uint16 版本计数器永远不是新鲜度的唯一证明:过线时要么加宽,要么与 {slot, gen} 同行。

2.3 CSO 与可变对象

  • CSO(render state、vertex elements、sampler、sampler view、shader)在 client 侧内容寻址(Mesa cso_cache 先例),LRU 淘汰时发 delete_*;render-state 容量 64(P13 重调)、sampler state 256。
  • Buffer / Texture / Renderbuffer 走 create / respecify / subdata / destroy;Framebuffer / Xfb 是 per-context 身份 + set_* payload。
  • 两条偏离:D-G1 Espryt 的 vertex-elements 按 VAO 身份寻址(twin 持驱动 VAO 名,不能共享);D-F2 Espryt 的 sampler view 按纹理身份寻址,sampler state 内容寻址且带引用计数(被 BuiltinSampler 指着的项不许被 LRU 挤掉)。

3. 调用目录

3.1 单一真相源

MobileGL/MG_Pipe/PipeCalls.def:一行一个 X(Name, Payload, Class, Flags, WaitClass)。线上 opcode 就是行序,只能追加、退役保留槽位;行数由 MGP_CALL_LIST_DOCUMENTED_COUNT(今天 81)与 PipeCatalogueTest 钉住。生成器产物提交进树,CI pipe-gates 重生成并 git diff --exit-code:

产物 内容
G1 PipeTables.inc 两张函数指针表
G2 PipeThunks.inc monolith 直调 thunk
G3 PipeWire.inc wire 记录、尺寸 static_assert、applier 边界检查
G4 PipeVerify.inc MOBILEGL_PIPE_VERIFY 逐字段比对器(PipeFields.def)
G5 PipeFilled.inc PipeInputs 字段 id 与逐 verb 世代 poison
G6 PipeCoverage.inc 后端读点 → 调用映射(Coverage.def),0 UNMAPPED 是门
G7 PipeSpanTable.inc render-state pipeline 子集成员表
G8 PipeFieldOwnership.inc 每个 PipeInputs 字段恰属 RECORD-SUPPLIED / APPLIER-DERIVED / BARRIER-PULLED / FATAL 之一(P5f 起 0 BARRIER-PULLED)

另有 FillPoints.def(填充点)与 DirtySurface.def(§5.2)。G1 codegen 规则:capability gate 替换指针表达式时保留表达式形状,否则 pull .text 漂移。

3.2 分组与 flag

  • Class:kScreen(caps、resource、persistent map、fence、ApplierReset)、kCtxQuery、kCtxCso(create/bind/delete × 五种 CSO)、kCtxState(Set* 状态、SetContextValues、SetProgramBindings、迁移期的 SetResidualValueState)、kCtxObject(subdata、readback、mip、ObjectDeath …)、kCtxVerb(blit、clear、DrawVbo、dispatch、XFB、Present …)。
  • Flags:kNeedsAck、kHasBlob、kVarTail、kHostSpan、kReplySlot、kOptional。第五列 WaitClass(P5e)决定 run-ahead 下 client 等不等(§11.6)。
  • 20 个 draw 入口塌成 DrawVbo 一条;Clear 一条判别式;sampler 集合没有 stage 维度(192 单元合并空间);SetTextureParams 按资源寻址、与 sampler view 分开(D10);SetIndexBuffer 独立于 VAO 配置(D5)。
  • 显式不移植:GetIntegeri_v / GetInteger64i_v / GetProgramiv、set_pixel_unpack_state(前端已解析)、压缩格式概念、pipe_transfer。

3.3 能力位(MGPCapBit)

CallMask 取代"槽位是否为 null"这一隐式能力探测;MGPCaps = DynamicBackendParameters + CallMask + 格式能力表与 renderer 字符串两个 blob,握手后快照、MakeCurrent / InitCapabilities 后按 generation 重发布。位包括 viewport array、fp64 顶点、kCapResidentSubData、XFB / query 家族、kCapNeedsHostIndexBytes、kCapNeedsHostUboBytes、kCapBackendOwnsXfbCapture、kCapRunAheadApply(MGPipeRunAheadCapBitsFor:Espryt 自 P5e、Magma 自 P5f 之后的 Magma run-ahead 起发布,后者契约 MG_Remote/CONTRACT-MAGMA-RUNAHEAD.md)。不存在"归属开关"(D-B7):multi-draw 分档与 restart 重写永远由 server 拥有,client 只在 caps 要求时提供索引字节。

4. 记录与 payload 约定(MG_Pipe/MGPipeTypes.h)

  • 每个 payload 是平坦 POD、显式 padding、static_assert 精确尺寸;永不含指针。
  • MGPBlobRef{Offset, Size, Seg} 指向 blob 区;MGHostSpan 是唯一随传输而变的形状(monolith 下是指针,split 下字节在 SEG_STAGE),只进变长尾。
  • wire 记录头 MGPWireRecHeader{Op, Flags, Size}(8 B);没有逐记录序号,seq 就是记录序数。
  • 单条记录上界 = ring 一半,记录本身永不分块(R-10);内容侧分块:会超出 SEG_STAGE 的 blob 按 MGPipeStageChunkBytes()(默认 segment/4 = 8 MiB)切成多条记录(buffer 范围走查、纹理整宽 slab,server 拼回整级)。

4.1 关键 payload

payload 要点
MGPResourceDesc buffer / 纹理 / renderbuffer 一个判别式 create/respecify 形状;ImageBindableHint、ViewOf(视图的存储属主)、HasDefinedContent
MGPRenderStateDesc / MGPBindRenderState / MGPDynamicState §5.3
MGPVertexElements blob 同时带解析后的属性与绑定点视图(记录自洽)
MGPSamplerView / MGPTextureParams view 只带视图限制;纹理参数挂纹理对象,BuiltinSampler 句柄不许为空(D-E1)
MGPProgramDesc 逐 stage SPIR-V + 反射归档(§7)
MGPFramebufferState 8 color + depth + stencil + client 解析后的 read surface;ContentHash 同时是 render-pass memo 键与发射抑制器;Target 含 Named = 3,applier 按 framebuffer 句柄存
MGPSubData / MGPSubRegion §6
MGPDrawInfo / MGPDrawRange DrawVbo;multi-draw 范围与用户索引在变长尾,indirect 的 DrawCount 由 client 解析

每条 kVarTail 的 set_* 带 ContentHash,hash 未变就不发(§5.4)。

5. 前端 state tracker(MG_Impl/Pipe/Tracker)

5.1 推送发生在 verb 之前的 validate 时刻,不在 GL setter 里

per-setter 推送会把 Blaze3D 每个 batch 的冗余 glEnable/glDisable 变成接口调用,严格慢于今天;正确形态是 gallium st_validate_state。落地为唯一的 MGPipeValidateForVerb(MGPipeVerb)(九个 validate 类,FillPoints.def),恰在每次经表调用之前、每个提前返回之后。只有今天就在 GL 调用时刻分发的资源 op(BufferBackendOps 的七个 hook)在调用时刻推送。

5.2 dirty 位:值类零新增记账,对象类 5 个聚合世代

  • 值类组(render state、pixel pack、patch、attrib defaults、shader bindings …)直接复用既有版本计数器作快门。
  • 对象类组(vertex buffers、framebuffer、sampler views / samplers / images、const / shader buffers、SO targets)靠 5 个聚合世代把快门降成一次 Uint64 比较——没有聚合就回答不了"有没有哪张已绑定纹理动了"。
  • 完整性由 scripts/gen_pipe_dirty_surface.py 保证(每个 mutator → 必须 bump 的世代,--check + --self-test 进 CI)。快门看不见自己的主体是踩过三次的坑:新增字段必须带"记录字段 → setter → 快门"一行,优先混入已有世代(新计数器会撑大 pull 对象,G1 不允许)。

5.3 渲染状态:整块 blob 过线,身份只取 pipeline 子集(D-B1)

create_render_state(cso, pipelineSubsetChunks)   // 只带 pipeline 子集
bind_render_state(cso, version, pipelineVersion) // 稳态 12 B
set_dynamic_state(dynamicChunks, version)        // 只带变化的动态 chunk
  • RenderStateParameters(1168 B)整块过线:Espryt 做三段 memcmp,字段顺序承重;拆成多个 CSO 要手工维护 ~150 字段划分且无绊线。
  • 身份只取 pipeline 子集,否则 glViewport / glScissor 每次都铸新 CSO、冲掉 server 的 pipeline memo。划分只写在一处:MGPipeRenderStateSpans 的 chunk 表(7 pipeline chunk 396 B + 8 dynamic chunk 772 B,static_assert 完整性),G7 断言"子集 hash 变 ⟺ m_pipelineStateVersion 变"。
  • Espryt 的 SyncRenderState 一行不动;Magma 的 pipeline memo 键是 cso.slot。

5.4 验证不变式、合并与抑制器

规范(D-B3):**一条 verb 的全部 set_* / bind_* 在该 verb 之前完成;server 在 verb 处从已推送状态惰性特化 shader 与 pipeline。**除"资源 create 先于对它的 bind"外,set_* 之间无顺序要求。合并:整块优于逐字段;高水位直接是 count 实参;只发 program 解析过的集合;集合 hash 抑制器在 client 侧去抖(原先在后端的 ~175 行)。

5.5 sampler view 在 client 侧解析

shader 看见哪个纹理取决于 sampler uniform 类型、mipmap 完备性与未定义默认纹理;解析留在 client(带 memo),gallium "每槽一个 view" 就是解析后的形态。两处后端特定后处理留在 server:Espryt 的 raw-depth-fetch sampler 替换、Magma 的 feedback-loop 检测。

5.6 生命周期、共享组、composite program

  • resource_create 在前端对象构造时发,存储由 resource_respecify 惰性定义,resource_destroy 在析构时发;死亡转发给每个 emitter(只释放 slot 曾导致 UAF)。view 先于存储属主销毁、FBO attachment 钉住纹理、buffer texture 钉住 buffer。
  • program pipeline 合成体(CompositeResolver)完全在前端合成、推一个句柄;释放恰好一次(resolver 换掉或 ~ProgramObject,D-H7);记忆按 (ContextId, 管线 GL 名) 键控。

5.7 emulation 的归属

规则:驱动表达不了的变换在 tracker 里 lowering,硬件 / 驱动强加的变换在 driver 里 lowering。

归 client(读前端字节的纯 CPU 变换) 归 server
client 顶点 / 索引数组(字节过线,永不是指针)、最大索引扫描、*IndirectCount 计数解析、CopyImage shadow 镜像、XFB CPU 图元计数、压缩纹理 / unpack 规整 primitive-restart 重写、multi-draw 分档与展平(从索引宿主镜像读,§10.3)、viewport-array 回放、fp64 顶点窄化、image-bindable 存储加宽、生成 mipmap(client 分配 level 存储)

陈旧索引纪律是逐站点表:client 侧扫描前的 reconcile 必须逐字复现 monolith 的集合(最大索引扫描 = SyncPersistentMappedRange() + SyncGpuWrites();*IndirectCount 只前者)。

6. 纹理 subdata 与 dirty 归属

  • 纹理上传由 Espryt 在 sync 时刻按累积区域做(Mali 按作业数计价,~100 个 rect 对一个 union box 实测 +6 ms/帧),逐 glTexSubImage 发记录会复现这个悬崖。
  • 因此 client 在自己的 rect 模型里累积,在下一个 validate / flush 点发一条 ResourceSubData,同时携带 union box 与 region 列表,由 server 选上传形状(决策留在付 GPU 代价的一侧)。步长显式携带,不再靠指针比较。
  • dirty 归属反转:client 维护发射游标、发射后清自己的标志;清 dirty 要等 applier 的 acceptance(被拒的 level 留在脏表里);server 自己的重新变脏是 server 的事。
  • TextureUploadShapeScenario 把上传形状录成金标(P3b/P4b D2 起是门;Mali 帧时增量无设备,按 Adreno 记录)。

7. Shader state = SPIR-V + 反射归档

  • CreateShaderState 过线的是逐 stage SPIR-V + 反射归档,不是源码:glslang 全在 client、SPIRV-Cross 全在 server,文件级切割;server 侧无编译池。归档序列化器 ProgramArtifactsCodec,monolith 只在 verify 构建里调它(D-H3)。
  • server 在 verb 时刻从已推送状态惰性特化(D-B2:draw FBO clamp mask、fragColor 广播数、storage-block 绑定签名、image 格式、patch 参数等)——正是两个后端原来的做法。
  • link / compile 失败不需要同步返回:GL_LINK_STATUS 由 client 从 ProgramObject 回答,后端失败以 OnLog ≥ERROR 无损呈现。
  • 内部 shader(颜色 blit、深度 mip、multisample resolve)烘焙成签进树的 SPIR-V(Wire*Spirv.h),新鲜度门 MOBILEGL_BAKED_INTERNAL_SHADERS(MG_Test/SelfTest/BakedInternalShadersTest.cpp)重跑树内 glslang 逐字节比对(P7)。

8. 反向通道

8.1 MGPipeCallbacks(MG_Pipe/MGPipeCallbacks.h)

九个具名回调 + 一个正向终止符(ResourceSubDataComplete),取代后端直接 poke 前端对象的 95 个调用点(具名化是有意偏离 D8)。monolith 下直调,split 下是 SEG_EVENT 上的记录。

回调 作用
OnGlError 延迟的 GL 错误;必须对命令流有序
OnGpuWritten GPU 写过的 buffer 范围(收窄 client 保守自建的 pending 集)
OnBufferWriteback PBO 回读、XFB 捕获结果;必须与 epoch bump 有序
OnTextureWriteback CPU 回退生成 mip 的纹素
OnTexturePullRequest §8.4
OnMipLevelsGenerated 只带形状
OnSurfaceChanged 默认帧缓冲的格式与尺寸(server 拥有显示时 client 靠它得知窗口尺寸)
OnCapsInvalidated 取代 InvalidateCompileEnv
OnLog ≤WARN 有损,≥ERROR 无损 + 速率限制

后端凭空造的前端对象(Magma 占位纹理、swapchain 默认 FB 占位)改为 server 原生;pull 构建里的 m_backend 类成员真删会动 sizeof(G1),随 P13 退役(D-K)。

8.2 有序性是正确性要求

每次 writeback 之后的 epoch bump 必须在任何后续读该 handle 的命令之前被 server 应用。反向通道需要与正向通道相同的有序保证。

8.3 错误、ack 与日志

  • 唯一允许同步 ack 的入口是 glBufferStorage:ResourceRespecify 的 kNeedsAck 由逐记录谓词收窄到"不可变 buffer"(D-A2),纹理 OOM 在 monolith 里本来就推迟到 sync 时刻。respecify 带逐 level 作用域与"只换元数据"的形式,不误丢已接受的待上传。
  • 其余错误一律晚到,走有序的 OnGlError。

8.4 唯一的新停顿类:server 发起的纹理重铸拉取(D-B6)

server 不保留纹素;image-bindable 重铸、整格式再生、view 源重铸会要求重发 level。缓解:ImageBindableHint 预防主因;拉取异步(阻塞 apply 线程,不阻塞应用线程);保留 LRU 默认关(MOBILEGL_PIPE_TEXEL_RETAIN_MB=0);显式终止符可带零个 region。真实语料上发生率可忽略(780 个统计窗口 2 次);split 下这条路径今天仍是具名 Fatal{UnmigratedEmulation, "texture-remint-pull"},归 P9。

8.5 XFB scatter 留在 server

补丁循环需要"捕获前的字节",而拆分后权威影子归 server(StagedShadow),所以 scatter 在 server 原地跑完;回程只有一条 OnBufferWriteback,载补好的整段,按 SEG_EVENT 容量的四分之一切片(MGPipeBufferWritebackSliceBytes),最后一片落地即蕴含之前每一片。孤儿目标(HasDefinedContent == 0)零填充后照常散射。OnXfbScatterReady 已删除(回调 10 → 9)。落地论证见 notes/p34b/README.md(espryt D1)。

9. 后端状态机改造

9.1 原样不动的东西

Espryt 的 persistent ring、buffer pool、fallback-repack、scratch FBO、驱动绑定影子、Adreno / Mali workaround、SPIRV-Cross 会话、restart 重写与 multi-draw 分档;Magma 的 VulkanRenderer memo、PipelineFactory、ProgramFactory、UniformManager、五个 Vk*Manager、SwapchainObject、D18 的节点式容器纪律。"输入变了、算法不许动"的那一类由 G5 字节一致门把关(scripts/p3a_untouched_regions.sh 十一函数、scripts/p4a_untouched_regions.sh 17 区),re-pin 必须两份同改。

9.2 strangler 脚手架:PipeInputs + 逐 verb 填充 + poison 世代(P1)

// MG_Backend/MGPipe/PipeInputs.h —— 按 memo 键组织,访问器类型与后端原来读到的完全一致
#if MOBILEGL_PIPE_PUSH
#  define MGB_CTX (&::MobileGL::MG_Pipe::gPipeInputs)
#else
#  define MGB_CTX (::MG_State::pGLContext)
#endif

三步:A 别名(P1,机械替换 + 逐 verb 类填充点,nm 不变)→ B 推送(P2,tracker 填 gPipeInputs,verify 构建逐 draw 比对快照版)→ C 句柄化(P3a–P4a、P7,SharedPtr<前端对象> → 句柄 + 描述符,写回变回调)。poison 是逐 verb 世代:读一个当前 verb 未填的字段是 Fatal{UnmigratedPipeInput, "<Getter>@<Verb>"}。

9.3 Track V / Track H

Track V(值类型:render state、pixel store、capability 位、标量)是机械迁移;Track H(167 个 SharedPtr<MG_State…> 读点)是真活。74% 的读点是翻译输入,所以"bump 一个版本让 server 自己拉"行不通,值本身必须过去。

9.4 残余值块

迁移期临时调用 SetResidualValueState(ResidualValueBlock):尺寸只降不升(1248 → 8,只剩 CapabilityBits),P13 变成 static_assert(sizeof == 0);布局逐成员 offsetof 断言;stats 单独计字节。

9.5 身份 memo 的重键

{slot, gen} + 显式 destroy 让 21 条身份 memo 中 11 条直接删除、2 条的去抖搬到 client(§5.4)、7 条重键成更便宜的比较、1 条(D18)不动。统一事实:进 memo 键的版本计数器要么会回绕,要么根本不被它害怕的 mutation bump,身份比较是堵洞的补丁。

9.6 A/B 与口径收窄

MOBILEGL_PIPE_PUSH 子系统位图在阶段 B 是真 A/B;阶段 C 之后位清零时后端仍跑重键后的代码,所以编译期 MOBILEGL_PIPE_LEGACY_MEMOS(默认 ON)保留 pre-handle 臂,随 pull 路径在 P13 退役。位依赖两侧都拒(客户端族门里根本不发射,否则会"客户端已清 dirty、服务端却走旧臂"而丢上传);没有消费者的后端一条不发。退役的 twin 成员仍在 pull 构建里编译——真删会动 G1。

10. server 侧

10.1 对象表与 applier

MG_Remote/Server/PipeApplier:解码 → 更新对象表与 PipeInputs → 调后端。server 不持有 buffer 的完整副本(只有 staged shadow)、不持有前端对象图;任何传输下都不得有 SharedPtr 或裸前端指针跨过 applier 边界(P5c 角色守卫、P5e 规则 F)。InProcessTransport 与 spawn 走完全相同的编解码路径。

10.2 monolith 侧的净收益

即使 IPC 永不上线:复用地址 ABA 一整类不可表达;FBO → program 排序 hazard 消失;SwapchainObject 写 MG_Impl 的分层倒置消失;inproc = 渲染线程。monolith 净代码量是增加的,论据只看逐线程 CPU。

10.3 索引宿主镜像(Server/IndexHostMirror,P8,待重裁)

设计:kCapNeedsHostIndexBytes 下,server 从本来就要收的 ResourceCreate/Respecify/SubData 流增量维护 element-array buffer 的宿主镜像,零额外流量;预算 MOBILEGL_PIPE_INDEX_MIRROR_MB(默认 64),超预算退化为逐 draw 经 MGHostSpan 传送。P6 的 a6 审计实测宿主索引 span 无 producer、client 索引已是 owned buffer,所以 P8 要按真实消费者重新裁定,不照抄本节(notes/p8/README.md)。

11. 传输与数据面(MobileGL/MG_Remote/)

11.1 段

段 拥有者 默认 内容
SEG_CMD client(server 只读) 8 MiB RingControl 页 + POD 记录 + 小内联负载
SEG_STAGE client 32 MiB bulk 字节:buffer / 纹理 subdata、UBO、client 数组、persistent-map 脏块
SEG_REPLY server 16 MiB,8 × 2 MiB slot readback 像素、acceptance 答案(ID-47)
SEG_EVENT server 256 KiB SPSC ring 回调事件
SEG_SHADOW / SEG_ADOPT client / server — 零拷贝 shadow(Phase 2)/ ≥16 MiB 采纳(P11);stream 数据面上具名拒绝

段在同机上由 ShmSegment 创建(Android ASharedMemory、Linux memfd、Windows CreateFileMappingW),描述符经 SCM_RIGHTS 传递;stream 数据面上两端各持一份同尺寸的私有段(§11.9)。

11.2 RingControl(Ring.h)

一页 4 KiB,每个争用组一条 cache line,按写者分组(P6 lk):producer 线 cmdHead / submittedSeq;consumer 线两个 tail;四个 consumer 写的水位收成 Progress(appliedSeq / retiredSeq / completedFrameSerial / presentAckSerial);最后是 epoch、generation、park 标志与 eventRingFull / eventDropped。游标是单调字节计数、2 的幂掩码、永不重置;pad 记录处理 wrap;不可能的头 → Fatal{ProtocolCorruption}。

11.3 双向 doorbell(Doorbell.h)

两个方向都是"先自旋、再置 parked、再阻塞",发布方仅当对端 parked 时敲;丢失唤醒由两个 seq_cst fence 关闭。CondVarDoorbell(同进程,带 Kill())与 SocketDoorbell(跨进程,对端关闭即死亡检测)。stream 数据面上门铃是本地条件变量,由读线程敲。

11.4 控制面(protocol.fbs、Framing.h、ITransport.h)

  • 一份 FlatBuffers schema:热路径是 struct 直接进 ring(与 POD 逐条 static_assert 对齐);罕见 / 变长 / 需演进的走控制连接的 CtrlMsg(Hello、Welcome、Refuse、CapsSnapshot、SurfaceOp/SurfaceReply、SessionFault、LogLine …),union tag 只追加,控制修订号钉在 sha256(protocol.fbs) 上。
  • 生成物提交进树,CI flatc-check 重生成并 diff;codegen 不进默认构建图。
  • 封帧 [u32 'MGLF'][u32 len][payload],64 MiB 上限,读时校验,坏帧立即闩住失败。

11.5 WAR 危害、拷贝账与背压

Phase 1(今天):GL 调用时刻把字节拷进 stage slot,slot 到 apply 越过它为止不可变,危害按构造消除,代价一次 memcpy;Phase 2(SEG_SHADOW 零拷贝)未做。server 没有第二份 BufferObject,不存在中间拷贝。分配失败升级:扩容 → 对最老未退休批次有界等待 → 硬 drain + generation bump(之后 tracker 全部重推)。

11.6 publish、序号与 credit

  • 每条记录 release-store cmdHead,仅当 consumer parked 时敲门铃;显式门铃点:present、kNeedsAck、eglMakeCurrent、glFlush、stage 余量不足、轮询类入口。
  • seq = 记录序数;字节 credit 与 present credit 两个独立窗口。
  • 等待规则由 WaitClass 列决定(P5e):kWaitReply / kWaitPresent / kWaitApplied / kWaitNone;run-ahead 武装时 kWaitNone 发布即返回。barriered 谓词(MGPipeBarriered)client 与 server 用同一个函数、同一份数据算。
  • 强制等待点:MapBuffer(READ) / GetBufferSubData 源走 SyncGpuWrites;glFinish = 等 appliedSeq 追上再排空反向通道;每个 Server* EGL forwarder 之前等一次。glGetError 最多晚一个 present credit。

11.7 事件回传与溢出

client 在 glGetError、query / sync 查询、eglSwapBuffers、buffer 回读与每轮等待循环中排空 SEG_EVENT。日志有损;语义事件无损:环满时 server 置 eventRingFull、在记录边界停 apply、敲 client,client 排空后清标志并敲回(两半都在,才不死锁)。run-ahead 下 producer 阻塞等排空,整笔预算 MOBILEGL_IPC_EVENT_WAIT_MS;client 不排空 / 已走 / 会话停止 → ReverseChannelForfeit{…} 弃投并闩住(Ph PH-6),而不是 Fatal 打掉 server。不发布 kCapRunAheadApply 的 server(lockstep 臂)保留 P5c 的 Fatal。

11.8 fence 与无 present 负载(P10)

fence 完成度必须来自真的逐 fence 退休,不是 present 水位(DirectGLES 的 g_completedFrameSerial 今天只在 Present 前进);无 present 循环需要 server 插非 present fence tick。Magma 那半已随 run-ahead 落地。

11.9 传输栈的两根轴(P6.5)

终局(2026-09-22):client 与 server 可在不同机器、不同 OS / 架构上经 TCP 连接,同机 spawn 保留。传输栈拆成两根独立可选、握手协商、可混搭的轴,上层只见两个接口:

轴 接口 实现 选项(MOBILEGL_IPC_CONTROL / MOBILEGL_IPC_DATA)
控制面 ITransport(只承载控制帧,不承载描述符) SocketTransport fork(继承 fd)、unix:<path>、tcp://host:port
数据面 ILink(记录预留 / 提交、水位、reply、event、span 解析) ShmLink(共享段 + RingControl + 门铃)、StreamLink(分块封帧,水位与 reply 变消息,发送窗口) shm、stream、auto
  • 配对在会话建立时选定一次;LinkTerms(数据面、窗口尺寸、maxReplyBytes …)由 server 陈述,client 请求可被钳制。
  • 共享段交付与控制面解耦:ShmLink 自带 AF_UNIX aux 汇合名,在 Welcome 里公布,所以同机"TCP 控制 + 共享段数据"是合法配对。auto 的判据是段实际交付成功,回落到 stream 必须具名进日志。
  • 上层零链路种类分支:ShmLink / StreamLink / RingControl 等只出现在 Transport/ 与其测试(纯度 grep 门);角色谓词回答"谁在哪个进程",不回答"用什么链路"。
  • 两端是不同二进制:wireFingerprint(PipeFields.def 派生的逐成员布局摘要 + 目录摘要 + 字节序 / 指针宽度)永远比较;buildFingerprint 只在 Dial == Fork(或 MOBILEGL_IPC_REQUIRE_SAME_BUILD=1)下比较;不一致是 Refuse,不是 abort。
  • TCP 上每条 kWaitReply 就是一个 RTT、SEG_STAGE 字节对的是 30–100 MB/s 的链路:这两列从"记录项"变成可行性数(开放问题 19、20)。

第一波(全 TCP 控制 + stream 数据)已落地;同机混搭(nd auto / sd)是第二波。细节 notes/p65/README.md,契约 MobileGL/MG_Remote/CONTRACT-P65.md。

12. persistent map 与 ≥16 MiB 采纳

AcquirePersistentMap 是永久的地址空间捐赠(≥16 MiB 可变 store 自动采纳,MC 26.3 p99 163 → 21 ms),整个 monolith 改造期不动(D-B4)。拆分下三档由 POST 探针选择(spike B 实测):

档 形态 结论
T0 client 分配 AHardwareBuffer BLOB,server 导入(Vulkan / GLES) 唯一在两台设备、两个后端上都完整读写的档(P11 主攻)
T1 server 导出 opaque fd 仅 Adreno 的 Vulkan 路径
T2 拒绝,client 侧推送 永久正确回退;今天 split 用的档(MOBILEGL_IPC_ADOPT_TIER=2),stream 数据面上强制

T2 下 client 侧推送三件套:不做 map/unmap 命令对(payload 带 hasLiveHostWrites);在每个 validate 点按块(MOBILEGL_IPC_PERSISTENT_BLOCK_KB,默认 64)推送可达的已映射 buffer,脏页追踪 + 哈希抑制只发变化的块(P5d);门 PersistentCoherentMapScenario。mpr 数的是每一次 MapPersistent 发射(铸成或拒绝都算),所以在 monolith 下也可断言。

13. 回读、roundtrip 清单与验证

13.1 稳态零 roundtrip 与不可避免的阻塞点

  • 零 roundtrip:全部 draw / clear / blit / copy / dispatch / XFB / bind / CSO / set_* / 上传 / present;全部 caps 站点;glGetError / glFinish / glFlush;fence 与 query 的创建和非阻塞轮询;eglSwapBuffers;restart / multi-draw。
  • 不可避免(罕见):握手;surface 生命周期;glReadPixels 到客户内存;GPU-write pending 的 buffer 首次 CPU 读;带超时的 sync / query 结果;glBufferStorage 的 ack;纹理拉取;ring / stage 耗尽与 present credit。
  • 跨机上每条 kWaitReply(ResourceCreate、SetTextureParams、纹理 ResourceSubData)都是一个 RTT:逐帧次数是 P6.5 必测数,减少它是 P9 / P10 的活。

13.2 五部分验证门(取代 monolith 的字节一致门)

  1. 接口纯度:A 门 include 图(scripts/check_include_closure.py)、B 门 server 镜像不引用 MG_State::GLState 与 glslang(P6 的 a6 实测 server 仍链 MG_Impl,由 scripts/link_ratchet.py 的下行棘轮追踪,P13 转绿)、C 门 MG_Backend 里零 pGLContext;外加"每个后端 memo 键都是 {slot, gen}"(HandleRecycleScenario)。
  2. 语义影子比对 MOBILEGL_PIPE_VERIFY=1:tracker 再用 SnapshotFromGLContext() 填一份 PipeInputs,G4 逐字段每 draw 比对,打印第一个分歧;verify 构建永不出货,活过 P13。
  3. 行为 A/B:trace 语料在 monolith-pull / monolith-push / split 下 SSIM ≥ 0.99;DirectGLES. 与 DirectGLES.Split. 等名集合逐名相同(G2)、测试名只增不删(G14);CTS 逐后端 0.5 pp 内;上传形状金标。
  4. 性能:Redmi 2f7cbe2e reboot-clean、同热窗口配对 A/B,指标是逐线程 CPU 时间;对着 pull 臂只记录、不设门(用户 2026-09-08)。
  5. 覆盖 + poison + 句柄纪律:G6 0 UNMAPPED、dirty-surface 0 未映射、G8 归属完备、逐 verb poison、G7、残余块棘轮。

两条幸存的字节级等式:MOBILEGL_BUILD_DISAGGREGATED=OFF 时库里零 MG_Remote 符号;G1 pull 构建的符号集与 .text 对基线恒等(每阶段 0 增 / 0 删 / 0 resize / 0 重命名)。每个门必须能因它存在的理由变红(R-16):带阴性对照并真跑过一次红;公共 GL 看不见的改白盒断言;拒绝普查逐用例读私有日志(ctest -V 是假零)。

13.3 长期语义门:MGPipe recorder(P13)

MG_Test 的 mock 后端变成 MGPipe recorder,在一组 fixture 上录每 draw 的已推送状态做金标;它只覆盖推送内容,不覆盖后端对它的解释(开放问题 12)。

14. Present、线程与帧节奏

  • eglSwapBuffers → present{frameSerial} → publish + 敲门铃 → 返回,除非超出 credit;present 与 eglSwapBuffers 严格 1:1(后端的帧边界排空只在 Present 内发生)。
  • MOBILEGL_IPC_PRESENT_CREDIT 默认 1(range 1..8):credit 是 run-ahead 唯一的稳态背压,FrameSerial 由 client 铸造、1 起算,server 在 Present() 返回后归还 credit;等待发生在编码之前。credit > 1 从未量过(P10)。
  • 预算(~850 draw/帧):SEG_CMD ~70 KB/帧,SEG_STAGE ~1 MB/帧;跨机时这 ~1 MB/帧对着的是 Wi-Fi。
  • 线程:client v1 不加线程,编码在 GL 线程直接写 ring;server 有 io 线程与终身持有原生 context 的 mgl-srv-apply。全库无有效亲和性控制(Redmi 内核忽略 app 线程的 sched_setaffinity)。
  • 拆机顺序:publish + server 排空 → 停 apply 线程 → 关传输 → client 排空编译池 → MobileGL::Destroy()(落地形状见 §17)。

15. 进程、EGL 与平台(P6 / P6.5 / P12)

15.1 启动与握手

  • 端点:fork(socketpair + fork/execve,fd 3 = socket;server 由 MOBILEGL_IPC_SERVER_PATH 或 dladdr 同目录的 libMobileGLServer.so 定位)、unix:<path>、tcp://host:port。设备侧 supervisor mobilegl_server_main <endpoint> --serve:listen → 认证 → 每会话 fork 一个子进程(或进程内线程,P12),同时只服务一个会话,第二个连接 Refuse{Busy}。
  • server 进程里 Transport 读作 Spawn,不强制 monolith(两百多处 Transport != Monolith 判断靠它选 server 臂;CONTRACT-P6.md §3.1 修订了原"子进程强制 monolith"的规则)。防无界 fork 链靠不继承的 Dial == No(MOBILEGL_IPC_DIAL=no),spawn 时构造的 envp 剔除 MOBILEGL_TRANSPORT 与 MOBILEGL_IPC_* 作第二道独立保险;server 主程序自设 MOBILEGL_IPC_ROLE=server。
  • Hello(abi、后端类型、指纹、配置、令牌、LinkTerms 请求)→ Welcome(server 陈述的尺寸、段 / aux 名、dataNonce、真实 pid)或 Refuse{code, detail, 对端值};握手路径零 abort。非 loopback 监听必须有令牌(≥16 字节、常量时间比较、数据面按 dataNonce 绑定到已认证的控制连接、fork 前认证;Ph,OQ-21 裁定不做 TLS)。
  • TCP keepalive / 心跳失败由传输层报告为挂断,进 device-lost 闩;"闩绝不取自 apply 超时"不因此松动。冷启动期 server 发 SurfaceProgress,client 预算这段沉默(控制修订 2)。

15.2 Android

  • 交付链(spike A 已证):APK 唯一可 exec 的位置是 lib/<abi>/,server 以 add_executable + lib*.so 命名构建;从 untrusted_app 进程 fork+execve 同域、零 avc denial;posix_spawn 在 minSdk 26 不可用。一份共享库两个角色(stub dlopen + dlsym("mobilegl_server_main"))。
  • 离屏 server:前台 MobileGLServerService(:mglsrv),从 nativeLibraryDir exec,fork-per-session,EGL 是 pbuffer / surfaceless。
  • 上屏 server(P12 子集):窗口归 server 自己(client 的窗口不跨进程)。MobileGLDisplayActivity(:mglwin)持全屏 SurfaceView,在自己的进程里用一个线程跑 TCP server(会话串行、会话间重置闩、后端按进程钉住);ServerDisplay 以 acquire / release / 几何三个钩子持有窗口,apply 线程以租约使用它。client 设 MOBILEGL_IPC_SURFACE=server 后无头:eglCreateWindowSurface(NULL) 发一帧 WindowKind::ServerOwned(控制修订 3),不发 SetWindowHandle,尺寸由 reply 带回,resize 变成对 server 窗口的几何请求。一台设备同一时刻一个 server;一个会话一种表面模式(SurfaceModeMismatch);无显示的 server 对 ServerOwned 具名拒绝(NoServerDisplay)。
  • 失窗:surfaceDestroyed 有界(3 s)阻塞,直到 apply 线程(批内每条记录前也检查)放掉后端表面并以 Fatal{ServerWindowLost} 闩住会话;server 继续监听。进程内会话结束时手动做进程退出本来白给的清理(Espryt twin 与单元影子、swap interval、Magma renderer)。
  • HeadlessGL 的 fork 预检会产生孤儿 server:server 的 EOF 检测即时且无条件退出。

15.3 Linux / Windows / 崩溃

  • Linux / WSL / CI 永不开窗(EGL_PLATFORM=surfaceless);Wayland 不支持。Windows / 桌面 client 在 TCP 终局下是真实形态(Windows 是第二批,开放问题 22);macOS 不拆分;Windows 机器不是正确性门。
  • server 死:client 读到挂断 → device-lost 闩(GL 调用 no-op、eglSwapBuffers 返回 EGL_CONTEXT_LOST、reset status GL_UNKNOWN_CONTEXT_RESET);Session::Fail 漏斗向对端发 SessionFault 帧命名家族(FatalFamilies.def)。不重启:MOBILEGL_IPC_RESPAWN 今天是具名拒绝。client 死:server 读到 EOF 立即销毁 context 并退出(supervisor 继续服务下一连接)。

16. 构建布局

MobileGL/MG_Pipe/            永远进构建:*.def 目录与表、MGPipe*.h payload / 句柄 / 回调、PipeApply、PipeRoute、generated/*.inc
MobileGL/MG_Impl/Pipe/       Tracker、PipeFill、SlotAllocator、CsoCache、CompositeResolver、ResourceTracker、SetHashSuppressor、*Emit.h
MobileGL/MG_Backend/MGPipe/  PipeInputs.{h,cpp}
MobileGL/MG_Remote/          仅 MOBILEGL_BUILD_DISAGGREGATED:CONTRACT-*.md、CapsCodec、FatalFamilies.def / FatalFunnel、Handshake.h
  Protocol/   protocol.fbs、generated/、SurfaceOpCodec、mg_protocol_base.h
  Transport/  ITransport、SocketTransport、InProcessTransport、ILink、ShmLink、StreamLink、Ring、SessionRings、ReplySlot、EventRing、
              Doorbell、ShmSegment(+Posix/Win32)、FdPassing、Framing、AuthToken、ControlInbox、LinkMetrics、WireLog
  Wire/       PipeWireCodec(记录编解码、WireVerbSink)
  Client/     BackendObject_Remote、ClientSession、EmitTables、WireTables、CapsMirror、PersistentMapTracker、GpuWritePending
  Server/     PipeApplier、ServerLoop、ServerSession、ServerMain / ServerEntry / ServerSpawn、PreAuthGate、StagedShadow、StagedTextureStore、
              SurfaceControlFrame、ServerDisplay、InProcessServer、DisplayServerJni
  • MOBILEGL_BUILD_DISAGGREGATED(默认 OFF)追加 MG_Remote/**;OFF 时 MG_Config::Transport 是 constexpr Monolith。MOBILEGL_BUILD_DISAGGREGATED_INPROC 隐含前者并加角色隔离:两个角色靠 apply 线程与控制邮箱分开,而不是给 1494 个 pGLContext-> 读点加 TLS。
  • MOBILEGL_TRANSPORT = monolith | inproc | spawn 是拓扑(G1 相关);控制面与数据面由 §11.9 的两个变量选。split build 不设 MOBILEGL_TRANSPORT 时是 monolith 对照臂。
  • 四个构建 flavour:pull(默认)、push、verify、split;Android 三份 APK flavour(pull / push / split)。
  • 测试接线:ctest ENVIRONMENT 用 mgl_itest_join_environment(...) 构造;每条 split / spawn / tcp 条目带独立日志路径,日志按角色分文件(<base>.client.log / <base>.server.log);spawn、tcp 车道与 split 名集合一致(scripts/ci/spawn_lane_parity.py),每条目记 arm 证明。
  • CI(.github/workflows/test.yml、apk.yml):pipe-gates(G1–G8、生成器 self-test、符号报告、dirty-surface、字段归属、G5、文档引用 lint)、flatc-check、include 闭包、各 flavour 的 build / integration / retrace 车道、APK + AVD。两份 workflow 里的 feat/disaggregated 触发器是临时的,合入 dev 前移除。

17. 阶段落地形状(索引)

各阶段落地时的形状、偏差与论证原文在 notes 里;下表只列仍然生效的规则。

原节 主题 仍生效的规则 全文
17.1 verb barrier 与诚实的同地址空间传输(P5) 同地址空间不得成为旁路(R-2):encoder 把 MGHostSpan::Ptr 恒写 nullptr;reply 以记录序号为 slot id(R-3);等待看门狗 120 s 只判死锁,不是性能门 notes/p5/README.md
17.2 后端槽三类、caps、tight readback(P5) A 本地答 / B 发射 / C 具名拒绝,永不回落 monolith(R-4);能力只读 CallMask(R-8);ReadPixels 线上恒 tight,client 按自己的 pack state 散射 同上
17.3 server 角色、shadow 与退出(P5) apply 线程终身持有 native context,EGL 罕见操作经控制邮箱进 apply 线程;staged shadow 只交 server-owned 拷贝;有界退出顺序 同上
17.4 lane 隔离(P5) 每条 split 条目私有日志;E1 / E3 控制把"选中条目被跳过"判为失败 同上
17.5 class-C verb 迁移(P5b) 迁移顺序由动态普查决定;一次迁移 = 一个发射器 + 一个 sink 体;记录逐字带 GL 调用参数,未测量的形式按名拒绝(规则 D) notes/p5b/README.md
17.6 共享地址空间访问归零(P5c) server 端纹理 staged shadow;SEG_EVENT 是唯一反向通道;sink / twin 按记录句柄解析;applier_reset / object_death 控制记录;角色守卫 Fatal{RoleViolation} notes/p5c/README.md
17.7 run-ahead(P5e) 规则 F:unbarriered 记录的 apply 不读任何 client 内存(违反即具名 Fatal,与 strict 开关无关);gPipeInputs 是 server 角色内存;RunAheadArmed() 在首次 caps 采纳时锁存,只能关不能开;RUN_AHEAD=0 是 A/B 对照,VERB_BARRIER=0 会连带关掉 run-ahead notes/p5e/README.md

附 A:开关

CMake:MOBILEGL_BUILD_DISAGGREGATED(OFF)、MOBILEGL_BUILD_DISAGGREGATED_INPROC(OFF,隐含前者)、MOBILEGL_PIPE_PUSH(OFF;push / verify / split flavour 打开)、MOBILEGL_PIPE_VERIFY(OFF;隐含 push,永不出货)、MOBILEGL_PIPE_LEGACY_MEMOS(ON)、MOBILEGL_BUILD_SERVER_SPIKE(仅 Android spike)、MOBILEGL_FLATC_EXECUTABLE(只服务 flatc-check)。

运行时,MGPipe(MobileGL/Config.h、ConfigLoader.cpp):

变量 默认 说明
MOBILEGL_PIPE_PUSH pull 0;push 0x1fff 子系统位图(MG_Pipe/MGPipe.h,位永不复用;依赖两侧都拒);位 63 是"关 CSO 内容寻址"的行为对照
MOBILEGL_PIPE_VERIFY / _VERIFY_FATAL / _VERIFY_CORRUPT / _POISON_OMIT 0 / 1 / 空 / 空 影子比对与两个阴性对照(verify 构建才有)
MOBILEGL_PIPE_HANDLE_ABA_CONTROL 0 故意打掉句柄身份的阴性对照
MOBILEGL_PIPE_STATS / _STATS_PERIOD / _STATS_FILE 0 / 120 / 空 边界计数器(附 B);dump 按角色写 <base>.client.json / <base>.server.json
MOBILEGL_PIPE_LEGACY_MEMOS / _TEXEL_RETAIN_MB / _INDEX_MIRROR_MB ON / 0 / 64 pre-handle 臂、纹理拉取保留、索引镜像预算(P8)

运行时,传输与 IPC:

变量 默认 说明
MOBILEGL_TRANSPORT monolith 拓扑:monolith / inproc / spawn
MOBILEGL_IPC_CONTROL / MOBILEGL_IPC_DATA fork / auto 控制面端点与数据面(§11.9);MOBILEGL_IPC_SERVER_PATH 定位 fork 出的 server
MOBILEGL_IPC_TOKEN / MOBILEGL_IPC_REQUIRE_SAME_BUILD 空 / 0 令牌(≥16 字节;无令牌只允许 loopback);要求两端同一构建(车道纪律)
MOBILEGL_IPC_PREAUTH_* / MOBILEGL_IPC_AUTH_BACKOFF_* — server 预认证的上限与失败退避(Ph)
MOBILEGL_IPC_CONTROL_TIMEOUT_MS / MOBILEGL_IPC_COLD_START_MS 5000 / 20000 控制回复的稳态上限与冷启动预算
MOBILEGL_IPC_LOG_FORWARD 远端时开 server 日志经控制连接前送,client 写进自己的 <base>.server.log
MOBILEGL_IPC_SURFACE offscreen server = 使用 server 自有窗口,client 无头(P12)
MOBILEGL_IPC_RING_MB / MOBILEGL_IPC_STAGE_MB 8 / 32 SEG_CMD / SEG_STAGE;stage 的 1/4 是内容分块预算
MOBILEGL_IPC_RUN_AHEAD 1 P5e run-ahead;只是合取式的一半(server 须发布 kCapRunAheadApply);0 是 A/B 对照
MOBILEGL_IPC_VERB_BARRIER 1 0 只作阴性对照,且同时关掉 run-ahead
MOBILEGL_IPC_PRESENT_CREDIT 1 1..8,§14
MOBILEGL_IPC_BATCH_WAITS 1 lockstep 下值类记录发布即返回;verify 强制 0
MOBILEGL_IPC_EVENT_WAIT_MS 2000 反向事件遇满环时 server 等 client 排空的整笔预算(§11.7)
MOBILEGL_IPC_WIRE_DEFERRED_MB 64 Magma wire 臂延迟回收的水位线;0 是 M2 阴性对照
MOBILEGL_IPC_PERSISTENT_BLOCK_KB / _PERSISTENT_HASH_SUPPRESS 64 / 1 persistent-map 推送粒度 / 只推变化的块;0 是对照
MOBILEGL_IPC_ADOPT_TIER 2 auto/0/1/2;T0 / T1 今天是 Fatal-at-use
MOBILEGL_IPC_STRICT_ERRORS / MOBILEGL_IPC_AUDIT / MOBILEGL_IPC_ROLE_SPLIT_STATE 0 / 0 / 0 残余输入读升级为 Fatal / 退休 staging 填 0xDD / 双块演练(P5f)
MOBILEGL_IPC_SPIN_US / MOBILEGL_IPC_SERVER_AFFINITY 50 / auto park 前自旋预算 / apply 线程亲和(Redmi 内核忽略)
MOBILEGL_IPC_RESPAWN — 具名拒绝:没有阶段实现 server 重启后的全量重推

显式不设立:MOBILEGL_IPC_PROGRAM(没有 relink 档)、MOBILEGL_IPC_VALIDATE_SERVER。计划中未接线:MOBILEGL_IPC_POLL_ESCALATE(P10)、MOBILEGL_IPC_IDLE_EXIT_S(未解析)。

附 B:边界计数器(MobileGL/MG_Util/Metrics/PipeStats.h)

关闭时每站点一次全局 load + 一条永不命中的分支。字节类(stage buffer / texture / UBO / client 数组、pmap、resid、csob-blob、seg = 写进 SEG_STAGE 的字节)、调用类(draws、accessor-calls、纹理上传 emit / box / rect / jobs、csom / csob、mpr、trp、rsp、wrec、maxrec / ringwraps / ringpads / ringwaits、门铃 srv / srvpark / cli / clipark)与六个 memo 门的 hit/miss。每 MOBILEGL_PIPE_STATS_PERIOD 帧一条 MGPipe stats: 汇总行,按角色写日志。读法陷阱:accessor-calls 是静态下界,判性能只看 CPU 时间;spawn 下 client 不推进帧窗口(frames=0),窗口化字段在 spawn client 上无效,run total 仍有效(notes/p6/README.md §12.1)。站点清单(哪些路径没有接线)写在 PipeStats.cpp 头部,那份清单是契约。