mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Docs] (Disaggregated): P5f section 1 - the pointer fact, and one draw's full lifecycle through gPipeInputs
The section explained what gPipeInputs IS and what breaks under spawn, but not the one fact
that decides the shape of the fix, and not how a call actually reaches the driver through it.
Both are now in the document rather than only in the conversation that found them.
- THE POINTER FACT, now stated first because everything else follows from it: several
BARRIER_PULLED fields do not hold values, they hold pointers into client-owned objects.
PipeFill.cpp:274 fills m_textureUnitBase with `&ctx.GetTextureUnitObject(0)` - a raw base
into the frontend's array - and :245 fills m_programForDraw with a SharedPtr<ProgramObject>.
PipeInputs.h:669 dereferences it as `m_textureUnitBase[unit]`. PipeFill.cpp:2591-2592 says
the same of the shader-buffer field: "four raw bases into the frontend's binding-point
table".
The consequence gets its own paragraph in 1.4, because it is the step most likely to be got
wrong: this CANNOT be fixed by moving the block into shared memory. Mapping the same pages
into both processes does not make a client heap address meaningful in the server, and a
SharedPtr's refcount does not cross at all. The 15 fields each need a different SOURCE, not
a shared location.
- NEW 1.2, one draw end to end: glBindTexture marks a tracker bit and sends nothing, because
push only happens at a verb boundary; glDrawArrays enters MGPipeValidateForVerb
(PipeFill.cpp:3347) whose four steps are barrier decision, dirty walk, emission (records,
carrying handles), and the residual fill - which is where BARRIER_PULLED happens; the record
goes on SEG_CMD; the apply thread dispatches to the backend; PrepareForDraw reads either a
record-carried handle against the server's own tables, or gPipeInputs; then the real driver
call. Plus the field lifetime: stamped with verb N's serial, read during verb N's apply,
Fatal{UnmigratedPipeInput} if read at verb N+1 without a refill - one verb, never longer.
- The same example carries the Espryt/Magma split concretely: the texture binding reaches
Espryt as a record with a handle (P5e retired GetTextureUnitObject for it) and reaches Magma
through gPipeInputs into the client's array. That is FieldOwnership.def's own
"P5e (Espryt unbarriered), P7 (Magma)" made legible, and it is why section 2.1's table has
eight rows in that state.
Sections renumbered 1.1-1.4. Doc citation lint: 129 citations, 0 problems.
This commit is contained in:
@@ -32,8 +32,8 @@ P5c 立过同样的规则(规则 E),但它的判据是**审计**——人
|
||||
inline PipeInputs& gPipeInputs = *new PipeInputs();
|
||||
```
|
||||
|
||||
一个**普通的进程级全局对象**,不在任何共享内存段里。它是“后端被推送的状态块”——
|
||||
backend 不再每 draw 去读 `MG_State::pGLContext`,改为读这个块。每个字段带一个**归属类**
|
||||
一个**普通的进程级全局对象**,不在任何共享内存段里。它是“后端被推送的状态块”——backend 不再每
|
||||
draw 去读 `MG_State::pGLContext`,改为读这个块。每个字段带一个**归属类**
|
||||
(CONTRACT-P5 表 2 / R-7,手维护的一半在 `MG_Pipe/FieldOwnership.def`):
|
||||
|
||||
| 归属类 | 量 | 含义 |
|
||||
@@ -43,38 +43,101 @@ backend 不再每 draw 去读 `MG_State::pGLContext`,改为读这个块。每
|
||||
| `BARRIER_PULLED` | **21 行 / 15 个字段** | **P5 的债**:server 读的是 client 的残余填充留在“那唯一一份共享的 `gPipeInputs`”里的值 |
|
||||
| `FATAL` | 4 行 | 没有载体,缩减路径也不读;读到即 abort |
|
||||
|
||||
写它的是 client(`MG_Impl/Pipe/PipeFill.cpp`),读它的是 backend。每个字段带一个**逐 verb 的
|
||||
世代戳**,读到比当前 verb 序号旧的戳就是
|
||||
`Fatal{UnmigratedPipeInput, "<Field>@<Verb>"}`(`MOBILEGL_PIPE_POISON`)。
|
||||
**最要紧的一点,先说:`BARRIER_PULLED` 里好几个字段存的不是值,是指向 client 对象的指针。**
|
||||
|
||||
### 1.2 为什么今天能工作
|
||||
```cpp
|
||||
// MG_Impl/Pipe/PipeFill.cpp:274 —— 裸指针,指进 client 的纹理单元数组
|
||||
dst.m_textureUnitBase = &ctx.GetTextureUnitObject(0);
|
||||
// MG_Impl/Pipe/PipeFill.cpp:245 —— 前端对象的 SharedPtr
|
||||
dst.m_programForDraw = ctx.GetProgramForDraw();
|
||||
```
|
||||
|
||||
读侧照着解引用(`PipeInputs.h:669`):
|
||||
|
||||
```cpp
|
||||
TextureUnit& GetTextureUnitObject(Int unit) { return m_textureUnitBase[unit]; }
|
||||
```
|
||||
|
||||
`PipeFill.cpp:2591-2592` 对 `set_shader_buffers` 那组的说法是同一件事:该字段是
|
||||
“four raw bases into the frontend's binding-point table”。**所以这不是一块照搬过去就能用的状态。**
|
||||
|
||||
### 1.2 一次 draw 的完整生命周期
|
||||
|
||||
以 `glBindTexture(GL_TEXTURE_2D, 7)` 之后一次 `glDrawArrays(GL_TRIANGLES, 0, 3)` 为例。
|
||||
|
||||
**① `glBindTexture`**:MG_Impl 更新 `MG_State` 的影子状态,tracker 置脏位。**什么都不发。**
|
||||
推送只发生在 verb 边界。
|
||||
|
||||
**② `glDrawArrays`** → `MGPipeValidateForVerb(kVerbDrawArrays)`(`MG_Impl/Pipe/PipeFill.cpp:3347`),
|
||||
四步:
|
||||
|
||||
| 步 | 做什么 | 碰 `gPipeInputs` 吗 |
|
||||
|---|---|---|
|
||||
| 1 | 判定本条记录设不设障(`barriered` / `fillOwed`) | 决定第 4 步填不填 |
|
||||
| 2 | 脏位走查 `tracker.Update(*ctx, verbClass)` | 否 |
|
||||
| 3 | **发射**:14 个 emitter 把脏子系统变成**记录**;纹理绑定变成 `set_texture_bindings`,带的是**句柄** | 否 |
|
||||
| 4 | **残余填充**:记录没供给的字段,`CopyField(inputs, *ctx, field)` 从活的 GLContext 抄进块里,盖上本 verb 的世代戳 | **是——`BARRIER_PULLED` 就是这一步** |
|
||||
|
||||
随后 draw 记录本身写进 `SEG_CMD` ring 并敲门铃。设障就等 `appliedSeq`;不设障就直接返回(P5e)。
|
||||
|
||||
**③ server apply 线程**:从 ring 取记录 → `PipeApplier::ApplyOne` → 分发到 verb sink → 调后端
|
||||
`DirectGLES::DrawArrays`。
|
||||
|
||||
**④ 后端 `PrepareForDraw()` 取它要的东西**,两条路:
|
||||
|
||||
- **走记录**:用记录带来的句柄查 **server 自己的对象表**。跨进程成立。
|
||||
- **走 `gPipeInputs`**:`GetTextureUnitObject(0)` 返回 `m_textureUnitBase[0]`,而那个指针指进
|
||||
**client 的数组**。今天成立,只因为两个角色同地址空间、且 barrier 保证此刻 client 停着。
|
||||
|
||||
**⑤** 后端发出真正的 `glDrawArrays` 给厂商驱动。
|
||||
|
||||
**字段的寿命**:第 4 步填入并盖上 verb N 的戳;后端在 verb N 的 apply 期间读;到 verb N+1 若不
|
||||
重填,读到的就是旧戳 → `Fatal{UnmigratedPipeInput, "<Field>@<Verb>"}`(`MOBILEGL_PIPE_POISON`)。
|
||||
**一个 verb 一次,不跨 verb 存活。**
|
||||
|
||||
同一个例子今天分两条臂,正好是 §2.1 那张表的缩影:
|
||||
|
||||
| | 纹理绑定怎么到后端 |
|
||||
|---|---|
|
||||
| **Espryt** | 走记录:`set_texture_bindings` 带句柄 → server 自己的表。P5e 已退役 `GetTextureUnitObject`。**跨进程成立。** |
|
||||
| **Magma** | 仍读 `gPipeInputs.GetTextureUnitObject(0)` → client 的数组。**跨进程不成立。** |
|
||||
|
||||
`FieldOwnership.def` 里这一行的原文就是 `"P5e (Espryt unbarriered), P7 (Magma)"`。
|
||||
|
||||
### 1.3 为什么今天能工作
|
||||
|
||||
因为两个角色在**同一个进程、同一个 `gPipeInputs` 对象**上,而 verb barrier 保证同一时刻
|
||||
{GL 线程, apply 线程} 至多一个可运行——所以这个块任何瞬间只有一个写者
|
||||
(`PipeInputs.h:904-908` 的裁定)。client 填,server 读,中间不经过任何载体。
|
||||
|
||||
`BARRIER_PULLED` 这一类的定义就是:"server 在 barrier 把两个线程隔开时,读 client 的残余填充
|
||||
留在 `gPipeInputs` 里的值。**合法,并且被计数**(`PipeStats::CallClass::ResidualPulls`)。"
|
||||
`BARRIER_PULLED` 这一类的定义就是这句话:“server 在 barrier 把两个线程隔开时,读 client 的残余
|
||||
填充留在 `gPipeInputs` 里的值。**合法,并且被计数**(`PipeStats::CallClass::ResidualPulls`)。”
|
||||
|
||||
### 1.3 拆进程后它会怎样
|
||||
### 1.4 拆进程后它会怎样
|
||||
|
||||
**client 进程和 server 进程各有一个 `gPipeInputs`,是两个不同的对象。**
|
||||
|
||||
- `RECORD_SUPPLIED` / `APPLIER_DERIVED` 的 **45 个字段**没问题:它们的值本来就由记录或 server
|
||||
自身产生,server 侧的块会被正常填上。
|
||||
- `BARRIER_PULLED` 的 15 个字段**没有填充者**。client 进程里那次 fill 写的是 client 自己的块;
|
||||
server 进程的块里那些字段的世代戳永远是旧的 → 每一次读都是
|
||||
`Fatal{UnmigratedPipeInput}`。
|
||||
- `MG_Impl` 根本不在 server 进程里,所以连"让 server 自己去 fill"这条退路都没有。
|
||||
- `RECORD_SUPPLIED` / `APPLIER_DERIVED` 的 **45 个字段**没问题:值本来就由记录或 server 自身产生,
|
||||
server 侧的块会被正常填上。
|
||||
- `BARRIER_PULLED` 的 **15 个字段没有填充者**。第 4 步写的是 **client 那一份**;server 那一份从来
|
||||
没人写过,`FilledGen` 恒为 0 → 后端每次读都 `Fatal{UnmigratedPipeInput}`。
|
||||
- `MG_Impl` 根本不在 server 进程里,所以连“让 server 自己去 fill”这条退路都没有。
|
||||
|
||||
头文件自己写着这件事(`PipeInputs.h:279`):当前 verb 边界的清理"对 inproc 足够——两个角色共享
|
||||
一个进程、一个 `gPipeInputs`——**而对 P6 是误导的,那里 `MG_Impl` 根本不在 server 里**"。
|
||||
以及 `:902`:`rsp` 的值"**就是 P6/P7/P8 债务的大小**"。
|
||||
**而且不能靠“把这个块也放进共享内存”解决**(这是最容易想歪的一步):里面存的是裸指针和
|
||||
`SharedPtr`(§1.1)。就算两个进程映射同一段内存,`m_textureUnitBase` 指向的 client 堆地址在
|
||||
server 进程里没有意义,`SharedPtr` 的引用计数更跨不过去。
|
||||
|
||||
**所以 P5f 对 `gPipeInputs` 的要求**:`BARRIER_PULLED` 归零。每个字段要么升成
|
||||
`RECORD_SUPPLIED`(值随记录过线),要么升成 `APPLIER_DERIVED`(server 自己推),要么降成
|
||||
`FATAL`(缩减路径不读它)。**块本身不需要跨进程共享,它需要的是每个字段都有一个不依赖对方
|
||||
地址空间的来源。**
|
||||
头文件自己写着这件事(`PipeInputs.h:279`):当前 verb 边界的清理“对 inproc 足够——两个角色共享
|
||||
一个进程、一个 `gPipeInputs`——**而对 P6 是误导的,那里 `MG_Impl` 根本不在 server 里**”。
|
||||
以及 `:902`:`rsp` 的值“**就是 P6/P7/P8 债务的大小**”。
|
||||
|
||||
**所以 P5f 对 `gPipeInputs` 的要求**:不是共享它,而是让这 15 个字段**各自换一个不依赖对方地址
|
||||
空间的来源**——升成 `RECORD_SUPPLIED`(值随记录过线)、升成 `APPLIER_DERIVED`(server 自己推)、
|
||||
或确认缩减路径根本不读它而降成 `FATAL`。
|
||||
|
||||
这也正是 §4 双块演练为什么有效:`inproc` 下就给两个角色各一份,第 4 步写 client 的、后端读
|
||||
server 的,**今天所有靠“其实是同一个对象”蒙混过去的路径立刻变成具名 Fatal**,带字段名和 verb 名
|
||||
——不用拆进程就拿到完整清单。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user