atagen

monolith

public
Star0
Watch0
ce349838db85f88a96287cd1aa940b5311057744
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: research/round2-reports.md

57f44a63d99fatagen2026-10-16 05:34 UTC1
# Round 2 research reports
57f44a63d99fatagen2026-10-16 05:34 UTC2
57f44a63d99fatagen2026-10-16 05:34 UTC3
Raw reports gathered while the tag/layout/animation design is being hardened. Paths prefixed `smithay/` and `niri/` refer to shallow clones at HEAD 2026-10-15 (smithay `023b78b`, niri `45739bc`).
57f44a63d99fatagen2026-10-16 05:34 UTC4
57f44a63d99fatagen2026-10-16 05:34 UTC5
Status
57f44a63d99fatagen2026-10-16 05:34 UTC6
57f44a63d99fatagen2026-10-16 05:34 UTC7
- [x] Smithay and niri internals
57f44a63d99fatagen2026-10-16 05:34 UTC8
- [x] NVIDIA driver state
57f44a63d99fatagen2026-10-16 05:34 UTC9
- [x] ext-workspace and decoration semantics
57f44a63d99fatagen2026-10-16 05:34 UTC10
- [x] Protocol inventory
57f44a63d99fatagen2026-10-16 05:34 UTC11
57f44a63d99fatagen2026-10-16 05:34 UTC12
---
57f44a63d99fatagen2026-10-16 05:34 UTC13
57f44a63d99fatagen2026-10-16 05:34 UTC14
## 1. Smithay and niri internals
57f44a63d99fatagen2026-10-16 05:34 UTC15
57f44a63d99fatagen2026-10-16 05:34 UTC16
### Smithay
57f44a63d99fatagen2026-10-16 05:34 UTC17
57f44a63d99fatagen2026-10-16 05:34 UTC18
**1.1 Tearing.** No async flip or tearing control. No match for `PAGE_FLIP_ASYNC`, `ASYNC`, `tearing_control` or tearing-control-v1 anywhere under `smithay/src`. The only atomic flags used are `ALLOW_MODESET`, `TEST_ONLY`, `PAGE_FLIP_EVENT` and `NONBLOCK` (`smithay/src/backend/drm/surface/atomic.rs:830`, `:900`, `:943`). Page flips go through `DrmSurface::page_flip` (`smithay/src/backend/drm/surface/atomic.rs:868`). `DrmCompositor` submit picks commit versus page_flip (`smithay/src/backend/drm/compositor/mod.rs:2541`). The narrowest patch point is `page_flip` plus the `FrameState` commit helpers (`smithay/src/backend/drm/compositor/mod.rs:697`), with a new `FrameFlags` bit plumbed from `render_frame` (`:1686`) into the try_assign paths. A custom commit path would also need drm-rs `AtomicCommitFlags` to gain an ASYNC bit first.
57f44a63d99fatagen2026-10-16 05:34 UTC19
57f44a63d99fatagen2026-10-16 05:34 UTC20
**1.2 Deferred submit in `Frame::finish`.** Allowed by design. `Frame` is defined at `smithay/src/backend/renderer/mod.rs:270`, with `wait` at `:349` and `finish` returning `SyncPoint` at `:362`. `OutputDamageTracker::damage_output_internal` (`smithay/src/backend/renderer/damage/mod.rs:475`) only calls `geometry`, `src`, `opaque_regions` and `damage_since`, so there is no GPU readback between draws. `render_output_internal` draws elements, finishes once and returns the sync in `RenderOutputResult`. `DrmCompositor::render_frame` binds the swapchain dmabuf and calls `render_output` (`smithay/src/backend/drm/compositor/mod.rs:2241`), then stores the result sync into the plane config (`:2316`, `:2337`). `GlesFrame::finish_internal` (`smithay/src/backend/renderer/gles/mod.rs:2552`) exports an EGL fence when available (`:2588`), else `glFinish` and signaled (`:2597`). `SyncPoint` is an optional fence with `wait`, `is_exportable` and `export` (`smithay/src/backend/renderer/sync/mod.rs:81`, `:96`, `:104`). `DrmCompositor` attaches the exported fd as in-fence and reports `needs_sync` when fencing is unsupported or not exportable (`smithay/src/backend/drm/compositor/mod.rs:230`, `:774`). niri does the consumer CPU wait at `niri/src/backend/tty.rs:1965`. A Vulkan renderer can record into an op list and submit in `finish`, as long as `wait` is honored and `finish` returns a real fence when work is pending.
57f44a63d99fatagen2026-10-16 05:34 UTC21
57f44a63d99fatagen2026-10-16 05:34 UTC22
**1.3 xdg configure/ack.** `ToplevelSurface::send_configure` (`smithay/src/wayland/shell/xdg/mod.rs:1489`) allocates a `SERIAL_COUNTER` serial, pushes `ToplevelConfigure` to `pending_configures` and sends it. `send_pending_configure` (`:1472`) only sends on `has_pending_changes`. `with_pending_state` (`:1698`) seeds `server_pending` from `current_server_state`. Ack handling (`smithay/src/wayland/shell/xdg/handlers/surface.rs:293`) calls `ack_configure` (`smithay/src/wayland/shell/xdg/mod.rs:221`), which sets `last_acked` and retains newer configures. The toplevel pre-commit hook (`:1585`) copies `last_acked` into cached pending state when a buffer is present (`:1617`). The committed serial is readable via `with_cached_state` or `with_committed_state` (`:1736`, `:1746`), and niri reads it that way (`niri/src/handlers/compositor.rs:322`). A transaction hold works through `add_pre_commit_hook` (`smithay/src/wayland/compositor/mod.rs:454`, invoked at `smithay/src/wayland/compositor/handlers.rs:276`) plus `add_blocker` (`smithay/src/wayland/compositor/mod.rs:554`). Blocker states are at `smithay/src/wayland/compositor/transaction.rs:61`, and `TransactionQueue::take_ready` (`:293`) enforces readiness and per-surface ordering. `blocker_cleared` (`smithay/src/wayland/compositor/mod.rs:641`) wakes the queue.
57f44a63d99fatagen2026-10-16 05:34 UTC23
57f44a63d99fatagen2026-10-16 05:34 UTC24
**1.4 Plane assignment.** Tries primary, then cursor, then overlay (`smithay/src/backend/drm/compositor/mod.rs:2851`). Primary scanout is tried only for the single last element when no underlay overlaps and either the clear is black/transparent or the element covers the output and is opaque (`:1995`). Overlay requires `ALLOW_OVERLAY_PLANE_SCANOUT` (`:3728`), element kind `ScanoutCandidate` or `Cursor` (`:3733`), a free plane, compatible zpos and properties, and an atomic `TEST_ONLY` pass (`:3809`). Cursor assignment (`:3022`) checks kind, single assignment, sticky plane reuse, size hints and re-render on move, and falls back to the primary composite when too big. `RenderFrameResult` exposes `primary_element`, `overlay_elements` and `cursor_element` (`smithay/src/backend/drm/compositor/frame_result.rs:40`). Overlay assignment prefers sticky planes by z-index (`:3791`), which can block a later higher element if ordering shifts.
57f44a63d99fatagen2026-10-16 05:34 UTC25
57f44a63d99fatagen2026-10-16 05:34 UTC26
**1.5 Vblank timing.** No `CRTC_SEQUENCE` or vblank timestamp ioctl. Smithay only consumes page flip events carrying sequence and monotonic time, as `DrmEventMetadata` (`smithay/src/backend/drm/device/mod.rs:493`). Callers must invoke `frame_submitted` after the event (`smithay/src/backend/drm/compositor/mod.rs:2614`, `smithay/src/backend/drm/output.rs:676`). Prediction is the compositor's job. Anvil estimates from last presentation plus frame duration (`smithay/anvil/src/udev.rs:1262`).
57f44a63d99fatagen2026-10-16 05:34 UTC27
57f44a63d99fatagen2026-10-16 05:34 UTC28
### niri
57f44a63d99fatagen2026-10-16 05:34 UTC29
57f44a63d99fatagen2026-10-16 05:34 UTC30
**1.6 Transactions.** A refcounted `Inner` with `AtomicBool completed` plus `Weak` `TransactionBlocker` (`niri/src/utils/transaction.rs:48`, `:159`). Timeout is 300 ms (`:18`) with a calloop deadline timer (`:86`). Drop of the last `Transaction` completes it and removes the timer (`:136`). Per-window state is `transaction_for_next_configure` plus a `pending_transactions` vec of (serial, Transaction) (`niri/src/window/mapped.rs:125`). `send` (`:1104`) pairs serial to transaction, and `take_pending_transaction` (`:460`) completes all serials up to the committed one so fast resizes collapse to the latest. The comment at `:466` acknowledges that a window waiting on a skipped intermediate size can look inconsistent. The close path creates a `Transaction`, passes the blocker into `start_close_animation`, and arms the timer only when not last (`niri/src/handlers/compositor.rs:272`). The `Blocker` impl maps live-and-incomplete to `Pending` (`niri/src/utils/transaction.rs:159`).
57f44a63d99fatagen2026-10-16 05:34 UTC31
57f44a63d99fatagen2026-10-16 05:34 UTC32
**1.7 Custom shaders.** Config keys are `animations.window_resize`, `window_close` and `window_open` `custom_shader`, consumed at `niri/src/niri.rs:1800`, `:1810`, `:1820`. Compilation wraps the user body with prelude, epilogue and rounding alpha (`niri/src/render_helpers/shaders/mod.rs:222`, `:270`, `:313`). Resize uniforms (`:235`) are `niri_progress`, `niri_clamped_progress`, geometry matrices, corner radius, clip flag, `niri_tex_prev` and `niri_tex_next`. Open and close expose `input_to_geo`, `geo_size`, `geo_to_tex`, `progress`, `clamped_progress`, `random_seed` and `niri_tex` (`:281`, `:324`). On compile error the setter warns and keeps the prior program (`:250`). Resize falls back to the built-in shader when no custom one is set (`:199`), while open and close skip the custom path.
57f44a63d99fatagen2026-10-16 05:34 UTC33
57f44a63d99fatagen2026-10-16 05:34 UTC34
**1.8 Resize rendering.** Mid-resize renders a crossfade between snapshots. `Tile` stores a `ResizeAnimation` with snapshot plus anim and fullscreen/expanded progress (`niri/src/layout/tile.rs:143`). A new size triggers `take_animation_snapshot` and a new `ResizeAnimation` (`:272`). Render builds `ResizeRenderElement` from `texture_prev` and `texture_next` with progress (`niri/src/render_helpers/resize.rs:24`, `niri/src/layout/tile.rs:1127`). Snapshots are lazy encompassing textures (`niri/src/render_helpers/snapshot.rs:54`). If the client never commits, the 300 ms deadline completes the blocker anyway, and later commits collapse via `take_pending_transaction`.
57f44a63d99fatagen2026-10-16 05:34 UTC35
57f44a63d99fatagen2026-10-16 05:34 UTC36
**1.9 Animation model.** Kinds are `Easing`, `Spring` and `Deceleration` (`niri/src/animation/mod.rs:32`), curves `Linear`, `EaseOutQuad`, `EaseOutCubic`, `EaseOutExpo` and `CubicBezier` (`:44`). `SpringParams` derive damping from damping ratio and stiffness (`niri/src/animation/spring.rs:19`), with analytic under-, critically and overdamped solutions (`:141`) and durations (`:48`, `:110`). Retarget via `restarted` (`niri/src/animation/mod.rs:111`) reuses the prior config, but the spring branch copies `self.initial_velocity` rather than the passed velocity (`:128`), so spring retargets do not carry fresh velocity while easing does after rate scaling. Likely a bug. Time comes from a lazy adjustable `Clock` (`niri/src/animation/clock.rs:12`) with rate and complete-instantly flags. Sampling is `value_at(now)` (`niri/src/animation/mod.rs:249`). niri advances animations at current time (`niri/src/niri.rs:816`), then freezes the clock at predicted presentation time before rendering (`:4869`), so rendered values sample predicted presentation time.
57f44a63d99fatagen2026-10-16 05:34 UTC37
57f44a63d99fatagen2026-10-16 05:34 UTC38
**1.10 Frame scheduling.** niri renders right after vblank or after an estimated-vblank timer. `on_vblank` (`niri/src/backend/tty.rs:1630`) feeds `frame_clock.presented` (`:1804`), then queues a redraw when needed or animations remain, else sends frame callbacks (`:1806`). Empty frames arm `queue_estimated_vblank_timer` for the target presentation time (`:2032`, implementation `:3056`). `FrameClock::next_presentation_time` (`niri/src/frame_clock.rs:56`) rounds up from the last presentation and returns now when VRR skipped more than one interval (`:92`). VRR toggling and on-demand VRR are at `niri/src/backend/tty.rs:1354` and `:2295`. Cursor-only updates are skipped during VRR via `SKIP_CURSOR_ONLY_UPDATES` (`:1951`, smithay logic at `smithay/src/backend/drm/compositor/mod.rs:2379`). Early rogue vblanks arriving in under half a refresh are throttled (`niri/src/utils/vblank_throttle.rs:33`). No deadline scheduling.
57f44a63d99fatagen2026-10-16 05:34 UTC39
57f44a63d99fatagen2026-10-16 05:34 UTC40
**1.11 Direct scanout and tearing.** Scanout requires a fullscreen `ScanoutCandidate` with matching dmabuf feedback modifiers, enabled through `ALLOW_PRIMARY_PLANE_SCANOUT_ANY` by default (`niri/src/backend/tty.rs:1934`), cursor plane only, overlays off unless the debug flag `enable_overlay_planes` is set (`:1941`). Feedback tranches restrict to the Linear modifier for multi-GPU (`:2952`). No tearing path exists. No ASYNC commit and no tearing-control protocol.
57f44a63d99fatagen2026-10-16 05:34 UTC41
57f44a63d99fatagen2026-10-16 05:34 UTC42
---
57f44a63d99fatagen2026-10-16 05:34 UTC43
57f44a63d99fatagen2026-10-16 05:34 UTC44
## 4. Protocol inventory
57f44a63d99fatagen2026-10-16 05:34 UTC45
57f44a63d99fatagen2026-10-16 05:34 UTC46
wry paths are under `src/ifs/` in `/home/bolt/code/wry`. smithay helpers are modules under `smithay/src/wayland/`. niri globals are created around `niri/src/niri.rs:2500` plus custom implementations in `niri/src/protocols/`. Hand-verified that niri has no syncobj code and wry has no keyboard-shortcuts-inhibit.
57f44a63d99fatagen2026-10-16 05:34 UTC47
57f44a63d99fatagen2026-10-16 05:34 UTC48
| protocol | wry | smithay helper | niri | purpose |
57f44a63d99fatagen2026-10-16 05:34 UTC49
| --- | --- | --- | --- | --- |
57f44a63d99fatagen2026-10-16 05:34 UTC50
| wl_compositor, subcompositor, shm, wl_output, wl_seat, wl_data_device | y | y (`compositor`, `shm`, `output`, `seat`, `selection`) | y | core |
57f44a63d99fatagen2026-10-16 05:34 UTC51
| xdg-shell | y `xdg_wm_base.rs` | y `shell::xdg` | y | app windows |
57f44a63d99fatagen2026-10-16 05:34 UTC52
| xdg-decoration, kde-decoration, xdg-dialog | y | y `shell::xdg`, `shell::kde` | y | decoration mode, modal dialogs |
57f44a63d99fatagen2026-10-16 05:34 UTC53
| wlr-layer-shell | y | y `shell::wlr_layer` | y | panels, wallpapers, lockers |
57f44a63d99fatagen2026-10-16 05:34 UTC54
| ext-session-lock | y | y `session_lock` | y | screen locker |
57f44a63d99fatagen2026-10-16 05:34 UTC55
| linux-dmabuf | y | y `dmabuf` | y | GPU buffers |
57f44a63d99fatagen2026-10-16 05:34 UTC56
| linux-drm-syncobj | y | y `drm_syncobj` | n | explicit sync |
57f44a63d99fatagen2026-10-16 05:34 UTC57
| drm-lease | y | y `drm_lease` | y (tty) | VR headsets |
57f44a63d99fatagen2026-10-16 05:34 UTC58
| ext-image-copy-capture plus capture sources | y | y `image_copy_capture`, `image_capture_source` | y | modern screen capture |
57f44a63d99fatagen2026-10-16 05:34 UTC59
| wlr-screencopy | y | n | y custom | legacy screen capture |
57f44a63d99fatagen2026-10-16 05:34 UTC60
| wp-tearing-control | y | n | n | tearing for games |
57f44a63d99fatagen2026-10-16 05:34 UTC61
| wp-fractional-scale | y | y | y | fractional scaling |
57f44a63d99fatagen2026-10-16 05:34 UTC62
| viewporter | y | y | y | crop and scale |
57f44a63d99fatagen2026-10-16 05:34 UTC63
| single-pixel-buffer | y | y | y (test only) | solid color buffers |
57f44a63d99fatagen2026-10-16 05:34 UTC64
| presentation-time | y | y | y | presentation timestamps |
57f44a63d99fatagen2026-10-16 05:34 UTC65
| content-type | y | y | n | game/video/photo hint |
57f44a63d99fatagen2026-10-16 05:34 UTC66
| alpha-modifier | y | y | n | per-surface alpha |
57f44a63d99fatagen2026-10-16 05:34 UTC67
| wp-fifo | y | y | n | FIFO present barrier |
57f44a63d99fatagen2026-10-16 05:34 UTC68
| wp-commit-timing | y | y | n | timed commits |
57f44a63d99fatagen2026-10-16 05:34 UTC69
| wp-color-management, wp-color-representation | y | n | n | HDR and color spaces |
57f44a63d99fatagen2026-10-16 05:34 UTC70
| wp-cursor-shape | y | y | y | named cursors |
57f44a63d99fatagen2026-10-16 05:34 UTC71
| pointer-constraints | y | y | y | pointer lock for games |
57f44a63d99fatagen2026-10-16 05:34 UTC72
| relative-pointer | y | y | y | raw motion for games |
57f44a63d99fatagen2026-10-16 05:34 UTC73
| pointer-gestures | y | y | y | touchpad gestures |
57f44a63d99fatagen2026-10-16 05:34 UTC74
| wp-pointer-warp | y | y | n | pointer warp |
57f44a63d99fatagen2026-10-16 05:34 UTC75
| tablet | y | y `tablet_manager` | y | stylus |
57f44a63d99fatagen2026-10-16 05:34 UTC76
| text-input, input-method | y | y | y | IME |
57f44a63d99fatagen2026-10-16 05:34 UTC77
| virtual-keyboard | y | y | y | synthetic keys |
57f44a63d99fatagen2026-10-16 05:34 UTC78
| wlr virtual-pointer | y | y | y | synthetic pointer |
57f44a63d99fatagen2026-10-16 05:34 UTC79
| idle-inhibit | y | y | y | block idle |
57f44a63d99fatagen2026-10-16 05:34 UTC80
| ext-idle-notify | y | y | y | idle notifications |
57f44a63d99fatagen2026-10-16 05:34 UTC81
| wp-security-context | y | y | y | sandbox identity |
57f44a63d99fatagen2026-10-16 05:34 UTC82
| keyboard-shortcuts-inhibit | n | y | y | games/VMs grab hotkeys |
57f44a63d99fatagen2026-10-16 05:34 UTC83
| xdg-activation | y | y | y | focus tokens |
57f44a63d99fatagen2026-10-16 05:34 UTC84
| ext-foreign-toplevel-list | y | y | y custom | window lists |
57f44a63d99fatagen2026-10-16 05:34 UTC85
| wlr-foreign-toplevel-management | y | n | y custom | control other windows |
57f44a63d99fatagen2026-10-16 05:34 UTC86
| ext-workspace | y | n | y custom | workspaces for bars |
57f44a63d99fatagen2026-10-16 05:34 UTC87
| wlr-output-management | y | n | y custom | runtime output config |
57f44a63d99fatagen2026-10-16 05:34 UTC88
| wlr-gamma-control | y | n | y custom | night light |
57f44a63d99fatagen2026-10-16 05:34 UTC89
| wlr and ext data-control | y | y `selection` | y | clipboard managers |
57f44a63d99fatagen2026-10-16 05:34 UTC90
| primary-selection | y | y `selection` | y | middle-click paste |
57f44a63d99fatagen2026-10-16 05:34 UTC91
| ext-background-effect | n | y | y | blur behind surfaces |
57f44a63d99fatagen2026-10-16 05:34 UTC92
| xdg-foreign | n | y | y | cross-client parenting |
57f44a63d99fatagen2026-10-16 05:34 UTC93
| xdg-system-bell | n | y | n | bell |
57f44a63d99fatagen2026-10-16 05:34 UTC94
| xdg-toplevel-icon | y | y | n | window icons |
57f44a63d99fatagen2026-10-16 05:34 UTC95
| xdg-toplevel-tag | y | y | n | stable window tag string |
57f44a63d99fatagen2026-10-16 05:34 UTC96
| xdg-toplevel-drag | y | n | n | tab drag-out |
57f44a63d99fatagen2026-10-16 05:34 UTC97
| ext-transient-seat | y | n | n | remote desktop seats |
57f44a63d99fatagen2026-10-16 05:34 UTC98
| xwayland-shell | y | y (xwayland feature) | n | Xwayland association |
57f44a63d99fatagen2026-10-16 05:34 UTC99
| xwayland-keyboard-grab | n | y (xwayland feature) | n | Xwayland grabs |
57f44a63d99fatagen2026-10-16 05:34 UTC100
| xdg-output | y | y | y | output geometry |
57f44a63d99fatagen2026-10-16 05:34 UTC101
| wl-fixes, wl-drm | y | y `fixes` | n (wl-drm) | registry fixes, legacy drm |
57f44a63d99fatagen2026-10-16 05:34 UTC102
| xdg-session-management | y | n | n | session restore |
57f44a63d99fatagen2026-10-16 05:34 UTC103
| jay-private (compositor control, tree query, tray, sync-file, screencast, xx-geometry) | y | n | n | jay IPC |
57f44a63d99fatagen2026-10-16 05:34 UTC104
| hyprland-focus-grab | y | n | n | focus grab for popups |
57f44a63d99fatagen2026-10-16 05:34 UTC105
| mutter-x11-interop | n | n | y custom | GNOME Xwayland interop |
57f44a63d99fatagen2026-10-16 05:34 UTC106
57f44a63d99fatagen2026-10-16 05:34 UTC107
No smithay helper, so monolith writes these itself if wanted. wp-tearing-control, wp-color-management and wp-color-representation, ext-transient-seat, xdg-toplevel-drag, wlr-foreign-toplevel-management, ext-workspace, wlr-output-management, wlr-screencopy, wlr-gamma-control, xdg-session-management.
57f44a63d99fatagen2026-10-16 05:34 UTC108
57f44a63d99fatagen2026-10-16 05:34 UTC109
---
57f44a63d99fatagen2026-10-16 05:34 UTC110
57f44a63d99fatagen2026-10-16 05:34 UTC111
## 3. ext-workspace and decoration semantics
57f44a63d99fatagen2026-10-16 05:34 UTC112
57f44a63d99fatagen2026-10-16 05:34 UTC113
Spec at `/tmp/monolith-research/ext-workspace-v1.xml`. Source revisions used by the reader include gtk main `ce6b23c`, gtk-3-24 `0f3aa45`, libadwaita main `0531b4c`, niri `45739bc`. Two early unsourced sub-reports (claiming GTK4 supports xdg-decoration and river 0.4 keeps tags) were discarded as wrong after checking source.
57f44a63d99fatagen2026-10-16 05:34 UTC114
57f44a63d99fatagen2026-10-16 05:34 UTC115
### 3.1 ext-workspace-v1
57f44a63d99fatagen2026-10-16 05:34 UTC116
57f44a63d99fatagen2026-10-16 05:34 UTC117
**Several active per group.** Allowed. `active` is a per-workspace state bit (xml:313), an unset bit means inactive (xml:330), `activate` "may or may not deactivate all other workspaces in the same group" (xml:391-393), and `commit` exists so deactivate-plus-activate is atomic (xml:85-86).
57f44a63d99fatagen2026-10-16 05:34 UTC118
57f44a63d99fatagen2026-10-16 05:34 UTC119
**Group membership.** A workspace "may only ever be assigned to a single group at a single point in time" (xml:186-187, :248). Workspaces start unassigned (xml:72). Groups carry a set of outputs via `output_enter`/`output_leave` (xml:130-135), and the spec allows per-output sets or "all outputs (or any other arbitrary grouping)" sharing (xml:39-41). A zero-output group is not forbidden.
57f44a63d99fatagen2026-10-16 05:34 UTC120
57f44a63d99fatagen2026-10-16 05:34 UTC121
**Capabilities.** Group has `create_workspace` with no guarantees (xml:144, :217-218). Workspace has `activate`, `deactivate`, `remove` (only for ungrouped workspaces, xml:371-372) and `assign(group)` (xml:337-340). Unsupported requests are silently ignored (xml:155-161, :351-357). Per-workspace `id` (stable across sessions), `name`, N-dimensional `coordinates`, states `urgent` and `hidden`.
57f44a63d99fatagen2026-10-16 05:34 UTC122
57f44a63d99fatagen2026-10-16 05:34 UTC123
**Mapping tags.**
57f44a63d99fatagen2026-10-16 05:34 UTC124
- Per-output tags map to one group per output, one workspace per tag, every viewed tag `active`, `coordinates=[n]` for order, `hidden` for empty tags if wanted.
57f44a63d99fatagen2026-10-16 05:34 UTC125
- Global tags map to one group whose outputs are all outputs. Switching modes at runtime regroups inside one `done` batch (xml:98-102).
57f44a63d99fatagen2026-10-16 05:34 UTC126
57f44a63d99fatagen2026-10-16 05:34 UTC127
**What it cannot express.** A primary tag among the active ones (only active, urgent, hidden exist). Focused output. Which window has which tag (no toplevel linkage). Per-tag client counts. Window-to-output binding. Whether `activate` is exclusive or toggling. Whether the mode is global or per-output.
57f44a63d99fatagen2026-10-16 05:34 UTC128
57f44a63d99fatagen2026-10-16 05:34 UTC129
**Client handling of several active workspaces.**
57f44a63d99fatagen2026-10-16 05:34 UTC130
- Waybar handles it, per-button `.active` class (`src/modules/ext/workspace_manager.cpp:387-410, :436` @4e207a0), has a deactivate action (`:475-476`), and also ships native river/dwl/mango tag modules.
57f44a63d99fatagen2026-10-16 05:34 UTC131
- quickshell handles it, and its docs explicitly mention tag-based WMs (`src/windowmanager/windowset.hpp:38-53` @4f508be).
57f44a63d99fatagen2026-10-16 05:34 UTC132
- astal handles it, "any combination of these is allowed" (`lib/workspace/src/workspace.vala:3-8` @cbcd9f4).
57f44a63d99fatagen2026-10-16 05:34 UTC133
- ironbar main has no ext-workspace backend and assumes exactly one focused monitor (`src/modules/workspaces/mod.rs:503-508` @149c0d0). PR #1465 adds multi-focus support.
57f44a63d99fatagen2026-10-16 05:34 UTC134
- sfwbar renders per-item focus fine but keeps a single global `focus` pointer (`src/workspace.c:11, 248-249` @a3594e5). Issue #389 open.
57f44a63d99fatagen2026-10-16 05:34 UTC135
- eww has no ext-workspace support.
57f44a63d99fatagen2026-10-16 05:34 UTC136
57f44a63d99fatagen2026-10-16 05:34 UTC137
**Protocols with tag semantics.**
57f44a63d99fatagen2026-10-16 05:34 UTC138
- river-status-unstable-v1 (river 0.3.x). Per-output `focused_tags` u32 mask, `view_tags` masks without window identity, `urgent_tags`. Seat sends `focused_output` and `focused_view`. Control is stringly typed `run_command`.
57f44a63d99fatagen2026-10-16 05:34 UTC139
- river 0.4 and later (0.5.0 released 2026-10-10). river-status and river-control are gone. river-window-management-v1 has no tags, the external WM owns them.
57f44a63d99fatagen2026-10-16 05:34 UTC140
- dwl-ipc-unstable-v2. Per-output `tag(index, state{active,urgent}, clients, focused)` events, `active` output flag, `layout`, title, appid, `frame` batching. Requests `set_tags(mask, toggle)` and `set_client_tags(and, xor)` on the focused client.
57f44a63d99fatagen2026-10-16 05:34 UTC141
- cosmic-workspace-unstable-v2. Extends ext-workspace with rename, tiling state, move_before/after, pin. Still no window mapping or counts.
57f44a63d99fatagen2026-10-16 05:34 UTC142
- Hyprland socket2 text events (`workspacev2`, `focusedmonv2`, `activespecialv2`, `movewindowv2`). One workspace per window, one regular workspace per monitor plus a special overlay (inferred).
57f44a63d99fatagen2026-10-16 05:34 UTC143
57f44a63d99fatagen2026-10-16 05:34 UTC144
**Reader recommendation.** Model a custom protocol on dwl-ipc v2, keyed on ext-foreign-toplevel handles with `set_window_tag(toplevel, tag)`, a manager event for per-output vs global mode, a seat-level focused-output event, per-tag `(state, clients, focused)` events and `frame` batching. Keep ext-workspace-v1 as the compatibility layer for generic bars.
57f44a63d99fatagen2026-10-16 05:34 UTC145
57f44a63d99fatagen2026-10-16 05:34 UTC146
### 3.2 Client-side decorations
57f44a63d99fatagen2026-10-16 05:34 UTC147
57f44a63d99fatagen2026-10-16 05:34 UTC148
**GTK3, GTK4, libadwaita.**
57f44a63d99fatagen2026-10-16 05:34 UTC149
- Neither GTK3 nor GTK4 implements xdg-decoration. Both bind only `org_kde_kwin_server_decoration_manager` (gtk4 `gdk/wayland/gdkdisplay-wayland.c:667`, gtk3 `:528`).
57f44a63d99fatagen2026-10-16 05:34 UTC150
- `gdk_wayland_display_prefers_ssd()` is true only when that KDE global defaults to SERVER (gtk4 `gdkdisplay-wayland.c:1270-1282`). `gtk_window_should_use_csd()` returns `!prefers_ssd` on Wayland (gtk4 `gtk/gtkwindow.c:3976-3996`, gtk3 `gtkwindow.c:6152-6177`). `GTK_CSD` is ignored on Wayland.
57f44a63d99fatagen2026-10-16 05:34 UTC151
- `gtk_window_set_titlebar` forces CSD unconditionally (gtk4 `gtkwindow.c:3195`). `AdwWindow`/`AdwApplicationWindow` always install a titlebar gizmo (libadwaita `src/adw-window.c:174-176, 325-328`). So every libadwaita and headerbar app keeps its headerbar no matter what.
57f44a63d99fatagen2026-10-16 05:34 UTC152
57f44a63d99fatagen2026-10-16 05:34 UTC153
**Tiled and constrained states.**
57f44a63d99fatagen2026-10-16 05:34 UTC154
- GTK4 maps tiled_* to `.tiled-*` classes (`gtkwindow.c:4674-4687`) and parses xdg-shell v7 `constrained_*` into per-edge resizability (`gdktoplevel-wayland.c:762-773`, landed 4.19.1 via MR !8445). Without v7, non-tiled edges count as resizable (`:565-586`).
57f44a63d99fatagen2026-10-16 05:34 UTC155
- libadwaita on any `.tiled*` sets window radius 0 and drops the outline, but keeps a 1px `$border_color` line plus a transparent 20px shadow (`src/stylesheet/widgets/_window.scss:49-63`). Only maximized/fullscreen remove everything (`:66-71`). Whether that 20px becomes input area is unverified.
57f44a63d99fatagen2026-10-16 05:34 UTC156
- GTK3 binds at most xdg_wm_base v6. Adwaita squares corners on `.tiled*`.
57f44a63d99fatagen2026-10-16 05:34 UTC157
- Qt records tiled edges only (qtbase `qwaylandxdgshell.cpp:169-180` @a4b1c40). Chromium tiled edges only (`ui/ozone/platform/wayland/host/xdg_toplevel.cc:256-264`).
57f44a63d99fatagen2026-10-16 05:34 UTC158
57f44a63d99fatagen2026-10-16 05:34 UTC159
**Other toolkits.**
57f44a63d99fatagen2026-10-16 05:34 UTC160
- Qt6 requests server-side unless frameless (`qwaylandxdgshell.cpp:197-211`), draws CSD only if refused.
57f44a63d99fatagen2026-10-16 05:34 UTC161
- Chromium/Electron default to CSD. SSD is a user opt-in that appears only when the xdg-decoration manager exists (crbug 40817442). Electron Wayland CSD default since v28 (electron#39582).
57f44a63d99fatagen2026-10-16 05:34 UTC162
- Firefox forces CLIENT on Wayland unless `MOZ_GTK_TITLEBAR_DECORATION` is set (`widget/gtk/nsWindow.cpp:7224-7244`), honours prefers_ssd when tabs are not in the titlebar (`:855-882`).
57f44a63d99fatagen2026-10-16 05:34 UTC163
- SDL2/SDL3 prefer xdg-decoration SERVER and fall back to libdecor only when the manager is absent (SDL3 `SDL_waylandwindow.c:2451-2456`, SDL#8173).
57f44a63d99fatagen2026-10-16 05:34 UTC164
- GLFW 3.4 prefers libdecor (`init.c:67`), else requests SERVER.
57f44a63d99fatagen2026-10-16 05:34 UTC165
- winit requests Server unless `prefer_csd` (winit-wayland `frame.rs:176-195`).
57f44a63d99fatagen2026-10-16 05:34 UTC166
- Java JBR WLToolkit defaults to GTK-drawn CSD, SSD with `-Dsun.awt.wl.WindowDecorationStyle=server` (`WLDecoratedPeer.java:50-68`).
57f44a63d99fatagen2026-10-16 05:34 UTC167
57f44a63d99fatagen2026-10-16 05:34 UTC168
**Other compositors.**
57f44a63d99fatagen2026-10-16 05:34 UTC169
- niri gates visibility of decoration globals on `prefer-no-csd` (`src/niri.rs:2917`) and sends all four tiled_* states as the "no CSD" signal (`src/utils/mod.rs:421-475`), with a `tiled-state` window rule override.
57f44a63d99fatagen2026-10-16 05:34 UTC170
- sway forces SERVER for tiled views and honours client mode only when floating (`xdg_decoration.c:64-91` @1652c54). KDE manager defaults to SERVER (`server.c:386-394`).
57f44a63d99fatagen2026-10-16 05:34 UTC171
- river 0.4 forwards the client hint to the WM, CSD by default (`river-window-management-v1.xml:552-600`), no KDE protocol.
57f44a63d99fatagen2026-10-16 05:34 UTC172
57f44a63d99fatagen2026-10-16 05:34 UTC173
**Reader takeaway.** Advertise both xdg-decoration and KDE server-decoration defaulting to SERVER, always send all four tiled_* plus v7 constrained states. GTK/libadwaita windows become square and shadowless, headerbars and libadwaita's 1px line remain.
57f44a63d99fatagen2026-10-16 05:34 UTC174
57f44a63d99fatagen2026-10-16 05:34 UTC175
---
57f44a63d99fatagen2026-10-16 05:34 UTC176
57f44a63d99fatagen2026-10-16 05:34 UTC177
## 2. NVIDIA driver state
57f44a63d99fatagen2026-10-16 05:34 UTC178
57f44a63d99fatagen2026-10-16 05:34 UTC179
Primary sources where available. nvidia-drm read from open-gpu-kernel-modules `main` (`version.mk` 615.78.08), lavapipe from Mesa `main`. URLs inline.
57f44a63d99fatagen2026-10-16 05:34 UTC180
57f44a63d99fatagen2026-10-16 05:34 UTC181
### 2.1 Current drivers
57f44a63d99fatagen2026-10-16 05:34 UTC182
57f44a63d99fatagen2026-10-16 05:34 UTC183
- Latest is 615.78.08, New Feature Branch, 2026-10-07 ([linuxiac](https://linuxiac.com/nvidia-615-78-08-linux-driver-brings-vulkan-latency-wayland-fixes/)).
57f44a63d99fatagen2026-10-16 05:34 UTC184
- Production branch R595, latest 595.104.02 ([9to5Linux](https://9to5linux.com/nvidia-595-104-02-linux-graphics-driver-is-out-now-with-better-wayland-support)). R595 made `nvidia-drm modeset=1` the default.
57f44a63d99fatagen2026-10-16 05:34 UTC185
- R610 (beta 610.43.02, May 2026) added the per-plane DRM color pipeline, FP16 EGL on Wayland, multi-planar YCbCr modifiers, mmap of dGPU dmabufs ([Phoronix](https://www.phoronix.com/news/NVIDIA-610.43.02-Linux-Driver)).
57f44a63d99fatagen2026-10-16 05:34 UTC186
57f44a63d99fatagen2026-10-16 05:34 UTC187
### 2.2 NVIDIA workarounds in local trees
57f44a63d99fatagen2026-10-16 05:34 UTC188
57f44a63d99fatagen2026-10-16 05:34 UTC189
- wry `src/state.rs:883` skips nvidia DRM devices when picking the initial render device unless nothing else works (likely a hybrid-laptop iGPU preference, uncommented).
57f44a63d99fatagen2026-10-16 05:34 UTC190
- wry `src/backends/metal/video.rs:2010-2016` detects nvidia by driver name. `src/backends/metal/present.rs:549, :582` never set `IN_FENCE_FD` on nvidia, any version.
57f44a63d99fatagen2026-10-16 05:34 UTC191
- wry `src/backends/metal/video/metal_cm/metal_cm_plane.rs:347` treats nvidia colorop 1D LUTs as low precision unless `WRY_MCM_NVIDIA_USE_LUTS` (`env.rs:232`). `metal_cm.rs:52` notes LUT size 1024.
57f44a63d99fatagen2026-10-16 05:34 UTC192
- smithay `src/backend/drm/compositor/mod.rs:1183-1206, :1366-1389` disables `IN_FENCE_FD` below 560.35.03, version read from `/sys/module/nvidia_drm/version` (`:4424`), for [ogkm#622](https://github.com/NVIDIA/open-gpu-kernel-modules/issues/622).
57f44a63d99fatagen2026-10-16 05:34 UTC193
- smithay `src/backend/egl/display.rs:932` ignores `EGL_BAD_PARAMETER` from `eglQueryDmaBufModifiersEXT` (nvidia since 520).
57f44a63d99fatagen2026-10-16 05:34 UTC194
- smithay `src/backend/winit/mod.rs:317` resize-before-make_current ordering note.
57f44a63d99fatagen2026-10-16 05:34 UTC195
- smithay `src/wayland/drm_lease/mod.rs:372` tolerates `EINVAL` on drop-master.
57f44a63d99fatagen2026-10-16 05:34 UTC196
- niri `src/backend/tty.rs:73` orders `Abgr2101010` first because nvidia dGPU to iGPU copies only handle X/ABGR.
57f44a63d99fatagen2026-10-16 05:34 UTC197
57f44a63d99fatagen2026-10-16 05:34 UTC198
### 2.3 Explicit sync
57f44a63d99fatagen2026-10-16 05:34 UTC199
57f44a63d99fatagen2026-10-16 05:34 UTC200
- nvidia-drm sets `DRIVER_SYNCOBJ | DRIVER_SYNCOBJ_TIMELINE` (`nvidia-drm-drv.c:1938`). linux-drm-syncobj-v1 client support since 555.58 (2024-06-27), needs kernel 6.8+.
57f44a63d99fatagen2026-10-16 05:34 UTC201
- NVIDIA does not participate in dmabuf implicit sync. Undocumented what `DMA_BUF_IOCTL_EXPORT_SYNC_FILE` returns on its buffers. Assume empty/signalled and require syncobj from clients.
57f44a63d99fatagen2026-10-16 05:34 UTC202
- Importing a timeline syncobj fd as an `OPAQUE_FD` timeline semaphore fails on NVIDIA by design ("would never", [gamescope#1662](https://github.com/ValveSoftware/gamescope/issues/1662)). The portable path is syncobj ioctls on the DRM fd plus binary `SYNC_FD` semaphore import/export, checking the importable feature bit at runtime ([forum](https://forums.developer.nvidia.com/t/vulkan-importing-external-linux-sync-file-descriptors-is-unsupported/268125)). This is what smithay does. `VK_EXT_external_semaphore_drm_syncobj` ([Vulkan-Docs#2692](https://github.com/KhronosGroup/Vulkan-Docs/pull/2692)) appears unmerged.
57f44a63d99fatagen2026-10-16 05:34 UTC203
57f44a63d99fatagen2026-10-16 05:34 UTC204
### 2.4 Vulkan extensions on NVIDIA
57f44a63d99fatagen2026-10-16 05:34 UTC205
57f44a63d99fatagen2026-10-16 05:34 UTC206
| extension | state |
57f44a63d99fatagen2026-10-16 05:34 UTC207
|---|---|
57f44a63d99fatagen2026-10-16 05:34 UTC208
| `VK_EXT_image_drm_format_modifier` | since 515.48.07, YCbCr fixes in 580.94.18 and 610 |
57f44a63d99fatagen2026-10-16 05:34 UTC209
| `VK_EXT_external_memory_dma_buf` | since 515.48.07, needs modeset=1 |
57f44a63d99fatagen2026-10-16 05:34 UTC210
| `VK_KHR_external_semaphore_fd` SYNC_FD | present, check feature bits |
57f44a63d99fatagen2026-10-16 05:34 UTC211
| timeline semaphore as syncobj | not supported, by design |
57f44a63d99fatagen2026-10-16 05:34 UTC212
| global priority | EXT since 455, KHR since 470. gamescope with CAP_SYS_NICE failed `vkCreateDevice` on 515 ([gamescope#521](https://github.com/ValveSoftware/gamescope/issues/521)). Query and fall back. |
57f44a63d99fatagen2026-10-16 05:34 UTC213
| `VK_EXT_descriptor_buffer` | since 515.49.25 |
57f44a63d99fatagen2026-10-16 05:34 UTC214
| `VK_EXT_descriptor_heap` | 595.58.03 stable |
57f44a63d99fatagen2026-10-16 05:34 UTC215
| `VK_EXT_queue_family_foreign` | since 470.56.05 |
57f44a63d99fatagen2026-10-16 05:34 UTC216
| BDA, dynamic rendering | core 1.2/1.3 |
57f44a63d99fatagen2026-10-16 05:34 UTC217
57f44a63d99fatagen2026-10-16 05:34 UTC218
### 2.5 KMS
57f44a63d99fatagen2026-10-16 05:34 UTC219
57f44a63d99fatagen2026-10-16 05:34 UTC220
- **Async flips.** `async_page_flip = true` (`nvidia-drm-drv.c:364`), atomic `PAGE_FLIP_ASYNC` maps to tearing (`nvidia-drm-crtc.c:1683-1795`). Kernel core only lets async commits change FB_ID, so expect fallbacks to sync.
57f44a63d99fatagen2026-10-16 05:34 UTC221
- **VRR.** `VRR_ENABLED` honoured (`crtc.c:2740`). The driver forces VRR-capable displays into VRR mode by default, `conceal_vrr_caps` hides the capability ([README](https://download.nvidia.com/XFree86/Linux-x86_64/615.78.08/README/kms.html)).
57f44a63d99fatagen2026-10-16 05:34 UTC222
- **Vblank events.** `nvidia_drm vblank` module parameter defaults to 0, so `enable_vblank` returns `-ENODEV` (`nvidia-drm-os-interface.c:47`, `crtc.c:2587`). `WAIT_VBLANK` and `CRTC_QUEUE_SEQUENCE` are off by default. Page-flip events still work, so timing must come from flip timestamps.
57f44a63d99fatagen2026-10-16 05:34 UTC223
- **IN_FENCE_FD** since 560.28.03 beta, multi-monitor freeze fixed in 560.35.03 (ogkm#622).
57f44a63d99fatagen2026-10-16 05:34 UTC224
- **OUT_FENCE_PTR.** Standard property unconfirmed. Vendor `NV_DRM_OUT_FENCE_PTR` signals when the buffer is flipped away, not on display (`drv.c:467`).
57f44a63d99fatagen2026-10-16 05:34 UTC225
- **Overlay planes** exist per hardware layer (`crtc.c:3426-3437`), count per GPU unknown, direct-scanout usability undocumented.
57f44a63d99fatagen2026-10-16 05:34 UTC226
- **Cursor plane** sized from `maxCursorSizeInPixels` (`drv.c:351`).
57f44a63d99fatagen2026-10-16 05:34 UTC227
- **COLOR_PIPELINE** on by default with R610+ and kernel 6.19+. Pipelines can contain non-bypassable colorops, which blanks the screen on HDR enable for compositors that don't handle them. Escape hatch `color_pipeline=0` ([README](https://download.nvidia.com/XFree86/Linux-x86_64/615.78.08/README/wayland-issues.html)).
57f44a63d99fatagen2026-10-16 05:34 UTC228
- **HDR_OUTPUT_METADATA** on connector (`nvidia-drm-connector.c:56, :569`), plus vendor `NV_HDR_STATIC_METADATA`.
57f44a63d99fatagen2026-10-16 05:34 UTC229
- `DEGAMMA_LUT`, `CTM`, `GAMMA_LUT` supported. `COLOR_ENCODING`, `COLOR_RANGE` not.
57f44a63d99fatagen2026-10-16 05:34 UTC230
57f44a63d99fatagen2026-10-16 05:34 UTC231
### 2.6 GBM and scanout
57f44a63d99fatagen2026-10-16 05:34 UTC232
57f44a63d99fatagen2026-10-16 05:34 UTC233
- GBM since 495.44 via `nvidia-drm_gbm.so`, needs Mesa libgbm 21.2+.
57f44a63d99fatagen2026-10-16 05:34 UTC234
- Native buffers use block-linear modifiers. Without a modifier NVIDIA assumes pitch-linear. Intersect plane `IN_FORMATS` with renderer modifiers and use dmabuf feedback scanout tranches.
57f44a63d99fatagen2026-10-16 05:34 UTC235
- Same-GPU direct scanout on the primary plane works (smithay, KWin). No source on scanout from sysmem or iGPU buffers, assume a copy.
57f44a63d99fatagen2026-10-16 05:34 UTC236
57f44a63d99fatagen2026-10-16 05:34 UTC237
### 2.7 Hybrid laptops
57f44a63d99fatagen2026-10-16 05:34 UTC238
57f44a63d99fatagen2026-10-16 05:34 UTC239
- Internal panel usually on the iGPU, HDMI often on the dGPU ([zamundaaa 2026-07](https://zamundaaa.github.io/wayland/2026/07/31/fixing-multi-gpu.html)).
57f44a63d99fatagen2026-10-16 05:34 UTC240
- Compositors usually render on the iGPU and copy to nvidia outputs. Known slow, high CPU, ~50 fps clamping, regressions as recent as 590 ([KDE 452219](https://bugs.kde.org/show_bug.cgi?id=452219)).
57f44a63d99fatagen2026-10-16 05:34 UTC241
- linux-dmabuf v6 per-output target device exists, NVIDIA's side unreleased as of July 2026. No automatic mux switching, no public API to power down VRAM.
57f44a63d99fatagen2026-10-16 05:34 UTC242
- Requirements are a per-output render GPU, cross-GPU copies with format quirks, and not keeping the dGPU awake.
57f44a63d99fatagen2026-10-16 05:34 UTC243
57f44a63d99fatagen2026-10-16 05:34 UTC244
### 2.8 Known pitfalls 2025-2026
57f44a63d99fatagen2026-10-16 05:34 UTC245
57f44a63d99fatagen2026-10-16 05:34 UTC246
- VRR plus cursor-only commits swing refresh and flicker OLEDs ([niri#1214](https://github.com/niri-wm/niri/issues/1214)). Throttle cursor to the game's frame rate under fullscreen VRR.
57f44a63d99fatagen2026-10-16 05:34 UTC247
- "Pageflip timed out" persists on 595.58.03 with Blackwell ([forum](https://forums.developer.nvidia.com/t/kwin-wayland-drm-pageflip-timed-out-this-is-a-bug-in-the-nvidia-drm-kernel-driver/331077?page=2)). Freeze on monitor wake regressed in 570.124.04.
57f44a63d99fatagen2026-10-16 05:34 UTC248
- Suspend VRAM preservation via kernel notifiers is default in 615.78.08. Mixing with `PreserveVideoMemoryAllocations` and systemd units breaks resume. Treat lost device state after resume as recoverable.
57f44a63d99fatagen2026-10-16 05:34 UTC249
- NVIDIA heap doesn't return freed buffers, niri climbs toward 1 GiB without `GLVidHeapReuseRatio=0` ([niri wiki](https://github.com/niri-wm/niri/wiki/Nvidia)). GL-specific, Vulkan unknown.
57f44a63d99fatagen2026-10-16 05:34 UTC250
- simpledrm alongside nvidia-drm needs `fbdev=1`, now default.
57f44a63d99fatagen2026-10-16 05:34 UTC251
- Xwayland and Electron need explicit sync (`WaylandLinuxDrmSyncobj` for Electron).
57f44a63d99fatagen2026-10-16 05:34 UTC252
57f44a63d99fatagen2026-10-16 05:34 UTC253
### 2.9 lavapipe for CI
57f44a63d99fatagen2026-10-16 05:34 UTC254
57f44a63d99fatagen2026-10-16 05:34 UTC255
- dmabuf since Mesa 24.1. `EXT_external_memory_dma_buf` and `EXT_image_drm_format_modifier` need libdrm at build time and a winsys fd or writable `/dev/udmabuf` at runtime (`lp_screen.c:200-207`, `lvp_device.c:1541-1556`). Without udmabuf only import works and the modifier extension is hidden.
57f44a63d99fatagen2026-10-16 05:34 UTC256
- Only `DRM_FORMAT_MOD_LINEAR` is offered (`lvp_formats.c:264, :278, :297`). Tiled modifiers, multi-modifier negotiation and KMS are untestable.
57f44a63d99fatagen2026-10-16 05:34 UTC257
- Advertises descriptor_buffer, descriptor_heap, queue_family_foreign, timeline semaphores, global priority up to REALTIME (`lvp_device.c:173-302, :1763`).
57f44a63d99fatagen2026-10-16 05:34 UTC258
- Accepts a pipe as a sync_file where ANV rejects it, so fence bugs can pass CI. Treat it as a rendering-correctness test only.
57f44a63d99fatagen2026-10-16 05:34 UTC259
57f44a63d99fatagen2026-10-16 05:34 UTC260
### 2.10 Uncertainties
57f44a63d99fatagen2026-10-16 05:34 UTC261
57f44a63d99fatagen2026-10-16 05:34 UTC262
Overlay counts per GPU, standard `OUT_FENCE_PTR`, which version added the `vblank` parameter, CAP_SYS_NICE and global priority today, NVIDIA's dmabuf implicit fences, direct scanout from sysmem. The README `kms.html` is stale against source in several places, source was trusted.
57f44a63d99fatagen2026-10-16 05:34 UTC263
57f44a63d99fatagen2026-10-16 05:34 UTC264
---
57f44a63d99fatagen2026-10-16 05:34 UTC265
57f44a63d99fatagen2026-10-16 05:34 UTC266
## 5. Smithay tearing status
57f44a63d99fatagen2026-10-16 05:34 UTC267
57f44a63d99fatagen2026-10-16 05:34 UTC268
GitHub data pulled unauthenticated (rate limit hit partway), line numbers against the local smithay clone `023b78b`.
57f44a63d99fatagen2026-10-16 05:34 UTC269
57f44a63d99fatagen2026-10-16 05:34 UTC270
### 5.1 Upstream PRs and issues
57f44a63d99fatagen2026-10-16 05:34 UTC271
57f44a63d99fatagen2026-10-16 05:34 UTC272
- [#1325](https://github.com/Smithay/smithay/pull/1325) `wp_tearing_control_v1` helper (PolyMeilex). Open since 2024-02, mergeable. Drakulix "Wayland side LGTM". YaLTeR wants compositor-decided policy. Unmerged only because the DRM side never landed.
57f44a63d99fatagen2026-10-16 05:34 UTC273
- [#1335](https://github.com/Smithay/smithay/pull/1335) initial async flip (PolyMeilex). Draft, conflicting, last activity 2026-02-28. cmeissl on 2026-02-16 "imo it does not make sense without the atomic backend".
57f44a63d99fatagen2026-10-16 05:34 UTC274
- [#1948](https://github.com/Smithay/smithay/pull/1948) async page flip v2 (cmeissl). Open, conflicting, no maintainer activity since March 2026. Compositor passes desired mode into `render_frame`, overlays disabled while async, async only when the primary plane alone changed compatibly, `async_flip_failed` triggers sync retry. Testers report it working on AMD, niri on AMD, NVIDIA Turing (nvidia-open) and RTX 4060 (610.43, kernel 6.18). Design intent is to keep the decision compositor policy.
57f44a63d99fatagen2026-10-16 05:34 UTC275
- [#1949](https://github.com/Smithay/smithay/pull/1949) async formats (cmeissl, draft). On top of #1948, adds `IN_FORMATS_ASYNC` per plane and an async dmabuf feedback tranche, needed for Intel.
57f44a63d99fatagen2026-10-16 05:34 UTC276
- [#2099](https://github.com/Smithay/smithay/pull/2099) closed 2026-07-17, Drakulix redirected to #1325 and #1948.
57f44a63d99fatagen2026-10-16 05:34 UTC277
- Tracker [#781](https://github.com/Smithay/smithay/issues/781) lists tearing-control as not implemented. No CHANGELOG entries.
57f44a63d99fatagen2026-10-16 05:34 UTC278
57f44a63d99fatagen2026-10-16 05:34 UTC279
### 5.2 drm-rs
57f44a63d99fatagen2026-10-16 05:34 UTC280
57f44a63d99fatagen2026-10-16 05:34 UTC281
`AtomicCommitFlags::PAGE_FLIP_ASYNC` exists in 0.14.0, 0.14.1 and `develop` (`src/control/mod.rs:1524`). `DriverCapability::AtomicASyncPageFlip` and `PageFlipFlags::ASYNC` exist too. `atomic_commit` passes bits straight to the ioctl. No drm-rs patch needed. The gap is smithay's `AtomicDrmSurface::page_flip` hardcoding `PAGE_FLIP_EVENT | NONBLOCK` (`smithay/src/backend/drm/surface/atomic.rs:898-903`).
57f44a63d99fatagen2026-10-16 05:34 UTC282
57f44a63d99fatagen2026-10-16 05:34 UTC283
### 5.3 Other compositors
57f44a63d99fatagen2026-10-16 05:34 UTC284
57f44a63d99fatagen2026-10-16 05:34 UTC285
- cosmic-comp pins upstream smithay `cf43a02`, no tearing. [#248](https://github.com/pop-os/cosmic-comp/issues/248) open, "not a very high priority". [PR #2588](https://github.com/pop-os/cosmic-comp/pull/2588) closed by Drakulix, who said it belongs in smithay and that composited tearing still syncs on the cross-GPU copy, gaining little latency.
57f44a63d99fatagen2026-10-16 05:34 UTC286
- niri [#844](https://github.com/niri-wm/niri/issues/844) open. YaLTeR "We're mainly waiting for Smithay/smithay#1335", and in [discussion #2450](https://github.com/niri-wm/niri/discussions/2450) "There's nobody driving the Smithay PR... just someone to finish the Smithay PR." No timeline.
57f44a63d99fatagen2026-10-16 05:34 UTC287
- Forks carrying #1948 plus #1949. [urayde/niri](https://github.com/urayde/niri) (AUR `niri-tearing-git`, pins `urayde/smithay@6df9a643`, users report busy-looping frame callbacks while tearing), niri-spicy (`losnoco/smithay` `spicy-master`), [alexparlett/smithay#4](https://github.com/alexparlett/smithay/pull/4) rebase from 2026-10-10. Pinnacle, MagmaWM, StrataWM, fht-compositor have nothing.
57f44a63d99fatagen2026-10-16 05:34 UTC288
57f44a63d99fatagen2026-10-16 05:34 UTC289
### 5.4 Kernel constraints (torvalds master)
57f44a63d99fatagen2026-10-16 05:34 UTC290
57f44a63d99fatagen2026-10-16 05:34 UTC291
- 6.8 added atomic async flips and `DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP`. 6.11 allowed changing `IN_FENCE_FD` and `FB_DAMAGE_CLIPS`. 6.15 let drivers accept non-primary planes via `atomic_async_check`. 6.18 fixed a 6.15-6.17 regression for unchanged values on non-primary planes.
57f44a63d99fatagen2026-10-16 05:34 UTC292
- `drm_atomic_set_property` (`drm_atomic_uapi.c:1190-1297`). Connector and CRTC props only with unchanged values. Plane props unchanged except `FB_ID`, `IN_FENCE_FD`, `FB_DAMAGE_CLIPS`, on primary always and on others if the driver allows. smithay writes `VRR_ENABLED` each flip (`atomic.rs:1198`), fine since unchanged.
57f44a63d99fatagen2026-10-16 05:34 UTC293
- amdgpu fast updates only, overlays from about 6.15, cursor rejected, async disables PSR, `IN_FORMATS_ASYNC` in 7.2.
57f44a63d99fatagen2026-10-16 05:34 UTC294
- i915/xe require unchanged stride, modifier, format, rects and plane set. The first async request is silently done as sync. `IN_FORMATS_ASYNC` in 6.16.
57f44a63d99fatagen2026-10-16 05:34 UTC295
- nouveau primary only. nvidia-drm maps async to tearing (515-615), primary only, format handling unknown.
57f44a63d99fatagen2026-10-16 05:34 UTC296
- Async flips with NONBLOCK still deliver a flip event, at latch time not vblank. On failure userspace must retry sync. Async plus VRR unverified on all drivers.
57f44a63d99fatagen2026-10-16 05:34 UTC297
57f44a63d99fatagen2026-10-16 05:34 UTC298
### 5.5 Options
57f44a63d99fatagen2026-10-16 05:34 UTC299
57f44a63d99fatagen2026-10-16 05:34 UTC300
- **Wait.** Nothing is moving. #1948 has had no maintainer action for about seven months.
57f44a63d99fatagen2026-10-16 05:34 UTC301
- **Contribute upstream, carry #1948 meanwhile (reader's recommendation).** Rebase onto HEAD, which conflicts with `commit_frame` and `handle_flip(&PreparedFrame)` (`smithay/src/backend/drm/compositor/mod.rs:2484-2600`). Touch points `FrameState::commit`/`page_flip` (`:732`, `:747`), `submit` (`:2541`), `handle_flip` (`:2570`), overlay and cursor assignment (`:3709`, `:3022`), the `render_frame` diff base (`:1784`), `DrmSurface::page_flip` (`surface/mod.rs:438`), `AtomicDrmSurface::page_flip` (`atomic.rs:868`), capability probing in `device/mod.rs`. Then #1949 and #1325. About 400 lines across 12 files on a fast-moving file.
57f44a63d99fatagen2026-10-16 05:34 UTC302
- **Side-channel commit, not viable.** `current_frame`/`pending_frame`/`queued_frame` (`:1069-1072`) go stale and `render_frame` diffs against them (`:1784`). `DrmDevice` reports every flip as an anonymous `DrmEvent::VBlank(crtc)` (`device/mod.rs:544-554`), so `frame_submitted` (`:2618`) would retire the wrong frame and could re-render into a buffer still on screen. `used_planes` (`atomic.rs:171`) and swapchain accounting drift. Concurrent commits likely EBUSY (unverified).
57f44a63d99fatagen2026-10-16 05:34 UTC303
- **Composited tearing** (async-flip the compositor's own buffer) works on primary-only drivers, gains little where a cross-GPU copy is involved, reasonable fallback on single GPU when the game can't be scanned out.
57f44a63d99fatagen2026-10-16 05:34 UTC304
57f44a63d99fatagen2026-10-16 05:34 UTC305
---
57f44a63d99fatagen2026-10-16 05:34 UTC306
57f44a63d99fatagen2026-10-16 05:34 UTC307
## 6. wry `master` animation system (the reference design)
57f44a63d99fatagen2026-10-16 05:34 UTC308
57f44a63d99fatagen2026-10-16 05:34 UTC309
Snapshot of wry `master` (`8b7ad7e6`, 2026-08-11) extracted to `/tmp/monolith-research/wry-master`. Paths relative to that root. This supersedes `wry-reports.md` section 5 for animation semantics, which described the later `jay-port` branch.
57f44a63d99fatagen2026-10-16 05:34 UTC310
57f44a63d99fatagen2026-10-16 05:34 UTC311
### 6.1 Data model
57f44a63d99fatagen2026-10-16 05:34 UTC312
57f44a63d99fatagen2026-10-16 05:34 UTC313
Animations interpolate integer `Rect`s per tree node, nothing else. `AnimationState` (`src/animation.rs:28`) holds `windows` (`WindowAnimation`), `phased` (`PhasedWindowAnimation`) and `exits` (`ExitAnimation`) plus a tick handle. `WindowAnimation` stores `from, to, start_nsec, duration_nsec, curve, last_damage, retained` (`:480`). `PhasedWindowAnimation` stores `segments`, `start_nsec`, per-phase `duration_nsec`, `curve`, `last_damage`, `final_rect`, `route_edges`, `retained` (`:506`). `ExitAnimation` adds a retained toplevel, `frame_inset`, `source_body_size`, `active`, `layer` (`:666`). Render queries `visual_rect(node, layout, now)` (`src/renderer.rs:218, :481, :1090`). Containers offset children through `presentation_child_body` (`src/renderer.rs:471`). Floats animate outer frame and body as two nodes (`src/tree/float.rs:401`).
57f44a63d99fatagen2026-10-16 05:34 UTC314
57f44a63d99fatagen2026-10-16 05:34 UTC315
### 6.2 Multiphase planning
57f44a63d99fatagen2026-10-16 05:34 UTC316
57f44a63d99fatagen2026-10-16 05:34 UTC317
A phase is a synchronized step list where every step is a pure horizontal or vertical move or scale (`crates/layout-animation/src/lib.rs:1650`). Diagonal or mixed-axis motion of a single rect is rejected. A plan is `Vec<MultiphasePhase>` (`:362`).
57f44a63d99fatagen2026-10-16 05:34 UTC318
57f44a63d99fatagen2026-10-16 05:34 UTC319
`plan_forward` (`:696`) tries single-action, then `SpaceThenOrthogonalGrowth` per axis, then `HierarchyOrderedScales`, then `OrientationChange` per axis, then `SwapLanes` per axis. `plan_no_overlap_explained` (`:522`) rejects empty bounds, empty windows, duplicate nodes, initial or final overlap (`:651`), and undersized targets via `sane_min_size` of `bounds/8 min 1` (`:674`, `:1777`). It tries forward, then the reversed request with the plan reversed (`:1682`, `:1699`). Strategy enum `NoOp, SingleAction, MixedSinglePhase, HierarchyOrderedScales, OrientationChange, SwapLanes, SpaceThenOrthogonalGrowth, ReversedForwardPlan` (`:317`).
57f44a63d99fatagen2026-10-16 05:34 UTC320
57f44a63d99fatagen2026-10-16 05:34 UTC321
Batching partitions windows into motion groups by intersecting `from.union(to)` expanded by clearance (`:618`, `:1750`). Clearance is `border` or `gap + 2*border` (`src/state/animations.rs:274`). Groups with any `Plain`-style node run plain (`:259`), otherwise phased with plain fallback on diagnostic failure (`:280`).
57f44a63d99fatagen2026-10-16 05:34 UTC322
57f44a63d99fatagen2026-10-16 05:34 UTC323
`validate_plan_continuous_diagnostic` (`crates/layout-animation/src/lib.rs:1444`) checks each step starts at the current rect, no duplicate or unknown node per phase, pairwise linear motion overlap-free across the whole phase via rational time-interval intersection (`:1540`), and every node ends at its target. `validate_phase_paths` does the same for retarget paths (`:572`). Swap lanes snap sub-pixel remainders within `SWAP_AXIS_SNAP_PX = 1` (`:169`).
57f44a63d99fatagen2026-10-16 05:34 UTC324
57f44a63d99fatagen2026-10-16 05:34 UTC325
### 6.3 Interruption
57f44a63d99fatagen2026-10-16 05:34 UTC326
57f44a63d99fatagen2026-10-16 05:34 UTC327
Plain `set_target` restarts from the current visual rect (`src/animation.rs:190`). Same target keeps running, `from == new` cancels (`:206`). No velocity stored or used, sampling is `lerp_rect(from, to, curve(t))` (`:751`).
57f44a63d99fatagen2026-10-16 05:34 UTC328
57f44a63d99fatagen2026-10-16 05:34 UTC329
Batched multiphase interruption builds requests from `visual_rect(node, old, now)` (`src/state/animations.rs:246`) and tries, in order, existing phased retarget, bridged retarget, fresh plan (`:280`).
57f44a63d99fatagen2026-10-16 05:34 UTC330
- Existing retarget routes each node through its stored `route_edges` graph to the new target with BFS `rect_graph_route` and validates the combined paths (`src/animation.rs:554, :599`, `src/state/animations.rs:320`).
57f44a63d99fatagen2026-10-16 05:34 UTC331
- Bridged retarget routes from the current visual position back to the stale `candidate.old`, pads shorter bridges with holds, then appends a fresh plan from settled rects (`src/state/animations.rs:355, :64`).
57f44a63d99fatagen2026-10-16 05:34 UTC332
- Duplicate candidates within one layout pass coalesce to first `old` and last `new` with chained hierarchy (`:32`).
57f44a63d99fatagen2026-10-16 05:34 UTC333
57f44a63d99fatagen2026-10-16 05:34 UTC334
Repeated interruption re-roots every plan at displayed rects while `route_edges` accumulate history, so paths can reverse along prior lanes (tests `phased_route_remembers_original_path_after_retarget` at `src/animation.rs:964`, `bridged_retarget_handles_second_rotation_interrupt` at `src/state/animations.rs:698`).
57f44a63d99fatagen2026-10-16 05:34 UTC335
57f44a63d99fatagen2026-10-16 05:34 UTC336
### 6.4 Curves and timing
57f44a63d99fatagen2026-10-16 05:34 UTC337
57f44a63d99fatagen2026-10-16 05:34 UTC338
`Linear` or pregenerated piecewise cubic bezier (`crates/layout-animation/src/lib.rs:8`). Presets linear, ease, ease-in, ease-out, ease-in-out, unknown falls back to ease-out (`:30`). Custom beziers need finite values and `x1, x2` in `0..=1` (`:40`). Sampling flattens to at most 33 points and lerps (`:71`, `:86`).
57f44a63d99fatagen2026-10-16 05:34 UTC339
57f44a63d99fatagen2026-10-16 05:34 UTC340
`duration_ms` is total for plain and exit, per phase for phased, so phased total is `duration * phases` (`src/animation.rs:523`). Zero duration or empty rect disables (`:200`, `:255`). Time is `eng.now()` (`src/state.rs:598`), sampled at queue, render and latch time, no predicted presentation time (`src/animation.rs:731`, `src/renderer.rs:209`).
57f44a63d99fatagen2026-10-16 05:34 UTC341
57f44a63d99fatagen2026-10-16 05:34 UTC342
Ticks are driven by output latch events. `ensure_animation_tick` attaches a latch listener to every output and damages full output extents (`src/state/animations.rs:623`). `after_latch` re-damages active unions and detaches when idle (`src/animation.rs:725`).
57f44a63d99fatagen2026-10-16 05:34 UTC343
57f44a63d99fatagen2026-10-16 05:34 UTC344
### 6.5 Client buffers
57f44a63d99fatagen2026-10-16 05:34 UTC345
57f44a63d99fatagen2026-10-16 05:34 UTC346
No resize transaction or blocking. `tl_change_extents` queues the animation from `prev` to `rect` and configures the client immediately (`src/tree/toplevel.rs:193`). The renderer stretches the live surface into the current visual body with a scissor equal to it, `stretch` set whenever content size differs (`src/renderer.rs:774, :836, :1111`). A new buffer mid-animation is simply stretched into the next sampled rect. Moves never snapshot, `retained` is `None` for plain, phased and spawn-in (`src/animation.rs:230, :280, :296`). Only close captures a `RetainedToplevel` (`:87`).
57f44a63d99fatagen2026-10-16 05:34 UTC347
57f44a63d99fatagen2026-10-16 05:34 UTC348
### 6.6 Spawn, close, other
57f44a63d99fatagen2026-10-16 05:34 UTC349
57f44a63d99fatagen2026-10-16 05:34 UTC350
Spawn-in grows from an empty rect at the target centre (`src/animation.rs:739`, test `:1057`). Spawn-out shrinks from the current visual rect to centre, rendering the retained texture plus border and background (`:317`, `src/renderer.rs:530`). Tiled resize/move use multiphase when plannable, else plain. Floating map uses spawn-in (`src/tree/float.rs:164`), floating move/resize queue plain animations for outer and body (`:401`), tile-to-float is linear (`src/tree/toplevel.rs:1252`). Mono collapse and child removal set `animate_next_layout` or wrap in `with_layout_animations` (`src/tree/container.rs:1301, :1977`).
57f44a63d99fatagen2026-10-16 05:34 UTC351
57f44a63d99fatagen2026-10-16 05:34 UTC352
### 6.7 Scheduling and damage
57f44a63d99fatagen2026-10-16 05:34 UTC353
57f44a63d99fatagen2026-10-16 05:34 UTC354
Start damages `old.union(new)` plus border (`src/state/animations.rs:208`). Multiphase start damages the union of all phase endpoints (`:446`). Each latch tick runs `damage_active` over `last ∪ current ∪ target` per animation, drops finished ones, returns liveness (`src/animation.rs:419`). Layout batching wraps `perform_layout` in `begin_layout_animation_batch` / `finish_layout_animation_batch` inside the `container_layout` task (`src/tree/container/tasks.rs:14`).
57f44a63d99fatagen2026-10-16 05:34 UTC355
57f44a63d99fatagen2026-10-16 05:34 UTC356
### 6.8 Config
57f44a63d99fatagen2026-10-16 05:34 UTC357
57f44a63d99fatagen2026-10-16 05:34 UTC358
`[animations]` with `enabled` (default false), `duration-ms` (160), `style` `plain` or `multiphase` (default multiphase), `curve` preset or `[x1,y1,x2,y2]` (default ease-out) (`crates/toml-spec/spec/spec.generated.md:1081`, `crates/jay-config-schema/src/animations.rs:1`). Runtime IPC mirrors these, duration clamped to 10000 (`src/config/handler/options.rs:61`, `src/config/handler/dispatch.rs:395`). Disabling clears state and damages root (`src/state/animations.rs:564`). Scoped overrides `with_layout_animations`, `with_linear_layout_animations`, `suppress_animations_for_next_layout` (`:597`, `:606`, `src/state.rs:253`).
57f44a63d99fatagen2026-10-16 05:34 UTC359
57f44a63d99fatagen2026-10-16 05:34 UTC360
### 6.9 Tests
57f44a63d99fatagen2026-10-16 05:34 UTC361
57f44a63d99fatagen2026-10-16 05:34 UTC362
About 40 planner tests in `crates/layout-animation` (swaps, rotations, lane direction and clearance, uneven splits, orientation changes, hierarchy-ordered scales, generated sibling swaps and redistributions, mixed single phase vs diagonal rejection, reverse equivalence, diagnostics, continuous validation). `src/animation.rs:772` onward covers lerp, bezier, spawn/exit, per-phase duration, route memory, interruption and retargeting. `src/state/animations.rs:643` onward covers plain-style groups, bridged interrupts and candidate coalescing.
57f44a63d99fatagen2026-10-16 05:34 UTC363
57f44a63d99fatagen2026-10-16 05:34 UTC364
### 6.10 Limitations visible in code
57f44a63d99fatagen2026-10-16 05:34 UTC365
57f44a63d99fatagen2026-10-16 05:34 UTC366
- Only single-axis move or scale steps are expressible. Diagonal motion falls back to overlapping plain interpolation.
57f44a63d99fatagen2026-10-16 05:34 UTC367
- No-overlap holds only within a clearance-expanded motion group. Cross-output or cross-scale moves skip animation (`src/state/animations.rs:177`).
57f44a63d99fatagen2026-10-16 05:34 UTC368
- Durations are per phase, so complex plans exceed `duration-ms`.
57f44a63d99fatagen2026-10-16 05:34 UTC369
- Interruption is position-based, no velocity continuity.
57f44a63d99fatagen2026-10-16 05:34 UTC370
- Retained snapshots only for exits. Live resize visibly stretches the client buffer.
57f44a63d99fatagen2026-10-16 05:34 UTC371
- 1px swap snap and `bounds/8` minimum shrink are deliberate imperceptibility heuristics.