Skip to content

Kernel Graphics: DRM/KMS, GBM, EGL & dma-buf

The Linux graphics stack runs from kernel DRM/KMS (modesetting and buffer management) through GBM (buffer allocation) and EGL (GPU context) to the compositor. This article covers the full display pipeline, atomic modesetting, dma-buf zero-copy sharing, and the lifecycle of a frame from render to vblank.

Display Pipeline

graph LR
    FB["Framebuffer<br/>(pixel data)"] --> PLANE["Plane<br/>(scaling, format)"]
    PLANE --> CRTC["CRTC<br/>(scanout engine)"]
    CRTC --> ENC["Encoder<br/>(signal conversion)"]
    ENC --> CONN["Connector<br/>(HDMI, DP, virtual)"]
Object Role
Connector Physical port (HDMI-A-1, DP-1). Reports EDID, connection status.
Encoder Converts CRTC pixel stream to connector signal (TMDS, LVDS).
CRTC Scanout engine -- reads planes, drives timing (mode lines).
Plane Image layer. Primary (main fb), cursor, overlay (video HW scaling).
Framebuffer Handle wrapping a GEM/dma-buf with format + dimensions + pitches.

Atomic Modesetting

Legacy KMS set properties one-at-a-time (flickering, tearing). Atomic modesetting commits all changes in a single drmModeAtomicCommit():

drmModeAtomicReq *req = drmModeAtomicAlloc();

/* Set plane framebuffer and position */
drmModeAtomicAddProperty(req, plane_id, prop_fb_id, fb_id);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_id, crtc_id);
drmModeAtomicAddProperty(req, plane_id, prop_src_w, width << 16);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_w, width);

