diff --git a/docs/DRM.md b/docs/DRM.md index ac5d10d..1c67929 100644 --- a/docs/DRM.md +++ b/docs/DRM.md @@ -1,197 +1,269 @@ -# DRM/KMS Backend Notes +# DRM/KMS Backend -Evergreen reference for Hearthspace's display backends. This explains how the -native DRM/KMS path differs from the nested winit path, what state is shared, and -which native-backend constraints are intentional. +This document explains Hearthspace's display backend model: what changes when we +run nested inside another desktop versus when we own the display natively, what +DRM/KMS is responsible for, and which boundaries contributors should preserve. -## Nested vs Native +The policy is: -Hearthspace can run three display backends: +- Keep compositor behavior backend-neutral wherever possible. +- Use the nested winit backend for everyday development inside an existing + desktop session. +- Use the native udev/DRM/KMS backend when Hearthspace owns a seat, reads input + devices directly, and presents frames directly to displays. +- Treat native display ownership as an operating-system integration boundary, + not just another windowing mode. -- **winit:** nested development backend. Hearthspace is a Wayland/X11 client in - an existing desktop session. This is the default everyday path. -- **headless:** offscreen backend for command-socket screenshots and e2e tests. -- **udev/DRM/KMS:** native backend. Hearthspace owns a VT/session, reads input - through libinput, renders into GBM buffers, and presents directly with KMS. +## The Mental Model -| Concern | winit (nested) | udev/DRM/KMS (native) | -| -------------- | ------------------------------------ | ---------------------------------------- | -| Where it runs | A window inside another compositor | Bare VT/session, owns the display | -| Display output | Host compositor presents the window | Direct KMS commit to a connector/CRTC | -| Frame timing | Host decides; winit is calloop-driven | DRM vblank/page-flip events pace redraws | -| Input | winit translates host input | libinput reads evdev through session fds | -| Buffers | winit surface/framebuffer | GBM buffers exported as dmabufs | -| Screenshots | Renderer readback | Unsupported until native readback exists | +Hearthspace has one compositor core and multiple display backends. -## Runtime Selection +The compositor core owns: -Backend selection is explicit when requested and auto-detected otherwise: +- Wayland protocol handling. +- Window management, stacking, hit testing, and focus. +- Canvas and viewport state. +- Shell command handling. +- Shared scene construction and rendering logic. + +A display backend owns: + +- How pixels reach a display target. +- How input events enter the compositor. +- How frame timing is paced. +- How renderer buffers are acquired and submitted. +- Which OS resources need to be opened, paused, resumed, or released. + +Keep those concerns separate. A window drag, resize edge, popup, client commit, +or viewport animation should not need to know whether Hearthspace is nested in a +host compositor or running as the native compositor on a VT. + +## Backend Modes + +Hearthspace supports three display modes: + +- `winit`: nested backend for normal development. Hearthspace is a client of an + existing Wayland/X11 desktop and renders into a host-managed window. +- `headless`: offscreen backend for screenshots, command-socket tests, and e2e + automation. +- `udev`/DRM/KMS: native backend. Hearthspace owns a session, opens DRM and input + devices, renders into scanout-capable buffers, and presents directly to KMS. + +Backend selection is intentionally explicit when requested: ```text --headless -> headless --winit -> winit ---tty -> udev/DRM +--tty -> udev/DRM/KMS auto -> winit when WAYLAND_DISPLAY or DISPLAY is set, otherwise udev ``` -Native development commands: +Native smoke and triage commands live in `docs/NATIVE_TESTING.md`. -```sh -cargo run --features udev -- --tty -cargo run --no-default-features --features udev -- --tty --no-shell -``` +## Nested vs Native Ownership -See [NATIVE_TESTING.md](./NATIVE_TESTING.md) for VT smoke commands, log capture, -and native failure triage. +The nested backend borrows a display from another compositor. The native backend +owns the display stack directly. -Useful native safety exits: +| Concern | Nested winit | Native udev/DRM/KMS | +| --- | --- | --- | +| Display ownership | Host compositor owns outputs | Hearthspace owns KMS outputs | +| Presentation | Submit to a host window | Commit GBM buffers to CRTC/connector targets | +| Input | Host windowing system translates input | libinput reads devices through the active session | +| Frame pacing | Host/window events drive redraws | Vblank/page-flip completion paces redraws | +| Cursor | Host cursor presentation | Hearthspace-owned hardware/software cursor policy | +| Screenshots | Renderer readback | Requires native readback support | -- `Ctrl+Alt+Backspace` -- `Ctrl+Alt+Esc` -- `SIGINT` / `SIGTERM` -- `--exit-after-ms INTEGER` for timed VT smoke tests +This ownership difference is why native mode has more failure modes. It must +handle session activation, session pause, DRM hotplug, input-device access, mode +selection, scanout buffers, and page-flip completion. Nested mode delegates most +of that to the host desktop. -## Cargo Features +## Native Display Stack -The default feature set keeps native-only dependencies out of everyday builds: +DRM/KMS is the Linux kernel display API used for native presentation. -```toml -[features] -default = ["winit"] -winit = ["smithay/backend_winit"] -udev = [ - "smithay/backend_drm", - "smithay/backend_gbm", - "smithay/backend_libinput", - "smithay/backend_session_libseat", - "smithay/backend_udev", - "smithay/renderer_multi", -] -``` +The key concepts are: -Shared Smithay features remain on the dependency itself: `desktop`, -`renderer_gl`, and `wayland_frontend`. +- DRM device: kernel device node representing a GPU/display device. +- Connector: physical or virtual display connection, such as HDMI, DisplayPort, + or a VM display connector. +- CRTC: scanout engine that drives a mode on a connector. +- Mode: display resolution and refresh timing. +- Plane: scanout layer. The primary plane carries the main framebuffer; cursor + planes may carry hardware cursor buffers. +- KMS commit: request to present a framebuffer or update display state. +- Vblank/page flip: completion signal that a submitted frame reached scanout. -`renderer_multi` is enabled for native dmabuf import plumbing, but the first -native milestone still opens and renders through a single primary DRM device. +GBM and EGL/GLES sit beside DRM/KMS: -## Event Loop Model +- GBM allocates buffers that can be rendered into and scanned out. +- EGL creates the rendering context over the native device. +- GLES renders Hearthspace's scene into the acquired buffer. +- KMS presents that buffer to the selected output. -All backends share the same calloop-driven compositor loop: +Keep this split in mind: rendering produces pixels; KMS presents buffers; vblank +tells us when presentation completed. -- Wayland display fd dispatches client requests. -- The shell command socket accepts control commands. -- Backend event sources drive input/redraw timing. -- Post-dispatch maintenance imports pending dmabufs, advances idle/viewport - state, applies cursor changes, renders if needed, flushes clients, and cleans - popups/outputs. +## Session And Device Access -The native backend adds fd-driven sources for: +Native mode must not open input and DRM devices as arbitrary files. It uses a +seat/session manager so device access follows Linux desktop rules. -- `LibSeatSessionNotifier` for VT/session pause and activation. -- `UdevBackend` for DRM device add/change/remove events. -- `LibinputInputBackend` for keyboard/pointer events. -- `DrmDeviceNotifier` for page-flip/vblank completion. +The session layer is responsible for: -The native render path is vblank-paced. `App::request_redraw` marks state dirty; -if a KMS page flip is pending, the request is deferred until the vblank handler -clears the pending frame. Native animation pacing also wakes from vblank rather -than a timer. +- Granting access to DRM and input device fds. +- Telling Hearthspace which seat is active. +- Pausing device use when the VT/session is no longer active. +- Re-activating device use when the session returns. -## Native Backend Architecture +When the session pauses, native code should stop committing to DRM and stop +processing direct input. Wayland clients remain connected; the compositor is +paused at the display/input boundary, not logically torn down. -Native setup currently uses one selected primary DRM device: +When the session activates again, native code should re-open or reactivate +devices as needed, rescan connectors, recreate KMS surfaces if required, and +force a redraw. -1. Acquire a `LibSeatSession` and seat name. -2. Enumerate DRM devices through Smithay's udev backend. -3. Select `primary_gpu(seat)` and log secondary DRM devices as ignored for now. -4. Open the selected DRM node through the session. -5. Build explicit ownership records: - - `RenderNode`: DRM fd plus `GlesRenderer`. - - `ScanoutNode`: DRM fd plus `DrmDevice`. - - `KmsOutputSurface`: selected connector/CRTC/mode plus `GbmBufferedSurface`. -6. Enumerate connected connectors, choose the first usable target, and create a - Smithay `Output` from connector metadata. -7. Create a GBM allocator/device and EGL/GLES renderer backed by the DRM node. -8. Render with the shared `App::render_frame` into GBM buffers and queue KMS - commits through `GbmBufferedSurface`. +## Output Model -The render and scanout nodes are structurally separate even though they point at -the same DRM node today. That keeps the code ready for future render-node, -scanout-node, and per-output allocator ownership without enabling multi-GPU -behavior prematurely. +Wayland clients see `wl_output` globals. Native KMS sees connectors, CRTCs, and +modes. Hearthspace bridges those with compositor-owned output records. -## Output State +The output model should preserve these rules: -The compositor tracks outputs as an `OutputSet`: +- Client-visible output state is compositor state. +- KMS connector state is backend state. +- A KMS connector can update, disappear, or reappear without changing the rest of + the compositor model. +- Pointer bounds, shell bars, and output layout should be reconciled when output + geometry changes. -- One primary output backs the current KMS render target. -- Secondary connected connectors are advertised as Smithay `Output`s with - Wayland globals, but are not rendered to yet. -- Secondary outputs are laid out horizontally after the primary in connector - enumeration order. -- Existing secondary outputs update their mode/scale/location on connector - resync. -- Removed secondary outputs have their Wayland globals disabled. -- Absolute pointer mapping clamps against the aggregate logical output bounds. +Hearthspace's native path is intentionally staged: render through a selected +primary DRM device and primary output target first, while keeping client-visible +output state capable of representing connected secondary outputs. Future +multi-output rendering should preserve the same boundary: KMS owns scanout +surfaces; compositor output records own client-visible geometry. -Primary-output rebuild after the selected KMS target changes is still future -work. The first connected target remains the rendered output for now. +## Render And Scanout Ownership -## Dmabuf Feedback And Clients +Native rendering has two separate ownership roles, even when one implementation +backs both roles with the same DRM node: -Native dmabuf feedback uses the opened DRM node's `dev_t` as the main device. -The native renderer advertises its dmabuf import formats, and client dmabufs are -imported through the udev `GlesRenderer` before the next full redraw. +- Render ownership: EGL/GLES renderer, dmabuf imports, texture sampling, and + scene rendering. +- Scanout ownership: KMS resources, connector/CRTC/mode state, GBM surfaces, and + page flips. -Import failures should include enough detail to debug Mesa/GTK/client issues: -size, format, plane count, modifier presence, and node. +Keep those roles separate in the code model. That makes future work easier for +render nodes, scanout nodes, multiple GPUs, per-output allocators, and device +handoff. -The compositor keeps running after recoverable live errors from client dispatch, -client flush, frame callbacks, or render submission. Those errors are logged and -the next redraw is forced full where appropriate. Initialization failures remain -fatal. +The normal native frame flow is: -## KMS Damage Clips +1. Something marks compositor state dirty. +2. The event loop asks the native backend for a scanout buffer. +3. The shared renderer draws the scene for the output's logical render view. +4. The native backend queues the buffer for KMS presentation. +5. Page-flip/vblank completion marks that output ready for another frame. +6. Deferred redraws or animations schedule the next frame after completion. -Renderer-side damage tracking remains enabled and useful. It minimizes GLES -redraw work into the GBM buffer. +Do not submit endless native frames just because the event loop is awake. Native +mode should be paced by vblank/page-flip completion so it does not render ahead +of scanout. -Native KMS commits intentionally do **not** pass scanout damage clips today. -During Firefox/GNOME Calculator testing on the Parallels/virgl VM, forwarding -damage through Smithay's `GbmBufferedSurface::queue_buffer` produced repeated KMS -commit failures: +## Event Loop And Pacing -```text -Page flip commit failed on device Some("/dev/dri/card1") (Invalid argument (os error 22)) -``` +All backends share the same calloop-driven compositor loop. Native mode adds OS +event sources for: + +- Session pause/activation. +- DRM device hotplug and connector changes. +- Direct input through libinput. +- DRM page-flip/vblank completion. + +The event loop should remain the coordination point. It imports pending client +dmabufs, advances compositor state, applies cursor state, renders when dirty, +flushes clients, and cleans transient Wayland state. + +In native mode, redraw requests are dirty-state requests, not immediate page +flips. If a KMS frame is already pending, the backend should remember that more +work is dirty and submit the next frame after the current page flip completes. + +## Dmabuf Feedback + +GPU clients need to know which device and formats they should allocate buffers +for. Hearthspace advertises linux-dmabuf feedback based on the renderer backend. + +The rule is: client buffers should be compatible with the renderer that will +sample them. + +For native mode, the advertised main device comes from the opened DRM/render +device. The renderer's importable formats become the advertised dmabuf formats. +Client dmabufs are imported into the native renderer before they are composited. + +Import failures are recoverable client/rendering compatibility failures, not KMS +mode-setting failures. Logs should include enough detail to debug them: size, +format, plane count, modifier presence, and node. + +See `docs/RENDERING.md` for client-buffer readiness and dmabuf synchronization. + +## Damage Tracking And KMS Damage + +There are two different damage concepts: + +- Renderer damage: regions the compositor needs to redraw into its render target. +- KMS damage clips: regions the backend asks the kernel/display stack to treat as + changed for scanout. + +Renderer-side damage tracking is useful and should stay enabled. It reduces GLES +work when the scene only changes partially. + +KMS damage clips are a separate optimization and should be treated as optional. +Some virtual or driver stacks reject framebuffer damage clips during commits. If +KMS damage clips are unreliable on a device, the compositor should submit the +frame without KMS damage clips and keep running. + +Hearthspace's native policy is conservative: keep renderer-side damage, but do +not forward damage clips to KMS by default. Enabling KMS damage should be per +output/device and should fall back cleanly if a commit fails with an +unsupported-parameter error. + +## Failure Model -The likely failing path is Smithay converting renderer damage into -`PlaneDamageClips` / `FB_DAMAGE_CLIPS` and attaching that blob to the primary -plane commit. Until we have per-device fallback, native commits pass `None` for -KMS damage clips and submit full-plane scanout updates. +Native backend errors fall into two broad classes: -Future re-enablement should be per output/device: try clips, retry the same -frame once without clips on `EINVAL`, then disable KMS damage clips for that -output/device for the rest of the run. +- Initialization failures: required resources cannot be created, so native mode + cannot start correctly. +- Live/recoverable failures: a client import, frame callback, flush, hotplug + update, or individual render submission fails while the compositor can keep + running. -## What Stays Shared +Prefer keeping the compositor alive for live recoverable failures. Mark affected +state dirty, force a full redraw when appropriate, log enough context to debug +the device/client, and continue serving Wayland clients. -Almost all compositor logic is backend-neutral: +Session pause is not an error. It is expected OS behavior. During pause, DRM +commits and direct input should stop. During activation, device and output state +should be rebuilt enough to resume presentation. -- Wayland protocol handlers. -- Window management and hit-testing. -- Viewport geometry and animations. -- Idle daemon/window activity tracking. -- Shell command socket. -- Xilem shell integration. -- Shared `App::render_frame` over `GlesRenderer`. +Useful native safety exits are documented in `docs/NATIVE_TESTING.md` and include +the emergency keyboard chords plus `SIGINT`, `SIGTERM`, and timed exits. -Backend-specific code owns output creation, frame scheduling, renderer/buffer -acquisition, dmabuf main-device discovery, and input/event sources. +## Contributor Rules -## Reference +Use these rules when changing native display code: -Smithay's reference compositor anvil implements winit, x11, and udev/DRM -backends over one shared compositor state and remains the canonical model for -this structure. +- Keep compositor behavior backend-neutral unless the behavior is truly about OS + display/input integration. +- Keep session/device access behind the native backend. +- Do not let input handlers or window-management code submit KMS frames directly. +- Treat redraw requests as dirty state; let backend pacing decide when a frame is + submitted. +- Keep render ownership and scanout ownership separate even if a backend shares a + DRM fd between them. +- Keep renderer damage and KMS damage clips conceptually separate. +- Prefer recoverable handling for live native failures. +- Update `docs/NATIVE_TESTING.md` when changing native run commands, expected + logs, or failure triage. +- See `docs/CURSOR.md` for cursor-specific native presentation policy.