Expand description
DRI3 zero-copy X11 capture: the X server blits the screen into GPU buffers this process allocated, and the hardware encoder reads them where they lie.
It serves a server whose screen lives on the GPU (XLibre’s Xvfb with glamor, an Xorg on a
DRM driver), where the XShm path in super would read the screen back into host memory
for the hardware session to upload again. A small pool of dmabufs is allocated through GBM
on the render node the server draws with, each is wrapped in an X pixmap
(PixmapFromBuffers), and every frame is one CopyArea from the root window into the next
pixmap, which glamor runs as a GPU-to-GPU blit. The encoder then imports the dmabuf in place,
through the same FrameEncoder::encode_dmabuf the Wayland zero-copy path uses, so a frame
is never touched by the CPU between the screen and the bitstream.
Completion is waited for with a one-pixel GetImage of the destination pixmap: the server
answers only after the blit it ordered before has landed, so the encoder never reads a
half-copied frame, and no fence object or implicit-sync assumption is involved.
Where the server offers SELKIES-SEMAPHORE (the images’ Xvfb) and the encoder can wait on a GPU semaphore (NVENC), the server signals one the encoder made after each blit instead, and the encode is queued at once behind a wait on it. Under a GPU-bound client each GPU job that has to wait for the CPU to submit it waits a draw of that client for its turn, so the GetImage cost two draws a frame where the semaphore costs one: an uncapped 72 fps client streamed at 36 fps with the GetImage. A checked Signal request is the round trip the wait follows, which answers once the server has queued the signal, not once the GPU ran it: a wait queued before the server’s signal lost its turn as well.
Nothing here reads pixels, so what stands in for the XShm path’s per-stripe content hashing is
the Damage extension on the root window: the server’s report is both what says a frame is worth
encoding and what ends the wait for one, within the pacing crate::pace keeps, so a change is
published as it lands rather than on the next tick. Its region says how much changed, so a
blinking caret stays a small change and a still screen is cleaned up around it. The XFixes
cursor is composited by the server through Render, on the GPU, so the capture_cursor overlay
costs no readback either; the server keeps its own software cursor out of a copy from the
screen, so the cursor is never drawn twice. Moving that cursor damages nothing on the root,
so while it is captured the pointer is read on every tick and a move over a still screen is a
change of its own, where the cursor was drawn and where it now goes
(cursor_motion_rects); otherwise the stream’s cursor would move only with something else
drawn.
Everything that can be checked is checked before a frame is delivered, and the path is declined otherwise: a codec no hardware engine serves, software encoding, a server without DRI3, Damage, or Render, a server drawing on a GPU other than the encode node’s, a buffer the server will not import, or a first frame the encoder cannot read. A watermark is not among them: it is composited by the server through Render, as the cursor is. The capture then runs the XShm path with nothing half-built.
A DRI3 1.0 server (the NVIDIA X driver before 595) takes no layout with a buffer, so the pool is linear there and each buffer goes over with its stride alone.
Structs§
- Cursor
Sprite 🔒 - The XFixes cursor image uploaded once per cursor change as an ARGB pixmap the server composites over each frame.
- GpuBuffer 🔒
- One dmabuf the server blits into: the GBM allocation, the encoder-side description of it, and the pixmap and Render picture the server knows it as.
- GpuCapture 🔒
- A running zero-copy capture, on the thread that owns every context in it.
- Sprite 🔒
- An ARGB image the server composites over each frame through Render: the XFixes cursor, re-uploaded whenever it changes, or the watermark, uploaded once.
- XScreen 🔒
- The X side of the capture: the private connection and the server objects the blit needs.
Constants§
- GEOMETRY_
POLL_ 🔒FRAMES - Frames between framebuffer-size polls, matching the XShm path’s geometry cadence.
- MAX_
X_ 🔒FAILURES - Consecutive X request failures before the capture reports itself dead: the server is gone rather than momentarily late, and the Python watchdog restarts the display.
- POOL_N 🔒
- GPU buffers the screen is blitted into, used round-robin so an encoder still reading the previous frame never has the next blitted over it.
- SEMAPHORE_
EXTENSION 🔒 - The images’ Xvfb extension for GPU semaphores shared with the screen’s renderer.
- UNDRAWN_
CURSOR_ 🔒REACH - Half the side of the box a cursor not drawn yet is taken to fill around the pointer: an image up to that size, its hotspot anywhere in it, lies within the box.
Functions§
- cursor_
motion_ 🔒rects - Where on the root a cursor moving from
fromtotochanged the frame: the rectangle it was drawn in (drawn, root coordinates) and the same rectangle moved with the pointer, or boxes around both positions where it was not drawn or the pointer’s earlier place is unknown. The exact rectangle the grab then draws is added to the frame’s change beside these. - declined 🔒
- Why the DRI3 path was not taken, for the one line that says so.
- device_
of_ 🔒fd - The DRM device a file descriptor names, as the kernel numbers it.
- intel_
compressed 🔒 - Whether
modifieris one of Intel’s compressed (CCS) layouts. The plane count does not tell: DG2 and later keep the compression state outside the buffer, so theirs are single-plane, and the media driver reads such a buffer as if it were uncompressed. Intel’s uncompressed layouts are the few named here; every other one it defines is a CCS variant. - open 🔒
- Build the zero-copy capture for these settings, or report why it cannot serve them.
- prune_
modifier 🔒 - Take
chosenout of the modifiers the next allocation may pick. A choice the list never named came from the driver itself, so the whole list goes and the driver picks again unconstrained. - run_
capture - Run the zero-copy X11 capture until
controls.stopis set, or report that it cannot start. - same_
gpu 🔒 - Whether two DRM nodes belong to one GPU: an Xorg whose glamor runs on the modesetting driver hands DRI3 clients its primary node, where the encoder opens the render node.
- x_err 🔒