Commit Graph
4044 Commits
Author SHA1 Message Date
BZLZHH 7fd7f71d6c [Feature] (EGL): Wayland window surfaces, presented through wl_shm
A Wayland client's window is its own wl_surface, and what the compositor shows is the wl_buffer
attached to it - but the pixels of a MobileGL frame are drawn where the backend is, on a split
build in another process on another OS.  So far a wl_egl_window was taken for an X11 window and
its client-side address sent to the Android server (EGL_BAD_NATIVE_WINDOW at best), and under
glvnd a Wayland display never reached this vendor at all.

A window on a Wayland display - asked for as EGL_PLATFORM_WAYLAND, or a wl_display recognised by
its first word being wl_display_interface - is now a window surface in the EGL state at the
wl_egl_window's size with a pbuffer of that size behind it in the backend, whatever
MOBILEGL_IPC_SURFACE says.  At every swap the frame is read back out of the default framebuffer
through this library's own glReadPixels (the application's read framebuffer, pack buffer and pack
state put back as they were), its rows flipped and swizzled into a wl_shm buffer, and attached,
damaged and committed on the application's wl_surface.  Three buffers rotate on their release
events, dispatched on an event queue of the surface's own.  libwayland-client is loaded at run
time, so neither the build nor the library depends on it.  EGL_KHR_platform_wayland and
EGL_EXT_platform_wayland are announced on Linux.

Measured: glmark2-wayland runs its build scene at 13 FPS (400x300) and the window shows the frame
under kwin.  A window resize is not followed yet; the surface keeps its creation size.
2026-10-01 10:59:07 -04:00
BZLZHH 353daa265e [Fix] (Pipe): the records two families keep of their own are forgotten with the session too
MGPipeForgetAllPublications cleared the publication latch, but two death helpers do not ask the
latch: vertex elements ask the vertex-input emitter's RecordIsPublished and buffers ask the
resource tracker's WasPublished.  A VAO and its buffers destroyed with the GL context after the
split roles were down still found themselves published there, and glmark2's eglTerminate stopped
on the same assertion eglinfo's did (ResolveVertexElements, then ResolveResourceIn for a buffer).

Both records are now dropped beside the latch: the emitter's latches whole, the tracker's
Published flags.
2026-10-01 10:59:06 -04:00
BZLZHH 69728a4f9b [Fix] (Remote): the create window starts empty with each session
The window of creates whose answers are read later is process-global, and every entry names a
sequence number of the session that wrote it.  A process can run several sessions one after
another - eglinfo terminates one platform display and initializes the next - and an entry left by
the previous session made the new session's first create wait, in DrainCreateWindow, for a
sequence number the new server would not reach: the second session hung on its first texture
create.

InstallClientWireTables, which runs at the start of every session, now empties the window and the
create suspects; their answers and their objects died with the session that wrote them.
2026-10-01 10:31:20 -04:00
BZLZHH c6f828532a [Fix] (Pipe): nothing is published once the split session is gone
MobileGL::Destroy shuts the split roles down first and resets the GL context after, by design.  The
frontend objects that die in that reset still found their handles published, so their deletes were
routed to whichever applier was installed by then - the monolith one, which never had those
resources and asserted ("ResourceDestroy named {slot, gen}") from eglTerminate.  The abort also
threw away the program's buffered output, so eglinfo printed nothing but the assertion.

Every twin the server held died with the session, so ShutdownSplitRoles now clears the publication
latch (MGPipeForgetAllPublications): the deletes that follow are local bookkeeping only.
2026-10-01 10:31:20 -04:00
BZLZHH 5bb28140cb [Feature] (Remote): the TCP supervisor serves sessions side by side
A desktop is a compositor AND the GL programs run inside it, each a client with a session of its
own.  The TCP supervisor answered every authenticated Hello after the first with Refuse{Busy}, so
with kwin connected any other program - eglinfo, glxinfo, glmark2 from konsole - waited out its
Welcome and died on Fatal{CapsBeforeFirstSnapshot}.

