6.2 KiB
Boundary and Object Model (formerly ARCHITECTURE §1–§2)
Design details. The design highlights and an index of all sections are in
../ARCHITECTURE.md. Section numbers follow the originalARCHITECTURE.md;ARCHITECTURE.md §Nin code comments resolves by number here.
1. Boundary
1.1 In one sentence
MG_Backend was already a state machine glued to the target API (Espryt = DirectGLES, Magma = DirectVulkan); what it lacked was an explicit declaration of "what I was told". MGPipe is that declaration: the frontend pushes changes across before every verb, and the backend no longer pulls MG_State::pGLContext. The interface is derived backward from the key structures the two backends maintain themselves (SetupDrawSnapshot, DrawTextureSyncKeys, ResolvedVertexBindings, g_syncedRenderStateParameters, BufferBackendOps …); gallium is where the vocabulary is headed, not a premise of the derivation.
1.2 Two function pointer tables
MGPipeScreen (share-group scope: caps, resource, persistent map, fence) and MGPipeContext (everything else), generated from PipeCalls.def.
- A struct of function pointers instead of a virtual base class: a null entry already means "not implemented, the frontend falls back", and
MG_Testcan mock the whole thing by replacing the table. - The two tables are separate from day one; splitting them later would renumber the records; v1 has only one screen, one context and one flow.
- EGL lifecycle and the caps side stay on the virtual functions of
pActiveBackendObject(a rare path, on the control plane).
1.3 Three forms, one backend
| Form | What the tables hold | Purpose |
|---|---|---|
monolith (default) |
the backend's own functions; callbacks call MG_State directly |
ship |
inproc |
emitter → applier on a second thread in the same process | CI main acceptance; that is, the monolith render thread |
spawn |
emitter → data plane → applier in another process → the same set of backend functions | two-process form; the control plane can be fork / unix: / tcp:// (§11.9), and the server can be on another machine |
The only hook point is one #if MOBILEGL_BUILD_DISAGGREGATED branch inside MG_Backend::Init(): when MG_Config::Transport != Monolith it installs MG_Remote::BackendObject_Remote. Zero #ifdefs at MG_Impl's boundary call sites.
2. Object model
2.1 Handle = {slot, gen} (MG_Pipe/MGPipeHandles.h)
- An 8-byte POD; the client mints it, the server never returns a handle → the whole catalog has zero creation round trips (deviation D1 from gallium).
- Slots are allocated densely per kind (free list + high-water mark), the server's object table is an array;
genis incremented only when a slot is reused. - kind:
Buffer, Texture, Renderbuffer, Framebuffer, Xfb, RenderStateCso, VertexElementsCso, SamplerCso, SamplerViewCso, ShaderCso, Fence, Query, Context. Reserved:{0,0}= null;Framebuffer {0,1}= the default framebuffer; the high segment ofShaderCsois for program pipeline composites. - GL names are for diagnostics only (
GlNameForDiag), never for identity, memo keys or content hashes.
2.2 Two kinds of generation, strictly separated
| Owner | Answers | Crosses the wire | |
|---|---|---|---|
MGPipeHandle::Gen |
client | "Is it still the same GL object?" | yes |
MGGen (backend epoch, e.g. g_bufferMutationEpoch) |
server | "Did I re-mint the driver object myself?" | never (it appears only in the form of a texture pull request, §8.4) |
The client's wrapping Uint16 version counter is never the sole proof of freshness: when it crosses the wire, either widen it or send it alongside {slot, gen}.
2.3 CSO and mutable objects
- CSO (render state, vertex elements, sampler, sampler view, shader) is content-addressed on the client side (Mesa
cso_cacheprecedent), and sendsdelete_*on LRU eviction; render-state capacity 64 (retuned in P13), sampler state 256. - Buffer / Texture / Renderbuffer go through create / respecify / subdata / destroy; Framebuffer / Xfb are per-context identity +
set_*payload. - Two deviations: D-G1 Espryt's vertex elements are addressed by VAO identity (the twin holds the driver VAO name and cannot be shared); D-F2 Espryt's sampler view is addressed by texture identity, sampler state is content-addressed and reference-counted (an entry pointed to by a
BuiltinSamplermust not be evicted by the LRU).
2.3.1 sampler state cache: reference counting and value lifetime
sampler state is value-addressed on the client side (MGPipeSamplerCsoCache, capacity 256): hash, probe, then confirm with memcmp; trusting a 64-bit hash match alone would merge two states into one CSO, causing incorrect filtering that no gate can see.
- Live handles avoid the LRU by reference count rather than by capacity: a published
MGPTextureParamskeeps pointing at itsBuiltinSamplerwhile the texture parameters do not change, the applier does not resolve this handle, and eviction does not count as a parameter change, so the handle is neither re-sent nor rejected. Therefore everyAcquiretakes a reference andReleasereturns one; the LRU considers only entries with zero references; when everything is pinned and capacity is exceeded, mint beyond capacity and count it inOverCapacityMints(an extra slot costs memory, a stolen handle costs correctness). - The holders are the texture's built-in sampler (released when the texture dies or when parameters change and it re-acquires) and every CSO currently bound by
bind_sampler_states; any caller that will still name the handle after returning must hold one. Capacity greater thankMGPipeMaxTextureUnitsis only a size bound: a cache that cannot hold one full round of emission will mint beyond capacity every round. - Slots carry no lifetime id: a CSO belongs to a value, not to a frontend object (another
SamplerObjectmay hold the same value),~SamplerObjectfrees no slot, and the only death path is client-side LRU eviction. So aNotifyStateObjectDestroyed(SamplerCso, lifetimeId)coming from~SamplerObjectnever has a handle behind it; the backend must not key sampler twins by aSamplerObject's lifetime id; a twin's life is only fromcreate_sampler_stateto thedelete_sampler_stateissued by the LRU.