Files
MobileGL/docs/Disaggregated/notes/p5f/f0-egl.md
T
swung0x48 e75e00cb7a [Docs] (Disaggregated): P5f f0 - the eight-way read-only census reports
Statics, reverse channel, Magma cross-role sites, EGL control plane,
registry rekey, build/gate mechanics, the 15 BARRIER_PULLED fields'
carrier rulings, and the dual-block mechanism's design recon.
2026-09-19 09:08:56 -04:00

19 KiB
Raw Blame History

f0-egl — P5f §2.3 EGL / surface 控制面普查(只读)

基线 feat/disaggregated @ 8b68b92c(代码头 25fba0d5)。路径在 MobileGL/ 下。 本文件只读普查,不改任何代码。对应 P5c 审计 G4 行(docs/Disaggregated/ROADMAP.md:71), 原计划记给 P6(MobileGL/MG_Remote/CONTRACT-P5C.md:537),P5f 计划 §2.3 (docs/Disaggregated/P5F-WIRE-COMPLETENESS.md:181-186)将其收回 P5f 的包 fc。


1. 单槽邮箱机制(现状)

机制本体。 十二个 forwarder 共享同一条过线通道:

  • ServerLoop.h:220:using ControlWork = MobileGLResult (*)(void* user); —— 一个裸函数指针 加一个 void*。注释(ServerLoop.h:217-219、ServerLoop.cpp:784-787)说明为何不是 std::function:析构路径不得分配(ID-8)。
  • ServerLoop.cpp:788-793:每个 forwarder 用同一个无捕获 trampoline +[](void* user) { return static_cast<Args*>(user)->Run(); } 和指向调用者栈上 Args 的 指针调用 RunOnApplyThread。
  • ServerLoop.cpp:645-707(RunOnApplyThread):m_callerMutex 串行化多个投递者(:653); 在 m_controlMutex 临界区内发布 {m_controlWork, m_controlUser, m_controlPending} 四元组, 影子位 m_controlPosted 最后发(:685-694);发布后再敲门铃(:702-704,先发布后敲门是 Doorbell 的硬规则);然后阻塞在 m_controlDone.wait(:705),返回 m_controlResult(:706)。
  • ServerLoop.cpp:533-568(PumpControlRequest):apply 线程在 drain 批之间取走请求、 执行 work(user)(:560)、回填结果并 notify(:561-567)。
  • 泵点:ApplyThreadMain 主循环 :397-398 与退出前 :453;park 谓词 ready(:391-395)把 ControlIsPending() 列为唤醒条件之一。
  • 重入臂:RunOnApplyThread:651——apply 线程自己投递就内联执行(拆构路径 ~BackendObject_DirectGLES → ReleaseEGLResources 走这里)。
  • 无线程时的行为:backend 活着而线程不在 → Fatal{ApplyThreadNotRunning}(:670-676); backend 已不在 → 返回 MOBILEGL_ERR_NOT_INITIALIZED(:678-683)。

邮箱字段在 ServerLoop.h:367-389:m_callerMutex / m_controlMutex / m_controlPosted(一位影子)/ m_controlDone / m_controlWork / m_controlUser / m_controlResult / m_controlPending / m_controlFinished。

今天为何能工作:两个角色同进程、同地址空间。函数指针是双方都能执行的代码地址; &args、major/minor、&handle 指向的栈帧由阻塞握手(:705 的 wait)保证在整个执行期间存活; 单槽够用是因为"verb barrier 同一时刻只留一个 client 线程可运行"(ServerLoop.h:367-368 注释),加上 m_callerMutex 兜底。跨进程后这三样全部失效:代码地址、栈地址、以及"阻塞等待 同进程另一个线程"本身。

P5f 处置建议:邮箱机制在 inproc 下整体保留(它是 apply 线程归属语义的一部分),但在其上 叠一层"帧化"接缝:每个 forwarder 的参数先收敛成一个纯值结构(见 §4 草案),inproc 臂把该 结构放在栈上传指针(现状等价),spawn 臂把同一结构编码进 SurfaceOp 帧。这样 fc 包不动 线程模型,只动载荷的形状。

2. 十二个 forwarder 逐个点名

声明在 ServerLoop.h:459-475,定义在 ServerLoop.cpp:802-1033。下表"载荷"一列标出 值(可直接序列化)/ 栈上指针(指向调用者存储)/ 句柄值(client 进程的 EGL 句柄, 跨进程无意义)。

