Home/Blog/Pure-GPU Wayland capture
Engineering · June 9, 2026

How we built pure-GPU Wayland screen capture on NVIDIA.

Recording a Wayland screen on an NVIDIA GPU sounds like it should be solved. Doing it at the exact region the user picked, with no extra picker dialog, with the cursor intact, and without ever copying a frame through the CPU — that took a proper pipeline and one genuinely annoying detour.

Screenray's whole premise is that a recording should come out looking produced — automatic cursor zoom, a designed background, captions. All of that leans on two things from the capture layer: high-quality frames and a precise, live cursor position. On X11 that's straightforward. On Wayland — especially with NVIDIA's proprietary driver — neither comes for free.

The capture path: dma-buf to NVENC, no CPU in the loop

The first decision was how to get frames at all. The obvious route is xdg-desktop-portal, which is what most apps use. We don't — for one specific reason: on GNOME it forces a screen-share picker dialog and hands you a whole-monitor surface. For a tool where you've already chosen a precise region in our preflight window, that's a redundant consent dialog and an off-surface crop.

Instead we talk to GNOME's Mutter directly over D-Bus and call org.gnome.Mutter.ScreenCast's RecordArea(x, y, w, h) — the same API gnome-shell's own recorder uses. It captures exactly the rectangle, with no dialog and no coordinate guesswork.

From there, frames come out of PipeWire as dma-buf handles. The goal is to never touch system memory:

PipeWire dma-buf  (tiled, on the GPU)
   -> import as EGLImage
   -> de-tile in a GL pass
   -> hand to CUDA
   -> NPP converts RGBA -> NV12
   -> NVENC encodes H.264

Every step stays on the GPU. A couple of NVIDIA-specific gotchas cost us time: the buffers arrive tiled, and CUDA can't register a tiled EGLImage — so we blit to a linear RGBA8 image first. And you have to cuCtxSetCurrent before allocating any CUDA memory or constructing the encoder, or it fails in confusing ways. With those sorted, capture, color-conversion, and encode all run on the card.

Then we noticed the cursor was gone

The video looked great. The cursor wasn't in it.

On NVIDIA, the pointer is drawn as a hardware cursor — a GPU overlay composited at scanout, which the screencast pipeline simply never sees. We needed the cursor for two reasons: to draw it in the final video, and because its motion is what drives our automatic zoom. So we went looking for the cursor position. Four times:

  • X11 XQueryPointer over XWayland. Returns a frozen, constant position during a real recording — XWayland only tracks the pointer when it's over XWayland surfaces, not during normal movement over native Wayland windows.
  • Mutter screencast cursor metadata. We requested cursor-mode metadata and sent a correct cursor-meta request. The stream accepted it without complaint — and never delivered a single cursor packet.
  • RemoteDesktop-linked metadata. Linking a RemoteDesktop session to the screencast (the "proper" way to get input metadata) — same result: no cursor.
  • xdg-desktop-portal cursor metadata. The path OBS uses. Same outcome on this setup.

The common root cause: that hardware-cursor overlay. If the compositor never rasterizes the pointer into the stream, no amount of asking the stream for it will help.

The cursor isn't in the frames because it was never in the frames. It's a separate plane the GPU composites at the very end.

Reading the cursor straight off the DRM plane

If the pointer lives on a separate hardware plane, the honest fix is to read that plane. The Linux DRM/KMS subsystem exposes the cursor plane's position as the CRTC_X / CRTC_Y properties — and you can read them as a non-master client, without taking over the display. So that's what we do: sample the cursor plane directly and map its position into the recorded region.

It's exact, it's live, and — crucially — it leaves the hardware cursor exactly where it is. We never fall back to a software cursor or ask the user to disable HW cursors. The pointer in the recording is pixel-aligned with where it actually was.

Two pieces finished the job:

  • Clicks come from evdev on /dev/input, gated by a udev uaccess rule so no elevated privileges are needed — that's what powers the click ripples.
  • Cursor shape (arrow vs. I-beam vs. hand) is read by a tiny GNOME Shell extension that watches the compositor's cursor tracker and publishes the current shape over D-Bus, which the app polls.

The result

Full X11 parity on Wayland: the exact region you selected, no picker dialog, the hardware cursor intact, and a precise live cursor track feeding the automatic zoom. Screen, system audio, microphone, and webcam all line up. The frames never leave the GPU until they're an encoded H.264 stream.

None of this is visible in the product — and that's the point. You press a hotkey, pick a region, and get a clean recording that's ready for the editor to make beautiful.


Screenray is a $30 one-time purchase. Buy it to try the pipeline yourself, or read how recording works end to end.

Try it on your machine.

A GPU-first screen recorder for Linux — $30, one-time.

Buy Screenray — $30