- 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.
46 KiB
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 的字节一致门)
- 接口纯度: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)。 - 语义影子比对
MOBILEGL_PIPE_VERIFY=1:tracker 再用SnapshotFromGLContext()填一份PipeInputs,G4 逐字段每 draw 比对,打印第一个分歧;verify 构建永不出货,活过 P13。 - 行为 A/B:trace 语料在 monolith-pull / monolith-push / split 下 SSIM ≥ 0.99;
DirectGLES.与DirectGLES.Split.等名集合逐名相同(G2)、测试名只增不删(G14);CTS 逐后端 0.5 pp 内;上传形状金标。 - 性能:Redmi
2f7cbe2ereboot-clean、同热窗口配对 A/B,指标是逐线程 CPU 时间;对着 pull 臂只记录、不设门(用户 2026-09-08)。 - 覆盖 + 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。设备侧 supervisormobilegl_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 不可用。一份共享库两个角色(stubdlopen+dlsym("mobilegl_server_main"))。 - 离屏 server:前台
MobileGLServerService(:mglsrv),从nativeLibraryDirexec,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 statusGL_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 头部,那份清单是契约。