contents

Gaming#

The pieces that matter for games, and how to turn each one on.

Tearing#

Async page flips need three things at once:

  1. allow-tearing on the game's output,
  2. the game fullscreen on that output,
  3. the game requesting tearing via wp_tearing_control_v1, or a window rule granting it allow-tearing:
output "DP-3" {
    allow-tearing
}
window-rule {
    match app-id=#"^steam_app_"#
    allow-tearing #true
}

The kernel must support atomic async flips (modern Intel/AMD do). When any condition is missing, carrot stays on vsync; when a cursor update or VRR change collides with a frame, that frame goes out synced and tearing resumes after. Presentation feedback drops its vsync flag on torn frames, so a frame-timing overlay can tell which frames actually tore.

VRR / adaptive sync#

Per output, in config:

output "DP-3" {
    variable-refresh-rate on-demand=#true
}

on-demand engages while a fullscreen window is on that output; the bare flag holds VRR on always; omitting the node leaves it off. The display must report vrr_capable; carrot warns once if it does not.

Mouse input#

  • zwp_pointer_constraints_v1 and zwp_relative_pointer_v1 give games real pointer lock and raw relative motion. Accelerated and unaccelerated deltas are tracked separately, so in-game sensitivity is unaffected by desktop acceleration.
  • Set your mouse's true dpi in a device rule and carrot normalizes speed to a 1000 DPI baseline; accel-profile "flat" gives games the raw curve.

Per-game key remaps#

Remap profiles rewrite keys only while a matching window is focused, which fixes games with immovable keybindings without touching your desktop. Match on app-id (for Steam games, steam_app_<appid>) and map away.

Direct scanout#

A fullscreen game skips the compositor's renderer entirely. When a lone, mode-sized, opaque dmabuf is all the output shows, carrot rides the client's buffer on the primary plane as its own framebuffer, imported once per buffer and cached, with the client's implicit fence in the atomic commit and the buffer's release parked until the replacing flip lands: zero compositor GPU work, latency down to the flip itself, and presentation feedback reporting honest ZERO_COPY. Anything the plane cannot express (an overlay, a rule effect, a notification on top) falls back to the composed path for exactly as long as it must.

Frame pacing#

carrot schedules frames instead of reacting to them: frame callbacks fire at latch so clients get the whole frame budget (full-rate pacing, every vblank), and the compositor holds each dirty frame until just before the last instant that still makes the next scan. The safety margin adapts on its own, growing sharply on a missed flip and decaying when stable, and after consecutive misses the scheduler parks itself on the conservative path until things settle. Both knobs live in debug config: latency-policy switches the scheduler, latch-margin-us floors the margin.

Eye candy and games#

Fullscreen windows are exempt from all of it: move and close animations, rounded corners, shadows, blur, and dim-inactive all skip a fullscreen surface, so nothing composites over a running game. A window rule with no-anim silences a game's open animation too, and animations { off } in config stops motion globally.

X11 games#

Most games still run through XWayland; carrot's built-in X window manager handles them like any other window, rules and remaps included.