mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 14:18:31 +09:00
[Docs] (Disaggregated): record P4a as landed - the fourteen family calls and six re-keyed kinds, the seven contract corrections and the defect each one closed, the three-arm A/B on a pinned Adreno 830 with active cooling, the upload shape that did not move, and the seam taxonomy the next brief has to carry
This commit is contained in:
@@ -62,6 +62,8 @@
|
||||
|
||||
CSO 在 client 侧内容寻址(Mesa `cso_cache` 先例):每类一张 `ska::flat_hash_map<xxHash, MGPipeHandle>`,容量上限 render-state 64 / vertex-elements 1024 / sampler 256 / sampler-view 4096 / shader 跟随 `ProgramObject` 生命周期,LRU 淘汰时发 `delete_*`。两个不同 program 设置了相同状态时 server 零状态转换。
|
||||
|
||||
> **[deviation] D-F2(P4a 落地)**:上面这行的 **sampler-view 4096 项内容寻址是 P7(Magma)的形状**;Espryt 这一波把 sampler **view** 做成**按纹理对象身份寻址**——一纹理一视图,按纹理自己的 lifetime id 铸造,视图限制变了就在**同一个句柄**上重发。理由是 Espryt 的视图没有可共享的驱动侧对象,内容寻址只会多一张表和一次哈希。sampler **state** 的 256 项内容寻址照做,并且 P4a 给它加了**引用计数**:`MGPTextureParams::BuiltinSampler` 指着的项不允许被 LRU 挤掉(全被引用时超容铸造,计数在 `OverCapacityMints`),否则一次驱逐就会让一条标准记录指向一个已经换代的句柄。
|
||||
|
||||
**[deviation] D-G1(P3a 落地):vertex-elements CSO 在 P3a 是身份寻址,不是内容寻址。** Espryt 根本没有 vertex-elements CSO,它有的是**逐 VAO 的 twin**(`BackendVertexArrayObject`,`MobileGL/MG_Backend/DirectGLES/Managers.h:957-1163`),twin 持有一个驱动 VAO 名(`:1096`)、32 个 client-array scratch buffer id(`:1097`)与 32 个 fp64 scratch id(`:1101`);两个格式相同的前端 VAO 不能共享它,因为驱动 VAO 同时持有 element-array 绑定与逐属性缓冲绑定,共享 CSO 会把它们变成每次 `BindVertexElements` 都要重发——严格比今天更慢。所以 P3a 逐前端 `VertexArrayObject` 铸一个 `VertexElementsCso` 句柄(配置变化时**在同一句柄上重发** `CreateVertexElements`,`MGPipeHandle::Gen` 只在槽位复用时递增),**这是一个命中率恒为 1 的合法内容寻址缓存**。上面那张 1024 项的内容寻址表是 **P7** 的活——Magma 的 `VertexInputStateFactory` 接管 CSO 时,`VkPipelineVertexInputStateCreateInfo` 要的正是内容寻址;它加在 P3a 同一组 `CreateVertexElements`/`BindVertexElements`/`DeleteVertexElements` 与同一个 slot 分配器**之上**,P3a 的线上形状与 applier 记录都不妨碍它。
|
||||
|
||||
## 3. 调用目录(P0 已落地)
|
||||
@@ -97,7 +99,7 @@ Flags:`kNeedsAck`(调用方等 server 确认;目录里目前无条目携
|
||||
|
||||
- 今天 20 个 draw 入口塌成 `DrawVbo` 一条,`MGPDrawRange[]` 就是 `MultiDraw*` 族今天的形状;`Clear` 一条判别式合并 `glClear` + 4 个 `glClearBuffer*` + 4 个 `glClearNamedFramebuffer*`。
|
||||
- `SetSamplerViews` / `BindSamplerStates` **没有 stage 维度**:MobileGL 的纹理单元空间是合并的(`TextureState::m_textureUnits` 是 192 个单元的一个数组,每 stage 32 只是广告数字),同一单元可被两个 stage 采样;stage 只在目标 API 需要时由 server 从反射归档推导。
|
||||
- `SetTextureParams` 按资源寻址、与 sampler view 分开(D10):只作 FBO attachment / image 单元 / `glCopyImageSubData` 端点的纹理没有 sampler view,但 Espryt 对 attachment 也同步纹理参数,且 `RequireImageBindableStorage` 需要在前端参数版本不动时强制重同步。
|
||||
- `SetTextureParams` 按资源寻址、与 sampler view 分开(D10):只作 FBO attachment / image 单元 / `glCopyImageSubData` 端点的纹理没有 sampler view,但 Espryt 对 attachment 也同步纹理参数,且 `RequireImageBindableStorage` 需要在前端参数版本不动时强制重同步。**P4a 有意把这个缺口关掉了**(Espryt 从 `SyncNeccessaryTextures` 侧补上 READ-only attachment 的参数同步),并且**证明它此前确实是坏的**:证据不是公共 GL 的图——回读模拟自己会写 `GL_DEPTH_STENCIL_TEXTURE_MODE`、`IsDrawSyncClean` 又在首次采样时把参数推下去,所以任何纯 GL 序列都看不见它——而是 `MG_IntegrationTest/Harness/PipeApplyPeek.{h,cpp}` 的白盒断言(读 applier 的参数记录与 Espryt 已应用状态),变异注入下 4/4 变红。
|
||||
- `SetIndexBuffer` 独立于 VAO 配置版本(D5):索引 slot 重绑不移动 VAO config version。
|
||||
- `SetGlobalConstants` 只覆盖默认 uniform block(D6):`globalUboScratch` 是 link phase B 的 CPU 数组,没有 GL name、没有 `BufferObject`。
|
||||
|
||||
@@ -131,9 +133,9 @@ Flags:`kNeedsAck`(调用方等 server 确认;目录里目前无条目携
|
||||
| `MGPRenderStateDesc` / `MGPBindRenderState` / `MGPDynamicState` | 48 / **12** / 32 | §5.3 |
|
||||
| `MGPVertexElements` | 40 | blob 同时带解析后的 `MGPVertexAttribWire[]` **和** `MGPVertexBindingPointWire[]`(P3a 落地的两个 POD 线上形,24 B / 16 B,`MobileGL/MG_Pipe/MGPipeValueTypes.h:568-600`)。**两个视图都过线的理由是记录自洽,不是 stride 消歧**:前端已经把 pointer 调用的 stride 0 解析成 element size,一个活到 `VertexAttribute::Stride` 的 0 只可能来自 binding 模型(`MobileGL/MG_Pipe/MGPipeValueTypes.h:496-503`),后端从来不读 binding point(`MG_Backend` 里 `VertexBufferBindingPoint` / `GetAttributeBindingIndex` / `GetAttributeRelativeOffset` 零命中);真正承重的是 `MGPVertexElements` **声明**了 `BindingPointCount`,一条不描述自己 blob 的记录会让 applier 的边界门永远无法收口。代价按配置变化付一次、不按 draw 付(blob 只搭 `CreateVertexElements`),裁掉第二个视图是 P13 的重调项。`IsLong` 与 `Type == Float64` 分开携带;`Divisor` 不在属性视图里(走 `MGPVertexBuffer::Divisor`);仅供查询的 `LegacyStride/LegacyPointer` 留在 client |
|
||||
| `MGPSamplerDesc` | 32 | `SamplerParameters` 逐字节过线**含 `borderColorForm`**(三种 border color 表示永远都被数值填满,没有它后端无法在 `Iiv`/`fv` 或 `VkBorderColor` 家族间选择) |
|
||||
| `MGPSamplerView` / `MGPTextureParams` | 36 / 32 | view 只带视图限制(min/num level、min/num layer、别名格式);纹理参数(base/max level、swizzle、depth-stencil mode、LOD 钳、`ForceResync`)挂在纹理对象上 |
|
||||
| `MGPSamplerView` / `MGPTextureParams` | 36 / **40**(P4a) | view 只带视图限制(min/num level、min/num layer、别名格式);纹理参数(base/max level、swizzle、depth-stencil mode、LOD 钳、`ForceResync`)挂在纹理对象上。**[deviation] D-E1**:`MGPTextureParams` 从 32 涨到 **40**,多出 `BuiltinSampler`(该纹理自带 `SamplerObject` 所对应的 sampler CSO 句柄,**不允许是空句柄**——每个 `ITextureObject` 都有一个 sampler 对象,空句柄是协议损坏而不是"没有 sampler")与第二个 resync 位 `SamplerResync`;参数本身一项没变。句柄从 client 的**内容寻址 cache** 取(`MGPipeSamplerCsoCacheInstance().Acquire`),不是按纹理身份铸的——这条缝在 P4a 里被踩了两次:客户端一度按对象身份铸、Espryt 的 twin 一度按身份查,两边都拿不到那条内容寻址的记录 |
|
||||
| `MGPProgramDesc` | 192 | 逐 stage SPIR-V blob ×6 + 反射归档 blob + `StageMask`/`GlobalUboSize`/`ReservedNumSamplesOffset` + 四个状态字节,§7 |
|
||||
| `MGPFramebufferState` | 304 | 8 color + depth + stencil + **client 解析后的 `ReadSurface`**(按结构消灭 read-buffer-shared-FBO 缺陷类);`MGPSurface::InternalFormat` 内联(四个跨对象 mask 推送时零查表);`ContentHash` 既是 server 的 render-pass memo 键也是 client 的发射抑制器 |
|
||||
| `MGPFramebufferState` | 304 | 8 color + depth + stencil + **client 解析后的 `ReadSurface`**(按结构消灭 read-buffer-shared-FBO 缺陷类);`MGPSurface::InternalFormat` 内联(四个跨对象 mask 推送时零查表——P4a 发现只有格式不够,`MGPSurface` 的 `Pad0` 因此变成 `TextureTarget`,否则 `ShouldUseCaveatTextureFormat` 这类 mask 仍得回前端问纹理目标);`ContentHash` 既是 server 的 render-pass memo 键也是 client 的发射抑制器。**P4a:多一个 `Target` 字节,记录按绑定目标发**(`Draw` / `Read` / `Both`,`Complete` 是后端无关的 `CheckCompleteness()` 答案,D-C3),**并且 applier 把记录按 framebuffer 句柄存成每对象一张表**外加两个"当前绑定"句柄——第四个取值 `Named = 3` 就是"这条记录描述它点名的那个 framebuffer,不动任何绑定",`BlitNamedFramebuffer` 与四个 `ClearNamedFramebuffer*` 之前正是因为只有两条绑定记录而打进了一个从未收到附件的 FBO |
|
||||
| `MGPSubData` / `MGPSubRegion` | 72 / 40 | §6 |
|
||||
| `MGPDrawInfo` / `MGPDrawRange` / `MGPDrawIndirect` | **56** / 12 / 40 | `Flags` 门控 `MinIndex/MaxIndex`(只在 client-memory 数组路径算)与 `XfbCpuCapturedVertices`(只在 XFB scatter 路径读)——不是每 draw 都算;`NumDraws` 个 `MGPDrawRange` 在变长尾;用户索引的 `MGHostSpan` 只在 `kDrawHasUserIndices` 时进变长尾;indirect 的 `DrawCount` 由 client 解析,server 永不读 indirect 命令块来数 draw |
|
||||
| `MGPShaderBuffers` / `MGPBufferRange` | 32 / 24 | range 不内联 host span;`kCapNeedsHostUboBytes` 下 Uniform 类带第二个变长尾 `MGHostSpan[HostSpanCount]`,与 range 数组下标对齐 |
|
||||
@@ -209,7 +211,7 @@ GL 是每 unit 每 target 各一个绑定;shader 看见哪一个取决于 samp
|
||||
|
||||
- `resource_create` 在前端对象构造时发,存储由 `resource_respecify` 惰性定义;`resource_destroy` 在析构时发。三条顺序约束由 payload 表达:view 先于存储属主销毁(`ViewOf` + server keep-alive)、FBO attachment 钉住纹理(surface handle 隐含 keep-alive)、buffer texture 钉住 buffer(`BufferForTexBuffer`,范围实时解析)。
|
||||
- 共享组:v1 一个 screen、一个 context、一条 flow;`eglMakeCurrent` 是 flow 所有权转移,在既有 `EGLOperationMutex` 下发射(顺手让 `ReleaseThread` 与 `SwapInterval` 也取该锁)。
|
||||
- program pipeline 合成体:`GLContext::GetProgramForDraw()` 今天就完全在前端合成(join、签名查 cache、`Link(true)`)。tracker 拿到 `SharedPtr<ProgramObject>` 推**一个** handle,slot 从 `ShaderCso` 保留高位段分配,pipeline cache 淘汰时释放 slot、`gen++`、发 `delete_shader_state`。合成体从不过线,server 不需要任何"解析后的 draw program"钩子;副带收益是阻塞的 `JoinLinkAndSpirv()` 离开 server 的 draw path。
|
||||
- program pipeline 合成体(**P4a 落地:`MobileGL/MG_Impl/Pipe/CompositeResolver.h`**):`GLContext::GetProgramForDraw()` 今天就完全在前端合成(join、签名查 cache、`Link(true)`)。tracker 拿到 `SharedPtr<ProgramObject>` 推**一个** handle,slot 从 `ShaderCso` 保留高位段分配(段基 983040,独立稠密表——按 slot 索引的向量在这个段上要 ~236 MB),pipeline cache 淘汰时释放 slot、`gen++`、发 `delete_shader_state`。**释放有两条路,都必须恰好一次**:合成体自己被换掉时由 resolver 释放,程序对象死亡时由 `~ProgramObject` → `MGPipeEmitShaderCsoDestroyAndFree` 释放(D-H7)。**记忆按 `(ContextId, 管线 GL 名)` 键控**——resolver 是进程级单例而 GL 名是每上下文的,只按名字记会在两个上下文用同一个管线名时把对方还活着的合成体释放掉(复审在真实 make-current 序列上打出来的);`Reset()` 只清"新鲜度",不清"该释放"这件事,否则一次 make-current 之后释放路径就永久失灵。合成体从不过线,server 不需要任何"解析后的 draw program"钩子;副带收益是阻塞的 `JoinLinkAndSpirv()` 离开 server 的 draw path。
|
||||
|
||||
### 5.7 emulation 的归属
|
||||
|
||||
@@ -252,13 +254,15 @@ GL 是每 unit 每 target 各一个绑定;shader 看见哪一个取决于 samp
|
||||
- `MGPSubRegion` 显式携带 `SrcRowStride/SrcSliceStride`,`MGPSubData::SourceIsVerbatimLevelShadow` 显式携带原来由 `uploadData == mipData` 指针比较回答的问题:"这批字节是未经转换的 level shadow 吗"。split 下 client 既不发整 level 也不在 server 留整 level 镜像,指针比较不成立;Espryt 的上传路径改为从描述符取步长,`UNPACK_ROW_LENGTH` 从 `SrcRowStride/bpp` 设。形状照抄已存在的 `UnpackStagingBlock`(ring 路径本来就紧密重打包、不发 `glPixelStorei`)。
|
||||
- **dirty 归属反转**:client 保留 rect 模型、维护一份发射游标、发射后清自己的标志,server 从不碰 client 的标志。安全,因为 `MG_Impl` 里没有任何 `IsStorageDirty/GetStorageDirtyRects/GetStorageDirtyRegion` 调用点(前端从不读自己的 dirty 状态)。逐 level "server 权威位"与纹理 ack 协议因此不必存在。
|
||||
- 发射游标按**存储属主**键控 `(storageOwnerHandle, ownerUploadTarget, ownerLevel)`:`TextureObjectView` 把 dirty 查询/清除全部转发给属主并做索引重映射,view 与属主共用同一份 dirty 状态。门:通过 view 上传、经属主采样(及反向),跨 draw 边界各一次。
|
||||
|
||||
> **P4a 落到哪一步**:**排水列表(drain list)是这一波的**——client 在 validate 点把每个脏的 `(存储属主, 上传目标, level)` 发成**一条** `resource_subdata`,逐区带 `SrcOffset`/`SrcRowStride`/`SrcSliceStride`,`RegionCount == 0` 合法且表示"并集框就是全部"。**上面那条按存储属主键控的发射游标与索引重映射仍然是 P3b/P4b 的**,P4a 没有做。两条 P4a 学到的规矩写在这里:(1) **清 dirty 要等 applier 的 acceptance**,不是发射即清——`MGPipeApplyResourceSubData` 返回 `Bool`,被拒的记录必须让 level 留在脏表里(否则拉取路径再也不会补它);(2) **服务端自己的重新变脏是服务端的事**——`RequireImageBindableStorage` 会让一个已经上传过的 level 重新需要上传,client 的标志早就清了,所以 twin 直接重新武装 applier 记录里的待上传项。`TextureUploadShapeScenario`(形状金标)**已经建好但只记录不设门**,要等 Mali 侧的帧时增量才升级成门。
|
||||
- 后端真正在 shadow 里写字节的两处——CPU 回退生成 mip(RGB16F/RGB32F)与 `glCopyImageSubData` 目的地镜像——分别由 `OnTextureWriteback` 与"CopyImage 镜像搬到 client"处理。
|
||||
- Unpack PBO 完全在 client 解析;压缩纹理永不到达后端;`glCopyTexSubImage*` 与 `glClearTexImage` 整体留在 client(今天就是纯前端操作:借一次 `ReadPixels` 进 CPU scratch 再写 shadow),拆分后恰好是一次阻塞 ReadPixels round trip,脏区按普通 subdata 下发。
|
||||
|
||||
## 7. Shader state = SPIR-V + 反射归档
|
||||
|
||||
- `CreateShaderState` 的 payload 是逐 stage SPIR-V + 反射归档(`LinkArtifacts` + `SpirvArtifacts` 全结构体),**不是源码**。"server 从源码重新 link"这条路显式关闭:链接真 `ProgramObject` 就链接 glslang。glslang 全在 client,SPIRV-Cross(`TranspileSpirvToEssl`)全在 server,文件级切割。没有 `MOBILEGL_IPC_PROGRAM` 开关、没有 server 侧 compile pool。
|
||||
- 归档机制:`Visit()` + `sizeof` 绊线(`static_assert(sizeof(LinkArtifacts) == MGL_LINKARTIFACTS_SIZE)`),一份字段表服务序列化两个方向。必须覆盖四个 `ResourceReflection`(各带 `TypeFacts`)、`uniformSamplerOrImageUnitIndex`、`uniformBlockBinding`、`shaderStorageBlockBinding`(按名字)、`explicitOpaqueUniformBindings`、`xfbVaryings/xfbStrides/xfbPackedStride/xfbNeedsScatteredCapture`、`computeLocalSize`、GS/TCS/TES 事实、`usesReservedNumSamples`、`uniformOffsets`。`XfbVarying` 带两套拼写(GL 名字 + block 实例/成员/元素)。
|
||||
- 归档机制:`Visit()` + `sizeof` 绊线(`static_assert(sizeof(LinkArtifacts) == MGL_LINKARTIFACTS_SIZE)`),一份字段表服务序列化两个方向。**P4a 落地:序列化器在树里了**(`MobileGL/MG_Pipe/ProgramArtifactsCodec.{h,cpp}`),但 **monolith 只在 verify 构建里调它**——句柄臂上 server 读的仍是前端自己的归档(D-H3:制品不在 monolith 过线,编解码因此不在热路径上),真正逐帧走 codec 是 P5 之后的事。**未完的一件配套**:`ProgramArtifacts.h` 的 libc++/NDK 尺寸钉仍是惰性的(`MGL_ARTIFACT_SIZES_LIBCXX_PINNED` 没定义),也就是说 Android 构建上这个 58 成员结构还没有绊线——记在 P3b/P4b 的清单里。必须覆盖四个 `ResourceReflection`(各带 `TypeFacts`)、`uniformSamplerOrImageUnitIndex`、`uniformBlockBinding`、`shaderStorageBlockBinding`(按名字)、`explicitOpaqueUniformBindings`、`xfbVaryings/xfbStrides/xfbPackedStride/xfbNeedsScatteredCapture`、`computeLocalSize`、GS/TCS/TES 事实、`usesReservedNumSamples`、`uniformOffsets`。`XfbVarying` 带两套拼写(GL 名字 + block 实例/成员/元素)。
|
||||
- **P0.5 硬前置**:反射类型今天声明在 `ProgramObject.h` 里,而它 include `ShaderObject.h`(→ glslang)与 `SpvcSession.h`(→ spirv_reflect)。P0.5 把 `TypeFacts`、`ResourceReflection`、`XfbVarying`、`LinkArtifacts`、`SpirvArtifacts` 抽到 `MG_State/GLState/ProgramState/ProgramArtifacts.h`(只 include `<Includes.h>` 与容器),8 个 includer 靠类内 `using` 别名零改动,加 CI `-H` 闭包断言(已落地,见 `ROADMAP.md` P0.5 行;`DynamicBackendParameters` 留在 `BackendObject.h`,所以闭包门 A 断言的是 `MGPipeValueTypes.h` 而不是 `MGPipeTypes.h`)。同批抽取 `MG_Pipe/MGPipeValueTypes.h`(`MAX_DRAW_BUFFERS`、`PerBufferBlendState`、`StencilFaceState`、`PixelStoreParameters`、`RenderStateParameters`、`SamplerParameters`、`BorderColorForm`、`VertexAttribute`、`VertexBufferBindingPoint`),它不 include `MG_State/GLState` 任何东西;`MGPipeTypes.h` 今天为此临时 include 了 `BackendObject.h` 与 `RenderState.h`(文件头注明为 P0.5 债务)。没有这一步,P7 的 `nm -D | grep glslang` 判据不可达。
|
||||
- server 侧惰性特化(D-B2):后端 program 还依赖 8 个额外输入(draw FBO 的 snorm/unorm clamp mask、fragColor 广播数、storage-block 绑定签名、atomic counter 集、活的 image 格式、patch 参数;Magma 另加 FragCoord-Y-flip 的 default-FB 高度与 XFB 布局),`create_shader_state` 发布**制品**,server 在 verb 时刻从已推送状态特化——正是两个后端今天的做法,也是 gallium `st_variant` 的做法。
|
||||
- 后端 link/compile 失败不需要同步返回:今天只是一行 `MGLOG_E` 加 bind program 0 的空 draw,`GL_LINK_STATUS` 永不撤回,同步查询由 client 从 `ProgramObject` 回答。`OnLog` 逐字复现——由此要求日志按严重级分级(§8.3)。
|
||||
@@ -292,7 +296,7 @@ GL 是每 unit 每 target 各一个绑定;shader 看见哪一个取决于 samp
|
||||
### 8.3 错误、ack 与日志
|
||||
|
||||
- 纹理分配的 OOM 在 monolith 里就已推迟到 sync 时刻(`glTexImage*`/`glTexStorage*` 只 `MarkStorageDirty`,Espryt 惰性分配;连 `glRenderbufferStorage*` 也在 `SyncToBackend` 里惰性做),拆分不改变可观察行为,这批不同步 ack。
|
||||
- **唯一允许同步 ack 的入口是 `glBufferStorage`(真同步分配)**。`glRenderbufferStorage*` 不 ack:41 个 trace fixture 里 OOM 探测惯用法出现 0 次(9 次调用散在 5 个 fixture,无一在 3 个调用内跟 `glGetError`;语料里的成功性检查是 `glCheckFramebufferStatus`,client 本地作答)。**P3a 起 `ResourceRespecify` 携带 `kNeedsAck`,并且带一个逐记录谓词**:flag 是**逐调用**的静态属性,而 `ResourceRespecify` 同时服务 `glBufferData` 与 `glBufferStorage`,裸 flag 会把 Minecraft 的整块 chunk 上传变成每 store 一次往返。所以真正拍板的是 `MGPipeResourceRespecifyNeedsAck(desc) == (desc.Immutable != 0)`(`MobileGL/MG_Pipe/MGPipeTypes.h:680`),`PipeCalls.def:18-24` 的图例把这条规则写在目录里(调用行 `:82`),`MG_Test/Pipe/PipeCatalogueTest.cpp:552-585` 的 `ResourceRespecifyAcksOnlyImmutableStorage` 对两种惯用法各钉一次。monolith 下 ack 是 `((void)0)`(applier 只隔一次函数调用),P5 把门铃接到这个谓词上。
|
||||
- **唯一允许同步 ack 的入口是 `glBufferStorage`(真同步分配)**。`glRenderbufferStorage*` 不 ack:41 个 trace fixture 里 OOM 探测惯用法出现 0 次(9 次调用散在 5 个 fixture,无一在 3 个调用内跟 `glGetError`;语料里的成功性检查是 `glCheckFramebufferStatus`,client 本地作答)。**P3a 起 `ResourceRespecify` 携带 `kNeedsAck`,并且带一个逐记录谓词**:flag 是**逐调用**的静态属性,而 `ResourceRespecify` 同时服务 `glBufferData` 与 `glBufferStorage`,裸 flag 会把 Minecraft 的整块 chunk 上传变成每 store 一次往返。所以真正拍板的是 `MGPipeResourceRespecifyNeedsAck(desc) == (desc.Immutable != 0)`(`MobileGL/MG_Pipe/MGPipeTypes.h:680`),`PipeCalls.def:18-24` 的图例把这条规则写在目录里(调用行 `:82`),`MG_Test/Pipe/PipeCatalogueTest.cpp:552-585` 的 `ResourceRespecifyAcksOnlyImmutableStorage` 对两种惯用法各钉一次。monolith 下 ack 是 `((void)0)`(applier 只隔一次函数调用),P5 把门铃接到这个谓词上。**P4a 把这个谓词收窄到 buffer 目标(D-A2)**:纹理与 renderbuffer 从这一波起也走 `resource_respecify`,而 `glTexStorage*` 的 `Immutable` 同样是 1——不收窄的话每一次不可变纹理分配都会变成一次同步往返。收窄之后 `ResourceRespecify` 还多了两条 P4a 的语义:一是**逐 level 作用域**(带 `MGPRespecifiedLevel` 尾,说明这次重定义的是哪一级;空指针才是"整个资源"),二是**元数据更新**(存储定义字段与已存描述符逐项相同时,只换 `BindMask`/`ImageBindableHint`,不 ack、不清待上传)——前者是终审抓出的 critical:不带 level 时 `glTexImage2D(level 1)` 会把同一纹理上已经被 applier 接受、客户端也已清了 dirty 标记的第 0 级待上传一起丢掉,画面读回全黑。
|
||||
- 其余错误一律晚到,走有序的 `OnGlError`。
|
||||
- `OnLog` 分级:≤WARN 有损(覆盖最旧 + `eventDropped` 计数);≥ERROR 无损,加入触发 `eventRingFull` + 停止 apply 的语义事件集;每秒 ERROR 速率限制器,超限发一条 "N errors suppressed";`MGLOG_E_ONCE` 的 latch 变 per-server。理由:后端 link 失败只以一行 ERROR 呈现,统一有损会让最有诊断价值的那一行在日志压力下消失。
|
||||
|
||||
@@ -320,6 +324,8 @@ Magma:`VulkanRenderer` 全部 memo 与 scratch、`PipelineFactory`、`ProgramF
|
||||
|
||||
从"不动"里移出的一项:Espryt 的 sub-rect 上传判定与跨步计算(§6,从描述符取步长)。
|
||||
|
||||
**P4a 的字节一致行**(`scripts/p4a_untouched_regions.sh`,17 个区 / 3 个文件,8 个阴性对照按名变红;与 P3a 的十一函数并列):Espryt 的 depth-stencil 采样模拟与格式 caveat 两族——`ShouldUseCaveatTextureFormat`、`BackendTextureFormatAddsAlpha`、`StageBlocksIntoUnpackRing`、`UnpackRingAvailable`、`UnpackRingAllocate`、`RecomputeBackendColorSlots` 等——它们是"被推送的记录改变了输入、但算法本身不许动"的那一类,字节一致是唯一能证明这点的门。**仍在 `MG_Backend` 里的前端类型**:`g_rawDepthFetchSamplerState` 是一个前端 `SamplerObject`(Espryt 自己为原始深度取样铸的,client 从没见过它,因此永远不会有记录——句柄臂上"对象即权威"的那条分支就是为它留的),它的原生化归 P3b/P4b,是句柄化之后 Espryt 侧最大的一处残留。
|
||||
|
||||
唯一两处必须真改的 `MG_State` 类型内部用法(都在 Magma):占位纹理(构造真的 `TextureObject2D*` 只为复用 `SyncTextureAndGetDescriptor(ITextureObject&)` 签名,~120 行木偶戏 → ~60 行原生 `VkImage`+view+descriptor,34 个 `MOBILEGL_ASSERT(pGLContext)` 里的 9 个随之消失);两个内部 shader 烘焙(§7)。Espryt 的小号同类:`g_rawDepthFetchSamplerState` → 后端原生 sampler。
|
||||
|
||||
### 9.2 strangler 脚手架:`PipeInputs` + 逐 verb 填充 + poison 世代(P1)
|
||||
@@ -372,6 +378,7 @@ Track V 的 55% 不需要逐字段接口条目就能跑起来,所以 P2 发一
|
||||
|
||||
- **buffer 家族的 pre-handle 臂是 `Ops_*` 表加 `g_glesBufferBackendOps`,它们无条件编译**,不在 `MOBILEGL_PIPE_LEGACY_MEMOS` 之下——所以位 7 单独清零永远有一条真臂可跑,`NoArm` 对这个家族不可达(`Managers.cpp:2294-2298` 把这句写在代码里)。前端的十一处分发点(`MobileGL/MG_State/GLState/BufferState/BufferObject.cpp:45`、`:71`、`:87`、`:103`、`:237`、`:375`、`:438`、`:450`、`:493`、`:602`、`:656`)按 `MGPipeResourceSubsystemEnabled()` 二选一。
|
||||
- **VAO twin 的前端读取臂是有条件的**:pre-handle 的 `SyncToBackend` 本体与它读的那组 memo(`m_syncedIndexBufferVersion` / `m_syncedIndexBufferObject` / `m_hasSyncedConfigVersion` / `m_syncedConfigVersion` / `m_syncedAttributeVersions`)都在 `MOBILEGL_PIPE_LEGACY_MEMOS` 里(`MobileGL/MG_Backend/DirectGLES/Managers.h:1109-1130`)。位 8 清零 + `LEGACY_MEMOS=0` 是**没有任何 vertex-input 臂**的配置,`Managers.cpp:2343-2351` 明确报 `PipeLegacyMemosDisabled` 而不是静默。位 8 还要求位 7(属性的缓冲 id 经资源 slot 表解析),`0x17f` 会点名拒绝并回落到 legacy 臂。
|
||||
- **P4a 的六种 twin 同样有条件臂**(纹理、renderbuffer、framebuffer、sampler、sampler view、program):pre-handle 的本体与它读的那批身份 memo 都在 `MOBILEGL_PIPE_LEGACY_MEMOS` 之下,`#if !MOBILEGL_PIPE_LEGACY_MEMOS` 的分支留着但不可达(arm resolver 在第一次查找就停进程),保持**响亮**。这条开关给这一波多加了约一天:pull 构建被 `CMakeLists.txt` 强制打开它,所以每个"删掉旧 memo"的动作都只是让新臂不再读它、`sizeof` 一点不动——G1 的 0/0/0/0 正是这条的度量,真正的删除跟着 pull 路径在 P13 发生。
|
||||
- **base-instance 的 ambient 作用域同在其中**:`SetPendingFetchBaseInstance` / `GetPendingFetchBaseInstance` / `ScopedFetchBaseInstance` 与 `DirectGLES.cpp:5293` 的三个 scope(`Managers.h:1189-1200`)。句柄臂改由 `MGPipeApplierState::VertexFetchBaseInstance` 供给,但这三个声明**不能删**:删掉会从 pull 构建移走两个符号(G1)。
|
||||
|
||||
P13:删 `SnapshotFromGLContext()` 的非 verify 分支、`MGB_CTX`、`MOBILEGL_PIPE_PUSH`、`MOBILEGL_PIPE_LEGACY_MEMOS`;**保留 `MOBILEGL_PIPE_VERIFY` 连同它需要的 `SnapshotFromGLContext()` 与 `MG_State` include**(D-B5,verify 构建永不出货);三道纯度门在非 verify 构建上转绿。
|
||||
@@ -595,7 +602,7 @@ CMake:
|
||||
|
||||
| 变量 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `MOBILEGL_PIPE_PUSH` | pull 构建 `0`;**push 构建 `0x1ff`**(`kMGPipeSubsystemsMigratedAtP3a`,`MobileGL/MG_Pipe/MGPipe.h:95`,读入点 `MobileGL/ConfigLoader.cpp:257`;P2 的默认是 `0x7f` = `kMGPipeSubsystemsMigratedAtP2`,`MGPipe.h:94`,保留作分阶段对照) | 子系统位图,十进制或 `0x`;位按 ROADMAP 顺序分配、永不复用(`MobileGL/MG_Pipe/MGPipe.h:72-95`):`0x01` 渲染状态、`0x02` pixel pack、`0x04` patch state、`0x08` vertex attrib defaults、`0x10` residual values、`0x20` Espryt slots(Track H)、`0x40` Magma vertex input(Track H)、**`0x80` 位 7 resources(P3a:`resource_*` 家族,`kMGPipeSubsystemResources`,`MGPipe.h:84`)**、**`0x100` 位 8 vertex input(P3a:vertex elements / vertex buffers / index buffer,`kMGPipeSubsystemVertexInput`,`MGPipe.h:85`)**;位 9..62 留给后续阶段。**位 8 依赖位 7**:属性的缓冲 id 经资源 slot 表解析,只有位 7 填那张表,所以 `0x17f` 会打一行 ERROR 点名拒绝位 8 并回落到 legacy vertex-input 臂(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:2312`)。**位 63 不是子系统而是行为**:`kMGPipeBehaviourNoCsoContentAddressing`(`MGPipe.h:90`)关掉 client 侧 CSO 内容寻址(每次 pipeline 版本变化都铸新 CSO、永不探测 map),即 P2 的负面对照。`0` = 全 pull,但 P2 之后只有在 `MOBILEGL_PIPE_LEGACY_MEMOS` 编进了 pre-handle 臂时才是有效对照 |
|
||||
| `MOBILEGL_PIPE_PUSH` | pull 构建 `0`;**push 构建 `0x1fff`**(`kMGPipeSubsystemsMigratedAtP4a`;`0x1ff` = `kMGPipeSubsystemsMigratedAtP3a`、`0x7f` = `...AtP2` 都保留作分阶段对照,`MobileGL/MG_Pipe/MGPipe.h`,读入点 `MobileGL/ConfigLoader.cpp`)。**P4a 的四位与它们的三条依赖拒绝**:位 9 framebuffer、位 10 纹理资源、位 11 sampler、位 12 program;位 11 要位 10(每个 `MGPBoundView::Texture` / `MGPImageView::Res` 都是纹理句柄)、位 9 要位 10(`MGPSurface::Res` 同理)、位 10 要位 7(buffer texture 的 `BufferForTexBuffer`)**外加 P4a 加的第四条:位 10 要位 11**(`MGPTextureParams::BuiltinSampler` 是 sampler CSO 句柄,只有位 11 铸它,空句柄是 `Fatal{ProtocolCorruption}`)。**两侧都要拒**:服务端在各族的 arm resolver 里拒绝并跑旧臂,客户端在 `PipeFill.cpp` 的族门里**根本不发射**——只在服务端拒会出现"客户端已按 acceptance 清了 dirty、服务端却走旧臂"的丢上传(`0x7ff` 一度 438/491)。同一个族门上还挂着**消费者条件**:没有任何后端注册 `MGPipeResourceOps` 时四族一条不发(Magma 就是这种情形) | 子系统位图,十进制或 `0x`;位按 ROADMAP 顺序分配、永不复用(`MobileGL/MG_Pipe/MGPipe.h:72-95`):`0x01` 渲染状态、`0x02` pixel pack、`0x04` patch state、`0x08` vertex attrib defaults、`0x10` residual values、`0x20` Espryt slots(Track H)、`0x40` Magma vertex input(Track H)、**`0x80` 位 7 resources(P3a:`resource_*` 家族,`kMGPipeSubsystemResources`,`MGPipe.h:84`)**、**`0x100` 位 8 vertex input(P3a:vertex elements / vertex buffers / index buffer,`kMGPipeSubsystemVertexInput`,`MGPipe.h:85`)**;位 9..62 留给后续阶段。**位 8 依赖位 7**:属性的缓冲 id 经资源 slot 表解析,只有位 7 填那张表,所以 `0x17f` 会打一行 ERROR 点名拒绝位 8 并回落到 legacy vertex-input 臂(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:2312`)。**位 63 不是子系统而是行为**:`kMGPipeBehaviourNoCsoContentAddressing`(`MGPipe.h:90`)关掉 client 侧 CSO 内容寻址(每次 pipeline 版本变化都铸新 CSO、永不探测 map),即 P2 的负面对照。`0` = 全 pull,但 P2 之后只有在 `MOBILEGL_PIPE_LEGACY_MEMOS` 编进了 pre-handle 臂时才是有效对照 |
|
||||
| `MOBILEGL_PIPE_HANDLE_ABA_CONTROL` | 0 | 负面对照 C(push 构建才有,`MobileGL/Config.h:360-371`):故意打掉句柄身份,让 `HandleRecycle` 的 ABA 臂重现旧的 A-B-A 污染。它变绿即为控制失效 |
|
||||
| `MOBILEGL_PIPE_VERIFY` | 0 | 逐 draw 逐字段影子比对 |
|
||||
| `MOBILEGL_PIPE_STATS` | 0 | 边界计数器(§附 B) |
|
||||
|
||||
@@ -462,7 +462,7 @@ $ ctest --test-dir build-push -R 'HandleRecycle' --no-tests=error -j 4 --output-
|
||||
|
||||
所有入表运行前后 `pin_device.sh check` 都是 PINNED(小米 rd12/Magma 崩溃后 GPU pwrlevel 被重置,其后的 sodium/create-instancing 行两臂同状态)。
|
||||
|
||||
**读法。** (1) **P2 的边界在 Release 下的真实代价是 +6–12%**(`0x7f` 臂),两机两后端一致,比 -O0 表的 +8–18% 小但同量级;(2) **P3a 在 26.3 与 sodium 上几乎不再加价**(P3a 臂与 P2 臂在 26.3 上相差 −0.1 ~ +2 个百分点),**但在 rd12 上把差距从 +11% 推到 +27–30%**——rd12(Odin Lite 世界)每帧的 VAO/buffer 绑定切换远多于 26.3(26.3 的 1350 draw/帧大多复用同一 VAO),每次切换都走一遍 `set_vertex_buffers` 构造 + `ContentHash` + applier 记录 + Espryt 侧逐属性走查;Magma 上没有句柄消费者也多 15 个百分点,说明 client 侧发射本身就是大头;(3) **MC 26.3 在 Adreno 上的 p99**(`ROADMAP.md:19` 点名的那个数):pull 25.46 ms → P3a 26.32 ms(+3.4%),finish 开 25.48 → 26.29;对着 `MEASUREMENTS.md:87` 的采纳基线(p99 163 → 21 ms)仍在 21–26 ms 档,没有回到采纳前的形态——按口径记录,不判门;(4) `mpr`(map-persistent-roundtrips,按窗口累加):26.3 两机都是 8(首窗 4,之后两次 2——都是 ≥16 MiB store 定义时的采纳),sodium 1,rd12 与 create-instancing 0;P2 臂上恒 0(子系统关)——G10 在设备上成立;(5) `CreateVertexElements` 每帧字节数:统计行没有这一类(`vtxc` 是 client 数组),**未测**,留给 P4a 给汇总行加类;(6) 计数器(`acc/draw`、六个 memo 门、`resid=`、`csom/csob`)在 pull/P2/P3a 三臂间逐字相同——它们数的是代码路径,P3a 没有改它们的定义。
|
||||
**读法。** (1) **P2 的边界在 Release 下的真实代价是 +6–12%**(`0x7f` 臂),两机两后端一致,比 -O0 表的 +8–18% 小但同量级;(2) **P3a 在 26.3 与 sodium 上几乎不再加价**(P3a 臂与 P2 臂在 26.3 上相差 −0.1 ~ +2 个百分点),**但在 rd12 上把差距从 +11% 推到 +27–30%**——rd12(Odin Lite 世界)每帧的 VAO/buffer 绑定切换远多于 26.3(26.3 的 1350 draw/帧大多复用同一 VAO),每次切换都走一遍 `set_vertex_buffers` 构造 + `ContentHash` + applier 记录 + Espryt 侧逐属性走查;Magma 上没有句柄消费者也多 15 个百分点,说明 client 侧发射本身就是大头;(3) **MC 26.3 在 Adreno 上的 p99**(`ROADMAP.md:19` 点名的那个数):pull 25.46 ms → P3a 26.32 ms(+3.4%),finish 开 25.48 → 26.29;对着 `MEASUREMENTS.md:87` 的采纳基线(p99 163 → 21 ms)仍在 21–26 ms 档,没有回到采纳前的形态——按口径记录,不判门;(4) `mpr`(map-persistent-roundtrips,按窗口累加):26.3 两机都是 8(首窗 4,之后两次 2——都是 ≥16 MiB store 定义时的采纳),sodium 1,rd12 与 create-instancing 0;P2 臂上恒 0(子系统关)——G10 在设备上成立;(5) `CreateVertexElements` 每帧字节数:统计行原本没有这一类(`vtxc` 是 client 数组),**P4a 已补上并测了**——汇总行的 `bytes/f[...]` 多了一个 **`csob-blob`**(`cso-blob-bytes`:所有 CSO create 调用的 blob 字节,含 vertex-elements、sampler、shader)。79 例 retrace、`MOBILEGL_PIPE_STATS=1 MOBILEGL_PIPE_STATS_PERIOD=60`、push 构建(`8c458cd5`):**DirectGLES 27 个用例逐用例窗口均值的中位数 2928 B/帧、均值 42.8 KB/帧、无一为零**,最大 `rd12-odinlite` **1.06 MB/帧**(它每帧换 VAO 的次数远多于别人,正是 §20 读法 (2) 里 rd12 多花 17–19 个百分点的同一根因,这下有了字节口径);其后依次 `minecraft-1.21.4-in-world` 11.4 KB、`common-mods-inventory` 8.3 KB、`common-mods-in-world` 7.8 KB、`rei-inventory` 7.2 KB。**DirectVulkan 侧恒 0**——Magma 没有注册 `MGPipeResourceOps`,P4a 的消费者门因此让四族一条不发(见 `ARCHITECTURE.md` 的 `MOBILEGL_PIPE_PUSH` 行),这也是这个计数器第一次把那条门量化出来;(6) 计数器(`acc/draw`、六个 memo 门、`resid=`、`csom/csob`)在 pull/P2/P3a 三臂间逐字相同——它们数的是代码路径,P3a 没有改它们的定义。
|
||||
|
||||
**小米 rd12 + Magma 的崩溃**:三臂(含 pull)都在启动后数秒 `SIGABRT`:`scudo::reportMapError` ← `remapImpl` ← `scudo_calloc` ← `libMobileGL.so`(0x818b14 / 0x7e2c44,已剥符号),当时 MemAvailable 6.1 GB——一次巨大或负尺寸的 calloc,在 Adreno 830 + Magma + 这条 fixture 上;pull 库与 P3a 前的 Magma 路径符号一致,所以是**既有 bug**,不入 P3a 账,已开独立任务(先符号化再修)。Oppo/Magma 与小米/Espryt 上同一 fixture 正常。
|
||||
|
||||
@@ -518,6 +518,8 @@ $ ctest --test-dir build-push -R 'HandleRecycle' --no-tests=error -j 4 --output-
|
||||
|
||||
**`g_uploadRing` 不被重置的不对称:原样保留,记为 `dev` 侧跟进。** `OnBackendContextDestroyed`(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:2481`)对 `g_uboRing` 与 `g_unpackRing` 调 `ResetRingForNewContext`(`:2492-2493`),**不对 `g_uploadRing` 调**;`RingAvailable`(`:3186`)在首次使用时按 `contextGeneration` 自愈,所以它是良性的。P3a **故意不在飞地顺手修**(`ROADMAP.md:98` 那条纪律:拆分不得借机修不相关的 `dev` 问题),把它作为 `dev` 侧跟进项留在这里。
|
||||
|
||||
**P4a 在它旁边加第二条同类项:`ScopedDefaultUnpackState::s_synced` 没有失效路径(D-O)。** `Managers.cpp` 的 `ScopedDefaultUnpackState` 用一个**进程级**影子记住"默认 unpack 状态已经同步过",那个影子被写、被读,**却没有任何地方让它失效**——换上下文、别的代码路径自己调 `glPixelStorei`,它都不知道。P4a 既不修它也没让它更糟(句柄臂的 ring 路径一条 `glPixelStorei` 都不发,唯一的外部写点仍被 `!ringStaged` 挡着),按同一条纪律记为 `dev` 侧跟进。**两条并列的理由是同一个**:它们都是"缓存了一个事实、却没有让这个事实失效的路径",而 P4a 自己在 `Tracker.h` 上被同一类问题咬了三次(`glBindSampler` 不动位 13 的快门、SSO 下 `GetCurrentProgram()` 恒 null、`glBindImageTexture` 只换 level 时三个计数器都不动)——所以下一阶段的 brief 必须带一张"记录字段 → 写它的 setter → emitter 读的快门"的完备表,而不是让每个包各自去发现。
|
||||
|
||||
**`FlushPendingRangesNow` 定义一次,句柄臂另有一条自己的档梯。** G5 的第十项与 G1 的空 resize 集之间有一处真冲突:就地重构那几个 helper 会 resize 五个 pull 符号(`FlushPendingRangesNow +14` 在内),G1 不允许。落地形状是:**`FlushPendingRangesNow` 只定义一次、对 `5cb826b0` 逐字节相同**(在 `#if MOBILEGL_PIPE_PUSH` 的 `#else` 臂里,`Managers.cpp:1316`,调用点 `:1723`、`:2906`),句柄臂另有一个 `FlushPendingRangesFrom(twin, hostBase, size)`(`:1051`,调用点 `:1721`、`:2044`、`:2766`、`:2904`)。**在 P3a 接受档梯在 push 构建里被复制一份**(与 respecify 核心已经用过的形状相同),代价是两条梯子会漂移;对冲是 `CrossFrameBufferScenario` 的十三条加 `StreamedArenaScenario` 的两条 recycle 用例,以及 §20 的 MC 26.3 p99。**它随 pull 臂在 P13 退役**(`ARCHITECTURE.md:367`)。共享模板加访问器接口的方案被否决:它同样 resize pull 符号(G1)。
|
||||
|
||||
**`MG_Test/Buffer/BufferTest.cpp` 的 fixture 只 scope 了一半的表。** push 构建下后端在 bring-up 同时装 `BufferBackendOps` 与 `MGPipeResourceOps`,而 `ScopedBackendOps` 只 scope 前者,于是 86 条 `BufferBackendOps` 分发用例里有 **26 条**被路由进了 pipe(症状是 `EnsureGpuResidentStorage()` 返回 `false`、mock 从没被调用过)。这是**合并缝**的典型形态——两个分支各自绿、合起来红:espryt 那边没有东西经 pipe 发射,client 那边没有东西注册表。集成者落了单 scope 的修法(fixture 现在像 `MG_Test/Pipe/ResourceEmitTest.cpp` 的 `ApplierGuard` scope applier 那样,保存 / 置空 / 恢复 pipe 表):**修完 86/86,整套单元 1619/1619**。它不削弱任何东西——那 86 条是 `BufferBackendOps` 的分发测试,pipe 侧的分发有 `ResourceEmitTest` 自己的覆盖。**跟进(不属于本阶段)**:给这个 fixture 一个 pipe 形的 mock,让同样的 86 条断言在句柄路径上再跑一遍。
|
||||
@@ -526,3 +528,107 @@ $ ctest --test-dir build-push -R 'HandleRecycle' --no-tests=error -j 4 --output-
|
||||
|
||||
**峰值 RSS(push vs pull,79 例 retrace):工具不报,所以没有数。** `~/w7/retrace_gate.py` 只有五个参数(`--tree --lib --out -j --only`),代码里没有任何 `rss` / `maxrss` / `getrusage` 引用。这个数原本是用来盯第七张 slot 表泄漏的——一个没人销毁的 buffer 会永远漏掉它的 twin,而这对每一个正确性门都不可见;**这一轮拿不到它**,要拿必须先给那个工具加测量。对冲仍在:`ResourceDestroy` 是从 `~BufferObject` **无条件**发射的、不是靠清扫,顺序(先发射、后 `MGPipeSlots().Free`)由 `HandleRecycleScenario` 的三个臂把关(§16、§17)。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 22. P4a 五部分门(`6035c9d7` 全量 + `8c458cd5` 复跑,基线 `37da3c3a`)
|
||||
|
||||
P4a 的代码头是 **`8c458cd5`**;下面的"全量"一列跑在 `6035c9d7`(终审修复轮之前的那个头,`wsl_p4a_gate.sh` 完整五部分含三次 retrace),"复跑"一列是修复轮落地后在 `8c458cd5` 上重跑的同一组。两次之间只差终审那五个提交,门的口径没变。
|
||||
|
||||
| 门 | `6035c9d7`(全量) | `8c458cd5`(复跑) |
|
||||
|---|---|---|
|
||||
| **G1** pull 符号(认定 resize 集为空) | 0 增 / 0 删 / 0 重命名 / 0 resize,`.text` 字节不变 | 同上 |
|
||||
| **G5** 字节一致区 | P3a 十一函数 rc 0;P4a 自己 **17 区 / 3 文件** rc 0,self-test **8 个阴性对照全部按名变红** | 同上 |
|
||||
| **G2** pull vs push 测试名 | 差 0 | 差 0(**2902** 条) |
|
||||
| **G14** 测试名增删 | 0 删除 / +275 | 0 删除 / **+314** |
|
||||
| 单元 | 1772 × 3(linux / push / verify) | **1785 × 3** |
|
||||
| `integration-gpu` | **1091/1091 × 七臂**(默认 `0x1fff`、`0x1ff`、`0`、`0x7f`、pull、`ESPRYT_DISABLE_INVALIDATE_FLUSH=1`、族正则 497/497) | **1117/1117 × 七臂**(多了 `0x9ff`、`0x5ff` 两个依赖拒绝臂;DirectVulkan **559/559**) |
|
||||
| `integration-verify` | **896/896,零 `Fatal{`** | **920/920,零 `Fatal{`** |
|
||||
| retrace(79 例) | verify 臂 **79/79 全 armed、零分歧、零 Fatal**;push 臂 **79/79**;G3b 具名 **12/12** | push 臂 **79/79** |
|
||||
| 三个阴性对照脚本 | 全部 rc 0:`g7_negative_control.sh`、`p3a_vertex_input_negative_control.sh` 点名 `IsBgra`、**`p4a_descriptor_negative_control.sh` 点名 `Layered` 与 `borderColorForm`** | 同上 |
|
||||
| 子系统对照套件 | `CsoContentAddressing` + `ResourceSubsystemControl` + `ObjectSubsystemControl` **24/24**,`0x9ff` 依赖拒绝臂 **14/14**,`HandleRecycle`(verify)**180/180** | 同上 + `184/184` controls |
|
||||
| 八族拒绝普查 | — | **0**(16 条 needle × retrace 日志、13 行 × itest 日志) |
|
||||
|
||||
**两处口径,都是这一波踩出来的**:
|
||||
|
||||
1. **`ctest -V` 做拒绝普查是假零。** console sink 在发布配置里被编译掉,`ctest -V` 抓不到任何 `MGLOG_E`;必须逐用例单跑并读它自己的日志文件(工具 `~/w7/notes/tools/wsl_p4a_refusal_census.sh`)。一份"零拒绝"的普查如果是用 `-V` 取的,它证明的只是 sink 被关了。
|
||||
2. **普查的短语表必须覆盖全部八族**,而且要小心跨字符串字面量换行的句子——最初那版漏了 sampler 与 renderbuffer 两族,正好是后来真出问题的那两族。
|
||||
|
||||
## 23. 这一波真正的产出:缝的分类
|
||||
|
||||
P4a 的契约改了七次(`c0b`…`c0g`),外加一轮缝类审计与一轮终审修复。把它们按**类**记下来,比按包记有用得多——每一类都在多个包里重复出现过,而下一阶段的 brief 应当在开工前就把这几张表写死:
|
||||
|
||||
| 类 | 这一波的实例 | 症状 | 预防 |
|
||||
|---|---|---|---|
|
||||
| **编码没定死** | `MGPSubData::Target`(低字节资源目标 + 高字节上传目标 vs 裸枚举)、`DepthStencilMode`、`MGPSurface::Kind`、缺 `TextureTarget` | 两侧各自发明一套;`TextureUploadTarget::Texture1D == 0` 与 `kMGPipeResourceTargetBuffer == 0` 撞上,applier 的"这是 buffer 吗"判定被静默污染 | **编码表**:每个字段一行,写清位布局与零值含义,放进契约而不是包头 |
|
||||
| **身份 vs 内容** | 内置 sampler:client 按对象身份铸、cache 按内容铸;Espryt twin 按身份查那条内容寻址的记录 | 查找永远落空 → 整族拒绝(本波两次,其中一次让 17 条 Iris 光影 trace 全部用驱动默认采样器) | **每 kind 两侧 handle 规则表**:谁铸、按什么键、谁查、按什么键 |
|
||||
| **记录键错了维度** | framebuffer 记录按"当前绑定"存,DSA 的 `BlitNamedFramebuffer` / `ClearNamedFramebuffer*` 按名字来 | 打进一个从没收到附件的 FBO;SSIM 看得见但没有任何拒绝 | 记录按**对象**存,绑定另存句柄;第四个 target 值 `Named` |
|
||||
| **进程级单例 vs 每上下文命名** | `CompositeResolver` 按管线 GL 名记忆,GL 名是每上下文的 | 一次 make-current 就释放掉另一个上下文还活着的合成体 | 单例的键必须含上下文身份 |
|
||||
| **破坏性客户端动作缺前置条件** | 按 acceptance 清 dirty,但 (a) Magma 根本没有消费者,(b) D-K2 依赖位只在服务端拒 | 上传丢失:66 条 DirectVulkan 用例、`0x7ff` 下 438/491 | **消费者门 + 依赖门都要在客户端侧**:没消费者/依赖不满足时**一条不发**,而不是发了再在服务端拒 |
|
||||
| **快门看不见自己的主体** | `glBindSampler` 只动位 12 的世代;SSO 下 `GetCurrentProgram()` 恒 null;`glBindImageTexture` 只换 level 时三个计数器都不动 | 记录停在上一次的值,第一个真读该字段的消费者画错(`create-indirect` ssim 0.887) | **"记录字段 → setter → 快门"完备表**;新增计数器会撑大 pull 对象、G1 不允许,所以优先混入已有世代 |
|
||||
| **清得太宽** | `resource_respecify` 清掉整张待上传表 | 已被接受、客户端标志已清的那一级永久丢失(读回全黑) | 作用域随调用走:一级 / 截断链 / 整资源 |
|
||||
| **死亡没通知发射方** | 六个死亡 helper 只释放 slot | 已删但未复用的句柄仍解析到已释放的前端对象 → 下一个 validate 点对已释放内存调虚函数 | 死亡在 wire delete 与 free 之间转发给每个 emitter;查找按"活着"判定而不只按世代 |
|
||||
| **门不能变红** | `G7` 脚本因 scoped enum 写 0 而永远编译不过、`HighWater(ShaderCso)` 取的是段顶、G9 的红前态公共 GL 不可见 | 绿得毫无意义 | 每个门都要有阴性对照并**真跑过一次红**;公共 GL 看不见的,改白盒断言(`PipeApplyPeek`) |
|
||||
|
||||
**一个方法论上的结论**:本波六个包的 v1 全部通过了自己的门,**六份对抗性复审全部判 REWORK**,而其中最贵的两个缺陷(丢上传、delete 后 UAF)是**整体 diff 终审**才抓到的——因为它们跨包:发射方、applier、twin 各自都自洽。所以"每包一审 + 集成后整体终审"这条流程里,**终审不是形式**,它是唯一能看见跨包契约的那一轮。
|
||||
|
||||
## 24. P4a 设备配对 A/B(三臂)、MC 26.3 的 p99、上传形状(记录项)
|
||||
|
||||
**先说口径,再看数(`MEASUREMENTS.md:440` 那条告诫在 P4a 上再次成立)**:`acc/draw` 数的是**约十个热入口上的静态计数点**,所以"把读点搬走"和"把工作去掉"在它上面长得一模一样。P4a 恰好是**搬**的一波:79 例语料上 DirectGLES 的 `acc/draw` 普遍下降(26.3 `10.49 → 8.34`、`rei-inventory` `13.33 → 11.25`、`rd12` `8.09 → 6.09`),而同一批运行的逐线程 CPU 是**上升**的。**这不是矛盾,是这个计数器的定义**:后端不再每 draw 去 `pGLContext` 上取,改成读被推送的记录,站点自然少计——工作搬到了客户端的发射侧。要判性能只看 CPU 时间序列与门的命中/未命中对,`acc/draw` 只能与站点常量表一起读。
|
||||
|
||||
**设备与协议(与 §20 的两台机不同,这里换了机器)**:红米 M332BF(`2f7cbe2e`,SM8750 / **Adreno 830v2**,与 §20 的小米同 SoC 同定频点,数值可比)。reboot-clean → 大核 `policy6` 钉 1958400、小核 `policy0` 钉 1555200、GPU `pwrlevel 0`;**这台的 GPU 有效定频是 1050 MHz 不是 1100**——厂商把 `kgsl-3d0/thermal_pwrlevel` 常驻 1,root 写 0 无效(33 °C + 风扇全速下验证),`pin_device.sh` 按 1050 判定。全程**主动风扇恒定 level 2(~14.5k rpm)**:它对两臂是同一个常量,作用是把每用例之间的降温从 20–30 分钟压到 1 分钟以内,**40 个样本 40 个 `pin check` 全是 PINNED**(§20 那轮有 22 个样本因热漂移作废重跑)。APK 是 `8c458cd5` 的 Release trace 双臂(pull 8504077 B / push 8626957 B)。
|
||||
|
||||
**三臂表**(`--benchmark-no-finish`,尾 200 帧、best-of-3、逐线程 CPU p50 ms;`0x1ff` = P2+P3a 边界,`0x1fff` = P4a 默认):
|
||||
|
||||
| 用例 | 后端 | pull | `0x1ff` | `0x1fff` | Δ P2+P3a | Δ 合计 | **P4a 自己** |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| improved-transparency-26.3 | Espryt | 10.716 | 11.754 | 12.124 | +9.7% | +13.1% | **+3.4 pt / +0.37 ms** |
|
||||
| improved-transparency-26.3 | Magma | 10.603 | 11.543 | 11.585 | +8.9% | +9.3% | +0.4 pt(噪声) |
|
||||
| rd12-odinlite | Espryt | 8.210 | 10.798 | 11.182 | +31.5% | +36.2% | **+4.7 pt / +0.38 ms** |
|
||||
| rd12-odinlite | Magma | — | — | — | — | — | 三臂全 `rc=1`,见下 |
|
||||
| fabric-sodium | Espryt | 1.312 | 1.406 | 1.454 | +7.2% | +10.8% | +3.6 pt / +0.05 ms |
|
||||
| fabric-sodium | Magma | 0.474 | 0.502 | 0.507 | +5.9% | +7.0% | +1.1 pt(噪声) |
|
||||
| 1.21.4-in-world | Espryt | 2.369 | 2.732 | 2.867 | +15.3% | +21.0% | **+5.7 pt / +0.14 ms** |
|
||||
| 1.21.4-in-world | Magma | 1.028 | 1.147 | 1.145 | +11.6% | +11.4% | −0.2 pt(噪声) |
|
||||
| fabric-iris-bsl | Espryt | 1.727 | 1.740 | 1.811 | +0.8% | +4.9% | +4.1 pt / +0.08 ms |
|
||||
| fabric-iris-bsl | Magma | 0.742 | 0.788 | 0.786 | +6.2% | +5.9% | −0.3 pt(噪声) |
|
||||
|
||||
**读法。** (1) **P4a 自己在 Espryt 上是 +3.4 ~ +5.7 个百分点**(绝对值 0.05–0.38 ms/帧),大头仍然是 P2+P3a 那条边界——rd12 上 36.2% 里有 31.5% 是它。(2) **Magma 的 `0x1ff` 与 `0x1fff` 两臂在四个用例上逐个落在噪声内**(+1.1 / +0.4 / −0.2 / −0.3 pt),这是 c0f 那道"没有后端注册 `MGPipeResourceOps` 就一条不发"的门在设备上的读数——Magma 仍然要付 P2+P3a 的客户端发射(它消费那些族),但 P4a 的四族对它完全免费。(3) **`vanilla`(1.21.4-in-world)是 Espryt 上 P4a 占比最高的用例**(+5.7 pt),它 draw 少、状态切换密,正是句柄化最不划算的形状;`sodium`/`iris-bsl` 这种把状态压平的语料几乎不受影响。
|
||||
|
||||
**头条一:MC 26.3 在 Adreno 上的 p99。** pull **25.297** → P4a **26.841 ms(+6.1%)**,`0x1ff` 臂 26.387;Magma 侧 24.979 → 26.021。对照 §20 的 P3a 读数(25.457 → 26.322,+3.4%)与 `MEASUREMENTS.md:87` 的采纳基线(p99 163 → 21 ms),**仍在 21–26 ms 档内、没有回到采纳前的形态**——按口径记录,不判门。
|
||||
|
||||
**头条二:GUI/atlas 的纹理上传形状,pull 与 push 逐项相同。** 79 例语料两臂各跑一遍带 `MOBILEGL_PIPE_STATS=1` 的 retrace(`8c458cd5`,客户端计数器 `tex[emit/box/rect/jobs]`):
|
||||
|
||||
| | emit | box | rect | jobs |
|
||||
|---|---|---|---|---|
|
||||
| pull | 18451 | 16060 | 2391 | 39926 |
|
||||
| push | 18453 | 16062 | 2391 | 39928 |
|
||||
| 差 | **+2** | **+2** | **0** | **+2** |
|
||||
|
||||
**整份语料上唯一的形状差异是 2 次**,而且正是那 2 次 `trp`(纹理重铸拉取,见 `ROADMAP.md` 开放问题 2)带来的重放上传——即"盒 vs 矩形"的分解一格没动。这是 SSIM 看不见、Mali 那道 ~+6 ms/帧的悬崖就藏在里面的那个数(`ARCHITECTURE.md` §6),P4a 在这里是**中性**的。逐用例看也一致:`rei-inventory` Espryt 两臂都是 16/16/0/16,Magma 两臂都是 47/46/1/75。
|
||||
|
||||
**一条留给优化阶段的线索(不是缺陷,是读数)**:设备上 26.3 Espryt 的 `sve`(真正发出去的 sampler-view 集合数)**≈ draw 数**(9143 次 / 9138 draw,每窗口 120 帧),而桌面同一 fixture 只有 ~0.07/draw。`PipeStats.h` 给这四个集合计数器写的用途正是这个——"抑制器不再抑制时,它的计数会跟着 draw 数走而不是跟着状态变化走"。桌面与设备的差异说明这跟负载形状有关而不是无条件失效,但 **26.3 在设备上每 draw 重发一次 sampler-view 集合**是 Espryt 侧 P4a 那 +0.37 ms 最值得先查的去处,列进 P3b/P4b 的优化清单。
|
||||
|
||||
**`rd12` + Magma 在这台机上照样崩**:三臂(含 pull)全部 `rc=1`,与 §20 在小米上的记录一致(`scudo::reportMapError` ← `remapImpl` ← `scudo_calloc` ← `libMobileGL.so`)。**换了一台同 SoC 的机器仍然复现,进一步确认它是 `dev` 侧的问题而不是设备个例**;按 `ROADMAP.md:7` 的纪律不在本分支顺手修,处置沿用 §20:排除在 A/B 之外、留在桌面语料里(桌面两臂均通过)。
|
||||
|
||||
## 25. P4a DriverBench:T1 / T2 / T3(桌面,lavapipe + llvmpipe,记录项)
|
||||
|
||||
`wsl_p4a_bench.sh` 在 `8c458cd5` 上重跑(原始表 `~/w7/notes/p4a/bench/driverbench.{csv,md}`,repeats=5 / frames=240,空闲机)。臂:`pull`、`push`(`0x1fff`)、**`push7f` 一列在 P4a 里装的是 `0x1ff`**(P2+P3a 边界 = 设备侧那个 T2 的桌面对应物)、`push0`(`PIPE_PUSH=0`)、`nocso`(关 CSO 内容寻址的负面对照)。
|
||||
|
||||
| 臂 | `mc_vanilla_draw` | `mc_state_toggle` | `mc_pass_switch` |
|
||||
|---|---|---|---|
|
||||
| native | 4398.9 | 21387.3 | 412657.8 |
|
||||
| espryt-pull | 4684.4 | 22010.4 | 410936.2 |
|
||||
| espryt-push(`0x1fff`) | 5760.5 | 22974.9 | 419703.1 |
|
||||
| espryt-`0x1ff` | 5749.1 | 23768.2 | 419288.0 |
|
||||
| espryt-push0 | 5355.2 | 23618.8 | 420362.2 |
|
||||
| espryt-nocso | 5929.0 | 23685.0 | 418677.0 |
|
||||
| magma-pull | 15394.9 | 31678.5 | 420197.6 |
|
||||
| magma-push(`0x1fff`) | 16023.9 | 32417.4 | 415641.9 |
|
||||
| magma-`0x1ff` | 15847.5 | 32100.3 | 416670.4 |
|
||||
| magma-push0 | 15946.6 | 31610.2 | 417410.5 |
|
||||
| magma-nocso | 15846.2 | 32495.9 | 411306.4 |
|
||||
|
||||
`mc_vanilla_draw` 上:**espryt T1(`0x1fff` − pull)= +1076.1 ns/draw,T2(`0x1ff` − pull)= +1064.7,T1 − T2 = +11.4**;magma T1 = +629.0、T2 = +452.6、T1 − T2 = +176.4。blend toggle:espryt +964.5 / magma +738.9 ns per toggle pair;pass switch:espryt +8766.9、magma −4555.7(后者符号为负,属该项的噪声量级)。
|
||||
|
||||
**桌面这台机上,"P4a 自己"落在本 bench 的噪声底以下,所以不要单独引用它。** 同一份脚本在 `6035c9d7`(终审修复前)上跑出的是 espryt T1 +1269.7 / T2 +1135.8 / **T1 − T2 = +133.9**,本轮是 +1076.1 / +1064.7 / **+11.4**——**两臂的绝对值在两轮之间各自漂了 ~200 ns,而它们的差只有 10–130 ns**,也就是说这个 bench 分辨不出 P4a 这一档的增量。真正可引用的是:(1) **T1 ≈ +1.1 µs/draw 的总边界**(对 pull 基线,Espryt;这条在两轮之间是稳的);(2) **设备侧的三臂表(§24)**——那里 P4a 自己是 +3.4 ~ +5.7 个百分点、0.05–0.38 ms/帧,样本全部在验证过的定频窗口里。**Magma 的 T1 − T2 = +176.4 ns 不是"Magma 在跑 P4a"**:c0f 的消费者门让它一条 P4a 记录都不发(设备侧 §24 的 Magma 两臂差也在噪声内),这 176 ns 是 tracker 多算的那几个快门加噪声。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MGPipe:MobileGL 前后端拆分
|
||||
|
||||
> 状态:**P0、P0.5、P1、P2、P3a 已落地**(`feat/disaggregated@fde5fda3`,基线 `dev@9eae9858`)。第 43 天 GO/NO-GO 判定为**继续**。P3a(handle wave 1:Espryt 的 buffer 与 VAO)已交付,**下一步 P4a**(handle wave 2:FBO / 纹理 / sampler / program 的身份与描述符)。见 `ROADMAP.md`。
|
||||
> 状态:**P0、P0.5、P1、P2、P3a、P4a 已落地**(`feat/disaggregated@8c458cd5`,基线 `dev@9eae9858`)。第 43 天 GO/NO-GO 判定为**继续**。P4a(handle wave 2:Espryt 的 FBO / 纹理 / sampler / program 身份与描述符)已交付,Espryt 的对象类读点至此全部走句柄;**下一步 P3b/P4b**(深化:memo 重键、发射游标、raw-depth-fetch sampler 原生化)与 **P5**(传输 + inproc applier,也只依赖 P4a)。见 `ROADMAP.md`。
|
||||
>
|
||||
> 性能纪律(2026-09-08 起):逐线程 CPU 与 tracker 绝对 ns **对着 pull 臂基线记录**,不再作阻塞门(push 比 pull 多约 10% 逐线程 CPU 已被接受;该读数出自 -O0 APK,Release 基准线见 `MEASUREMENTS.md` §20),专门的优化阶段排在路线图推完之后。
|
||||
|
||||
@@ -31,7 +31,7 @@ MGPipe 是 MobileGL 前端(`MG_State` + `MG_Impl`)与后端(`MG_Backend`
|
||||
|---|---|
|
||||
| `ARCHITECTURE.md` | 已定稿的设计与架构:句柄与世代、调用目录、记录约定、tracker、纹理路径、shader 制品、反向通道、后端改造、传输、persistent map 分档、进程/EGL/平台、构建与纯度门、验证策略 |
|
||||
| `ROADMAP.md` | P0…P13 阶段表、两条跑道、GO/NO-GO 清单、再基线检查点、仍然开放的问题 |
|
||||
| `MEASUREMENTS.md` | 逐阶段实测:P0(spike A/B、双设备边界计数器基线、桌面数据点、语料事实)、P1(verify harness 门)、P2(五部分门、两机配对 A/B、DriverBench T1/T2、计数器)、P3a(门、接缝缺陷、Track H 普查、两机 A/B)与复现命令 |
|
||||
| `MEASUREMENTS.md` | 逐阶段实测:P0(spike A/B、双设备边界计数器基线、桌面数据点、语料事实)、P1(verify harness 门)、P2(五部分门、两机配对 A/B、DriverBench T1/T2、计数器)、P3a(门、接缝缺陷、Track H 普查、两机 A/B)、P4a(门、契约七次修正与两轮终审修复、缝类审计、三臂设备 A/B、DriverBench T1/T2/T3)与复现命令 |
|
||||
|
||||
代码地图(P0 已落地的部分):
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user