# forwarder(定义行) 签名 语义 载荷分类
1 ServerInitializeEGLDisplay(ServerLoop.cpp:802-816) Bool (EGLDisplay dpy, EGLint* major, EGLint* minor) 初始化 server 后端的 EGL display,回写主/次版本 dpy 句柄值;major/minor 是指向 client 栈的 out 指针(:805-806)
2 ServerCreateEGLWindowSurface(:818-834) Bool (EGLSurface surface, const WindowHandle& handle) 建窗口 surface;成功后 ForgetCurrentTuple()(:829,N-3) surface 句柄值;&handle 是指向 client 存储的指针(:821/:832),且 WindowHandle::Handle 本身是 void*(BackendObject.h:561-566)
3 ServerResizeEGLWindowSurface(:836-850) Bool (EGLSurface, Uint32 w, Uint32 h) 重建 swapchain 全为值。最干净的一个
4 ServerCreateEGLPbufferSurface(:852-870) Bool (EGLSurface, EGLint w, EGLint h) 建 pbuffer;成功后 ForgetCurrentTuple()(:865) 全为值
5 ServerMakeEGLCurrent(:872-918) Bool (EGLDisplay, EGLSurface draw, EGLSurface read, EGLContext ctx) 经 ApplyMakeCurrent(:269-316)三分支:NativeBind / RepeatNoOp / ClientRelease(ID-54);真绑定后重发布 caps 快照(:899-913,R-12) 四个全是句柄值
6 ServerSwapEGLBuffers(:920-933) Bool (EGLDisplay, EGLSurface draw) 直接在 server 上 present 句柄值。无生产调用者(见 §5 发现 F8)
7 ServerSetEGLSwapInterval(:935-946) void (Int interval) 设置交换间隔;返回值被丢弃(:945) 值
8 ServerReleaseEGLSurface(:948-965) void (EGLSurface) 释放 surface;ForgetCurrentTupleIfItNames(:960,N-3) 句柄值
9 ServerReleaseEGLResources(:967-989) void () 按契约阻塞(:967-973 注释:之后 MobileGL::Destroy() 立即走);ForgetCurrentTuple()(:984) 无参数
10 ServerInitCapabilities(:991-1007) Bool () 跑 server 端 InitCapabilities 并 PublishCapsSnapshot()(:999-1002) 无参数
11 ServerInitWindowSurface(:1009-1020) Bool () server 端 InitWindowSurface 无参数。无任何调用者(见 §5 发现 F8)
12 ServerSetWindowHandle(:1022-1033) void (const WindowHandle& handle) 把窗口句柄交给 server 后端 &handle 是指向 client 存储的指针(:1024/:1031),内含 void*

参数里的函数指针:每一条都是同一个 trampoline(:790-791),它承载的唯一信息是"调哪个 Args::Run",即操作的身份。参数里的栈上 void*:&args 永远存在(:792),其内部再 含 client 栈指针的是 #1(major/minor)、#2 与 #12(handle)。

今天为何能工作:同进程;阻塞握手(RunOnApplyThread:705)使调用者栈帧在整个 RPC 期间 存活,所以 out 指针与引用参数安全。EGL 句柄值两边通用是因为同一个驱动在同一进程里认它们。