The fork shape now keeps a list of live children and forks for a Hello while fewer than eight are
live.  What made it a single slot was the data connection: a DataBind names its session only by
the data nonce, which the child minted, so the supervisor could hand it to nothing but "the" child.
The nonce is now minted by the supervisor just before fork(); the child adopts it
(PresetDataNonceForThisProcess) instead of minting its own, and a DataBind goes to the child whose
nonce it presents.  Nothing on the wire changes.  The thread shape (the in-process display server)
still serves one session, Busy for a second as before.
2026-10-01 10:31:20 -04:00
BZLZHH bf7fc6bf6b [Fix] (DirectGLES): an imported external texture is attached as the host frame canvas
The arms that bind the display host's frame in place of an imported texture were gated on the
attach target being 0x8D65, but an external texture is attached through the GL_TEXTURE_2D upload
target, so they never ran.  The texture's twin on the server has no storage - the dma-buf behind it
never crosses - and kwin's framebuffers over it were incomplete (status 0x8cd7, every draw
GL_INVALID_FRAMEBUFFER_OPERATION), so nothing it drew landed anywhere.

The record arm (SyncAttachmentSurface) now recognises a texture point whose target is
TextureExternalOES and attaches the host frame canvas before any twin adoption or storage sync,
which have nothing to offer a texture with no storage; it takes the host frame first if none is held
yet.  The legacy object arm keys on the texture object's own target the same way.  Measured: all
four of kwin's daemon-buffer framebuffers complete, and the desktop it draws reaches the glass.
2026-10-01 09:47:32 -04:00
BZLZHH b72d4c5dff [Feature] (DirectGLES): a stable canvas in front of the host frames
Every present hands the frame back to the display host and takes a different one, and the frame's
renderbuffer is deleted with it.  A compositor attaches its framebuffers once (kwin: one per daemon
buffer, for the life of the process), so attachments bound to a frame's own renderbuffer named a
deleted object from the second frame on.

The backend now keeps a canvas - a renderbuffer and framebuffer of its own, made at the first
frame's size and kept for the session.  The host-frame globals (the default-framebuffer redirect,
the storage external attachments are bound to) name the canvas instead of the frame, and keep
naming it between a frame being handed back and the next one arriving.  PresentHostFrame copies the
canvas into the frame before its fence; the copy puts back the compositor's framebuffer bindings,
scissor test and rasterizer discard exactly as they were.  Without a canvas the frame itself stays
the draw target, as before.
2026-10-01 09:47:24 -04:00
BZLZHH c88c6ff196 [Fix] (Remote): the present record presents a host-framed surface
A split client's swap reaches the server as a present record, and ServerVerbSink::OnPresent only
ran the backend's Present - an eglSwapBuffers of the pbuffer the context was made with.
BackendObject::SwapEGLBuffers is the only route to PresentHostFrame and a split client never takes
it, so over TCP the display host was never told a frame was drawn: the app offered thousands of
frames and presented none.

BackendObject::PresentHostFrameIfCurrent presents the host frame when the current surface is
host-framed and is a no-op otherwise; OnPresent calls it after Present and before the credit goes
back, so a client released by the credit is released by a frame that is on its way to the glass.
2026-10-01 09:47:18 -04:00
BZLZHH 16488d5801 [Feature] (GL): glFlush presents the display host's frame for a compositor that never swaps
kwin draws the desktop into the display host's frame and then calls glFlush - and glFlush alone;
it never calls eglSwapBuffers.  The stream reached the server and the server drew, but the display
host was never told a frame was done, so nothing reached the glass.  When the surfaceless-bind hook
has taken the host frame, glFlush now swaps that surface after flushing the stream: that swap is the
host's "frame is done".

