mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Docs] (docs/Disaggregated, MG_Backend/MGPipe, MG_IntegrationTest/Harness, MG_Pipe): shorten long comments and move their design arguments into docs
This commit is contained in:
@@ -28,6 +28,17 @@
|
||||
- 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.2.1 flag 与 WaitClass 的含义
|
||||
|
||||
- `kHasBlob` 严格表示 payload 拥有一个 `MGPBlobRef` 成员,而不是"这条调用带字节":解码器信任它就必须有成员可读。`ResourceRespecify` 的初始内容与 `ResourceFlushRange` 的字节不走 blobref,而以相邻的 `resource_subdata` 记录过线。
|
||||
- `kNeedsAck` 只表示这条调用的记录**可能**要 ack,由逐记录谓词决定(`MGPipeResourceRespecifyNeedsAck`,§8.3)。
|
||||
- `kReplySlot`:applier 返回 client 会据以动作的答案(清 level 脏标、闩参数)。flag 只导出一份 `kMGPipeCallFlags[]`(`generated/PipeWire.inc`),消费者不另抄。
|
||||
- 第五列 `WaitClass` 是值不是 opcode,生成为 `MGPipeWaitClassFor(op)`,是 `MGPipeBarriered`(§11.6)的静态一项;client 只在 run-ahead 武装(server 发布 `kCapRunAheadApply`)时据它决定等不等,否则走 lockstep:
|
||||
- `kWaitReply`:答案不可推导且 client 要用,阻塞在 reply slot。与 `kReplySlot` 同在同缺,`gen_pipe.py` 拒绝不一致的行:不等的 reply 行会让 client 读一个没人填的 slot,没有 slot 的等待行会等一个没人发的答案。逐记录 route 谓词可再放宽(如 `MGPipeSubDataWantsItsReply`)。
|
||||
- `kWaitPresent`:节奏由 encode 之前取的 present credit 决定,publish 之后不再等。
|
||||
- `kWaitApplied`:client 停到记录 apply 完(今天只有 `applier_reset`)。
|
||||
- `kWaitNone`:apply 不读 client 内存,发布即走。
|
||||
|
||||
### 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 起发布,后者契约 `docs/archive/contracts/CONTRACT-MAGMA-RUNAHEAD.md`)。**不存在"归属开关"(D-B7)**:multi-draw 分档与 restart 重写永远由 server 拥有,client 只在 caps 要求时提供索引字节。
|
||||
|
||||
@@ -14,6 +14,17 @@ per-setter 推送会把 Blaze3D 每个 batch 的冗余 `glEnable/glDisable` 变
|
||||
- 对象类组(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.2.1 dirty-surface 映射的答案(`DirtySurface.def`)
|
||||
|
||||
`DirtySurface.def` 为每个后端可观察的前端 mutator 写明"谁发布这次修改"。漏发布不报错、只会静默渲染陈旧状态,所以清单由 `scripts/gen_pipe_dirty_surface.py` 机械枚举:扫描 `MG_Impl/GLImpl` 与 `MG_State/GLState` 里按 `MUTATOR_PREFIXES` 匹配的 `pGLContext->` 调用和 `MGP_NOTE_MUTATION` 站点(后者带字段名,行写在外层函数上),`--check` 双向核对行集。
|
||||
|
||||
答案只列在**每条发生修改的路径**上都触发的发布者(值未变而提前返回不算修改);只在部分路径触发的位若单列,按表构造的快门会欠触发,而欠触发是危险方向。
|
||||
- `NEW_*`:`Tracker.h` 的 dirty 位,下一个 verb 发布;`kImmediate`:同一函数体直达后端;`kReverseChannel`:后端写回前端;`kNoBackendRead`:没有后端读点。
|
||||
- `kExplicitDestroy`:最后一个引用释放时,client 的 `MGPipeEmit*DestroyAndFree` 依次做 wire 删除(该类有删除行时)、后端通知、释放 slot;`kUnpublishedDestroy`:program pipeline 与 shader 没有 wire 句柄,无从删除。
|
||||
- `kPulledEveryVerb`:没有位移动,字段在其 verb 类的 may-read 表里,残余填充每个 verb 都拷贝;`kPulledPartialShutter|NEW_*`:同样靠拉取兜底,所附的位只在部分修改路径上触发。
|
||||
|
||||
位答案由脚本推导:RenderState 族与 `RenderState.cpp` 双向比对(`BumpVersions()` 动两个计数器,裸 `++m_version` 只动 `NEW_RENDER_STATE`);其余位把快门读的成员 / 字段与 mutator 传递写入的成员 / 字段求交,只能查出"该位的快门在任何路径上都不动"这一种错答,证明不了每条路径都动。写入根定位不了的函数被污染,依赖它的答案为 UNDECIDED,须列入 `MGP_DIRTY_SURFACE_UNDECIDED_LIST`,列了却已可判定同样报红。散文答案不经推导(`kPulledPartialShutter` 所附的位除外);扫描器按文本匹配函数体,lambda 里的 mutator 记到外层函数上,经 helper 发布的修改读作延迟发布,语义证明仍是 `MOBILEGL_PIPE_VERIFY`。
|
||||
|
||||
### 5.3 渲染状态:整块 blob 过线,身份只取 pipeline 子集(D-B1)
|
||||
|
||||
```
|
||||
@@ -56,6 +67,16 @@ shader 看见哪个纹理取决于 sampler uniform 类型、mipmap 完备性与
|
||||
- **dirty 归属反转**:client 维护发射游标、发射后清自己的标志;清 dirty 要等 applier 的 acceptance(被拒的 level 留在脏表里);server 自己的重新变脏是 server 的事。
|
||||
- `TextureUploadShapeScenario` 把上传形状录成金标(P3b/P4b D2 起是门;Mali 帧时增量无设备,按 Adreno 记录)。
|
||||
|
||||
### 6.1 server 侧待上传集合
|
||||
|
||||
- client 在 applier 接受某 level 的 `ResourceSubData` 时就清掉自己的 dirty 标志;后端的上传循环有提前返回的臂(Espryt:纹理不完备、multisample 目标只刷新),所以已接受的形状由 applier 按 `(uploadTarget, level)` 累积在 `MGPipeResourceRecord::PendingUploads`,只在后端真正上传处消费并清除,任意次 bail 都不丢纹素。
|
||||
- 累积沿用 client 的 rect 模型:union box 取并、rect 列表拼接;任一侧只有 box,或列表将超过 `kMGPipeMaxPendingUploadRegions`,该项退化为只有 box(仍覆盖全部纹素,只多传空隙)。
|
||||
- respecify 只丢弃它替换掉的那份存储上的待上传(盒子在那份存储的坐标系里,跨缩小保留会越界上传):
|
||||
- level 为空 = 整资源重定义(`glBufferData` / `glBufferStorage`、`glTexStorage*`、纹理视图),全部清空;
|
||||
- 具名 level = 一次 `glTexImage*D`,只丢该 `(uploadTarget, level)`,与描述符是否变化无关。整体清空会丢掉别的 level 已被接受、尚未上传的纹素:`glTexImage2D(0)` → draw(纹理不完备,后端 bail)→ `glTexImage2D(1)`;
|
||||
- 只换元数据(`RespecifyRedefinesNoStorage`:sticky 的 `BindMask` / `ImageBindableHint` 变化时重发的描述符)不丢任何项。
|
||||
- buffer 没有待上传项,也永远不算只换元数据。
|
||||
|
||||
## 7. Shader state = SPIR-V + 反射归档
|
||||
|
||||
- `CreateShaderState` 过线的是逐 stage SPIR-V + 反射归档,**不是源码**:glslang 全在 client、SPIRV-Cross 全在 server,文件级切割;server 侧无编译池。归档序列化器 `ProgramArtifactsCodec`,monolith 只在 verify 构建里调它(D-H3)。
|
||||
|
||||
@@ -13,6 +13,15 @@ Espryt 的 persistent ring、buffer pool、fallback-repack、scratch FBO、驱
|
||||
```cpp
|
||||
// MG_Backend/MGPipe/PipeInputs.h —— 按 memo 键组织,访问器类型与后端原来读到的完全一致
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
|
||||
#### 9.2.1 server 侧的 verb 戳与读检查
|
||||
|
||||
client 的残余填充在自己的 verb 边界给全部字段盖戳,server 读 `PipeInputs` 前必须换成自己的戳:与 client 共用一块时,不清零则每次读都“新鲜”、`rsp` 恒为 0,门形同虚设;两块分开时,没有戳则第一次读就中止。
|
||||
|
||||
- 盖戳点:`PipeApplier::StampVerbBoundary` 只在 `MGPipeVerbForWireOp`(`FieldOwnership.def` 的 `MGP_VERB_OP_LIST`)映到 verb 的 op 上调用 `MGPipeServerStampVerbBoundary`:递增 `CurrentVerbSerial`、记下 verb,给该 verb 类可读掩码(`FillPoints.def`)内的 `RECORD_SUPPLIED` / `APPLIER_DERIVED` 字段盖新戳,其余字段戳清零(“从未填充”先于 sticky 判定,所以清零也取消 sticky 豁免)。
|
||||
- 非边界记录(如两次 draw 之间的 `set_dynamic_state`)不盖戳,沿用前一边界的序号、掩码与 verb 名,但盖戳标志已在上一条记录后清除,读检查按盖戳之外处理;20 个 draw 入口都盖成 `DrawArrays`,掩码正确,Fatal 里的名字是泛称。
|
||||
- 读检查只在 server 盖戳的 verb 内按归属类(§3.1 的 G8)分流:`RECORD_SUPPLIED` / `APPLIER_DERIVED` 读到旧值是 `Fatal{RoleViolation}`,`FATAL` 直接中止,`BARRIER_PULLED`(仅供生成器对照,没有生产行)在该 verb 类内计入 `rsp`,由 `CountBarrierPull` 决定何时改为中止(记录未 barrier、两角色用不同块、strict 下未准入)。盖戳之外一律是普通 poison `Fatal{UnmigratedPipeInput}`。
|
||||
- 盖戳标志由 `PipeApplier::LeaveApplier` 在每条记录之后清除;server 进程里没有 client 半边替它清,漏清会让之后的读都按上一个 verb 的掩码判定。
|
||||
# define MGB_CTX (&::MobileGL::MG_Pipe::gPipeInputs)
|
||||
#else
|
||||
# define MGB_CTX (::MG_State::pGLContext)
|
||||
|
||||
@@ -41,6 +41,16 @@ Phase 1(今天):GL 调用时刻把字节拷进 stage slot,slot 到 apply
|
||||
- **等待规则由 `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.6.1 barriered 谓词的条款
|
||||
|
||||
`MGPipeBarriered(op, payload, applierState)`(`MG_Pipe/PipeApply.h`)由 server 的 `ApplyOne` 计算,决定这条记录能否读 client 内存;client 不调用它,而是在发布前对同样的条款求值(`ClientSession` 的等待规则读 `WaitClass` 列,`PipeFill.cpp` 的 `ClientVerbIsBarriered` 另判 XFB)。两侧答案一旦不同,失败是静默的(client 继续跑,server 读到已经变了的值),所以条款只读线上可见的数据:静态 `WaitClass` 列、记录自身的 payload、ring 上更早的记录写进 applier 的状态。条款只有三条:
|
||||
|
||||
1. `WaitClass != kWaitNone`;
|
||||
2. 打开的 XFB span 内的 `kCtxVerb`(XFB 保持 lockstep);
|
||||
3. 带 `kDrawClientArrays` 的 `draw_vbo`:run-ahead 下 client 在发布前就拒绝它,此条只为 lockstep 臂而在,使谓词完备。
|
||||
|
||||
新条款必须在同一次改动里同时加进这个函数与 client 侧的对应判断,不能只在一侧加。评判候选条款要问**哪条臂为它付代价**,而不是它让哪条车道变绿:曾为让 Espryt 间接 multi-draw 档读 `GL_DRAW_INDIRECT_BUFFER` 绑定合法化,把所有普通 multi-draw 升级为 barriered,于是默认档的每次 `glMultiDraw*` 都多一次会合,而这次读只发生在两个可选档里;最终改由做绑定的一侧按句柄回答(`MultiDraw.cpp` 的 `BoundDrawIndirectBufferId`),撤回了该条款。
|
||||
|
||||
### 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。
|
||||
|
||||
@@ -22,6 +22,13 @@ T2 下 client 侧推送三件套:不做 map/unmap 命令对(payload 带 `has
|
||||
- 不可避免(罕见):握手;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.1.1 kWaitReply 行的逐记录豁免
|
||||
|
||||
`kWaitReply` 是行级属性;一条记录是否真的阻塞由逐记录谓词决定,route 与 wire emit 表读同一个函数,避免一边等另一边没要的回包。因此上面第三条所列的纹理 `ResourceSubData` 在 wire 臂上已不逐条付 RTT。
|
||||
- `resource_subdata`(`MGPipeSubDataWantsItsReply`):buffer 那半的 Bool 无人读,不等。wire 臂上纹理那半也不等:纹理记录只在 server 消费纹理族时才发(caps 镜像里的 consumer 位,与 applier 的接受检查读同一事实),所以 applier 唯一的合法拒绝"无 consumer"落不到这条记录上;其余拒绝(如目标不是纹理、无纹素或无字节、未知句柄)都是 bug,server 侧会 trip wire 报告或计数具名。client 自己取消的记录(device lost、stage 耗尽)仍答"未接受",level 留脏重试。等待由此从每条记录移到 `SEG_STAGE` 窗口回收,与窗口成正比而不是与调用数成正比(实测见 `MEASUREMENTS.md`「纹理上传的等待粒度」一条)。
|
||||
- `resource_respecify`(`MGPipeResourceRespecifyWantsItsReply`):wire 臂上 buffer 那半不等;纹理那半等到对象的 create 被接受(`MGPipeHandleIsPublished`),之后 acceptance 已是 client 知道的事实,首次使用时付一次阻塞的 create;create 被拒则闩不置位,后续照常等真答案,代价是吞吐而不是静默。
|
||||
- monolith 下答案在调用线程上直接返回、没有代价,谓词保持原答案(纹理那半照收)。
|
||||
|
||||
### 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 把 172 条降到 27 条具名债务、`# P13` 注解清零并由脚本拒绝回潮,W7 的 `MobileGL_server_linkcheck` 把它变成真链接)、C 门 `MG_Backend` 里零 `pGLContext`;外加"每个后端 memo 键都是 `{slot, gen}`"(`HandleRecycleScenario`)。
|
||||
|
||||
@@ -31,3 +31,29 @@
|
||||
|
||||
- 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`)。闩之前创建的 context 永久丢失(`eglMakeCurrent` / `eglSwapBuffers` 返回 `EGL_CONTEXT_LOST`,`glGetError` 报一次 `GL_CONTEXT_LOST`,销毁时不往新 session 发任何记录)。**进程可恢复**(`MOBILEGL_IPC_RECOVER`,默认 1):应用下一次 `eglCreateContext` / `eglCreate*Surface` / `eglInitialize` 拆掉丢失的 session、重新拨号得到新 session,client 侧描述旧 server 的缓存全部清空(发布闩、CSO/sampler 缓存、emitter 闩、create window、applier-reset 序号、persistent map),wire epoch 前进使旧 GLContext 一律视为丢失;surface 保留并在下次 make-current 时于新 session 重建(EGL 对 power-management 事件的语义),Wayland 窗口的 shared image 在新 session 重新分配。in-process server 与 `fd:` 对不可重拨,仍是永久丢失。`MOBILEGL_IPC_RESPAWN`(server 重启后全量重推)仍是具名拒绝。client 死:server 读到 EOF 立即销毁 context 并退出(supervisor 继续服务下一连接)。
|
||||
|
||||
#### 15.3.1 Fatal 家族与 wire FatalCode
|
||||
|
||||
内部 Fatal 家族(`MG_Pipe/FatalFamilies.def`)远多于 wire 上 `FatalCode` 的 7 个值,所以家族是内部枚举:对端只收到粗粒度的码,细粒度的词留在本地日志与 `SessionFault` 帧的 `family` 字符串里。码只回答「这是哪一类结束」:
|
||||
|
||||
- `ProtocolCorruption`:无法正确执行的东西——畸形记录、未实现的 verb / surface、两个角色必须守住的不变式、不合契约的回复。对端读到的是「双方对协议的理解不一致」。
|
||||
- `RingOverrun`:共享环或事件通道溢出,是传输容量问题。
|
||||
- `SegmentMismatch`:共享段的尺寸放不下某个角色必须放进去的东西(尺寸错,不是一时满)。
|
||||
- `AbiMismatch`:两端的布局或协议版本不一致。
|
||||
- `ServerCrashed`:等待方归咎于对端的活性结束:排空未完成、apply 线程不在、server 拿不到必需的原生资源(`ResourceUnavailable`)、server 窗口被销毁(`ServerWindowLost`)、GPU 设备丢失(`BackendDeviceLost`,只闩住该 session)。拿不到资源不报 `SegmentMismatch`,那会把错归到共享段尺寸上;wire 上也没有「拿不到资源」的值,新增会改 `wireFingerprint`。
|
||||
- `DeviceLost`:只由 device-lost 闩设置,没有家族投影到它。
|
||||
|
||||
### 15.4 共享图像(`shared_image`)
|
||||
|
||||
server 分配并以 dma-buf 描述符导出的颜色缓冲,让一个 client 的帧不离开 GPU 内存就进入另一个 client 的纹理。一行记录(`MGPSharedImageOp`),每个操作都经 reply slot 以 `MGPSharedImageReply` 回答;server 交给 client 的描述符走 aux socket,先于 reply 到达;`ImageId` 由 server 铸造,server 生命期内不复用。`Damage` 是 GL 窗口坐标、左下原点的矩形,0 个 = 整个表面。
|
||||
|
||||
| 操作 | 语义 |
|
||||
|---|---|
|
||||
| `Allocate` / `Release` | 新建 `Width×Height` 的 `Format` 图像,reply 给出 id,描述符经 aux socket 送达 / 会话放掉对 `ImageId` 的引用 |
|
||||
| `Import` | 认领 client 在 aux socket 上排队的描述符(`MGPSharedImageFdOffer` 带本记录的 seq);须是本 server 导出的图像,尺寸与格式一致 |
|
||||
| `Present` | 当前 context 的默认帧缓冲按顶行在先拷进 `ImageId`(窗口帧成为 wl_buffer 的内容),只拷 `Damage`:client 按图像记录它自上次写入后缺的区域;GPU 拷完才回 reply |
|
||||
| `Attach` | 纹理 `Texture` 的存储变成 `ImageId`(`glEGLImageTargetTexture2DOES`),直到它下一次定义存储的 respecify |
|
||||
| `QueryBufferAge` | 在流中回答当前 draw surface 的 `EGL_BUFFER_AGE_EXT`(0 = 未知);应用先声明绘制区域(`EGL_KHR_partial_update`)时 `Format = kMGPBufferAgeDamageRegionFollows` |
|
||||
| `SetDamageRegion` | `eglSetDamageRegionKHR`:本帧将画的区域 |
|
||||
| `Flush` | 往图像里渲染的会话(X server 的 glamor)的 `glFlush` / `glFinish`:自上个边界用过的图像按 flush 的 fence 发布为已写、已读(隐式同步),发布后回答 |
|
||||
| `NativeFence` | `EGL_ANDROID_native_fence_sync`:会话已提交工作完成时 signal 的 sync_file(写图像的会话同时做 `Flush` 的发布),经 aux socket 先于 reply 送达,`Format = kMGPNativeFenceFdFollows`;`Format = 0` 表示 server 给不出描述符,reply 发出时工作已完成 |
|
||||
|
||||
Reference in New Issue
Block a user