P5f 处置建议(共性):

  • 函数指针 → SurfaceOp.kind 枚举(操作身份显式化)。
  • out 指针 → 帧的 reply 字段(#1 已被 SurfaceReply.eglMajor/eglMinor 覆盖)。
  • WindowHandle → windowKind + nativeToken + width/height 值字段;Handle 指针在 Android 上是 ANativeWindow*(P6-SPAWN-PLAN.md:38),子进程无意义,真窗口到达是 P12, P5f 只在 server 侧落具名拒绝 Fatal{UnmigratedSurface, "AndroidNativeWindow@P12"} (P6-CONTRACT-DRAFT.md:43 已预写这个 Fatal 名)。
  • EGL 句柄值 → client 铸造的 token(DisplayToken/SurfaceToken/ContextToken, P6-CONTRACT-DRAFT.md:41 已裁定:client 铸造、从 1 起稠密、session 内不复用,server 持 token → native 映射,永远不见 client 的 EGL 值)。protocol.fbs:189-190 的 display/surface: ulong 已经就是 token 的宽度。

3. client 侧调用面(BackendObject_Remote.cpp)

九个 EGL 虚函数的路由(:196-310),核心纪律是"先转发,再跑基类"(:198-201 注释):

  • InitializeEGLDisplay(:203-207)→ forwarder #1。
  • CreateEGLWindowSurface(:209-225)→ 先 #12(:214,"唯一告诉 server 窗口是哪扇的途径"), 再 #2(:215),随后 DrainPublishedEvents()(:223)——surface-changed 事件走 SEG_EVENT。
  • ResizeEGLWindowSurface(:227-234)→ #3 + drain(:232)。
  • CreateEGLPbufferSurface(:236-243)→ #4 + drain(:241)。
  • MakeEGLCurrent(:245-276)→ #5,成功后 PumpControlPlane() + drain + 刷新格式能力 (:265-274,codex 12:把 R-12 的"第二次到达即失效"闭合在造成它的那次 make-current 上)。
  • SwapEGLBuffers(:278-287)→ 不转发:present 走记录(class-B Present emitter), 注释明确 #6 "在 P5 没有调用者,这是刻意的"(:284-285)。
  • SetEGLSwapInterval(:289-297)→ #7。覆写的理由:基类会调 class-C 的 SetSwapInterval 槽而 Fatal(:291-295 注释)。
  • ReleaseEGLSurface(:299-302)→ #8。
  • ReleaseEGLResources(:304-310)→ #9,按契约阻塞。
  • 另有 InitCapabilities(:106-142)调 #10(:125);InitWindowSurface(:144-149)是 纯 client no-op,不调 #11。

每个 forwarder 调用前的 fence:WaitForApplyBeforeEglForwarder(:189-193),对除 #6 外的每一次转发先 WaitForApplyToCatchUp。理由(:176-187 注释、CONTRACT-P5E §2.5 CONTRACT-P5E.md:177-180):邮箱在 drain 批之间被泵,run-ahead 下控制请求会插进两条 在飞记录之间。client 入口侧:九个虚函数由 MG_Impl/EGLImpl/EGLImpl.cpp 的 EGL 入口调 (如 eglMakeCurrent → :284、eglSwapBuffers → :178、eglSwapInterval → :448、eglTerminate 路径 → :328)。

P5f 处置建议:这个 fence 在 spawn 下仍然是必要的——socket 控制通道与 SEG_CMD 是两条 通道,控制帧一样可以超过在飞记录。fc 包的帧化必须保留"发帧前先等 appliedSeq 追上 LastPublishedSeq"这一步;它不属于 wire 格式而属于发送纪律,应写进 fc 的契约行。

4. schema 对照与 wire 帧设计草案

4.1 现有 schema(Protocol/protocol.fbs,已定但无人使用)

  • SurfaceOpKind(:166-175):None / InitializeDisplay / CreateWindowSurface / CreatePbufferSurface / ResizeWindowSurface / ReleaseSurface / MakeCurrent / ReleaseCurrent, 共 7 个有效枚举。
  • WindowKind(:177-184):None / AndroidNativeWindow / X11 / Win32Hwnd / Surfaceless / Pbuffer。
  • SurfaceOp(:186-196):seq, kind, display, surface, windowKind, nativeToken, width, height, swapInterval。nativeToken 注释自述"Android transfers the window out of band"。
  • SurfaceReply(:198-204):seq, ok, eglMajor, eglMinor, defaultFb。
  • 信封:CtrlMsg union(:273-284)已含 SurfaceOp/SurfaceReply;CtrlEnvelope(:286-288)。

4.2 映射表:12 forwarder → op

forwarder 映射 缺的枚举 / 字段
#1 InitializeEGLDisplay InitializeDisplay;reply 的 eglMajor/eglMinor 正是 major/minor out 参数的线形 无
#2 CreateEGLWindowSurface CreateWindowSurface + windowKind/nativeToken/width/height 无(P12 前 server 具名拒绝 AndroidNativeWindow)
#3 ResizeEGLWindowSurface ResizeWindowSurface + width/height 无
#4 CreateEGLPbufferSurface CreatePbufferSurface + width/height 无
#5 MakeEGLCurrent MakeCurrent;其 ClientRelease 臂(ClassifyEglMakeCurrent,:257-267)落 ReleaseCurrent SurfaceOp 缺 readSurface 与 context 两个字段:MakeCurrent 的语义单元是 (dpy, draw, read, ctx) 四元组,schema 只有一个 surface 且没有 context 字段
#6 SwapEGLBuffers 不配帧:present 走记录(BackendObject_Remote.cpp:278-287) —
#7 SetEGLSwapInterval 用 swapInterval 字段 缺 SetSwapInterval 枚举
#8 ReleaseEGLSurface ReleaseSurface 无
#9 ReleaseEGLResources 无参数,阻塞 缺 ReleaseResources 枚举
#10 InitCapabilities 不配 SurfaceOp:应答物是 CapsSnapshot 帧(server 已在 :999-1002 发布;P6-CONTRACT-DRAFT.md:88-89 同此裁定) —(但 client 需要一个"等下一帧 CapsSnapshot"的同步点,见 4.4)
#11 InitWindowSurface 不配帧:client 侧 no-op(:144-149),无调用者 —
#12 SetWindowHandle 用 windowKind/nativeToken/width/height 缺 SetWindowHandle 枚举

缺的三个枚举与 P6-SPAWN-PLAN.md:76 的预测("预期缺 SetSwapInterval/ReleaseResources/ SetWindowHandle")逐字吻合,本普查确认。9 个需映射的 forwarder = 7 个现有枚举 + 3 个新增 枚举 - 1 个复用(ReleaseCurrent 由 #5 的释放臂复用),与 P6-CONTRACT-DRAFT.md:87-90 的 "the remaining nine map onto the seven plus §1's additions" 一致。追加枚举必须 append-only (schema 自述纪律在 :268-272 的 union 注释与 CapsSnapshot 的 deprecated 先例 :115-127)。

另外发现 schema 缺的字段(P6 计划未预写):

  1. SurfaceOp 缺 readSurface(MakeCurrent 需要 draw≠read 的场景)。
  2. SurfaceOp 缺 context token 字段(MakeCurrent/ReleaseCurrent 都要指名 ctx)。
  3. WindowKind 与 WindowBackend(BackendObject.h:551-559:Android/X11/MetalLayer/Win32) 不互洽:WindowKind 缺 MetalLayer,多 Surfaceless/Pbuffer(那两个不是窗口后端)。 需要一张显式映射表,不能按整数值直译。

4.3 帧设计草案(逐 forwarder)

公共规则:seq 由 client 单调铸造,SurfaceReply.seq 回声;携带 ok=false 等价今天的 Bool false 或 void 丢弃结果;server 侧答不出来等价 ServerBackendOrNull() 为 null (:798)时的 MOBILEGL_ERR_NOT_INITIALIZED。token 化按 P6-CONTRACT-DRAFT.md:41。

op 帧 载荷 reply 阻塞性
InitializeDisplay display(token) ok + eglMajor/eglMinor 阻塞(今天返回 Bool)
CreateWindowSurface surface(token) + windowKind + nativeToken + width/height ok + defaultFb(见 4.4 的路线冲突) 阻塞。可并入 #12(见下)
ResizeWindowSurface surface + width/height ok + defaultFb 阻塞
CreatePbufferSurface surface + width/height(windowKind=Pbuffer) ok 阻塞
MakeCurrent display + surface(draw) + 新字段 readSurface + 新字段 context ok 阻塞
ReleaseCurrent display(其余 token 为 0 合法,P6-CONTRACT-DRAFT.md:41 已许) ok 阻塞(今天经同一个 Bool 通道)
SetSwapInterval(新枚举) surface + swapInterval 可无 reply 可改 fire-and-forget:今天虽经阻塞邮箱但结果被丢弃(:945)
ReleaseSurface surface 可无 reply 可 fire-and-forget(:964 丢结果);但须保序
ReleaseResources(新枚举) 无 ok 必须阻塞:ServerLoop.cpp:967-973 的契约,Destroy() 在它返回后立即走
SetWindowHandle(新枚举) windowKind + nativeToken + width/height 可无 reply 可 fire-and-forget,但必须在后续 CreateWindowSurface 之前生效——同一有序通道即保证

两个可合并项(供 fc 包定夺,本普查只摆出):① CreateWindowSurface 的 schema 字段已自带 windowKind/nativeToken/width/height,#12 可以并进 #2,SetWindowHandle 枚举只在"先给窗口 后建 surface"的时序必须拆开时才需要——今天的代码就是拆开的(:214-215),保守做法是两者都 落;② ReleaseCurrent 也可用 MakeCurrent + 全 0 token 表达,现有枚举分开更好,保留。

4.4 三个与 schema 相邻的副作用,必须在 fc 的契约里点名

  1. caps 重发布:#5 的真绑定臂(:899-913)和 #10(:999-1002)都会让 server 主动推一帧 CapsSnapshot。跨进程后这是 server 自发帧;client 今天的吸收点(RPC 返回后 PumpControlPlane(),:266-272 与 :130)天然对应"reply 之后泵控制通道"。schema 无需加 字段,但 fc 应写明"MakeCurrent reply 到达后、下一帧 CapsSnapshot 到达前"的语义窗口。
  2. SurfaceReply.defaultFb 与 SEG_EVENT 是重复路线:P5c 已把默认 FBO 形状做成 surface-changed 事件,client 在 create/resize reply 后 DrainPublishedEvents() (:223/:232/:241)。defaultFb 字段(:154-160, :203)若启用是第二载体。建议 fc 二选一 并写明,不要两个都活(CapsSnapshot 的 deprecated 字段教训,:101-127,就是两种拼法 并存的代价)。
  3. server 死亡的具名化:今天的 Fatal{ApplyThreadNotRunning}(:670-676)在 spawn 下 对应 FatalCode::ServerCrashed(:240 已分配,P6-CONTRACT-DRAFT.md:44 已点名 P6 是 第一个能产生它的阶段)。fc 的 client 侧发送路径要把"发不出去/等不到 reply"接到这个码上。

5. 其他发现

  • F8:两个 forwarder 没有生产调用者。 ServerSwapEGLBuffers 只有单测调用 (MG_Test/Wire/ServerLoopTest.cpp:1549、:1568);ServerInitWindowSurface 全树无调用者 (client 的 InitWindowSurface 是纯 no-op,BackendObject_Remote.cpp:144-149)。 处置建议:fc 不为它们配帧(§4.2 已按此映射);ServerLoop.h:469/:474 的声明与 实现在 spawn 世界只剩 inproc 测试价值,fc 可具名删除或标注 inproc-only。
  • F9:文档行号漂移。 ROADMAP.md:71(P5c 审计 G4 行)引 ServerLoop.cpp:543-598, 当前 HEAD 的邮箱是 :533-568(PumpControlRequest)+ :645-707(RunOnApplyThread); CONTRACT-P5C.md:537 把控制面记给 P6,P5f 计划 §2.3(P5F-WIRE-COMPLETENESS.md:181-186) 已改记 P5f 的包 fc(P5F-WIRE-COMPLETENESS.md:252)。处置建议:fc 落地时顺手刷新 ROADMAP G4 行的行号与去向。
  • F10:ServerLoop.h:436 与 ServerLoop.cpp:968 引的 EGLImpl.cpp 行号(:284/:326) 与当前 MG_Impl/EGLImpl/EGLImpl.cpp 基本相符(:284 是 eglMakeCurrent;ReleaseEGLResources 实际在 :328)。仅为记录,不构成缺陷。

6. 结论汇总

  1. 十二个 forwarder 的过线载荷里,真正"不可序列化"的只有两类:trampoline 函数指针 (操作身份 → SurfaceOp.kind)和三处 client 栈/堆指针(#1 的 major/minor → reply 字段; #2/#12 的 WindowHandle* → windowKind+nativeToken 值字段,Android 的 ANativeWindow* 归 P12,P5f 只落具名拒绝)。其余参数全是值或 client 句柄值(→ token)。
  2. schema 缺口清点确认:缺 3 个枚举(SetSwapInterval / ReleaseResources / SetWindowHandle,与 P6-SPAWN-PLAN 的预测逐字一致)+ 缺 2 个字段(MakeCurrent 的 readSurface 与 context token)+ WindowKind/WindowBackend 需要显式映射表。
  3. #6、#10、#11 不配 SurfaceOp 帧:present 走记录、InitCapabilities 走 CapsSnapshot、 InitWindowSurface 无调用者。需要帧化的是 9 个 forwarder、10 种 op(含 ReleaseCurrent)。