A swap of a surface the EGL state does not know used to return EGL_BAD_SURFACE without a word - the
whole reason the host frame's presents were invisible - so that refusal is now reported once.
2026-10-01 09:47:12 -04:00
BZLZHH c16a51da62 [Feature] (EGL): a surfaceless compositor takes the display host's frame surface
A compositor on the anland route never creates a window surface: it imports the display host's
dma-bufs, draws into them through framebuffers and binds its contexts with EGL_NO_SURFACE.  The
host frame bridge is only opened by a host-framed surface being created and activated, so with no
surface the bridge stayed shut and the display host offered its frames into a name nobody served.

So the first surfaceless bind of a real context, in a session that asked for host-framed surfaces,
makes the surface eglCreateWindowSurface would have made - registered in the EGL state AND created
through the backend's window-surface entry, which the remote backend routes to the host frame - and
every later surfaceless bind binds it, because the compositor rebinds with EGL_NO_SURFACE all the
time and a bind without it leaves nothing current to present.  Registering it in the EGL state is
not optional: a backend-only token was refused by the state (EGL_BAD_SURFACE) on every bind and
every swap.

Only the compositor takes the host frame.  Desktops are full of processes that bind with no surface
(konsole, plasmashell, kactivitymanagerd), and a second process taking it made the activation fail;
the compositor's launcher marks it with MOBILEGL_HOST_FRAME_CLIENT=1.
2026-10-01 09:47:06 -04:00
BZLZHH 08e45dc9c6 [Fix] (GLState): an external texture is a 2D texture that remembers it is external
GL_TEXTURE_EXTERNAL_OES had a target of its own but nothing behind it: the texture factory had no
row for it (the compositor died on "Unimplemented texture type" the moment it bound one), the
attach path had no upload target for 0x8D65 (validation failed, the attachment was never recorded
and the compositor read GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT), and nothing downstream
could tell an imported texture from an ordinary one.

An external texture is now a TextureObject2D created with the TextureExternalOES target, attached
through the 2D upload target - which the three framebuffer-texture validations accept for an
external object - and the target crosses the pipe (TexExternalOES maps back on the server) so the
render server can recognise it.  To the driver it stays a 2D texture: MapToBackendTextureTarget
folds it to Texture2D like the other targets ES does not have.
2026-10-01 09:46:57 -04:00
BZLZHH b2ce268c86 [Fix] (GLState): a global buffer target asked through the indexed lookup is a warning, not an abort
Only four buffer targets have per-index binding points (Uniform, TransformFeedback, AtomicCounter,
ShaderStorage).  A caller that asks GetBindingPoint for any other target is reading the wrong
accessor, not passing a bad enum - and kwin reached exactly that right after its vertex-attrib
setup, where the assertion killed the whole compositor with Trace/breakpoint trap.

The answer is now a one-time warning and index 0 of the indexed array: the caller gets a valid
slot, the global binding is untouched, and the mismatch stays visible in the log.
2026-10-01 09:46:46 -04:00
BZLZHH df58f21d83 [Feature] (EGL): the vendor answers what a compositor resolves, and the imported texture is complete
A compositor's bring-up is a fixed sequence of questions that all have to be answered by the same
library: which devices exist, which display a device serves, what its DRM node is, which platform
the display comes from, which configs exist, a context, a surfaceless make-current, the GL entry
points for that context, and finally the dma-buf import it draws with.  Every missing answer in that
list cost a run to find, and each is measured rather than assumed - an unanswered device query was a
call through a null pointer, an unanswered GL name left glvnd's dispatch table empty so glGetString
answered NULL through libGL while the same context answered through eglGetProcAddress, an unanswered
node string produced "couldn't find dev node for drm device", and a rejected
GL_TEXTURE_EXTERNAL_OES binding is why the framebuffer over an imported dma-buf was reported
incomplete.

The EGL side is whole now: the device-query family is published as real entry points with the DRM
node named, the dispatch hook answers GL names as well as EGL ones, and the swap-and-sync family the
compositor checks between its context and its first surface - EGL_KHR_fence_sync, EGL_KHR_wait_sync,
the damage variants, EGL_EXT_buffer_age - is implemented and announced.  A surfaceless context is
recorded as what it is rather than as a surface that does not exist.  These entry points log what
they are asked at DEBUG level, because the next bring-up should not need a disassembler.