/* TEST_ONLY first to validate without applying */
int ret = drmModeAtomicCommit(fd, req,
    DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
if (ret == 0) {
    /* Commit for real, non-blocking with page-flip event */
    drmModeAtomicCommit(fd, req,
        DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK, userdata);
}
drmModeAtomicFree(req);

Key flags: TEST_ONLY (validate), NONBLOCK (don't stall), ALLOW_MODESET (permit mode changes that may blank the display briefly).

GBM Buffer Allocation

GBM (Generic Buffer Manager) allocates GPU buffers backed by GEM objects.

gbm_surface is legacy

The gbm_surface / gbm_surface_lock_front_buffer API is now considered legacy. KWin ported away from it in 2023, and wlroots uses explicit gbm_bo allocation + direct drmModeAddFB2. New compositors should allocate gbm_bo objects directly rather than going through gbm_surface.

struct gbm_device *gbm = gbm_create_device(drm_fd);
struct gbm_bo *bo = gbm_bo_create(gbm,
    1920, 1080, GBM_FORMAT_XRGB8888,
    GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);

/* Get dma-buf fd for zero-copy sharing */
int dmabuf_fd = gbm_bo_get_fd(bo);
Usage flag Meaning
GBM_BO_USE_SCANOUT Buffer can be scanned out by CRTC
GBM_BO_USE_RENDERING Buffer can be GL render target
GBM_BO_USE_LINEAR Force linear layout (needed for screencopy/VNC)

EGL Platform Setup

EGL binds OpenGL ES to a native display (GBM, Wayland, or X11):

/* For a compositor rendering on DRM/GBM */
EGLDisplay egl_display = eglGetPlatformDisplay(
    EGL_PLATFORM_GBM_KHR, gbm_device, NULL);
eglInitialize(egl_display, &major, &minor);

EGLConfig config;
eglChooseConfig(egl_display, attribs, &config, 1, &n);

EGLContext ctx = eglCreateContext(egl_display, config,
    EGL_NO_CONTEXT, context_attribs);

/* Create EGLImage from dma-buf for texturing client buffers */
EGLImage img = eglCreateImage(egl_display, EGL_NO_CONTEXT,
    EGL_LINUX_DMA_BUF_EXT, NULL, img_attribs);

wlroots does all of this internally. The compositor author uses wlr_renderer and never touches EGL directly.

dma-buf Zero-Copy Sharing

graph LR
    CLIENT["Client (Chromium)"] -- "dma-buf fd via<br/>wl_drm / linux-dmabuf" --> COMP["Compositor (Sway)"]
    COMP -- "dma-buf fd via<br/>wlr-screencopy" --> VNC["WayVNC"]
    CLIENT -- "same GPU<br/>memory" --> GPU["GPU Buffer<br/>(GEM object)"]
    COMP -- "EGLImage import" --> GPU
    VNC -- "mmap or GL read" --> GPU

A dma-buf is a file descriptor referencing a kernel buffer. Any process with the fd can map or import it -- zero copies between client, compositor, and VNC capture.

Key properties: fds are passed via SCM_RIGHTS over Unix sockets. Metadata (format, modifier, stride) travels alongside in the Wayland protocol.

If you know Unix pipes

dma-buf fds are like pipes but for GPU memory. Same fd passing (SCM_RIGHTS), but the kernel guarantees DMA-capable physical pages and zero-copy access across devices.

If you build OCI images

Building a custom Linux distro is like writing a Dockerfile for the whole machine. Base image = debootstrap. Layers = overlayfs. Registry = OSTree repo.

Direct Scanout Optimization

When a single client covers the entire output with a compatible buffer:

  1. Compositor detects 1:1 coverage, no transforms, supported format/modifier.
  2. Atomic commit with the client's buffer directly on the primary plane.
  3. No compositing pass -- GPU renders directly to display.
  4. Saves one full-screen copy per frame (critical at 4K 60Hz).

wlroots wlr_scene implements this automatically via wlr_scene_output_commit().

Frame Lifecycle

sequenceDiagram
    participant App as Client
    participant Comp as Compositor
    participant KMS as DRM/KMS

    App->>Comp: wl_surface.commit(dma-buf)
    Comp->>Comp: Scene graph damage check
    Comp->>Comp: Render (or direct scanout)
    Comp->>KMS: drmModeAtomicCommit(NONBLOCK)
    KMS->>KMS: Wait for vblank
    KMS->>Comp: Page-flip event
    Comp->>App: wl_callback.done(timestamp)
    Note over App: App renders next frame
  1. Client commits a buffer (dma-buf or shm).
  2. Compositor checks damage, composites if needed.
  3. Atomic commit queues the framebuffer for next vblank.
  4. Kernel flips at vblank, fires page-flip event.
  5. Compositor sends frame callback to client -- client may now render again.
  6. Old buffer released back to client.

This ensures no tearing (vblank sync) and no over-rendering (frame callbacks throttle the client).

Expert insight

"Our DRM testing is seriously lacking." -- Linus Torvalds, 6.8 release cycle, on the need for better CI coverage of kernel graphics drivers.

What's new (2025–2026)
  • Rust DRM bindings: rvkms (virtual KMS driver in Rust) merged upstream. Nova (NVIDIA) and Asahi (Apple) GPU drivers written in Rust are in active development.
  • Dave Airlie (DRM maintainer): "about a year away" from requiring Rust for new DRM drivers.
  • DRM panic handler merged in 6.12 -- displays QR codes on kernel panic for easy bug reporting. Written in Rust by Jocelyn Falempe (Red Hat).

Glossary

DRM (Direct Rendering Manager)
Kernel subsystem managing GPU hardware: modesetting, buffer management, command submission.
KMS (Kernel Mode Setting)
DRM subsystem controlling display outputs: modes, CRTCs, planes, connectors.
CRTC
CRT Controller -- scanout engine that reads pixel data from planes and drives display timing.
GBM (Generic Buffer Manager)
Userspace library allocating GPU buffers. Mesa's abstraction over driver-specific GEM ioctls.
GEM (Graphics Execution Manager)
Kernel buffer object API. Each driver implements GEM (i915, amdgpu, etc.).
dma-buf
Kernel framework for sharing buffers between devices/processes via file descriptors.
EGLImage
EGL handle wrapping a dma-buf for use as a GL texture or renderbuffer.
Atomic commit
Single ioctl updating all display properties at once, avoiding intermediate states.
Page flip
Swapping the displayed framebuffer at vblank. Kernel signals completion via DRM event.