Two MIT headers from libglvnd, verbatim, because MobileGL is about to implement the vendor half of
glvnd's EGL ABI and the distribution ships the runtime without them.
Two permissions decide whether a server on Android lives or dies, and both are invisible until
something goes wrong: without POST_NOTIFICATIONS (13+) a foreground service cannot show the
notification that is its reason to exist, and without the battery exemption a dozing phone freezes
the process - the listener stays in /proc/net and stops accepting, which from the other side of the
socket looks exactly like a network fault.
The control screen now shows the state of both and asks for whichever is missing, in the order that
makes sense: the notification prompt is a dialog of ours, the battery one leaves the app entirely,
and coming back from it re-runs the other branch through onResume. A denial is said out loud rather
than left to be discovered.
The manifest carries the two permissions; the service's own notification was already there.
The last thing between the host-framed surface and a run: the sub-byte on the wire that names it.
ServerOwned had one (7) and the host frame had none, so the encoder refused the op with
UnknownWindowKind - a correct refusal, and the reason the first run of MOBILEGL_IPC_SURFACE=host
failed in the codec rather than anywhere near the pixels.
- protocol.fbs: WindowKind::HostFrame = 8, appended, legal on CreateWindowSurface only and always
with nativeToken 0 - the same rules ServerOwned carries, for the same reason: no pointer of any
process's window system crosses (Rule G/H).
- SurfaceRefusal: HostFrameOnSetWindowHandle = 5, its own code rather than reusing the
server-owned one: a refusal that names the wrong owner sends the next reader to the wrong place.
- SurfaceOpCodec: the encode and decode arms, the two wire errors that go with them
(HostFrameTokenNotZero latches as corruption, HostFrameOnSetWindowHandle is a named refusal), and
the range bound that had ServerOwned as its upper limit now ends at HostFrame.
- protocol_generated.h regenerated with the pinned submodule's flatc (scripts/gen_protocol.py),
diff being exactly the two appended enumerators.
Both ends must be rebuilt together, as any protocol revision here: the container's client and the
device's server went out with this on either side.
Three things this path needs that the server-owned one never did, each because the pixels are not
in an EGL surface this time:
- The client's DEFAULT framebuffer has to resolve to the frame. Four places bind the driver's 0
for the default framebuffer; they now ask g_hostFrameFramebufferId, which is 0 everywhere except
while a host-framed surface is current, where it is the frame's framebuffer. Without this every
draw would land in the pbuffer the context was made with - a surface nobody ever sees.
- eglMakeCurrent binds the surface's own default framebuffer behind the engine's binding shadow, so
the frame has to be put back after every make-current, with the shadow invalidated first: a bind
the shadow thinks already happened never reaches the driver.
- The bind itself goes through FramebufferImpl::BindFramebufferId, not a raw glBindFramebuffer, for
the same reason - a raw bind leaves the shadow claiming the previous framebuffer.
Frame lifecycle is otherwise unchanged: the frame becomes the draw target when it arrives, and is
dropped after the host has been answered and has presented it.
Compiles and links (libMobileGL.so, libMobileGLServer.so, exit 0), deployed to the device, and the
container's client is rebuilding against it. The run that shows Plasma through it is next.
The third on-screen shape, and the one this whole line was built toward: Anland keeps Wayland,
input and the display, and the render server draws into the frames the display host allocates.
client (container) asks for a window surface with no window of its own
server (Android) takes a frame from the host over the host frame bridge and binds it as
the draw target of that surface
host (the app that
owns the screen) offers each frame, presents it when the server says it is drawn
The pieces, in the order a frame travels:
- Config: MOBILEGL_IPC_SURFACE=host beside =server, and HostFrameWindowSurfaces() as the one
predicate every client-side consumer asks (EGLImpl accepts a NULL window for either on-screen
shape, and BackendObject_Remote routes the creation to the server instead of sending a handle).
- Wire: the frame-local tag kHostFrameWindowBackend, the mirror of kServerOwnedWindowBackend - it
names no WindowBackend of the pull build's enum, and the server consumes it before the unpack
that would refuse it.
- Server: ApplyHostFrameWindowSurface replaces nothing, unlike the server-owned arm: there is no
window of either side's to substitute, because the backend takes the frames from the bridge.
- Backend: SurfaceKind::HostFrame, InitHostFrameSurface (the EGL context comes from the pbuffer
path; what changes is what it draws INTO) and PresentHostFrame (glFinish, answer the host, take
the next frame) - and SwapEGLBuffers routes to it, because a host-framed surface has no
swapchain to swap: the host is what puts the frame on the glass.
HostFrameTarget and HostFrameBridge, committed just before this, are what the backend uses.
Compiles and links in this tree (libMobileGL.so and libMobileGLServer.so, exit 0). What is NOT
here yet, and is the next thing: the run that puts Plasma on the phone's screen through it.
Two modules, and nothing else changes yet: they are the half of the host-framed surface that is
about the frame itself, kept apart from the surface lifecycle so they can be reviewed and built
on their own.
HostFrameTarget (DirectGLES): an AHardwareBuffer becomes a renderbuffer through an EGLImage and a
framebuffer that is the drawing target while its frame is current. The extension strings decide
first and the four entry points are taken from eglGetProcAddress only after they have, which is
the shape the T0 AHB import in Managers.cpp already uses; a driver that lacks one of them is
answered with its name, never an abort. Every failure path drops what it made, so no GL name and
no EGLImage outlives a refusal.
HostFrameBridge (MG_Backend): the display host's own protocol, restated field for field - a
handle ahead of a fixed-size description on one AF_UNIX connection, then the answer, then the
host's readback of the pixels it can see. The host dials; this side listens on the well-known
name. A handle that arrives without its description is released before the failure is reported,
because a reference held past the connection starves the pool the host draws its frames from.
Why the host allocates the frames at all is measured, not chosen: AHardwareBuffer_createFromHandle
accepts an AHardwareBuffer this process allocated and refuses one the display queue handed over
(gralloc BAD_VALUE) - same process, same descriptor, same handle shape. That result is what these
two modules are built around, and it is written down where the next reader will look for it.
Both compile against this tree's own flags. Wiring them into the surface lifecycle, the swap path
and the client's opt-in is the next step, not this one.
Measured on the Redmi today: the Droidspaces container shares the phone's network namespace, so a
client in the container reaches the server's loopback listener with no adb forward and no LAN
address; the server owns the window (MOBILEGL_IPC_SURFACE=server), the Adreno does the drawing,
and a screenshot of the phone shows the colour that headless client asked for.
The two traps are written down because both cost a run: MOBILEGL_IPC_REQUIRE_SAME_BUILD=1 on the
server turns a Connect handshake's build-stamp warning into Refuse{BuildFingerprint} against a
release APK whose stamp is 0, and the client then dies on a caps gate that names something else.
The leg now reports OK on the device: offered=3 imported=3 drawn=3 hostReadback=3 agreed=3 --
three self-allocated AHardwareBuffers from the display host imported as EGLImages by this side,
drawn into, and read back by the host at both the first pixel and the far corner.
Getting there took one measurement and one correction. The handle message is 256 bytes on this
path and recvHandleFromUnixSocket drains it exactly (pending 256 -> 100, then 0 -> 100, then
0 -> 0), so the description that follows it was never the problem; the comparison was. This leg
was checking the display host's messages against kT4Magic -- the dma-buf protocol's magic, which
belongs to the container peer -- while the host stamps AHB_BRIDGE_MAGIC on all three of its
messages. A perfect round trip was reported as 'the offer's sideband did not follow its handle',
which named the wrong cause and cost the time. kT4HostMagic is that value, and the leg now also
prints the handle's footprint and the bytes it actually read, so the next failure of this shape
cannot be mistaken for the other one.