GL_TEXTURE_EXTERNAL_OES is a target of its own (its TextureTarget, its pipe row, the GLenum mapping,
and the OES_EGL_image extension named), which is what lets the compositor's bind succeed at all; the
texture an imported image backs is marked backed, so a framebuffer built over it is complete.  The
client half of the import is new beside it: each plane's descriptor is described and shared with the
render server over the session transport, which is the change the server side has been waiting for.
2026-10-01 18:15:55 +08:00
gold 29a89cc10a [Fix] (gbm): the two defects only the Android build could find
Neither showed up in the Linux build that verified this library, and both are the kind that would
have been found by the first person to build the phone half - so they are fixed here rather than
left for that person.

bionic only declares memfd_create from API 30 and this library builds against 26. The kernel call is
far older than the declaration, so it is asked for by number: the same call with the name the
headers of this API level do not carry yet.

The library carries the tree's logger, which reaches Android's own log through liblog. The main
target gets that library in its own Android block; a second target built from the same sources needs
it for the same reason, and without it the link is what fails rather than anything in the code.
2026-10-01 08:23:04 +08:00
gold 8e3db2cb87 [Feature] (gbm): the server offers each taken frame to the container
The channel existed and had no callers.  Now the frame the server takes from the host is offered to
the container with the geometry the host decided, the apply loop consumes the releases the
compositor sends back, and the teardown that drops the host frame closes the channel.

Every call is on the optional path: with no container the render path is what it was.
2026-10-01 03:42:18 +08:00
gold 1ba7b1a96f [Feature] (gbm): the server sends each frame's dma-buf fd to the container
The container's GBM hands out buffers that are the display host's frames, and this is the phone end
of that: the server takes a frame from the host, offers its descriptor to the container over a unix
socket at a path both sides can reach, and takes the release back when the compositor is done with
it.

The channel is optional and silent by design. A compositor must render whether or not a container is
listening, and the render path must never wait on a socket - so it is opened lazily, every send is
bounded, and any error simply marks it down.
2026-10-01 03:37:50 +08:00
gold 1a7b4b6a2b [Feature] (gbm): the host-frame backend - the compositor's buffers are the phone's frames
The self-allocated backend makes the library stand on its own; this one makes it the thing the
project needs.  The display host on the phone owns a pool of frames, the render server draws into
them with Espryt and acks, and the host presents.  A gbm_bo created here is one of those frames: the
phone sends its dma-buf fd over a unix socket, the offer carries the geometry the host decided (not
the geometry the caller asked for), and destroying the bo sends the frame back.

Verified against a fake display host on this machine, with no phone involved: the buffer the fake
host fills is the buffer the bo maps - every pixel of the pattern is there - the fd the caller is
handed is a view of that same memory rather than a copy, a device with no frame waiting falls back
to allocating locally, and teardown closes exactly the descriptor it opened.  The self-allocated
path still passes its own test unchanged.

The channel is lazy and never blocking: a compositor must work whether or not the phone is there.
2026-10-01 03:27:22 +08:00
gold 55e5165e2f [Feature] (gbm): MobileGL's own GBM, so the compositor's buffers are the display host's frames
kwin never creates an EGL window surface.  Its undefined libgbm symbols are seventeen entry points
whose whole purpose is to hand out gbm_bo fds, and those fds are what Anland's display producer
sends to the daemon on the phone - so the question "whose pixels are on the screen" is answered by
"who allocated the bo", and today that is Mesa.  The client can be loaded, initialized and rendering
through glvnd with no window surface ever created, which is exactly what was measured.

This is a complete GBM: every public entry point of the vendored Mesa header, with the semantics a
compositor relies on - fds the caller owns, real mmap with refcounting, a swapchain that refuses to
hand out a busy buffer, import by fd, and teardown that leaves no descriptor or mapping behind.  The
buffers come from the display host's frames; where there is no host, the device allocates from the
dma-buf heap, falling back to an uncached heap and then to memfd, so the library stands on its own.

