mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-16 06:23:23 +09:00
Two things land together: the plan and contract draft P6 needs before it starts, and the
working notes that until now existed on exactly one machine.
1. P6's plan and contract draft (neither started; not one line of code)
docs/Disaggregated/P6-SPAWN-PLAN.md the package plan
docs/Disaggregated/P6-CONTRACT-DRAFT.md the contract draft (moves to
MG_Remote/CONTRACT-P6.md when c6 lands)
The three findings that mattered, all written into the documents:
a. P6 is SMALLER than its roadmap cell suggests. The transport primitives are already in
the tree and already tested: SocketDoorbell including hangup detection and the two
lost-wakeup fences (Doorbell.cpp:211-360, four cases in FdPassingTest), SCM_RIGHTS fd
passing, ShmSegment::Adopt, and the SurfaceOp / WindowKind schema already defined in
protocol.fbs. inproc uses a real second shm mapping rather than an alias, and
SessionRings.h:22-28 says that was done so today is not the first time it runs. P6 is
assembly plus a control plane plus a process, not "write a socket transport".
b. The evidence for "P6 is only a transport swap" has EXPIRED. It was bought by P5c, whose
audit found 59 direct memory accesses in a tree that already looked clean. P5e changed
the tree again. So the first package, a6, is a read-only audit that writes no code, and
every contract row that depends on it is marked PENDING a6 rather than decided in
advance.
c. P6 does not get a game running on the phone. ANativeWindow* is meaningless in the child,
and protocol.fbs:186-196's own comment says Android transfers the window out of band -
that band is P12. P6 lands pbuffer / surfaceless / offscreen, which is what HeadlessGL,
the split-equivalent lane and trace replay need, and which is exactly what P6's own exit
gate measures. A real window reaching the server is a named refusal. This gets its own
section because "P6 is done" would otherwise read as "the game runs now".
The contract adds rule G: a role may not name a process-local handle. It extends P5c's
rule E (a role may not name the other role's memory) from memory to identity - EGLDisplay /
EGLSurface / EGLContext / ANativeWindow* / fd number / pid / mapped address either cross as
a token this contract defines or they do not cross, and a descriptor crosses only through
SCM_RIGHTS, where the kernel renumbers it.
What P5e left for P6 gets its own section. The latch semantics are already designed
(ARCHITECTURE.md:504) and are copied unchanged. What is new is that after run-ahead the
client no longer waits while the peer process can DIE: the client holds records that were
published and will never be applied, and Android's freezer suspends the child without
closing anything. So slow and dead must be distinguishable WITHOUT a timeout threshold.
That is P6's only new design; everything else is carriage. It shares a predicate with E1's
outstanding re-specification (ID-122), and the two should be designed together.
Two gates carried over from P5e's lessons: G2 requires integration-spawn's test-name set to
match integration-split's name for name, because a differing set means the lane is quietly
running less; and the arm-proof gate requires every entry to record the child pid and
transport=spawn in its OWN private log - ID-124 established that this kind of false green
really happens, and a spawn lane that actually ran monolith would pass every other gate.
S2's shape must be settled before it is written, and the document says why: E1 is the
cautionary case.
2. The working notes move into the repository (docs/Disaggregated/notes/, 415 .md files)
ROADMAP / CURRENT_STAGE_PROGRESS / MEASUREMENTS cite ruling numbers, audit line numbers and
the provenance of gate figures from ~/w7/notes/, a directory that exists on one machine and
is under no version control. A record cited hundreds of times that lives only locally is not
a record, so it moves in whole, with its directory structure intact.
Only .md moves. Scripts, run logs, census and gate result JSON, patches, APKs and nm dumps
stay where they are: they are evidence DATA rather than documents - large, regenerated per
run, and mostly carrying absolute paths and device serials that mean something only on the
machine that produced them. The mapping and this split are written in
docs/Disaggregated/notes/README.md.
Two exceptions are recorded there too: P6's BRIEF has been promoted to a first-class
document and keeps no copy under notes/, so the two cannot drift apart; and recovered/ holds
material salvaged from failed workflow runs, which may duplicate the like-named phase
directories and is kept because a few of those were the only complete version at the time.
CI's citation lint takes docs/Disaggregated/*.md only (non-recursive), and notes/ is
deliberately outside it: those notes' file:line citations were written against their own
code heads, and resolving them against today's HEAD would fail wholesale - which is not an
error, it is their correct state as a historical record. To check one, use the revision it
declares (--rev).
3. Inconsistencies fixed in passing
- README.md's status line still said P5e was in progress while the stage table already said
closed.
- CURRENT_STAGE_PROGRESS's next-step item 1 still listed the exit gate as pending, though
(a) and (b) were run in the previous commit; rewritten to closed plus the one item that
actually remains (E1).
- ROADMAP's P6 row still had the pre-phase plan in three of its cells; it now carries the
package order, the scope boundary and the two new gates.
Checked with check_doc_citations.py: 125 citations across docs/Disaggregated/*.md, 0 problems.
602 lines
36 KiB
Markdown
602 lines
36 KiB
Markdown
# P5 scout — backend installation, `BackendObject_Remote`, and the thread/EGL question
|
||
|
||
Tree: `/home/swung/w7/pipe` @ `a29807cc` (P4a landed). READ-ONLY survey; nothing built, nothing run.
|
||
All line numbers are from that commit. Where I am guessing I say "guess".
|
||
|
||
---
|
||
|
||
## 0. Headline for the brief writer
|
||
|
||
Three things, before anything else:
|
||
|
||
1. **The two function-pointer tables are dead code today.** `gMGPipeScreen` / `gMGPipeContext`
|
||
(`MobileGL/MG_Pipe/MGPipe.h:137-138`) are defined, zero-initialised, and **never written by
|
||
anyone**; the generated thunks `MGP_*` (`MG_Pipe/generated/PipeThunks.inc:20-302`) that read
|
||
them are **never called by anyone**. The live P2..P4a boundary is a set of *direct function
|
||
calls* from `MG_Impl/Pipe/*` into `MGPipeApply*` (`MG_Pipe/PipeApply.cpp`). So "install
|
||
`BackendObject_Remote` in `Init.cpp`" **by itself redirects nothing**: P5 must also decide how
|
||
the ~50 `MGPipeApply*` call sites get routed through a table. See §2.
|
||
2. **The GL context is current on the app thread today, and moving it to `mgl-srv-apply` is not
|
||
a no-op**: `IsBackendContextCurrentOnThisThread()` (`DirectGLES.cpp:12002`) gates **16 call
|
||
sites** in `DirectGLES.cpp` and, via `CanTouchGLNow()` (`Managers.cpp:928`), **19 more** in
|
||
`Managers.cpp`. Every one of them silently DEGRADES (returns null / "already signaled" /
|
||
defers the upload) when called from a non-owner thread. Under inproc those calls arrive on
|
||
the *app* thread unless P5 moves them across the wire first. See §3 — this is the risk.
|
||
3. **`MOBILEGL_TRANSPORT` does not exist in any form.** No `MG_Config::Transport`, no enum, no
|
||
parse. `grep -rn "MOBILEGL_TRANSPORT\|TransportKind\|Transport" MobileGL/Config.h
|
||
MobileGL/ConfigLoader.cpp` → **zero hits**. Three of the P5 exit-gate artifacts
|
||
(`PersistentCoherentMapScenario`, a `Triangle` scenario, `DirectGLES.Split.*` lanes) also do
|
||
not exist yet. See §4, §6.
|
||
|
||
---
|
||
|
||
## 1. The installation path today
|
||
|
||
### 1.1 Startup chain
|
||
|
||
`MobileGL::Initialize()` — `MobileGL/Init.cpp:101-138`:
|
||
|
||
```
|
||
Init.cpp:107 MG_Util::Debug::InitFile()
|
||
Init.cpp:109 MG_ConfigLoader::Init() <- MOBILEGL_TRANSPORT would be parsed here
|
||
Init.cpp:114 MG_Util::PipeStats::Init()
|
||
Init.cpp:115 MG_State::Init()
|
||
Init.cpp:117 MG_Backend::Init() <- THE HOOK POINT
|
||
Init.cpp:119 MG_Impl::Init()
|
||
Init.cpp:121 glslang::InitializeProcess()
|
||
```
|
||
|
||
`Initialize()` is reached lazily from the first EGL entry point:
|
||
`MG_Impl/EGLImpl/EGLImpl.cpp:35-38` (`GetStateEnsureInitialized()` → `MobileGL::EnsureInitialized()`).
|
||
|
||
### 1.2 `MG_Backend::Init()` — the exact hook
|
||
|
||
`MobileGL/MG_Backend/Init.cpp`:
|
||
|
||
```
|
||
48 void Init() {
|
||
49 MGLOG_D("Initializing MobileGL Backend...");
|
||
50
|
||
51 switch (MG_Config::ActiveBackendType) { <- INSERT THE #if HERE
|
||
52 case BackendType::DirectGLES:
|
||
53 pActiveBackendObject = MakeUnique<DirectGLES::BackendObject_DirectGLES>();
|
||
54 break;
|
||
55 case BackendType::DirectVulkan:
|
||
56 pActiveBackendObject = MakeUnique<DirectVulkan::BackendObject_DirectVulkan>();
|
||
57 break;
|
||
58 case BackendType::Unknown:
|
||
59 default:
|
||
61 pActiveBackendObject = nullptr;
|
||
62 }
|
||
63
|
||
64 Bool result = InitSpecificBackendLibs();
|
||
...
|
||
70 }
|
||
```
|
||
|
||
**The single hook P5 is supposed to add goes at `MobileGL/MG_Backend/Init.cpp:50`** (immediately
|
||
before the `switch` at :51), matching `docs/Disaggregated/ARCHITECTURE.md:29`
|
||
("唯一 hook 点是 `MG_Backend::Init()`…`MG_Config::Transport != Monolith` 时装
|
||
`MG_Remote::BackendObject_Remote`,否则走今天的 `switch`"). ARCHITECTURE says P0's `Init.cpp`
|
||
does not contain it — confirmed, it does not.
|
||
|
||
Note the second half of the same file, which P5 also has to satisfy:
|
||
|
||
```
|
||
38 Bool InitSpecificBackendLibs() {
|
||
43 pActiveBackendObject->Initialize();
|
||
44 gBackendFunctionsTable = pActiveBackendObject->GetBackendFunctions();
|
||
45 return true;
|
||
46 }
|
||
```
|
||
|
||
`gBackendFunctionsTable` is populated *from the backend object* — so a `BackendObject_Remote`
|
||
whose `GetBackendFunctions()` returns an all-null table would turn 91 `MG_Impl/GLImpl` call sites
|
||
into null-pointer calls. (Count: `grep -rn gBackendFunctionsTable MobileGL/` → 91 in
|
||
`MG_Impl/GLImpl`, 2 in `MG_Impl/Pipe`, plus tests.) This is a real P5 item that ROADMAP's P5 row
|
||
does not name.
|
||
|
||
### 1.3 The globals
|
||
|
||
```
|
||
MobileGL/GlobalObjects.cpp:23 UniquePtr<BackendObject>& pActiveBackendObject = *new UniquePtr<BackendObject>();
|
||
MobileGL/GlobalObjects.cpp:24 GlobalBackendFunctionsTable gBackendFunctionsTable;
|
||
MobileGL/MG_Backend/BackendObjects.h:16-17 the two extern declarations
|
||
MobileGL/Config.h:21 extern BackendType ActiveBackendType; (a `Transport` sibling would go here)
|
||
```
|
||
|
||
Frontend readers of `pActiveBackendObject` outside `MG_Backend/` (the surface a role-shim must
|
||
cover) are few: `MG_Impl/EGLImpl/EGLImpl.cpp` (:40-46, :150, :263, :275, :284, :312, :326, :346,
|
||
:448), `MG_State/GLState/Core.cpp:34`, `MG_IntegrationTest/Harness/BackendCapsPeek.cpp:17,29`.
|
||
Everything else is tests.
|
||
|
||
### 1.4 The two tables — declared, generated, never installed
|
||
|
||
```
|
||
MG_Pipe/generated/PipeTables.inc:17 struct MGPipeScreen { 11 fn ptrs }
|
||
MG_Pipe/generated/PipeTables.inc:33 struct MGPipeContext { 60 fn ptrs }
|
||
MG_Pipe/generated/PipeTables.inc:94-96 kMGPipeScreenCallCount=11, kMGPipeContextCallCount=60, total 71
|
||
MG_Pipe/MGPipe.h:137-138 inline MGPipeScreen gMGPipeScreen{}; inline MGPipeContext gMGPipeContext{};
|
||
MG_Pipe/generated/PipeThunks.inc:20+ inline MGP_GetCaps(...) { gMGPipeScreen.GetCaps(...); } ... (71 thunks)
|
||
```
|
||
|
||
Generator: `scripts/gen_pipe.py` from `MG_Pipe/PipeCalls.def`. Consumer of the *catalogue*:
|
||
`MG_Test/Pipe/PipeCatalogueTest.cpp`.
|
||
|
||
**Verified absence** (WSL grep over the whole tree): the only occurrences of `gMGPipeScreen` /
|
||
`gMGPipeContext` are the two definitions in `MGPipe.h` and the 71 reads inside `PipeThunks.inc`.
|
||
No assignment, no `InstallPipe`-style function, no `= {…}` aggregate anywhere. And
|
||
`grep -rn "MGP_[A-Z][A-Za-z]*(" MobileGL/ | grep -v generated/` returns **nothing** — the thunks
|
||
have no callers. (The only "install" named in the tree is
|
||
`MGPipeInstallClientResourceCallbacks()`, `MG_Impl/Pipe/ResourceTracker.h:606`, called from
|
||
`MG_Impl/Pipe/PipeFill.cpp:628` — that is the *callback* direction, §2.3, not these tables.)
|
||
|
||
---
|
||
|
||
## 2. What the boundary actually is today, and what `BackendObject_Remote` must replace
|
||
|
||
### 2.1 The live path (P2..P4a)
|
||
|
||
```
|
||
app GL call
|
||
-> MG_Impl/GLImpl/* (validate)
|
||
-> MG_Impl/Pipe/{PipeFill.cpp, Tracker.h, CsoCache.h, TextureEmit.h, SamplerEmit.h,
|
||
FramebufferEmit.h, ProgramEmit.h, VertexInputEmit.h, ResourceTracker.h}
|
||
-> MGPipeApply*(...) DIRECT CALL, MG_Pipe/PipeApply.{h,cpp}
|
||
- writes MG_Pipe::gPipeInputs (defined MG_Backend/MGPipe/PipeInputs.h:706)
|
||
- dispatches object/resource work through MGPipeResourceOps (PipeApply.cpp:402, g_resourceOps)
|
||
-> backend reads gPipeInputs at draw time (MGB_CTX, ARCHITECTURE.md:344)
|
||
```
|
||
|
||
Representative `MGPipeApply*` call sites (all direct, not through a table):
|
||
`MG_Impl/Pipe/PipeFill.cpp:645, 678, 691, 705, 727, 747, 761, 784, 799, 839, 1403, 1424, 1465,
|
||
1502, 1523, 2013, 2026, 2115, 2184`; `MG_Impl/Pipe/CsoCache.h:157, 178`;
|
||
`MG_Impl/Pipe/FramebufferEmit.h:459`; `MG_Impl/Pipe/VertexInputEmit.h:161, 181, 245, 270, 399`.
|
||
Declarations: `MG_Pipe/PipeApply.h:740-1052` (≈45 entries).
|
||
|
||
`MG_Pipe/PipeApply.h:15-17` states the intent explicitly: *"the in-process applier: the SERVER
|
||
half of the calls P2 emits. Under split this file is `MG_Remote/Server/PipeApplier`"*.
|
||
|
||
**So the two readings P5 must choose between, and I cannot settle from the tree alone:**
|
||
|
||
- **(A) Retrofit the tables.** Turn each `MGPipeApply*` into `MGP_*` (the generated thunk), and
|
||
have monolith `Init` install a table of `&MGPipeApply*` while remote `Init` installs a table of
|
||
emitters. Cost: touching every call site listed above; benefit: exactly the shape
|
||
ARCHITECTURE §1.2 describes, and `MG_Test` can keep mocking by table substitution.
|
||
- **(B) Branch inside the applier.** Leave the call sites alone and put the transport fork inside
|
||
`PipeApply.cpp` / a thin `PipeEmitter`. Cheaper, but then `gMGPipeScreen`/`gMGPipeContext`
|
||
remain dead and the "one hook in `Init.cpp`" claim (`ARCHITECTURE.md:29`) becomes false.
|
||
|
||
*Evidence that would settle it:* whether any P5 gate asserts on the tables (nothing in the tree
|
||
does today), and whether the "B 门符号" purity gate
|
||
(`ARCHITECTURE.md:516`, `nm --undefined-only libMobileGLServer.so`) can pass under (B) — under (B)
|
||
the server links `PipeApply.cpp`, which includes `MG_Impl/Pipe` headers only in the client
|
||
direction, so I *think* it still can (**guess**).
|
||
|
||
### 2.2 `MGPipeResourceOps` — the precedent for how a backend registers a table
|
||
|
||
`MG_Pipe/PipeApply.h:85-102`:
|
||
```
|
||
85 struct MGPipeResourceOps { Create, Respecify, SubData, SubDataResident, FlushRange,
|
||
Readback, Destroy, MapPersistent, UnmapPersistent }; (9 members)
|
||
101 void MGPipeSetResourceOps(const MGPipeResourceOps* ops);
|
||
102 const MGPipeResourceOps* MGPipeGetResourceOps();
|
||
```
|
||
Storage `MG_Pipe/PipeApply.cpp:402` (`g_resourceOps`), setters at `:1094-1095`.
|
||
DirectGLES's table: `MG_Backend/DirectGLES/Managers.cpp:2298-2308` (`g_glesResourceOps`),
|
||
registered/unregistered around `RegisterBufferBackendOps()` (`DirectGLES.cpp:11933`,
|
||
`Managers.cpp:2584`). **DirectVulkan registers none** — that is the "consumer gate" that makes
|
||
the four P4a families emit nothing on Magma (`ARCHITECTURE.md:213-…`, and
|
||
`MG_IntegrationTest/Scenarios/ObjectSubsystemControlScenario.cpp:119, 784, 857`).
|
||
This is the pattern P5 should copy for the server's op tables; it is also the thing that will
|
||
make a DirectVulkan split lane vacuous.
|
||
|
||
### 2.3 `BackendObject` — the interface `BackendObject_Remote` must implement
|
||
|
||
`MobileGL/MG_Backend/BackendObject.h:567-644`. Lifetime: owned by
|
||
`UniquePtr<BackendObject>& pActiveBackendObject`; constructed in `MG_Backend/Init.cpp:53/56`,
|
||
destroyed at `MobileGL/Init.cpp:68` (`pActiveBackendObject.reset()`).
|
||
|
||
**8 pure virtuals — every one must be answered by the remote object:**
|
||
|
||
| line | member | what a remote must do |
|
||
|---|---|---|
|
||
| 572 | `void Initialize() = 0` | bring up transport + handshake (no GL) |
|
||
| 573 | `Bool InitCapabilities() = 0` | **this is the `MGPCaps` snapshot round trip** |
|
||
| 574 | `Bool InitWindowSurface() = 0` | ship the native token; `ARCHITECTURE.md:559-563` |
|
||
| 590 | `const RendererInfo& GetRendererInfo() const = 0` | from the caps snapshot; must return a **reference**, so the remote has to own a `RendererInfo` |
|
||
| 591 | `String GetBackendAPIVersionString() const = 0` | from snapshot |
|
||
| 592 | `const GlobalBackendFunctionsTable& GetBackendFunctions() const = 0` | see §1.2 — 91 `MG_Impl/GLImpl` sites read the table this returns |
|
||
| 593 | `const DynamicBackendParameters& GetDynamicParameters() const = 0` | from snapshot; struct is `BackendObject.h:297-…`, ~70 scalars incl. `MaxComputeWorkGroupCount[3]` |
|
||
| 595 | `BackendType GetBackendType() const = 0` | must answer the **server's** backend, not "Remote" — many frontend branches switch on it (**guess**: returning a new enumerator would break them) |
|
||
|
||
**9 EGL-lifecycle virtuals with base implementations** (`BackendObject.cpp:168-494`), the
|
||
"EGL 生命周期 8 项" of `ARCHITECTURE.md:19` (I count nine, not eight — `ResizeEGLWindowSurface`
|
||
is the likely uncounted one):
|
||
|
||
`:576 InitializeEGLDisplay`, `:577 CreateEGLWindowSurface`, `:578 ResizeEGLWindowSurface`,
|
||
`:579 CreateEGLPbufferSurface`, `:580 MakeEGLCurrent`, `:581 SwapEGLBuffers`,
|
||
`:584 SetEGLSwapInterval`, `:585 ReleaseEGLSurface`, `:586 ReleaseEGLResources`.
|
||
Plus 2 protected: `:624 InitPbufferSurface`, `:625 OnEGLSurfaceReleased`.
|
||
|
||
Also inherited, and **remote-hostile**, is the base class's own state
|
||
(`BackendObject.h:627-638`): `m_eglStateMutex` (recursive), `m_formatCapabilities`
|
||
(`FormatCapabilityCache` — a full per-target × per-format matrix, populated by GL probes in
|
||
`BackendObject_DirectGLES.cpp:855-866`), `m_eglCurrentThreads`, `m_eglSurfaces`.
|
||
`GetFormatCapabilities()` is **non-virtual** (`BackendObject.h:589`, impl
|
||
`BackendObject.cpp:478`), so a remote object must *fill* `m_formatCapabilities` from the caps
|
||
snapshot rather than override the accessor. Reader:
|
||
`MG_Backend/DirectGLES/DirectGLES.cpp:12446` (`ClampSamplesToBackendSupport`) and
|
||
`MG_IntegrationTest/Harness/BackendCapsPeek.cpp:29`.
|
||
|
||
Two base behaviours P5 must not lose:
|
||
- `BackendObject::MakeEGLCurrent` calls `InitCapabilities()` on first make-current per surface
|
||
(`BackendObject.cpp:341-347`) — i.e. **the caps snapshot's natural timing is already there**.
|
||
- `ActivateEGLSurface` clears `m_eglCurrentThreads` and resets
|
||
`m_backendCapabilitiesInitialized` (`BackendObject.cpp:301-302`).
|
||
|
||
---
|
||
|
||
## 3. THE THREAD QUESTION
|
||
|
||
### 3.1 Which thread calls the backend in monolith
|
||
|
||
**The app's calling thread, always. There is no render thread.**
|
||
Every `MG_Impl/GLImpl` entry point runs `gBackendFunctionsTable.GL.*` inline on the thread that
|
||
made the GL call (91 sites). `grep -rn "std::thread\b" MobileGL/` outside tests finds **no
|
||
thread creation at all** except `MG_Util/Async/ShaderCompilePool.cpp` — and the compile pool is
|
||
explicitly forbidden from touching the backend
|
||
(`MG_State/GLState/ProgramState/ShaderCompileTask.cpp:239`,
|
||
`ProgramLinkTask.cpp:564`, `ProgramSpirvTask.cpp:94`: *"no GL/EGL call, no
|
||
`pActiveBackendObject` read"*). So today: **one thread, the app's, does frontend + backend + the
|
||
driver call.**
|
||
|
||
### 3.2 Which thread owns the EGL/GL context
|
||
|
||
`DirectGLES` keeps **one process-global ES context**, and its owner is a single global slot:
|
||
|
||
```
|
||
DirectGLES.cpp:11856-11864 comment: "The single backend ES context migrates between app threads
|
||
(FCL/pojav-style LWJGL hands the EGL context from JVM thread to JVM
|
||
thread). Ownership must live in ONE global slot..."
|
||
DirectGLES.cpp:11865 std::atomic<std::thread::id> g_backendContextOwnerThread{};
|
||
DirectGLES.cpp:11919-11955 Bool MakeCurrent() — the real eglMakeCurrent at :11925
|
||
DirectGLES.cpp:11931 g_backendContextOwnerThread.store(std::this_thread::get_id(), release)
|
||
DirectGLES.cpp:11958-11975 Bool ReleaseCurrent() — eglMakeCurrent(NO_SURFACE…) at :11964,
|
||
owner cleared at :11961 / :11973
|
||
DirectGLES.cpp:12002-12025 Bool IsBackendContextCurrentOnThisThread()
|
||
DirectGLES.cpp:11980-11996 the EGL verification stamp (g_eglVerifiedFrameSerial /
|
||
g_eglVerifiedContextGeneration) — re-verifies against
|
||
eglGetCurrentContext() once per (frame, context generation).
|
||
Comment: glvnd's eglGetCurrentContext measured at 16% of the render
|
||
thread, hence the stamp.
|
||
```
|
||
|
||
**Is it ever current on the app thread today? Yes — it is only ever current on an app thread.**
|
||
The chain runs entirely on the caller:
|
||
|
||
```
|
||
app eglMakeCurrent
|
||
-> MG_Impl/EGLImpl/EGLImpl.cpp:240 MakeCurrent(...) [takes EGLOperationMutex, :241]
|
||
-> EGLImpl.cpp:284 backendObject->MakeEGLCurrent(dpy, draw, read, ctx)
|
||
-> MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp:935 MakeEGLCurrent
|
||
-> BackendObject_DirectGLES.cpp:963 DirectGLES::MakeCurrent()
|
||
-> DirectGLES.cpp:11925 g_EGLFuncs.eglMakeCurrent(...) <- ON THE APP THREAD
|
||
-> DirectGLES.cpp:11931 owner := this thread
|
||
-> DirectGLES.cpp:11933-11953 BufferImpl::RegisterBufferBackendOps() + SIX cache
|
||
invalidations (broadcast memo, indexed buffer bindings,
|
||
pixel buffer bindings, framebuffer bindings + handle arm
|
||
memos, pack state, synced render state) + ApplyRequestedSwapInterval
|
||
```
|
||
|
||
Line 11933-11953 is what `ARCHITECTURE.md:535` calls "`MakeCurrent` 的缓存失效风暴" — under
|
||
split it runs **once at startup on `mgl-srv-apply`** instead of on every app-thread migration.
|
||
|
||
`BackendObject` (the base) separately keeps a *multi-thread* map
|
||
`UnorderedMap<std::thread::id, EGLCurrentState> m_eglCurrentThreads` (`BackendObject.h:637`,
|
||
written `BackendObject.cpp:350-355`) — so the frontend's EGL bookkeeping already models "many
|
||
threads, one context", while DirectGLES's driver-side ownership is a single slot. These two are
|
||
consistent today only because the same thread does both.
|
||
|
||
### 3.3 What breaks if two threads touch it
|
||
|
||
Nothing crashes; **everything silently degrades**, which is worse. A non-owner thread makes
|
||
`IsBackendContextCurrentOnThisThread()` return false at `DirectGLES.cpp:12006`, and then:
|
||
|
||
`DirectGLES.cpp` — **16 guarded sites**:
|
||
|
||
| line | function | degraded behaviour |
|
||
|---|---|---|
|
||
| 12034 | `FenceSync` | returns `nullptr` → frontend substitutes an always-signaled sync |
|
||
| 12058 | `ClientWaitSync` | returns `GL_ALREADY_SIGNALED` |
|
||
| 12069 | `WaitSync` (server-side wait) | no-op |
|
||
| 12081 | `DeleteSync` | leaks the native `GLsync` (wrapper only freed) |
|
||
| 12093 | `GetSyncStatus` | reports signaled |
|
||
| 12197 | `BeginTimerQuery`-ish | returns null handle |
|
||
| 12212 | end-query | skipped |
|
||
| 12221 | `QueryCounterTimestamp` | null handle |
|
||
| 12242 | `BeginCoreQuery` | null handle |
|
||
| 12257 | end core query | skipped |
|
||
| 12299 | `glGetQueryObjectuiv` read | falls back |
|
||
| 12326 | query availability | falls back |
|
||
| 12373 | query delete | wrapper only |
|
||
| 12385 | `GetGpuTimestampNs` | returns 0 |
|
||
| 12399 | `WaitForFrameSerialCompleted` | returns false |
|
||
| 12428 | `Present()`'s frame fence | **no fence is created for the frame** → the buffer pool's recycle watermark never advances |
|
||
|
||
`Managers.cpp` via `CanTouchGLNow()` (`:928-930`) — **19 sites**: `1494` (persistent-map
|
||
acquire), `1589`, `1610`, `1632`, `1681`, `1747`, `1793`, `1919`, `1939`, `1986`, `2038`, `2087`,
|
||
`2170` (the second persistent-map tier), `2602` (`ProcessDeferredBufferReleases`), `3293`,
|
||
`3428`. These are the buffer paths: a false answer means the write is **deferred into
|
||
`pendingRanges` / `pendingResidentWrites`** and applied later — correct only if a later call on
|
||
the owner thread drains them.
|
||
|
||
Concretely, the single most dangerous one for the P5 exit gate:
|
||
`Managers.cpp:1494` and `:2170` are the `glBufferStorageEXT` + `glMapBufferRange` persistent-map
|
||
acquisitions, and `Ops_H_MapPersistentTracked` (`Managers.cpp:2306`) is the op behind
|
||
`MGPipeApplyMapPersistent` (`PipeApply.cpp:1918-1919`, called from `PipeFill.cpp:784`). If the
|
||
app thread calls it while the apply thread owns the context, it **declines** and the
|
||
`PersistentCoherentMapScenario` gate is unreachable. `ARCHITECTURE.md:502` already lists
|
||
`MapPersistent` (T1, once per storage definition) as one of the unavoidable blocking round trips
|
||
— i.e. the design says it must cross the wire, not be called locally.
|
||
|
||
### 3.4 What ARCHITECTURE.md says
|
||
|
||
- `ARCHITECTURE.md:535` (§14 Present、线程与帧节奏): *client: **v1 不加线程**, encoding happens on
|
||
the GL thread and writes the ring directly (the frontend is already a per-context single-thread
|
||
contract); foreign-thread sync/query reads are answered lock-free from `RingControl`; the few
|
||
that must emit take `ctrlMutex` and go over CTRL as an `AuxRequest` (the SPSC ring allows no
|
||
second producer); `ShaderCompilePool` stays as-is on the client.*
|
||
*server: `mgl-srv-io` (asio, framing, `SCM_RIGHTS`, doorbell, CTRL RPC), `mgl-srv-apply`
|
||
(**终身持有原生 context** — `g_backendContextOwnerThread` is written **once**, the MakeCurrent
|
||
invalidation storm becomes a one-off at startup, the per-frame EGL re-verification is
|
||
permanently true, **off-thread degradation disappears**), optional `mgl-srv-dec`.*
|
||
- `ARCHITECTURE.md:536`: bind `mgl-srv-apply` to a big core via `MOBILEGL_IPC_SERVER_AFFINITY`
|
||
(default auto), reusing `ShaderCompilePool`'s big-core probe.
|
||
- `ARCHITECTURE.md:26` (§1.3): inproc *is* the monolith's render thread — "把 `PrepareForDraw`
|
||
与驱动调用搬离 GL 线程,是本项目手上最大的单一 CPU 杠杆".
|
||
- `ARCHITECTURE.md:581` (§16): `MOBILEGL_BUILD_DISAGGREGATED_INPROC` **does not exist yet**; it
|
||
implies `MOBILEGL_BUILD_DISAGGREGATED` and adds a **role-isolation shim**, because in inproc
|
||
*both roles live in one process*. MGPipe cuts the process globals needing a role split from
|
||
four (`pGLContext`, `gBackendFunctionsTable`, `pActiveBackendObject`,
|
||
`pDefaultFramebufferInfo`) to **two** (the pipe tables and `pActiveBackendObject`).
|
||
|
||
**The unresolved inproc problem this creates, and P5 owns it:** `pActiveBackendObject` is ONE
|
||
global (`GlobalObjects.cpp:23`) and `MG_Backend::Init()` writes it once. In inproc the *client*
|
||
role needs `BackendObject_Remote` there and the *server* role needs the real
|
||
`BackendObject_DirectGLES` there, in the same address space. The Init.cpp hook as written in
|
||
`ARCHITECTURE.md:29` produces only one of the two. Two readings:
|
||
- **(i)** the server role never reads `pActiveBackendObject` at all, so the server side can hold
|
||
its `BackendObject_DirectGLES` in a private `UniquePtr` owned by `ServerLoop`. Against this:
|
||
`DirectGLES.cpp:12446` (`ClampSamplesToBackendSupport`) reads `pActiveBackendObject` from
|
||
*inside the backend*, on the server side of the seam.
|
||
- **(ii)** the role shim makes `pActiveBackendObject` role-scoped (a thread-keyed accessor).
|
||
`ARCHITECTURE.md:581` says both shims are "不在 GL 热路径的每次访问上", which reads like (ii).
|
||
*Evidence that would settle it:* an inventory of `pActiveBackendObject` reads reachable from
|
||
backend code (I found exactly one: `DirectGLES.cpp:12446`). If it is only that one, (i) wins
|
||
cheaply by passing the format cache down instead.
|
||
|
||
### 3.5 What has to change for the app thread to give up the context
|
||
|
||
Today the app thread acquires the context at `BackendObject_DirectGLES.cpp:963`
|
||
(`DirectGLES::MakeCurrent()`). Under split, that call must not happen on the app thread at all:
|
||
`BackendObject_Remote::MakeEGLCurrent` should emit a make-current record and let the *apply
|
||
thread* run `DirectGLES::MakeCurrent()` once, then never release. The frontend already has the
|
||
serialisation point: `EGLOperationMutex` (`EGLImpl.cpp:48-51`), held by `MakeCurrent` (:241),
|
||
`SwapBuffers` (:163), `DestroySurface` (:305). `ARCHITECTURE.md:213` says exactly this —
|
||
`eglMakeCurrent` is a flow-ownership transfer emitted under the existing `EGLOperationMutex`,
|
||
and `ReleaseThread` / `SwapInterval` should be made to take that lock too (they do **not**
|
||
today: `EGLImpl.cpp:341 ReleaseThread` and `:435 SwapInterval` take no lock).
|
||
|
||
Three call sites that today reach the driver on the app thread and must be re-pointed:
|
||
- `EGLImpl.cpp:284` → `MakeEGLCurrent` → `DirectGLES::MakeCurrent()`
|
||
- `EGLImpl.cpp:173` → `SwapEGLBuffers` → `BackendObject.cpp:396` `backendFunctions.Present()`
|
||
(note `BackendObject::SwapEGLBuffers` first checks `m_eglCurrentThreads` for **the calling
|
||
thread**, `BackendObject.cpp:375-384` — a remote object must override or relax this)
|
||
- `EGLImpl.cpp:448` → `SetEGLSwapInterval` → `BackendObject.cpp:400-405`
|
||
|
||
---
|
||
|
||
## 4. `MOBILEGL_TRANSPORT` parsing: **none exists**
|
||
|
||
- `MobileGL/Config.h`: no `Transport` member. The MGPipe block is `Config.h:319-398`
|
||
(`PipePush` at `:349`, `PipeVerify` at `:355`, `PipeStats`, `PipeLegacyMemos`,
|
||
`PipeTexelRetainMb`, `PipeIndexMirrorMb`, `PipeStatsPeriod`, `PipeStatsFile`).
|
||
- `MobileGL/ConfigLoader.cpp`: the MGPipe parse block is `:248-282`. No transport line.
|
||
Helpers available to copy: `QueryEnvVariable`, `QueryEnvFlag`, `QueryEnvUint32`,
|
||
`QueryEnvUint64` (`:171` documents the strict integer parse), `QueryEnvQuirkOverride`.
|
||
`ConfigLoader.cpp:34` — every `MOBILEGL_`/`LIBGL_` prefixed variable is accepted by
|
||
`InitializeAcceptedEnvVariables` by construction, so a new name needs no allow-list entry.
|
||
- The enum-style precedent to copy is `InitBackendType()` at `ConfigLoader.cpp:284-300`, which
|
||
writes the **global** `MG_Config::ActiveBackendType` (declared `Config.h:21`,
|
||
defined `GlobalObjects.cpp:13`) rather than a `Features` member. `ARCHITECTURE.md:576` requires
|
||
`MG_Config::Transport` to be a `constexpr Monolith` when `MOBILEGL_BUILD_DISAGGREGATED=OFF`, so
|
||
it cannot be a plain `Features` field — it needs the `#if` shape.
|
||
- Spec of the value: `ARCHITECTURE.md:583` —
|
||
`MOBILEGL_TRANSPORT = monolith | inproc | spawn | unix:<path> | pipe:<name>`.
|
||
- Related unset knobs (all P5+, none parsed today): `ARCHITECTURE.md:613`
|
||
(`MOBILEGL_IPC_SERVER_PATH`, `_RING_MB`(8), `_STAGE_MB`(32), `_PRESENT_CREDIT`(1),
|
||
`_SPIN_US`(50), `_POLL_ESCALATE`(64), `_PERSISTENT_BLOCK_KB`(64), `_ADOPT_TIER`,
|
||
`_SHADOW_SHM`, `_INLINE_PAYLOADS`, `_SERVER_AFFINITY`, `_STRICT_ERRORS`, `_AUDIT`, `_TRACE`,
|
||
`_ATTACH`, `_RESPAWN`, `_IDLE_EXIT_S`(30)).
|
||
|
||
### Build wiring that does exist
|
||
|
||
- `CMakeLists.txt:23` `option(MOBILEGL_BUILD_DISAGGREGATED ... OFF)`.
|
||
- `CMakeLists.txt:453-466` forces it OFF (normal variable, not cache) when
|
||
`3rdparty/flatbuffers/include` is missing.
|
||
- `CMakeLists.txt:503-520` appends only the 8 `MG_Remote/Transport/*.cpp` files — **no
|
||
`Client/` or `Server/` entries yet**.
|
||
- `CMakeLists.txt:572-574` `-DMOBILEGL_BUILD_DISAGGREGATED=1`.
|
||
- `CMakeLists.txt:605-609` adds the flatbuffers include dir.
|
||
- `CMakeLists.txt:484-499` is the `MOBILEGL_PIPE_PUSH` source list (`PipeApply.cpp`,
|
||
`PipeFill.cpp`, `SlotAllocator.cpp`, `MGPipeRenderStateSpans.cpp`, `PipeInputs.cpp`,
|
||
`ProgramArtifactsCodec.cpp`) — the P5 client/server files will need the same treatment.
|
||
- `MOBILEGL_BUILD_DISAGGREGATED_INPROC` does not appear anywhere in the tree
|
||
(only `ARCHITECTURE.md:581, 595`).
|
||
- `MG_Remote/` contains `Protocol/` and `Transport/` only. **No `Client/`, no `Server/`.**
|
||
|
||
---
|
||
|
||
## 5. Teardown — what a two-role build has to sequence
|
||
|
||
### 5.1 The three nested teardown paths today
|
||
|
||
**(a) `MobileGL::DestroyImpl` — `MobileGL/Init.cpp:38-98`, in order:**
|
||
```
|
||
:50 MG_Util::PipeStats::Shutdown() final stats line + JSON dump
|
||
:56 ShaderCompilePool::Get().StopAndDrain() "the one cancellation path that waits"
|
||
:62 MG_Impl::GLImpl::DestroyAllSyncObjects() drains while gBackendFunctionsTable is still live
|
||
:67 MG_Impl::GLImpl::DestroyAllQueryObjects() ditto
|
||
:68 MG_Backend::pActiveBackendObject.reset() -> ~BackendObject_DirectGLES -> DestroyEGLContext()
|
||
:69 MG_State::pGLContext.reset()
|
||
:70 MG_State::pEGLContext.reset()
|
||
:71 pProxyTextureManager.reset()
|
||
:72 pDefaultFramebufferInfo.reset()
|
||
:78 glslang::FinalizeProcess() MUST be after :69 (comment :73-77)
|
||
:82 ShaderCompiler::ResetPrewarmLatch()
|
||
:87-90 translation-cache stats + clear
|
||
:91 MG_Backend::gBackendFunctionsTable = {}
|
||
```
|
||
|
||
**(b) `MG_Impl::EGLImpl::Terminate` — `EGLImpl.cpp:319-338`:**
|
||
`state->TerminateDisplay(dpy)` → `backendObject->ReleaseEGLResources()` (:326) → and *only if*
|
||
`!HasAnyInitializedDisplay() && !HasAnyCurrentContext()` → `MobileGL::Destroy()` (:333).
|
||
So (a) is normally reached **through** EGL, not at process exit.
|
||
|
||
**(c) `DirectGLES::DestroyEGLContext` — `DirectGLES.cpp:12472-12510`:**
|
||
```
|
||
:12473-12477 BufferImpl / XfbImpl / MultiDrawImpl / OnRestartSubstitutionContextDestroyed /
|
||
ScratchFBOImpl :: OnBackendContextDestroyed()
|
||
:12478 ReleasePackedWordScratchTexture()
|
||
:12479-12484 invalidate framebuffer / VAO / pack-state / broadcast-memo caches
|
||
:12486 ++g_backendContextGeneration
|
||
:12489 g_backendContextOwnerThread.store({})
|
||
:12491 ++g_syncContextGeneration (all outstanding fences become "signaled")
|
||
:12494-12496 abandon the 4-slot frame-fence ring, floor the completed watermark
|
||
:12498-12509 eglMakeCurrent(NO_SURFACE) ; eglDestroyContext ; eglDestroySurface ; eglTerminate
|
||
```
|
||
I checked the three `OnBackendContextDestroyed` bodies — `Managers.cpp:2583-2596`,
|
||
`Managers.cpp:9849-9855`, `DirectGLES.cpp:1426-1433` — and **none of them issues a GL call**;
|
||
they only drop shadows, bump generations and unregister the ops table
|
||
(`Managers.cpp:2584 UnregisterBufferBackendOps()`). Good news: the destroy path itself is
|
||
thread-agnostic. (`Managers.cpp:2602`'s `CanTouchGLNow()` belongs to
|
||
`ProcessDeferredBufferReleases`, a *different* function starting at `:2598` — do not confuse
|
||
them; I nearly did.)
|
||
|
||
### 5.2 ARCHITECTURE's required split order, and where it disagrees with today
|
||
|
||
`ARCHITECTURE.md:537`:
|
||
> 拆机顺序:publish + server 排空并 ack → 停 apply 线程 → 关 transport → client 排空 compile
|
||
> pool(先于 `glslang::FinalizeProcess()` 与 `pGLContext` 析构)→ `MobileGL::Destroy()` →
|
||
> 释放 sync/query handle。
|
||
|
||
**This is not today's order.** Today `DestroyAllSyncObjects` / `DestroyAllQueryObjects`
|
||
(`Init.cpp:62, 67`) run **before** `pActiveBackendObject.reset()` (`:68`), and the comments at
|
||
`:58-66` say that is deliberate — the registries are drained "while the backend function table
|
||
can still release the backend handles". ARCHITECTURE puts sync/query release **after**
|
||
`MobileGL::Destroy()`. Two readings:
|
||
- **(i)** ARCHITECTURE means "release the *client-side* handle objects last, after the server is
|
||
gone", because in split a sync handle is minted client-side (`ARCHITECTURE.md:500`, "client 铸造
|
||
handle") and needs no backend call — so the ordering constraint that motivates today's `:62/:67`
|
||
simply evaporates.
|
||
- **(ii)** ARCHITECTURE is loose here and P5 should keep today's order.
|
||
*Evidence that would settle it:* whether `BackendObject_Remote`'s `GetBackendFunctions()` table
|
||
has real `DeleteSync`/`DeleteQuery` entries that must run before the transport closes. If it
|
||
does, (ii); if syncs are client-minted, (i). I could not settle this from the tree because
|
||
neither the remote table nor the client fence store exists yet.
|
||
|
||
### 5.3 Additional teardown hazards P5 must handle
|
||
|
||
1. **`EGLImpl.cpp:326` `ReleaseEGLResources()` is called from the app thread**, and for
|
||
DirectGLES it runs `DestroyEGLContext()` (`BackendObject_DirectGLES.cpp:981-985`), which
|
||
calls `eglDestroyContext`/`eglTerminate`. In split those must execute on `mgl-srv-apply` (the
|
||
context owner), so `BackendObject_Remote::ReleaseEGLResources` has to be a **blocking**
|
||
request, not fire-and-forget — otherwise `MobileGL::Destroy()` at `EGLImpl.cpp:333` proceeds
|
||
while the server still holds the context.
|
||
2. **`OnEGLSurfaceReleased` → `DestroyEGLContext`** (`BackendObject_DirectGLES.cpp:986-989`),
|
||
reached from `BackendObject::ReleaseEGLSurface` (`BackendObject.cpp:446-463`) and from
|
||
`DestroyPendingEGLSurfaceIfUnused` (`:419-431`). A surface destroyed while current is
|
||
*deferred* (`DestroyPending = true`, `:454`) until the last thread releases it. Two roles make
|
||
"the last thread" ambiguous.
|
||
3. **`~BackendObject_DirectGLES()` calls `DestroyEGLContext()`** (`BackendObject_DirectGLES.cpp:833-835`),
|
||
i.e. `pActiveBackendObject.reset()` at `Init.cpp:68` can destroy the native context **on the
|
||
app thread**. Same problem as (1).
|
||
4. **`Present()` is the only frame-boundary drain** (`DirectGLES.cpp:12424-12470`: fence poll,
|
||
`UboRingOnPresent` / `UnpackRingOnPresent` / `UploadRingOnPresent` / `TrimBufferPool`,
|
||
`PipeStats::OnPresent()` at `:12467`). `ARCHITECTURE.md:531` insists `present` ↔
|
||
`eglSwapBuffers` stays strictly 1:1 for exactly this reason. A shutdown that drops in-flight
|
||
presents starves the retire path.
|
||
5. **`PipeStats::Shutdown()` at `Init.cpp:50` runs in the client role**, but the
|
||
`persistent-map-push` counter the P5 gate demands is written where the push happens.
|
||
`MG_Util/Metrics/PipeStats.h:46-122` lists `persistent-map-push` as declared-but-unwired
|
||
(`ARCHITECTURE.md:617`: "P0 未接线"). Whichever role owns the counter must flush before its
|
||
half exits; with two roles there are two `PipeStats` instances in inproc unless the shim
|
||
scopes it (**guess**: it does not need scoping, since the counter is client-side).
|
||
|
||
---
|
||
|
||
## 6. Gate artifacts that do not exist yet (P5 must create them)
|
||
|
||
- `PersistentCoherentMapScenario` — named in `ARCHITECTURE.md:500` and in the ROADMAP P5 exit
|
||
gate; **no such file** under `MobileGL/MG_IntegrationTest/Scenarios/` (86 scenarios listed;
|
||
`ClearThenReadPixelsScenario.cpp` exists, a `Triangle*Scenario.cpp` does **not**).
|
||
- `DirectGLES.Split.` test prefix — the itest CMake has prefixes
|
||
`DirectGLES.` (`MG_IntegrationTest/CMakeLists.txt:739`),
|
||
`DirectGLES.ForcedDepthStencilEmulation.` (:786), `DirectGLES.UnlocatedIoBlocks.` (:817),
|
||
`DirectGLES.MallocPerturb.` (:839), `DirectGLES.AsyncOn./AsyncOff.` (:868, :895),
|
||
`DirectGLES.OptimisticShaderStatus.` (:921), `DirectGLES.NoViewportArrayEmulation.` (:945),
|
||
`DirectGLES.WidenedPacked16.` (:983), `DirectGLES.PointSizeDemotion.` (:1000),
|
||
`DirectGLES.HandleRecycle.Handles./Legacy.` (:1157, :1175). **No `Split.`**, and
|
||
no `DirectGLES.Pipe.` prefix either (ARCHITECTURE.md:518 names it as the target shape).
|
||
The env-joining helper is `mgl_itest_join_environment` (`MG_IntegrationTest/CMakeLists.txt:321`);
|
||
every list must append `${MGL_ITEST_COMMON_ENV}` (:1036, and the trap is restated at
|
||
`ARCHITECTURE.md:584`).
|
||
- trace-replay `SPLIT` suffix + `-DTRACE_TRANSPORT=`: `add_trace_replay_test` is
|
||
`tools/trace_replay/CMakeLists.txt:311-383` (the `run_trace_case.cmake` invocation is at
|
||
`:372`); `add_trace_replay_test_for_backends` at `:384-387`. Neither knows about `SPLIT` or
|
||
`TRACE_TRANSPORT` today.
|
||
- Wire-layer tests that DO exist and are the model for new ones:
|
||
`MobileGL/MG_Test/Wire/{RingTest,FramingTest,FdPassingTest,InProcessTransportTest,ProtocolSmokeTest}.cpp`,
|
||
registered only under the option (`MobileGL/MG_Test/CMakeLists.txt:93-95`).
|
||
- `InProcessTransport` today is a **mutex + two condvars** message queue
|
||
(`MG_Remote/Transport/InProcessTransport.cpp:41-51`: `std::mutex mutex`,
|
||
`condition_variable cv` for messages and `fdCv` for fd offers). It is the control plane only —
|
||
it does **not** create a thread and it does **not** run the G3 codec. P5's "InProcessTransport
|
||
runs the SAME G3 codec as spawn" means driving `PipeWire.inc`
|
||
(`MG_Pipe/generated/PipeWire.inc`, 939 lines) through it, plus a `SEG_CMD` ring
|
||
(`MG_Remote/Transport/Ring.{h,cpp}`) and a `Doorbell`. Note the header's own comment
|
||
(`InProcessTransport.h:13`): *"side is the monolith's own render thread, which is the single
|
||
largest CPU…"*.
|
||
|
||
---
|
||
|
||
## 7. Quick reference — the file:line list P5 will keep reopening
|
||
|
||
| what | where |
|
||
|---|---|
|
||
| **the hook** | `MobileGL/MG_Backend/Init.cpp:50` (before the `switch` at `:51`) |
|
||
| backend table populated from the object | `MobileGL/MG_Backend/Init.cpp:43-44` |
|
||
| the two globals | `MobileGL/GlobalObjects.cpp:23-24`; decls `MG_Backend/BackendObjects.h:16-17` |
|
||
| `BackendObject` interface | `MobileGL/MG_Backend/BackendObject.h:567-644` (8 pure, 9 EGL, 2 protected) |
|
||
| `GlobalBackendFunctionsTable` | `MobileGL/MG_Backend/BackendObject.h:293-299` |
|
||
| `DynamicBackendParameters` | `MobileGL/MG_Backend/BackendObject.h:297-…` |
|
||
| base EGL impl | `MobileGL/MG_Backend/BackendObject.cpp:168-494` (MakeEGLCurrent `:306`, caps-on-first-current `:341`) |
|
||
| DirectGLES EGL overrides | `MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp:875-989` |
|
||
| **context owner slot** | `MG_Backend/DirectGLES/DirectGLES.cpp:11865` |
|
||
| driver make/release current | `DirectGLES.cpp:11919` / `:11958` (real `eglMakeCurrent` `:11925` / `:11964`) |
|
||
| **owner predicate** | `DirectGLES.cpp:12002-12025` (+ stamp `:11980-11996`) |
|
||
| buffer-path owner gate | `MG_Backend/DirectGLES/Managers.cpp:928-930` (19 users) |
|
||
| native context destroy | `DirectGLES.cpp:12472-12510` |
|
||
| frontend EGL entry points | `MG_Impl/EGLImpl/EGLImpl.cpp` — MakeCurrent `:240`, SwapBuffers `:162`, Terminate `:319`, ReleaseThread `:341`, SwapInterval `:435`, DestroySurface `:304`, mutex `:48` |
|
||
| library teardown | `MobileGL/Init.cpp:38-98` |
|
||
| the two pipe tables | `MG_Pipe/generated/PipeTables.inc:17` / `:33`; instances `MG_Pipe/MGPipe.h:137-138` |
|
||
| the thunks nobody calls | `MG_Pipe/generated/PipeThunks.inc:20-302` |
|
||
| the applier actually in use | `MG_Pipe/PipeApply.h:740-1052`, `MG_Pipe/PipeApply.cpp` |
|
||
| backend op-table precedent | `MG_Pipe/PipeApply.h:85-102`; `PipeApply.cpp:402, 1094-1095`; `Managers.cpp:2298-2308` |
|
||
| server-side state block | `MG_Backend/MGPipe/PipeInputs.h:706` (`gPipeInputs`) |
|
||
| config | `MobileGL/Config.h:319-398`; `MobileGL/ConfigLoader.cpp:248-282`; enum precedent `:284-300` |
|
||
| CMake gating | `CMakeLists.txt:23, 453-466, 484-499, 503-520, 572-574, 605-609` |
|
||
| design of record | `docs/Disaggregated/ARCHITECTURE.md:29, 213, 500-502, 516-518, 531-537, 576-584, 595, 613` |
|
||
| ROADMAP P5 row | `docs/Disaggregated/ROADMAP.md:21` |
|