7.9 KiB
Reverse Channel (formerly ARCHITECTURE §8)
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.
8. Reverse channel
8.1 MGPipeCallbacks (MG_Pipe/MGPipeCallbacks.h)
Seven named callbacks replace the 95 call sites where the backend directly pokes frontend objects (the naming is a deliberate deviation D8). Under monolith they are direct calls; under split they are records on SEG_EVENT.
| Callback | Role |
|---|---|
OnGlError |
deferred GL errors; must be ordered with respect to the command stream |
OnGpuWritten |
buffer ranges the GPU has written (narrowing the client's conservatively built pending set) |
OnBufferWriteback |
PBO readback, XFB capture results; must be ordered with the epoch bump |
OnTextureWriteback |
texels of CPU-fallback generated mips |
OnMipLevelsGenerated |
shape only |
OnSurfaceChanged |
format and size of the default framebuffer (when the server owns the display, this is how the client learns the window size) |
OnCapsInvalidated |
replaces InvalidateCompileEnv |
OnLog has been removed (P9 W3; together with OnTexturePullRequest removed in W2, callbacks 9 → 7): zero producers, zero consumers, no EventKind. Server logs go through the control-plane LogLine (with level), forwarded by the queue + sending thread in MG_Remote/Transport/LogForward.h: ≤WARN is lossy (dropped when the queue is full, counted, the gap marked in-stream by LogForward{Dropped}), ≥ERROR is lossless + rate-limited (over-rate lines queue with delay; when the error byte cap is exceeded they are merged into one LogForward{Coalesced}), FATAL waits with a bound to be written out; the logging thread never waits on the socket.
Frontend objects invented out of thin air by the backend (Magma placeholder textures, the swapchain default FB placeholder) become server-native; truly deleting the m_backend-style members in pull builds would change sizeof (G1), so they retire with P13 (D-K).
8.2 Ordering is a correctness requirement
The epoch bump after each writeback must be applied by the server before any later command that reads that handle. The reverse channel needs the same ordering guarantee as the forward channel.
8.3 Errors, acks and logs
- The only entry allowed to ack synchronously is
glBufferStorage:ResourceRespecify'skNeedsAckis narrowed by a per-record predicate to the "immutable buffer" (D-A2); texture OOM was already deferred to sync time under monolith. respecify carries per-level scope and a "metadata-only" form, and does not mistakenly drop accepted pending uploads. - All other errors are late, going through the ordered
OnGlError.
8.3.1 The server log forwarding queue
LogForwardChannel in MG_Remote/Transport/LogForward.h: Offer only copies one line into the queue under the channel mutex, and the single sending thread writes frames in enqueue order, holding no lock while writing the socket, so for lines below FATAL, forwarding costs only one lock plus one copy (LogForwardCounters::maxOfferNs), regardless of whether the peer is reading. The server's own log file is written before forwarding (Log.cpp), so it always has all lines.
- DEBUG / INFO / WARN are lossy: a line that would push the lossy bytes over
lossyQueueBytesis dropped and counted; when the gap closes (the next ≤WARN line that fits, aLogFlushack, orClose), the channel itself inserts a WARNLogForward{Dropped}stating the count, bytes and start time. - ERROR is lossless and rate-limited: a token bucket (
errorBurstLinesto start, replenished byerrorLinesPerSecond); over-rate lines queue in order with delay and are not dropped; when queued error bytes exceederrorQueueBytes(8 times the lossy cap), further lines are not enqueued, replaced by one ERRORLogForward{Coalesced}placed before the next entry that can be enqueued. - FATAL = ERROR + waiting: wait at most
fatalFlushMs, until the sending thread has written it and everything before it; after one wait times out, later FATALs do not wait until the sending thread writes anything (MGLOG_Falso marks survivable cases, which may fire in succession).Log.cpphands off FATAL lines outside the log lock. - The
LogFlushack (OfferControlFrame) is ordered after every previously submitted line, soMGPipeSyncPeerLog's "when the ack arrives, all preceding lines are already in the file" holds; while it is queued, the sending thread is not rate-limited. Close()accepts no more lines and gives the queuecloseDrainMsto drain without rate limiting; if the sending thread is still stuck in a write, it calls the owner-suppliedunblock(ServerMain:shutdown(2)on the control socket) so the thread can be joined rather than leaked, and remaining entries count as abandoned.- Only the TCP server form forwards (
ServerMain.cpp,MOBILEGL_IPC_LOG_FORWARD); the unix spawn server and the inproc apply thread write<base>.server.logthemselves.
8.4 Texture re-mint: all bytes are in the server's hands, no pull (P9 W2 withdraws the original "only new stall class" D-B6)
The original design assumed the server kept no texels and re-minting had to pull levels back from the client. After P5c the server has StagedTextureStore, and GPU-written layers are already in the GPU image — neither backend's re-mint needs any bytes from the client:
| Source of re-mint bytes under split | monolith | |
|---|---|---|
| Espryt | RequireImageBindableStorageByHandle: layers that really exist on the driver are read back from the old GPU texture one by one, with pending boxes merged using the store; layers the driver does not have (defined only after the last sync) are not read, store coverage replays the store, otherwise they are skipped; when the driver refuses the read while the store covers it, fall back to the store; layers already immutable and needing no widening are kept as they are |
the frontend arm replays the client shadow |
| Magma | rebuild the VkImage with STORAGE, PreserveTextureContentsOnRecreate GPU→GPU copy |
same as left |
- Changing only what the store reads is not enough: GPU-written layers have old bytes or no bytes in the store; in the corpus both Espryt re-mints (
iris-photon,iris-derivative) are render targets with no bytes in the store. - "Whole-format regeneration" on both arms uploads only the pending layers, with no readback and no pull; "view source re-mint" leaves only the source needing widening and goes through the same ByHandle path.
- Removed:
OnTexturePullRequest(together withOnLogremoved in W3, callbacks 9 → 7), thetexture-remint-pullflag. op 50ResourceSubDataCompleteis append-only and kept as a retired line (decoding rejects it).MOBILEGL_PIPE_TEXEL_RETAIN_MBhas no consumers left, but in pull builds it is deleted with P13 (G1). ImageBindableHintis still a precaution: if the hint arrives before the first sync, storage is allocated image-bindable directly, saving one server readback + retransfer.- Known dev defect (not P9): monolith Espryt's frontend arm replays the client shadow, overwriting GPU-written texels.
- Census, coverage matrix, gates and red-once: see the git history.
8.5 XFB scatter stays on the server
The patch loop needs "the bytes before capture", and after the split the authoritative shadow belongs to the server (StagedShadow), so scatter runs to completion in place on the server; the return trip is a single OnBufferWriteback carrying the patched whole range, sliced to a quarter of SEG_EVENT capacity (MGPipeBufferWritebackSliceBytes), and landing the last slice implies every preceding one. Orphan targets (HasDefinedContent == 0) are zero-filled and scattered as usual. OnXfbScatterReady has been removed (callbacks 10 → 9). For the landing argument see the git history (espryt D1).