The vendored gbm.h is Mesa's, verbatim: the entry points are the ABI, not our idea of it.
2026-10-01 03:16:55 +08:00
gold 2860d09051 [Fix] (EGL): a second display is served, and glvnd gets its answers from one table
Two defects, both found by asking the client directly rather than by reading its log.

The backend refused to initialize any EGLDisplay but the first - "already bound to a different
EGLDisplay" - and the field it compared against has no reader anywhere else in the tree: the domain
rule was the whole of the rule.  What it cost was a compositor, which asks for the display it will
draw into AFTER probing another one.  The probe succeeded, the real display was refused, and the
caller reported the refusal one layer up as "no usable DRM render device" - a message that names
where it stopped, not why.

The second: glvnd asks a vendor for entry points from two places.  getProcAddress is how it builds
its own static EGL table, and getDispatchAddress is how it answers eglGetProcAddress for every name
it does not know itself - which is every extension entry point, the device queries among them.  This
side answered the second from a GL-only table, so an application asking for eglQueryDeviceStringEXT
got a null for a function the vendor implements.
2026-10-01 02:58:06 +08:00
gold c9206dd955 [Feature] (EGL): MobileGL serves EGL through libglvnd
On a system with libglvnd the libEGL.so.1 an application links is a dispatcher, not an
implementation, and the supported way to be the implementation it talks to is to be a vendor
library it loads and routes to.  On this machine it is also the ONLY way that reaches a caller which
does not link EGL directly: kwin goes through libepoxy, which dlopen()s libEGL.so.1 and dlsyms its
entry points at startup, so a library injected with LD_PRELOAD is invisible to it however loudly it
is named - measured, with the client in kwin's maps and not one call into it.

So this is the vendor half of that ABI: __egl_Main answers glvnd with the entry points by name, the
platforms this implementation serves, and the device it is associated with - the render node of the
machine, because that is what lets a compositor match its own node to the display it is about to
draw into, and because glvnd drops a vendor that does not answer at all.

Two details are the difference between a vendor that is loaded and a vendor that is used.  glvnd
requires setDispatchIndex to exist even when a vendor has nothing to record, which this one has -
its entry points are the implementation, they never re-enter glvnd.  And a vendor must hand over ITS
OWN entry points: with default visibility a reference inside this library resolves against the whole
process, where another libEGL that loaded first wins.  That is what -Bsymbolic-functions is for;
declaring the entry points protected says the same thing but does not compile against the platform
headers, which declare them default.
2026-10-01 02:58:06 +08:00
gold dfbda7ccb4 [Feature] (EGL): the platform and device extensions are one list, spelled once
A vendor string and an eglQueryString answer have to agree, and they are read by two different
parties: the system's libglvnd reads the first to decide which vendor a platform request should be
routed to, and an application reads the second before it decides to make that request at all.  Kept
apart they drift, and the drift has a signature - a library that is loaded, initialized, and never
called, while the caller reports that it found no provider for a function it never reached.

The list gains the device family for the same reason: a compositor asks which devices exist and
reads the DRM node off one before it asks for a display on it, so an implementation that answers
"no devices" is one it cannot use no matter how well the implementation draws.
2026-10-01 02:58:06 +08:00
gold 6a702afa72 [Chore] (glvnd): vendor the EGL vendor ABI headers
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.
2026-10-01 02:58:06 +08:00
gold ab685fb0e9 [Feature] (plugin): the server survives a dozing phone - a notification the user allows and a battery exemption the user grants
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.
2026-10-01 01:50:14 +08:00
gold 1ae7a8eaa4 [Feature] (disaggregated): WindowKind::HostFrame - the host-framed surface crosses the wire
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.
2026-10-01 01:26:02 +08:00
gold ead7a10b68 [Feature] (disaggregated): the default framebuffer on a host-framed surface is the host's frame
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.
2026-10-01 01:14:51 +08:00
gold 035f1b07ae [Feature] (disaggregated): MOBILEGL_IPC_SURFACE=host - the display host owns the screen and the frames
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.
2026-10-01 01:11:21 +08:00
gold c7ab618ab0 [Feature] (disaggregated): the host's frames as a draw target, and the socket they arrive on
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.
2026-10-01 00:57:14 +08:00
BZLZHH 2e43df6125 [Docs] (disaggregated): the container as a client of the phone's on-screen server, and the two things that cost a run
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.
2026-10-01 00:39:49 +08:00
BZLZHH 015b91f08d [Test] (extmem_probe): T4-host-image closes the round trip, and its first verdict was wrong for one constant
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.
2026-10-01 00:34:40 +08:00
BZLZHH 987b7ad13c [Test] (extmem_probe): T4-host-image is the other end - the buffer the display host is about to scan out, imported and drawn into, with the round trip closed by that host reading its own pixels back. 2026-09-30 23:53:49 +08:00
BZLZHH d26cf1e31b [Test] (extmem_probe): T4 asks the wrap question too - a driver without EGL_EXT_image_dma_buf_import is tried through AHardwareBuffer_createFromHandle before the offer is called untakeable, and both handle types are recorded so the answer names which one the platform reads. 2026-09-30 23:26:26 +08:00
BZLZHH 8196818e9f [Test] (extmem_probe): the T4 leg asks whether the render server can draw into an image allocated outside Android - and the device answers twice: this driver has no dma-buf import, Android-side allocation works end to end, and the offer must carry the plane layout rather than one linear plane. 2026-09-30 23:00:55 +08:00
swung0x48 648800a2da [Docs] (P8): close the phase - ruling ID-P8-19 (the exit device check and the P12 cross-host rerun), the P8 README and PLAN, the roadmap row and header, MEASUREMENTS row 19, the status page and the last debt 2026-09-30 08:25:38 -04:00
swung0x48 b4bee9dd38 [Docs] (P8): CONTRACT-P8 (the rules P8 landed, each with its code anchor and its test), rulings ID-P8-14..18 (MD + MF and the create-indirect fixture's alignment, SE, SV, the dev merge, the wave 2 integration gate) and the debts wave 2 hands on 2026-09-30 07:55:50 -04:00
swung0x48 494305381e [Merge] (P8): integrate wave 2 package MF - the push flush ladder gets dev's Adreno 830 queued-ring-copy guard (FlushPendingRangesFrom, create-instancing x Espryt x monolith 0.870 -> 0.99998 on the Redmi), and both G5 scripts pin the two reviewed ladder bodies under ID-P8-14 2026-09-30 07:12:50 -04:00
swung0x48 fb106a682e [Docs] (P8-MF): the package note - the push-ladder mirror of dev f973008c, create-instancing Espryt monolith 0.870 -> 0.99998 on the Redmi with spawn unchanged, why spawn was unaffected (the same two partial maps behind queued ring copies, the copy already submitted by the server's intervening whole-buffer maps), the red-onces and the gate 2026-09-30 07:09:28 -04:00
swung0x48 c8c4397475 [Merge] (P8): integrate wave 2 package SV - Magma's 3D native mip blit uses one layer, the depth/stencil mip decline gets its tally row, 1D depth mips decline by name, and the wire arm issues glDispatchComputeIndirect natively
# Conflicts:
#	MobileGL/MG_IntegrationTest/CMakeLists.txt
2026-09-30 06:11:04 -04:00
swung0x48 9c24a14d2b [CI] (scripts, P8-MF): pin G5's tenth row as well and re-pin both flush ladders on the reviewed post-fix bodies (ID-P8-14) - dev f973008c moved FlushPendingRangesNow past every base ref CI passes (the Adreno 830 queued ring copy) and ac9ab078 mirrors it into FlushPendingRangesFrom; both scripts' self-tests now run the pin-precedence and one-token controls on both ladders 2026-09-30 05:45:10 -04:00
swung0x48 ac9ab078cc [Fix] (Espryt, P8-MF): the push arm's flush ladder keeps a partial range-invalidating map behind an upload-ring copy still queued on the store, as dev f973008c's pull arm does - FlushPendingRangesFrom's ring tier stamps ringCopyRetireSerial and InvalidateFlushAccessFor refuses tier 1 for a partial range while that copy is unretired (the whole-buffer orphan-map stays exempt); Adreno 830 loses the queued copy and create-instancing on the monolith arm drew terrain sections empty 2026-09-30 05:43:55 -04:00
swung0x48 377d90d9f1 [Merge] (P8): integrate wave 2 package SE - the spawn/tcp default framebuffer publishes its surface to the client, the CPU mip arm declines on a driver-written base, D's case 3 runs on the Espryt split arms, the unmigrated-emulation name list is linted against its call sites, and the P8 notes' citations point at their content again 2026-09-30 05:24:11 -04:00
swung0x48 4cfb29a553 [Docs] (P8-SV): the note's gate table (G1 0/0, wire-declines 61/66, split_coverage, every lane, retrace 230/236) and the dirty-surface scan finding 2026-09-30 05:02:37 -04:00
swung0x48 51e95c9260 [Docs] (P8-SE): the note's gate and retrace readings 2026-09-30 04:47:27 -04:00
swung0x48 982958ebb1 [Test] (P8-SV): rename WireIndirectDispatchScenario's dispatch store member m_parameters -> m_groupCounts - gen_pipe_dirty_surface.py reads writes to shutter member names across all of MobileGL, and the name made NEW_PATCH_STATE's shutter look written outside its roots (self-test negative control 16 stopped tripping) 2026-09-30 04:42:06 -04:00
swung0x48 3717424229 [Merge] (P8): merge dev f973008c - the Espryt partial-flush fix for a queued ring copy on Adreno 830 (wave 2 MD), and dev's empty-binding uniform block fix deduplicated against P11's 7784d6bb (one code path), UnboundUniformBlockScenario registered on the split arms 2026-09-30 04:34:15 -04:00
swung0x48 f973008c4d [Fix] (Espryt): a partial buffer flush no longer takes the range-invalidating map while an upload-ring copy into the same store may still be queued - Adreno 830 loses the queued copy, and create-instancing drew whole Embeddium terrain sections empty 2026-09-30 04:07:41 -04:00
swung0x48 edcb9328e2 [Docs] (P8-SE): cite the integration CMakeLists by path - the bare basename resolves to the top-level file 2026-09-30 03:55:20 -04:00
swung0x48 e61060fbdf [Docs] (P8-SE): the package note, and the P8 package notes' line citations re-anchored by content at HEAD (A 14, B 29, C 10, D 12, E 18 plus the copy-image-shadow-mirror line, F 4) 2026-09-30 03:55:01 -04:00
swung0x48 3ce8ce3cb1 [Test] (P8-SE): the ForcedDs knob lanes run the default-framebuffer depth/stencil case again on all three arms (it was subtracted while red on spawn / tcp) 2026-09-30 03:55:01 -04:00
swung0x48 394d255487 [Docs] (P8-SV): the package note - the four items with their before/after, the design, the gates, ten red-once rows, the monolith observation and what was found but not fixed 2026-09-30 03:45:40 -04:00
swung0x48 0222852b94 [Fix] (Espryt, P8-SE): a spawn / tcp server publishes the default framebuffer's depth/stencil format (it has no frontend placeholder; the case leaves pending-fix for the three arms); the server CPU mip arm declines by name (generate-mipmap-store-declined) instead of filtering a store the driver wrote over, and E's refused-readback knob reaches it (CpuMipRefused. entries); WireIndirectDrawScenario case 3 runs on the Espryt split arms; copy-image-shadow-mirror goes and unmigrated_emulation_sites.py holds PipeCatalogueTest's list to the call sites 2026-09-30 03:44:29 -04:00