atagen

monolith

public
Star0
Watch0
5a760f013a020a7a0da6271a5f7d998caf240e8a
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
research/wry-reports.mdMarkdown645 lines89.3 KB

wry backend research reports

Raw reader reports on wry (jay fork) internals, gathered for monolith planning. Paths are relative to /home/bolt/code/wry at 6df16858. Wry forked from jay at f73b4efe (2026-08-16).

Status

  • Executor core (src/async_engine*)
  • io_uring, timers, cpu_worker, task usage
  • DRM/KMS metal backend and frame scheduling
  • Wire protocol and client handling
  • Vulkan renderer
  • Tree, transactions, multiphase animations
  • Process model, config, tests, utilities
  • Smithay extension points (comparison baseline)

0. Executor core

src/async_engine.rs, src/async_engine/ae_task.rs, src/async_engine/ae_yield.rs, about 500 lines total.

  • Single threaded, Rc based, nothing is Send. Wakers use a custom RawWakerVTable with a non-atomic refcount (NumCell), so a waker crossing threads would be UB. The cpu_worker bridges threads with eventfds instead.
  • One Box<Task> allocation per spawn. A wake pushes a two-word Runnable onto a VecDeque. No atomics or locks anywhere on the wake path.
  • Task state is a bitset (RUNNING, RUN_AGAIN, COMPLETED, EMPTIED, CANCELLED). A wake while running sets RUN_AGAIN instead of double-queueing.
  • Dropping the returned SpawnedFuture cancels the task, so lifetimes are structured by ownership. Awaiting it yields the task’s output.
  • Four ordered phases, EventHandling, Layout, PostLayout, Present. dispatch() drains phase 0 to a fixpoint, then phase 1, and so on. Work woken into an earlier phase by a later one waits for the next pass. After all phases the iteration counter bumps and yield_now() waiters wake.
  • now() / now_rt() are cached per pass, so everything in one pass sees the same timestamp.
  • spawn defaults to EventHandling. spawn2 pins a phase.

How the phases are used (src/compositor.rs:598-745). About 38 long-lived global tasks follow a dirty-queue pattern. Example container_layout (src/tree/container.rs:2706) loops on state.pending_container_layout.pop().await and checks a layout_scheduled flag. The task lives in Phase::Layout, so any number of requests that dirty a container during event handling collapse into one layout pass. The same pattern drives render positions, title textures, output render data, popup positioning, tree serial groups, configurables (all PostLayout), and screencast and present loops (Present). Client send tasks are PostLayout (src/client/tasks.rs:27), so events generated during a pass are flushed once after layout.


1. io_uring, timers, cpu_worker, task usage

1.1 io_uring layer (src/io_uring.rs, src/io_uring/ops/)

Opcodes wrapped, one module each (src/io_uring/ops.rs:4-19). READ/WRITE (ops/read_write.rs:18-30), READ/WRITE no-cancel with explicit offset (ops/read_write_no_cancel.rs:21-58), WRITE vec (ops/read_write_vec.rs:29-86), WRITEV (ops/writev_external.rs:25-47), RECVMSG (ops/recvmsg.rs:18-83), SENDMSG (ops/sendmsg.rs:22-85), POLL_ADD (ops/poll.rs:17-42, external callback variant ops/poll_external.rs:35-74), TIMEOUT (ops/timeout.rs:28-44, external ops/timeout_external.rs:49-73), LINK_TIMEOUT (ops/timeout_link.rs:18-29), ACCEPT (ops/accept.rs:15-37), CONNECT (ops/connect.rs:19-39), ASYNC_CANCEL (ops/async_cancel.rs:16-24), FUTEX_WAIT/FUTEX_WAKE (ops/futex.rs:33-75), READ_MULTISHOT (ops/read_multishot.rs:49-77). A provided buffer-ring helper exists (buffer_ring.rs:79-173) but outside tests is only wired to multishot reads.

Setup has no SQPOLL and no registered fds/buffers on the hot path. IoUring::new tries SUBMIT_ALL|COOP_TASKRUN|SINGLE_ISSUER|DEFER_TASKRUN then drops one flag at a time on ENOSYS (src/io_uring.rs:131-154), and hard-requires NODROP (src/io_uring.rs:155-157). Rings are mmaped directly, queue depth 32 on both main and worker rings (src/compositor.rs:276, src/cpu_worker.rs:367).

Submit. encode (src/io_uring.rs:488-525) drains to_encode into the SQ while space remains, reserves two slots for has_timeout ops, and sets IOSQE_IO_LINK on the parent so the following LINK_TIMEOUT fires as a link (src/io_uring.rs:516-518, ops/timeout_link.rs:40-46). Used by write and sendmsg with absolute Time (ops/read_write.rs:32-61, ops/sendmsg.rs:74-83).

Enter/wait. run (src/io_uring.rs:400-453) is the only blocking point. An empty queue submits 0 and waits for 1 with GETEVENTS. Pending submits with empty to_encode submit to_submit and wait for 1. Non-empty to_encode submits u32::MAX with no wait and loops to encode more (src/io_uring.rs:417-424). yields force min_complete=0 (src/io_uring.rs:425-427). EAGAIN|EBUSY|EINTR are ignored, other enter errors are fatal (src/io_uring.rs:438-443). A short submit retries with a pure wait (src/io_uring.rs:444-451). On teardown kill encodes, submits u32::MAX, then waits until tasks empties (src/io_uring.rs:594-617).

Wakeup. Almost every awaitable op takes pending_results.acquire() (src/io_uring/pending_result.rs:21-37), stores a clone in the task, schedules a pooled task struct, and awaits it (e.g. ops/read_write.rs:40-60). dispatch_completions matches user_data to the task, with CQE_F_MORE routed to complete_partial for multishot and terminal CQEs routed to task.complete (src/io_uring.rs:455-486). PendingResult::complete stores res and wakes the Waker (src/io_uring/pending_result.rs:66-74). poll translates negative res to OsError (src/io_uring/pending_result.rs:105-122). Non-Future completions use PollCallback, TimeoutCallback, WritevData::done, ReadMultishotCallback (ops/poll_external.rs:15-17, ops/timeout_external.rs:16-18, ops/writev_external.rs:20-24, ops/read_multishot.rs:28-31).

Cancel. Dropping an op future cancels it, because the future owns a Cancellable whose Drop calls cancel_task (src/io_uring.rs:527-544, src/io_uring.rs:640-644). Not-yet-encoded ops complete locally with synthetic ECANCELED (src/io_uring.rs:542-554). In-kernel ops get an ASYNC_CANCEL by user_data (ops/async_cancel.rs:16-24, ops/async_cancel.rs:41-44). is_cancel tasks are exempt from kill cancellation (src/io_uring.rs:596-604). PendingPoll, PendingTimeout, PendingReadMultishot Drop impls do the same (ops/poll_external.rs:25-33, ops/timeout_external.rs:26-34, ops/read_multishot.rs:39-47). read/write_no_cancel instead forbids userspace cancel with an on_drop panic guard, for worker-thread shm I/O (ops/read_write_no_cancel.rs:92-96).

No epoll fallback. CreateUring/NoDrop become CompositorError::IoUringError and exit (src/io_uring.rs:96-102, src/compositor.rs:227-241, src/compositor.rs:275-276). Grep finds no epoll/calloop fallback.

1.2 Main loop, one iteration (src/io_uring.rs:400-453)

Bump iteration, wake yields wakers, then loop eng.dispatch() plus dispatch_completions() until no more CQEs were consumed. Then encode() queued SQEs, accumulating to_submit, then one io_uring_enter with the wait policy above. Startup order is engine, ring 32, signal handler, wheel, run-toplevel, cpu worker, eventfd cache, then ring.run() (src/compositor.rs:275-286, src/compositor.rs:530-535). The compositor future only runs backend setup. Blocking lives in the ring (src/compositor.rs:538-588).

Unusual. Ready futures are drained to quiescence before entering the kernel, and Debouncer plus yields let hot paths skip the blocking wait (src/io_uring/debounce.rs:15-34, src/io_uring.rs:425-427).

1.3 Timers

wheel.rs is a single-timerfd, heap-multiplexed wheel (src/wheel.rs:102-114). Wheel::new creates a CLOCK_MONOTONIC timerfd and spawns dispatch (src/wheel.rs:117-137). timeout(ms) rounds to ms, re-arms with TFD_TIMER_ABSTIME only if earlier than the armed expiry, pushes {expiration,id} on a BinaryHeap, and registers the waiter (src/wheel.rs:156-194, src/time.rs:34-46). dispatch_once does ring.read(timerfd) then completes every due entry and re-arms to the next live entry, skipping cancelled ids (src/wheel.rs:218-254). Waiters are manual Futures storing a Waker (src/wheel.rs:65-100). Drop unregisters and recycles (src/wheel.rs:78-87). Consumers include client shutdown 5s (src/client/tasks.rs:35) and cursor tick (src/tasks/hardware_cursor.rs:17-22).

Separately, TimerFd::expired is ring.read(timerfd) plus timerfd_settime programming (src/utils/timer.rs:42-67), used by idle, 40Hz latch, damage, config, virtual-output (src/tasks/idle.rs:14-20, src/tasks/const_clock.rs:19-35, src/virtual_output.rs:731). Raw ring.timeout(nsec) with IORING_TIMEOUT_ABS is used for present/vblank, transactions, sqlite, session (src/io_uring/ops/timeout.rs:27-44, src/virtual_output.rs:475, src/output_schedule.rs:172-174, src/transactions.rs:453).

1.4 cpu_worker (src/cpu_worker.rs, src/cpu_worker/jobs/)

One background thread named cpu worker with its own AsyncEngine plus ring depth 32 (src/cpu_worker.rs:275-294, src/cpu_worker.rs:366-386). CpuJob::work returns the thread-side CpuWork, and completed runs back on the main thread (src/cpu_worker.rs:33-50).

Main submits with submit, leaking the job box and queueing Job::New{id, *mut dyn CpuWork} on an AsyncQueue, returning a PendingJob (src/cpu_worker.rs:323-339). An equeue_jobs task moves it under lock to the shared VecDeque and writes the have_new_jobs eventfd (src/cpu_worker.rs:249-261). The worker ring.reads that eventfd and runs CpuWork::run. None completes synchronously, Some(AsyncCpuWork) is spawned on the worker engine until WorkCompletion::complete (src/cpu_worker.rs:425-473, src/cpu_worker.rs:68-77). Completion pushes the id to the shared completed queue, signals a condvar for sync waiters, and writes have_completed_jobs (src/cpu_worker.rs:475-487). The main wait_for_completions task ring.reads it and invokes job.completed() (src/cpu_worker.rs:216-247). Dropping a pending job warns, sends Job::Cancel, and blocks dispatching until gone. detach marks it abandoned (src/cpu_worker.rs:151-204).

Call sites, all via cpu.submit.

  • CloseMemWork, munmap off-thread, detached (src/clientmem.rs:211-215)
  • Text RenderJob (src/text.rs:528)
  • Vulkan allocator FreeJob and AllocJob (src/gfx_apis/vulkan/allocator.rs:185, src/gfx_apis/vulkan/allocator.rs:303)
  • shm transfers, ImgCopyWork for pointer-backed and ReadWriteWork for fd-backed mem (src/gfx_apis/vulkan/transfer.rs:505, src/gfx_apis/vulkan/transfer.rs:539, src/cpu_worker/jobs/img_copy.rs:32-59, src/cpu_worker/jobs/read_write.rs:98-148)

1.5 eventfd_cache (src/eventfd_cache.rs)

Avoids eventfd() churn and forgotten counter drains. Pools fds, signaled bools, and Readable callback objects (src/eventfd_cache.rs:39-45), with a recycle task spawned at creation (src/eventfd_cache.rs:64-75). acquire reuses or creates (src/eventfd_cache.rs:77-89). Async wait is ring.readable (src/eventfd_cache.rs:97-104). External wait reuses a pooled Readable through readable_external (src/eventfd_cache.rs:120-137). Drop of a still-signaled fd pushes it to recycle, which ring.reads each fd empty before returning it to the pool (src/eventfd_cache.rs:156-224). return_signaled recycles the bool only when the cache holds the last ref (src/eventfd_cache.rs:208-215). Held by State and passed to the copy-device registry (src/compositor.rs:466-469).

1.6 Long-lived tasks in src/tasks/

  • handle_backend_events, demuxes the backend queue into connector/input/drm handlers (src/tasks.rs:22-25, src/tasks/backend.rs:12-28)
  • handle_slow_clients / handle_slow_ei_clients, backpressure kills via check_queue_size (src/tasks.rs:27-35, src/tasks/slow_clients.rs:8-28)
  • handle_const_40hz_latch, dummy-output latch clock (src/tasks.rs:37-44)
  • Per-connector ConnectorHandler, output lifecycle (src/tasks/connector.rs:92-98, src/tasks/connector.rs:113-141)
  • Per-DRM-device handler (src/tasks/drmdev.rs:74)
  • Per-input-device handler (src/tasks/input_device.rs:48-58)
  • idle, TimerFd-driven DPMS/idle state machine (src/tasks/idle.rs:13-32, src/compositor.rs:651)

1.7 Spawn survey

eng.spawn/spawn2 has 175 hits in 64 files.

  • start_global_event_handlers alone spawns about 38 named globals covering backend events, slow clients, cursor tick, layout, render positions/titles/textures, output render, managers, idle, popups, screencast, damage, matchers, seats, sqlite, sessions, transactions (src/compositor.rs:598-745).
  • Per client, 3 tasks per Wayland client plus 2-3 per EI client (src/client/tasks.rs:20-28, src/ei/ei_client/ei_tasks.rs:21-27, src/client.rs:258-260).
  • Acceptors/listeners are async accept loops, Wayland secure and insecure, IPC, tagged, security-context, EI (src/acceptor.rs:144-153, src/acceptor.rs:168-191, src/ipc.rs:114, src/tagged_acceptor.rs:84, src/security_context_acceptor.rs:90-95, src/ei/ei_acceptor.rs:124).
  • Protocol I/O is async everywhere. dbus auth/in/out, pipewire in/out, forker in/out, xcon in/out, tool clients (src/dbus/auth.rs:45-50, src/pipewire/pw_con.rs:356-359, src/forker.rs:123-135, src/xcon.rs:469-522).
  • Xwayland is xwayland::manage plus XWM plus per-transfer shm copy tasks (src/state.rs:1636, src/xwayland.rs:219, src/xwayland/xwm.rs:1788-1901).
  • Rendering/output is phased spawn2 layout/present work plus per-output schedule, vblank, virtual-output, backend event/present tasks (src/compositor.rs:611-680, src/tasks/connector.rs:186-189, src/virtual_output.rs:1023-1024, src/backends/x.rs:359-364, src/backends/metal/video.rs:1221).
  • Vulkan transfer/vblank await tasks are also spawned (src/gfx_apis/vulkan/transfer.rs:582-627).

Rough split of non-test spawns. Client/EI/IPC/dbus/pipewire about 35, render/layout/output/present about 45, backend/connector/DRM/input/seat about 15, xwayland/X11 about 8, global compositor/timer/sqlite/config about 25.

1.8 Client sockets

One supervisor plus receive, send, terminate tasks per client (src/client/tasks.rs:18-44). Receive parses with WlBufFdIn over ring.recvmsg, including SCM_RIGHTS fds (src/client/tasks.rs:59-93, src/utils/buffd/wl_buf_in.rs:52-126). Send buffers into OutBufferSwapchain. commit moves a non-empty cur to pending, and the send task waits on flush_request, swaps pending out, and BufFdOut::flushes each buffer with an absolute 5s sendmsg linked timeout (src/client/tasks.rs:112-147, src/utils/buffd/buf_out.rs:57-91, src/utils/buffd/buf_out.rs:102-131, src/io_uring/ops/sendmsg.rs:32-85). ECANCELED becomes Timeout, EPIPE/ECONNRESET become Closed (src/utils/buffd/buf_out.rs:121-130). Slow clients are reaped when pending exceeds 10 (src/utils/buffd/buf_out.rs:48-59, src/client.rs:520). Direct recvmsg/sendmsg with no userspace epoll shim. Generic bidirectional connections use the same pair through BufIo (src/utils/bufio.rs:101-114, src/utils/bufio.rs:143-179).

Notable vs calloop. Everything above plus pooled task structs, PendingResult slab recycling raw pointers, link-timeouts instead of timer races, and a second full runtime on the worker thread.


2. DRM/KMS metal backend and frame scheduling

2.1 KMS model

Atomic only. Device init enables DRM_CLIENT_CAP_ATOMIC with value 2 and then probes resources, planes, CRTCs, encoders (src/backends/metal/video.rs:2030, src/backends/metal/video.rs:2032, src/backends/metal/video.rs:2040). It also tries DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE and records support (src/backends/metal/video.rs:2035).

Desired state is BackendConnectorState in backend.rs (src/backend.rs:785). Committed DRM state is split into DrmPlaneState, DrmCrtcState, DrmConnectorState plus property structs in transaction (src/backends/metal/transaction.rs:71). create_transaction clones live drm_state for every non-leased plane, CRTC and connector and stores requested connector state separately (src/backends/metal/transaction.rs:221).

calculate_drm_state assigns CRTCs, primary and cursor planes, checks formats and modifiers, allocates scanout buffers when needed, and picks modes, VRR, tearing, colorspace, EOTF and HDR blobs (src/backends/metal/transaction.rs:314). calculate_change diffs new property structs against live objects with differs and prepare_conditional and only emits changed objects (src/backends/metal/transaction.rs:902, src/backends/metal/transaction.rs:928, src/backends/metal/transaction.rs:949). It also resets unknown or vendor default properties when asked (src/backends/metal/transaction.rs:874).

TEST_ONLY is used in the prepare path. MetalDeviceTransaction::prepare calls calculate_drm_state then calculate_change(true,false) (src/backends/metal/transaction.rs:1216). When test is true it calls Change::test with DRM_MODE_ATOMIC_ALLOW_MODESET, which adds DRM_MODE_ATOMIC_TEST_ONLY in video/drm.rs (src/backends/metal/transaction.rs:982, src/video/drm.rs:1097). Failure surfaces as AtomicTestFailed (src/backend/transaction.rs:124).

Normal apply first commits with flags 0 then retries once with ALLOW_MODESET after logging (src/backends/metal/transaction.rs:993). After a successful kernel commit it swaps new state into live drm_state, connector, CRTC and plane objects with mem::swap and bumps the connector version (src/backends/metal/transaction.rs:1092, src/backends/metal/transaction.rs:1100). Rollback keeps the pre-apply state and reapplies it with calculate_change(false,false) (src/backends/metal/transaction.rs:1194). The generic wrapper also rolls back on drop (src/backend/transaction.rs:248). Device init has an extra recovery loop that retries init while disabling direct scanout, non-XRGB formats and non-default modes (src/backends/metal/video.rs:2553, src/backends/metal/video.rs:2589).

2.2 Planes

Types are Overlay, Primary and Cursor (src/backends/metal/video.rs:1023). The transaction carries primary plus cursor per CRTC (src/backends/metal/transaction.rs:283). Overlay planes are explicitly discarded during assignment, so compositing uses at most primary plus cursor (src/backends/metal/transaction.rs:360).

Cursor uses fixed ARGB8888 (src/backends/metal/transaction.rs:281) and separate double-buffered cursor scanout buffers (src/backends/metal/video.rs:628). Cursor enable, move and swap produce CursorProgrammingType::Enable or Disable (src/backends/metal/present.rs:127).

Direct scanout puts a client dmabuf on the primary plane through add_fb instead of the compositor render buffer. It is attempted only when enabled and when prepare_direct_scanout succeeds (src/backends/metal/present.rs:901). That path rejects cross-device buffers unless the scanout device is the render device, rejects SHM, rejects buffers with pending lazy work, checks plane format and modifier support, and caches failures by a key of buffer ID, plane, CRTC, source and destination color descriptions, intent, gamma, cursor presence and pipeline mode (src/backends/metal/present.rs:746, src/backends/metal/present.rs:778, src/backends/metal/present.rs:845). A failed atomic commit with direct scanout retries once as a full composite and then marks that key impossible (src/backends/metal/present.rs:337).

2.3 Frame scheduling

Each connector spawns its own present_loop in phase Present (src/backends/metal/video.rs:1220). Page flip events arrive on a per-device task that awaits DrmMaster::event, which does an io_uring read on the DRM fd and parses DRM_EVENT_FLIP_COMPLETE and DRM_EVENT_CRTC_SEQUENCE (src/backends/metal/video.rs:2268, src/video/drm.rs:590, src/video/drm.rs:607).

The loop waits for present_trigger then requires both buffers_idle and crtc_idle (src/backends/metal/present.rs:189). For synced flips it computes next_vblank - pre_commit_margin - post_commit_margin and sleeps with ring.timeout until that deadline (src/backends/metal/present.rs:203). If already past the deadline it increments the expected sequence so a missed vblank can be detected (src/backends/metal/present.rs:210). Tearing flips skip that wait (src/backends/metal/present.rs:203).

It then calls before_latch with either now for tearing or next_vblank_nsec for synced output (src/backends/metal/present.rs:218). It aborts if the connector version changed while yielding (src/backends/metal/present.rs:226). Then present_once latches damage, renders or selects direct scanout, waits render then scanout fences, programs the atomic commit, and on success marks CRTC busy and buffers busy (src/backends/metal/present.rs:250, src/backends/metal/present.rs:314, src/backends/metal/present.rs:387).

pre_commit_margin starts at 16ms and post_commit_margin starts at 1.5ms (src/backends/metal/present.rs:145). The pre margin tracks the per-second maximum CPU time from wakeup through latch with a geometric decay of 0.5 (src/backends/metal/present.rs:238, src/backends/metal/video.rs:1208). The post margin grows by 500us per second containing missed flips and decays toward the device minimum otherwise (src/backends/metal/present.rs:147, src/backends/metal/video.rs:2425). Expected versus actual CRTC sequence decides misses (src/backends/metal/video.rs:2378).

Flip completion clears pending_flip, promotes next_framebuffer to active_framebuffer, sets buffers_idle, emits presentation feedback with HW_COMPLETION plus VSYNC and ZERO_COPY when applicable, then sets crtc_idle and reschedules if damage remains (src/backends/metal/video.rs:2368, src/backends/metal/video.rs:2387, src/backends/metal/video.rs:2406). It also maintains next_vblank_nsec from flip timestamps plus refresh interval and continuously queues CRTC_SEQUENCE for vblank timing unless emulation is active (src/backends/metal/video.rs:2347, src/backends/metal/video.rs:2418, src/backends/metal/video.rs:867). If queueing sequences returns EOPNOTSUPP it switches that output to vblank emulation (src/backends/metal/video.rs:881).

output_schedule.rs is separate from that pacing loop. It only throttles cursor updates in VRR mode. latched records present time and clears awaiting-latch flags (src/output_schedule.rs:100). run_once sleeps until last_present + cursor_delta when VRR plus a cursor change is pending, then damages either the hardware cursor or the connector (src/output_schedule.rs:157, src/output_schedule.rs:187). Tree latch plumbing calls schedule.latched from OutputNode::latched (src/tree/output.rs:355).

2.4 VRR and tearing

State carries vrr and tearing booleans (src/backend.rs:792). The transaction rejects VRR on connectors without vrr_capable and rejects tearing without atomic async flip support (src/backends/metal/transaction.rs:766, src/backends/metal/transaction.rs:772). vrr_capable comes from the vrr_capable property (src/backends/metal/video.rs:1423) and async support comes from DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP (src/video/drm.rs:404).

Per-frame tearing is persistent tearing AND supports_async_commit (src/backends/metal/present.rs:442). The atomic commit first tries NONBLOCK | EVENT | ASYNC, and on failure falls back to synced NONBLOCK | EVENT while also attaching IN_FENCE_FD and requesting OUT_FENCE_PTR except on Nvidia (src/backends/metal/present.rs:572, src/backends/metal/present.rs:581). Any plane geometry change or cursor plane change forces the synced path (src/backends/metal/present.rs:493, src/backends/metal/present.rs:516).

2.5 Color management

metal_cm selects a KMS programming for each frame from source and destination ColorDescription values plus render intent, gamma LUT, cursor presence and pipeline selection (src/backends/metal/video/metal_cm.rs:100). It caches programmings, matcher results and blobs (src/backends/metal/video/metal_cm.rs:60). A programming can set plane COLOR_PIPELINE, plane colorop bypass, type, interpolation, data and multiplier, and CRTC gamma LUT (src/backends/metal/video/metal_cm.rs:127, src/backends/metal/video/metal_cm.rs:138, src/backends/metal/video/metal_cm.rs:153). Matching logic decides whether matrix, source EOTF, destination EOTF or client gamma are needed (src/backends/metal/video/metal_cm/metal_cm_matcher.rs:124). The minimum LUT size considered useful is 1024 (src/backends/metal/video/metal_cm.rs:54). On AMD with a cursor plane it disables plane pipelines as a driver workaround (src/backends/metal/video/metal_cm.rs:185).

metal_cm_paths/generated.rs holds generated static tables of PlaneOpKind, matrix, LUT and multiply variants used by the matcher (src/backends/metal/video/metal_cm/metal_cm_paths/generated.rs:12). The handwritten wrapper defines Path, plane and CRTC op accessors and criteria filters (src/backends/metal/video/metal_cm/metal_cm_paths.rs:15).

Legacy properties are also handled. CRTC defaults include CTM, DEGAMMA_LUT, OUT_FENCE_PTR and AMD regamma (src/backends/metal/video.rs:1565). Plane defaults include AMD blend, shaper, 3D LUT, HDR multiplier, alpha, blend mode, rotation and COLOR_PIPELINE Bypass for non-primary planes (src/backends/metal/video.rs:1680). Plane pipeline use is opt-in through use_plane_color_pipelines, default false (src/backends/metal/video.rs:2123, src/backend.rs:672).

HDR uses connector Colorspace and HDR_OUTPUT_METADATA plus EDID-derived PQ and BT2020 support, primaries and luminance (src/backends/metal/video.rs:1351, src/backends/metal/video.rs:1452, src/backends/metal/video.rs:1500). The transaction creates the HDR metadata blob from EOTF, primaries and luminance (src/backends/metal/transaction.rs:802, src/video/drm.rs:919). The renderer supplies cd, linear and blend descriptions per frame, and metal_cm maps framebuffer description to output description (src/backends/metal/present.rs:277, src/backends/metal/present.rs:889).

2.6 Allocator and multi-GPU

Allocation is GBM based, not dumb buffers. It creates render and scanout GbmBo objects, imports them as gfx textures and framebuffers, and registers display framebuffers with add_fb2 (src/backends/metal/allocator.rs:878, src/backends/metal/allocator.rs:914, src/video/drm.rs:441). GEM handles are cached (src/video/drm.rs:484). Modifier lists come from the plane IN_FORMATS blob, with fallback to the legacy plane format list (src/backends/metal/video.rs:1616, src/video/drm.rs:531).

Same-GPU scanout uses one BO for render and scanout (src/backends/metal/allocator.rs:454). Cross-GPU tries configurable prime methods in order direct-pull, direct-push, udmabuf, direct-sampling and indirect-pull, controlled by WRY_PRIME_METHODS (src/backends/metal/allocator.rs:464, src/backends/metal/allocator.rs:615, src/backends/metal/allocator.rs:1528). Each method intersects render write and read modifiers, plane modifiers and copy device source and destination modifiers, with copy paths forced to linear when available (src/backends/metal/allocator.rs:1224, src/backends/metal/allocator.rs:1595). Buffer kinds are direct sampling versus three copy device topologies plus direct push, which is noted as unsafe on AMD (src/backends/metal/allocator.rs:85, src/backends/metal/allocator.rs:621). Per-frame copy_to_dev performs the needed GPU or copy device transfer (src/backends/metal/allocator.rs:154).

2.7 Hotplug, session, lease

Hotplug watches drm and input through udev (src/backends/metal.rs:307). A task waits for the monitor fd with io_uring readable readiness and dispatches add versus change (src/backends/metal/monitor.rs:42). DRM change diffs kernel connectors against known connectors, emits Disconnected plus Removed, revokes affected leases, refreshes properties and display data, creates new connector objects and reinitializes the device (src/backends/metal/video.rs:1844).

Logind handling takes session control, takes each device fd, and installs pause and resume handlers (src/backends/metal.rs:295, src/logind.rs:76, src/backends/metal/monitor.rs:402). Pause marks the DRM device paused, hides non-desktop outputs and parks leases in leases_to_break (src/backends/metal/monitor.rs:166). Resume clears paused state, retries lease revocation, refreshes properties and reschedules presents (src/backends/metal/monitor.rs:97, src/backends/metal/video.rs:2248). Losing DRM master surfaces as EACCES on commit and is intentionally ignored per frame (src/backends/metal/present.rs:379).

Leases are supported for non-desktop outputs. Creation requires connected non-desktop connectors, allocates an unused CRTC and primary plane per connector, calls the kernel lease ioctl and emits Unavailable (src/backends/metal/video.rs:264, src/video/drm.rs:638). Dropping the lease object revokes it or parks it for later revocation and makes the connector available again (src/backends/metal/video.rs:550, src/backends/metal/video.rs:514).

2.8 Unusual choices

  • Overlays are enumerated but never assigned, unlike many compositors (src/backends/metal/transaction.rs:360).
  • Direct scanout participates in color pipeline search and has its own impossible-cache (src/backends/metal/present.rs:778).
  • Frame timing uses two adaptive margins rather than a fixed deadline, with the pre margin from measured CPU time and the post margin from missed sequence counts (src/backends/metal/present.rs:238, src/backends/metal/video.rs:2425).
  • The modeset path blocks on buffer clear and copy syncs inside transaction calculation and can synchronously poll a CRTC out fence before blanking (src/backends/metal/transaction.rs:693, src/backends/metal/transaction.rs:843).
  • Init and hotplug paths reset unknown KMS properties to driver defaults, which helps against firmware or desktop leftover state (src/backends/metal/transaction.rs:886, src/backends/metal/video.rs:2589).

3. Wire protocol and client handling

3.1 Protocol description format

wire/ holds a custom DSL, one file per interface. wire/wl_surface.txt:1-8 shows request destroy, typed fields, (destructor), (since = 2), (new). No XML is stored in tree. XML appears only at conversion boundaries.

The DSL parser lives in build/wire/parser.rs:209-224 for Type with Id, U32, I32, U64, U64Rev, Str, OptStr, BStr, Fixed, Fd, Bool, Array, Pod, plus build/wire/parser.rs:236-246 for Message and build/wire/parser.rs:350-403 for message parsing.

xml-to-wire/src/main.rs:9-14 plus xml-to-wire/src/builder.rs:27-42 converts Wayland XML to wire/*.txt. Special handling maps x_hi plus x_lo pairs to u64 or u64_rev (xml-to-wire/src/builder.rs:83-106) and maps untyped arrays or dev_t blobs to array or pod (xml-to-wire/src/builder.rs:132-160). wire-to-xml/src/main.rs:41-60 does the reverse for debugging or interop.

The build script (build/build.rs:53-67) runs wire::main and emits to OUT_DIR. build/wire.rs:497-539 scans wire/, parses every file, and writes wire/mod.rs plus one generated wire/<iface>.rs per interface. It also emits id! plus Interface constants (build/wire.rs:455-494).

A generated request type is a plain struct with self_id plus fields (build/wire.rs:57-76). Its RequestParser impl is emitted in build/wire.rs:78-202. Fixed-size messages destructure *parser.data() directly (build/wire.rs:112-170). Variable-size messages call parser.uint, str, bstr, fd, binary and then parser.eof (build/wire.rs:171-198).

A generated event writer implements EventFormatter (build/wire.rs:203-302). Fixed events use one fmt.data word array (build/wire.rs:214-250). Variable events use fmt.header plus per-field writers (build/wire.rs:252-291). Request and event handler traits are emitted together (build/wire.rs:311-431), with server traits using Object plus Client and client-side traits using UsrObject plus UsrCon (build/wire.rs:327-348).

Parsing is largely zero-copy. MsgParser borrows the &[u32] body plus fd queue (src/utils/buffd/parser.rs:33-47). str, bstr, array, binary_array return borrowed &str, &BStr, &[u8], &[T] (src/utils/buffd/parser.rs:96-146, src/utils/buffd/parser.rs:159-176).

File descriptors travel as ancillary data. WlBufFdIn collects them with recvmsg (src/utils/buffd/wl_buf_in.rs:110-126). MsgParser::fd pops from that queue (src/utils/buffd/parser.rs:122-127). MsgFormatter::fd queues them and write_len records their message offset (src/utils/buffd/formatter.rs:93-96, src/utils/buffd/formatter.rs:135-149). Sending splits at fd boundaries (src/utils/buffd/buf_out.rs:102-120). This split-queue design is unusual relative to libwayland.

3.2 Object model

Each Client owns an Objects store (src/client.rs:355, src/client/objects.rs:87-135). There is one generic registry map plus many typed CopyHashMap maps for surfaces, buffers, seats and others (src/client/objects.rs:88-134).

ID allocation splits the space at MIN_SERVER_ID 0xff000000 (src/client/objects.rs:137). Client-created IDs must be nonzero and below that bound (src/client/objects.rs:266-282). Server IDs are allocated from a bitset freelist (src/client/objects.rs:235-250, src/client/objects.rs:314-325).

Dispatch starts in the receive task. It reads WlMessage obj_id message body fds (src/client/tasks.rs:64-72), looks up the object (src/client/tasks.rs:73-80), then calls handle_request (src/client/tasks.rs:81-90). Every object forwards through object_base! to generated handle_request_impl (src/macros.rs:41-66). The generated dispatcher matches the opcode and emits a version >= since guard (build/wire.rs:387-411). Unknown opcodes become InvalidMethod (build/wire.rs:413, src/client/error.rs:18-19).

Versioning is also enforced at bind time. WlRegistry bind checks interface name and version <= global.version (src/ifs/wl_registry.rs:64-89). Globals expose fixed versions, for example compositor version 7 (src/ifs/wl_compositor.rs:82-86).

Protocol errors use wl_display/error.

  • Unknown objects trigger send_invalid_object plus shutdown (src/client/tasks.rs:73-78, src/ifs/wl_display.rs:79-82).
  • Unknown requests trigger send_invalid_request plus shutdown (src/client.rs:394-416, src/ifs/wl_display.rs:68-77).
  • Handler failures trigger send_implementation_error plus shutdown (src/client/tasks.rs:94-108, src/ifs/wl_display.rs:84-86).
  • Explicit protocol_error does the same (src/client.rs:489-494).
  • Method wrapping preserves interface, id, method and source error (src/client/error.rs:35-42).

Lifetime uses Rc with explicit cycle breaking. Object has a default empty break_loops (src/object.rs:25-27). Client teardown calls Objects::destroy, which drains the generic registry through break_loops and clears typed maps (src/client/objects.rs:186-233). ClientHolder Drop drives that plus shutdown, activation token and timeline cleanup (src/client.rs:320-337). WlSurface break_loops drops children, dnd, cursors, transactions, feedback, viewporter and timelines (src/ifs/wl_surface.rs:2104-2127). Server ID removal can emit delete_id when symmetric deletion is active (src/client/objects.rs:284-308). Typed insertion and lookup go through dedicated_add_obj! (src/macros.rs:341-361), while simple objects and globals use simple_add_obj! and simple_add_global! (src/macros.rs:363-373). Explicit teardown instead of Weak everywhere is unusual relative to wayland-rs.

3.3 Client I/O

Input uses WlBufFdIn with an 8 KiB buffer and 4 KiB max message (src/utils/buffd/wl_buf_in.rs:15-29). It validates word alignment and 8 <= len <= 4096 (src/utils/buffd/wl_buf_in.rs:69-77), compacts unconsumed bytes (src/utils/buffd/wl_buf_in.rs:100-108), and caps queued fds at 32 (src/utils/buffd/wl_buf_in.rs:122-124). Error variants cover oversize, undersize, unaligned, missing and excess fds (src/utils/buffd.rs:24-44) with limits BUF_SIZE 4096 and MAX_IN_FD 32 (src/utils/buffd.rs:46-47).

Output uses OutBufferSwapchain with current, pending and free buffers (src/utils/buffd/buf_out.rs:50-69). Each output buffer is 8 KiB and considered full past 4 KiB (src/utils/buffd/buf_out.rs:14, src/utils/buffd/buf_out.rs:42-46). Client::event formats into the current buffer, commits on full, then signals the sender (src/client.rs:496-514). The sender swaps pending buffers and flushes with a 5 second timeout (src/client/tasks.rs:112-132). Used buffers are recycled through free (src/client/tasks.rs:128-131, src/utils/buffd/buf_out.rs:62-68).

Backpressure is bounded but not lossy for compliant clients. More than 10 pending buffers counts as over limit (src/utils/buffd/buf_out.rs:57-60). Over-limit clients enter the global slow queue once (src/client.rs:505-511). A background handler rechecks after a yield and kills still-slow clients (src/client.rs:520-530, src/tasks/slow_clients.rs:9-14). Event flushing is otherwise triggered, not synchronous after every request, as seen by the commented-out flush (src/client/tasks.rs:91) and trigger use (src/client.rs:513).

3.4 clientmem.rs

SHM pools are mapped with mmap64, length rounded up to page size, with an empty mapping for zero length (src/clientmem.rs:104-124). Read-only pools use PROT_READ, writable pools add PROT_WRITE, normally MAP_SHARED (src/clientmem.rs:78-123). Private mappings are a separate constructor (src/clientmem.rs:68-76).

Truncated client memory is handled with SIGBUS. Startup installs a SA_SIGINFO plus SA_NODEFER handler (src/clientmem.rs:273-284). Safe access pushes a thread-local record, runs the closure, then reports Sigbus if marked failed (src/clientmem.rs:170-189). The handler checks whether the fault address lies inside a tracked mapping (src/clientmem.rs:241-254), replaces that range with anonymous memory via MAP_FIXED (src/clientmem.rs:255-262), marks failure and returns (src/clientmem.rs:266-267), otherwise restores default and raises SIGBUS (src/clientmem.rs:233-239, src/clientmem.rs:269).

Sealed memfds skip the handler path. If F_SEAL_SHRINK plus a large enough st_size is observed, the mapping is marked sigbus_impossible (src/clientmem.rs:89-96). It may first extend the file with ftruncate (src/clientmem.rs:105-109). Direct pointer access is allowed only in that case, otherwise callers get an fd plus offset (src/clientmem.rs:319-324). Destruction unmaps inline or offloads close plus unmap to the CPU worker (src/clientmem.rs:207-222).

3.5 leaks.rs

track! registers each new Rc against its client (src/leaks.rs:3-7). Without rc_tracking the tracker is a zero-sized phantom (src/leaks.rs:9-39). With rc_tracking it assigns tracker IDs, installs a tracing global allocator, records allocation backtraces, and on demand groups unreleased objects by client with containing allocation and stack evidence (src/leaks.rs:59-178, src/leaks.rs:193-240, src/leaks.rs:268-299). This is a debug facility, not a production GC.

3.6 Acceptors

The primary acceptor creates two Unix stream sockets, insecure wayland-N and secure wayland-N.wry, plus a lock file, scanning 1..1000 (src/acceptor.rs:66-132). It unlinks stale paths, takes a nonblocking flock, binds, listens with backlog 4096 and stores the lock fd (src/acceptor.rs:81-142). It then spawns separate secure and insecure accept loops (src/acceptor.rs:144-156). Each accepted fd becomes a Client (src/acceptor.rs:168-191).

Tagged acceptors create extra named sockets on demand for a string tag, starting at wayland-1000 and skipping locked names (src/tagged_acceptor.rs:63-101, src/tagged_acceptor.rs:156-197). Their metadata carries the tag and non-sandboxed status (src/tagged_acceptor.rs:72-79). Their accept loop is otherwise the same (src/tagged_acceptor.rs:115-135).

Security context acceptors do not bind. They accept from a client-supplied listen fd and watch a close fd (src/security_context_acceptor.rs:22-31, src/security_context_acceptor.rs:112-137). New clients are marked sandboxed with caller-supplied sandbox engine, app id, instance id and bounding caps (src/security_context_acceptor.rs:59-80, src/security_context_acceptor.rs:122-129). Dual secure plus insecure sockets for one compositor is jay/wry specific.

3.7 wl_usr

wl_usr is a Wayland client stack used by the compositor itself. UsrCon::new connects over a Unix socket with io_uring (src/wl_usr.rs:99-129). from_socket reserves IDs 0 and 1, installs UsrWlDisplay object 1, and spawns incoming plus outgoing tasks (src/wl_usr.rs:131-187). Outgoing requests reuse the same swapchain and formatter design as server-to-client events (src/wl_usr.rs:260-279, src/wl_usr.rs:281-316). Incoming events dispatch by object id to handle_event, ignoring unknown server IDs but erroring on unknown client IDs (src/wl_usr.rs:339-357).

Client-side objects live in src/wl_usr/usr_ifs/. For example UsrWlCompositor::create_surface sends the request and tracks the new object (src/wl_usr/usr_ifs/usr_wl_compositor.rs:17-30). The generated client trait uses UsrObject, UsrCon and UsrConError (build/wire.rs:339-347). The portal uses this to connect to nested displays named wayland-*.wry (src/portal/ptl_display.rs:298-343), then binds compositor, fractional scale, viewporter, dmabuf and other globals (src/portal/ptl_display.rs:346-373).

3.8 Performance notes

  • Fixed messages avoid per-field calls and copy words directly. Variable strings, byte strings and arrays borrow from the input buffer.
  • Small inline storage appears widely, including SmallVec for rects, syncs, copies and seats and ArrayVec for descriptor layouts, barriers and singleton broadcasts (src/rect.rs:33, src/video/dmabuf.rs:327, src/ifs/wl_surface/commit_timeline.rs:553-561, src/globals.rs:410, src/utils/buffd/buf_in.rs:73).
  • Object maps use custom CopyHashMap over hashbrown with unsafe interior access (src/utils/copyhashmap.rs:21-80).
  • Output buffers and Buf storage are recycled rather than reallocated in the swapchain and sender paths.
  • Slow-client detection avoids blocking the compositor on a non-draining peer.

4. Vulkan renderer

The reader returned this one as a single dense block with fewer line citations. It is restructured here. Claims marked (verified) were spot-checked by hand. The “bridge” description was corrected, since the code gates it on for_rendering (src/gfx_apis/vulkan/image.rs:535).

4.1 Abstraction

The compositor is renderer-agnostic. GfxApiOp (src/gfx_api.rs:115) has FillRect (:117), CopyTexture (:118), RoundedFillRect (:119) and RoundedCopyTexture (:120). The FillRect struct (src/gfx_api.rs:286) and CopyTexture (src/gfx_api.rs:304) carry acquire_sync (:309) plus release_sync and lazy (:318). The rounded variants (:323, :344) mirror that shape.

  • LazyTexture (src/gfx_api.rs:373) lets a backend defer work, with perform_lazy_work feeding a Vec<FdSync>.
  • AcquireSync (src/gfx_api.rs:426) has None, Implicit, FdSync and Unnecessary, with from_fd_sync (:434). ReleaseSync (src/gfx_api.rs:452) is the release side.
  • GfxFramebuffer (src/gfx_api.rs:503) exposes render_with_region_impl (:506) and wrappers through :889.
  • GfxTexture (src/gfx_api.rs:931) and GfxContext (src/gfx_api.rs:1030) complete the surface, with supports_color_management (:1108) and supports_alpha_modes (:1112).
  • Direct scanout filtering lives near src/gfx_api.rs:1529, rejecting FillRect (:1536) and RoundedCopyTexture (:1540).

A second backend exists. create_gfx_context (src/gfx_apis.rs:17) tries APIs (:26) as OpenGL then Vulkan, looping software false then true (:29), with dispatch at :70. The GL backend (src/gfx_apis/gl.rs plus src/gfx_apis/gl/, about 3.3k lines) is a full parallel renderer using EGL images, GL textures, and its own TexCopyType and TexSourceType.

4.2 Frame recording

A frame is an op list turned into one Vulkan submit. RendererBase in compositor code emits FillRect and CopyTexture ops, and VulkanRenderer executes them. convert_ops (src/gfx_apis/vulkan/renderer.rs:1007, verified) sorts by color, z order and index (:1030), then groups by pipeline key. TexPipelineKey (src/gfx_apis/vulkan/renderer.rs:416, verified) includes copy type, source type, alpha mode, EOTF pair, color matrix presence, grayscale and rounded. A separate OutPipelineKey (:434) covers the resolve pass.

Recording centers on record_draws (src/gfx_apis/vulkan/renderer.rs:1729), begin_rendering (:1620), submit (:2393) and execute (:2475).

  • begin_rendering uses Vulkan 1.3 dynamic rendering with cmd_begin_rendering (:1685).
  • Full-area clears use load op CLEAR (:1667), otherwise partial clears go through cmd_clear_attachments (:1694). The blend buffer pass uses DONT_CARE (:1668).
  • Viewport and scissor cover the full target (:1702).

Vertex data uses device-addressable buffers. tex.common.glsl (src/gfx_apis/vulkan/shaders/tex.common.glsl:8) declares a buffer_reference Vertices with push-constant Data carrying vertices and mul (:12). This avoids vertex bindings and lets one instanced draw consume many rects from a data buffer.

Specialization uses seven constants in frag_spec_const.glsl (src/gfx_apis/vulkan/shaders/frag_spec_const.glsl:4): src_has_alpha, has_alpha_multiplier, eotf, inv_eotf, has_matrix, alpha_mode and grayscale. Pipeline creation consumes those as specialization constants.

Descriptors have three tiers. Device probing (src/gfx_apis/vulkan/device.rs:433) prefers descriptor_heap plus maintenance5, falls back to descriptor_buffer (:441), and finally legacy push_descriptor. Enablement (:481) adds heap and maintenance5 or buffer names. Features (:490) enable timeline semaphores, synchronization2, dynamic_rendering (:492), buffer_device_address (:500), plus heap or buffer features (:538). Color bindings are fixed in tex_set.glsl (src/gfx_apis/vulkan/shaders/tex_set.glsl:4) with texture2D tex plus ColorManagementData matrix, EotfArgs and InvEotfArgs.

Wait setup (src/gfx_apis/vulkan/renderer.rs:2254) imports dmabuf sync files and explicit FdSync into TOP_OF_PIPE waits. Submit uses queue_submit2 (:2409) and stores release_sync (:2417). Pending frames (:2439) retain command buffers, images, semaphores and heap entries and spawn await_release on the async engine (:2463).

4.3 Upload and import

  • SHM textures are created OPTIMAL, SAMPLED plus TRANSFER_DST, and filled from staging buffers with BufferImageCopy2 limited to damage. transfer.rs computes copies with queue-family ownership barriers and granularity masks and expands for 4-byte alignment.
  • The async path uses CpuWorker jobs IoTransferJob and CopyTransferJob (src/gfx_apis/vulkan/transfer.rs:633, :642, verified) with ring readable waits for gfx queue release. The sync path submits a buffer-image copy on the gfx queue directly.
  • Dmabuf import in image.rs uses explicit DRM format modifier creation with per-plane fd import and bind_image_memory2, and checks disjoint planes and single-file reuse.
  • When a dmabuf’s modifier is not renderable, rendering goes into an intermediate “bridge” image and is copied out (src/gfx_apis/vulkan/image.rs:97, :113, :535, verified).
  • Shm reuse checks dimensions and format before reallocating. Internal framebuffers, read-write images and blend buffers are cached by size.
  • The blend buffer is ABGR16161616F (src/gfx_apis/vulkan/format.rs:100, verified). It holds linear working space, then an out pass resolves to the framebuffer.
  • Staging shells and dmabuf host buffers reduce mapping churn.

4.4 Synchronization

FdSync (src/gfx_api.rs:1384) is SyncFile or Syncobj, with async wait (:1392), blocking wait (:1408), poll (:1434) and external ring integration (:1474).

  • The acquire path (src/gfx_apis/vulkan/renderer.rs:2254) dups explicit sync files (:2303) and exports dmabuf sync files with DMA_BUF_SYNC_READ for textures (:2313) and DMA_BUF_SYNC_WRITE for the framebuffer (:2322), plus an extra FdSync list (:2329).
  • The release path (:2343) attaches the submit fence or timeline point to buffer reservations (:2357) or imports into dmabufs (:2361), with a fallback warning relying on implicit sync.
  • Async SHM tracking sets last_gfx_use (:2381).
  • Device sync creation (:2401) returns a fence or timeline semaphore, and submit signals it.
  • Timeline opaque fd export is probed (src/gfx_apis/vulkan/device.rs:472) and mapped to DRM syncobj points with a two-point validation handshake in timeline_semaphore.rs. Binary semaphores import SYNC_FD in semaphore.rs.
  • Pending frame release uses the async engine and io_uring readable waits.

4.5 Memory

Memory uses gpu-alloc, not a custom allocator and not VMA. Cargo.toml:55 pins ash as jay-ash 0.12.0, :56 gpu-alloc 0.6.0 and :57 gpu-alloc-types 0.3.0 (verified). allocator.rs wraps AshMemoryDevice in VulkanAllocator and VulkanThreadedAllocator with CpuWorker async alloc and free, and zeroed pages via Config::i_am_prototyping. Separate small caches exist for shader buffers, uniform buffers and descriptor buffers, plus size-keyed blend buffer and image caches.

4.6 Color management and HDR

Color work happens in shaders, driven by CPU-side color descriptions.

tex.frag steps in order.

  1. Sample with textureLod (src/gfx_apis/vulkan/shaders/tex.frag:18).
  2. Unpremultiply electrical-premultiplied inputs (:21).
  3. Apply EOTF (:24) and handle optical unpremultiply.
  4. Apply matrix (:29).
  5. Optional grayscale with Rec 709 luma (:31).
  6. Inverse EOTF (:35).
  7. Repremultiply (:37).
  8. Alpha multiplier (:41).

out.frag (src/gfx_apis/vulkan/shaders/out.frag:16) fetches from the blend buffer and repeats EOTF, matrix and inverse EOTF (:17).

eotfs.glsl defines TF_LINEAR through TF_COMPOUND_POWER_2_4 (src/gfx_apis/vulkan/shaders/eotfs.glsl:7), with PQ (:38), BT1886 with four args (:20), ST240, log, ST428, pow and compound variants, dispatched by apply_eotf (:121) and apply_inv_eotf (:139). Alpha modes (src/gfx_apis/vulkan/shaders/alpha_modes.glsl:4) are premultiplied electrical, premultiplied optical and straight.

4.7 copy_device.rs

A separate minimal Vulkan copy engine for multi-GPU movement, used by neither screencast nor the main compositor render path. PhysicalCopyDevice and CopyDevice classify TransferType and direction as Blit, Intra, Download or Upload. Entry points are create_buffer (src/copy_device.rs:1037, :1342), create_fixed_copy (:1233) and create_copy (:1242). State holds render_ctx_prime_copy_device (src/state.rs:277), assigned from the DRM device (src/state.rs:2803). Metal allocator call sites copy render to device BOs (src/backends/metal/allocator.rs:1149, :1212, :1235, :1395), including udmabuf staging (:1507). Prime modifier feedback reads render_ctx_prime_modifiers (src/dmabuf_feedback.rs:141).

4.8 Decorations

Effects stop at rounded rectangles and borders. The rounding SDF (src/gfx_apis/vulkan/shaders/rounding.glsl:1) returns zero outside bounds, selects corner radius and center per quadrant, and smoothsteps distance with half-pixel width (:24). Shader families include fill, rounded_fill, tex and rounded_tex, plus legacy variants under src/gfx_apis/vulkan/shaders/legacy. No blur and no shadow passes exist. RendererBase emits tab bars, borders and highlights as FillRect and RoundedFillRect ops.

4.9 Coupling and requirements

Coupling is broad outside the Vulkan directory. vulkan.rs, renderer.rs, image.rs, device.rs, transfer.rs, shm_image.rs and pipeline.rs import crate backend, cmm, gfx_api, theme, utils, video, allocator, state, async engine, ring and CPU worker types. The GL context (src/gfx_apis/gl.rs:21) shows the same pattern. A port would carry color management, dmabuf, syncobj, timeline and worker abstractions or stub them.

  • Vulkan 1.3. VULKAN_API_VERSION (src/vulkan_core.rs:356) equals API_VERSION_1_3 (:15). The loader uses libvulkan.so.1 (:67, :73).
  • Required instance extensions (src/vulkan_core.rs:268) are debug_utils only.
  • Required device extensions (src/gfx_apis/vulkan/device.rs:728) include external_memory_fd, external_semaphore_fd, external_fence_fd, external_memory_dma_buf, queue_family_foreign, image_drm_format_modifier and push_descriptor. Optional paths add descriptor_heap plus maintenance5, or descriptor_buffer.
  • Shaders compile offline. compile (compile-shaders/compile/src/main.rs:18) maps frag and vert to shaderc kinds (:33) and writes SPIR-V (:44), with an include callback (:27) and hash tracking via TREES (:9).

4.10 Unusual choices

  • Linear fp16 blend buffer with a separate resolve pass.
  • Device-address vertex buffers fed through push constants.
  • Specialization-constant-driven color pipelines.
  • Damage-limited staging copies, optionally on the cpu_worker thread.
  • Threaded gpu-alloc.
  • Vulkan timeline semaphore to DRM syncobj point mapping.

5. Tree, transactions, multiphase animations (jay-port branch, NOT the reference, see round2-reports.md section 6)

5.1 Commit timeline

Each client owns CommitTimelines (src/client.rs:229) and each WlSurface owns a CommitTimeline (src/ifs/wl_surface.rs:778). Shared state is in src/ifs/wl_surface/commit_timeline.rs:62-70, with per-surface own_timeline, effective_timeline plus waiters for fifo, commit time, restore, serial and unmap (src/ifs/wl_surface/commit_timeline.rs:77-87).

wl_surface.commit validates release protocols then hands PendingState to the timeline (src/ifs/wl_surface.rs:1286-1316). Sync subsurface commits do not reach the timeline. They merge into the parent pending and abort (src/ifs/wl_surface/wl_subsurface.rs:414-427). Sync state is sync_requested or sync_ancestor (src/ifs/wl_surface/wl_subsurface.rs:255-256). Desync flushes cached child state through the timeline (src/ifs/wl_surface/wl_subsurface.rs:287-293).

Immediate apply happens only with no dependencies, an empty queue and no fifo barrier (src/ifs/wl_surface/commit_timeline.rs:275-282). Otherwise an EntryKind::Commit is queued. Client depth is capped at 256 with a Depth error (src/ifs/wl_surface/commit_timeline.rs:58-60, src/ifs/wl_surface/commit_timeline.rs:283-285).

The collector gathers acquire points, shm uploads, dmabuf presence, prime need, acquire files, commit time and restore flags (src/ifs/wl_surface/commit_timeline.rs:243-263, src/ifs/wl_surface/commit_timeline.rs:963-1005).

  • Explicit acquire points come from set_acquire_point (src/ifs/wl_surface/wp_linux_drm_syncobj_surface_v1.rs:59-63).
  • Implicit dmabuf fences are added as plane fds when neither syncobj sync nor sync-file acquire is present (src/ifs/wl_surface/commit_timeline.rs:977-984).
  • Sync-file acquire is added in src/ifs/wl_surface/commit_timeline.rs:986-988.
  • Fifo flags come from set_barrier and wait_barrier (src/ifs/wl_surface/wp_fifo_v1.rs:43-50).
  • Commit time comes from set_timestamp (src/ifs/wl_surface/wp_commit_timer_v1.rs:45-61).

Waits are registered without blocking the main thread. Syncobj uses wfs.wait (src/ifs/wl_surface/commit_timeline.rs:321-331). Implicit fds use ring.readable_external (src/ifs/wl_surface/commit_timeline.rs:340-350). Shm uploads, prime copies, commit time, restore, serial and unmap mark needs_flush (src/ifs/wl_surface/commit_timeline.rs:333-364). The queue head is flushed when previously empty (src/ifs/wl_surface/commit_timeline.rs:366-368).

Queues are FIFO (src/ifs/wl_surface/commit_timeline.rs:569-579). The head applies only when all dependencies clear (src/ifs/wl_surface/commit_timeline.rs:582-653). Other clients have separate Inner queues, so one blocked surface does not stop another client except through explicit cross-timeline links. Parent and subsurface timelines are unified through set_effective_timeline (src/ifs/wl_surface/commit_timeline.rs:1008-1034) using Wait and Signal entries (src/ifs/wl_surface/commit_timeline.rs:529-534). Later wakeups go through per-client process_flush_requests (src/ifs/wl_surface/commit_timeline.rs:1041-1053).

Completion callbacks decrement counters and retry the list. This covers syncobj (src/ifs/wl_surface/commit_timeline.rs:430-442), shm upload (src/ifs/wl_surface/commit_timeline.rs:453-468), prime copy (src/ifs/wl_surface/commit_timeline.rs:470-485), dmabuf poll (src/ifs/wl_surface/commit_timeline.rs:487-501) and commit timer timeout (src/ifs/wl_surface/commit_timeline.rs:504-521).

Fifo barrier handling parks Queued commits as Registered and stores fifo_waiter (src/ifs/wl_surface/commit_timeline.rs:600-612). Barrier release pushes the waiter (src/ifs/wl_surface/commit_timeline.rs:376-381). Latch clears it for normal presentation but defers to vblank under tearing (src/ifs/wl_surface.rs:2561-2584), with vblank cleanup in src/ifs/wl_surface.rs:2543-2552. Applied commits set the barrier (src/ifs/wl_surface.rs:1664-1688).

Commit timer registration subtracts render plus flip margins from the requested time (src/ifs/wl_surface/commit_timeline.rs:702-730). It uses ring.timeout_external and attaches a before_latch_listener, then before_latch releases the commit when presentation time has passed (src/ifs/wl_surface/commit_timeline.rs:405-427, src/ifs/wl_surface.rs:2555-2559).

Destroy versus loop breaking is split (src/ifs/wl_surface/commit_timeline.rs:210-235), with dependency state discarded in src/ifs/wl_surface/commit_timeline.rs:197-208 and destroyed timelines drained through a Gc entry (src/ifs/wl_surface/commit_timeline.rs:660-663).

5.2 Layout transactions

Tree mutation uses Transactionable and TransactionData (src/transactions.rs:20-38, src/transactions.rs:65-71). Any op schedules the object and triggers serial groups (src/transactions.rs:195-209).

Transactions::commit snapshots scheduled objects, surfaces and effects, creates or recycles a Transaction plus Blocker, queues surface blockers and assigns member ops (src/transactions.rs:216-282). Surfaces receive UnblockCommitsUntil (src/transactions.rs:230-237). Apply runs before_apply, member ops, after_apply, then recycles the transaction (src/transactions.rs:284-299).

Blocker drop schedules apply exactly once by version (src/transactions.rs:345-359). Timeout applies the same version after a 50ms default (src/transactions.rs:78-80, src/transactions.rs:441-467). The apply loop is separate (src/transactions.rs:469-475).

Per-surface blocking is in src/transactions.rs:116-127. unblock_transactions_until drops blockers at or before the committed serial (src/transactions.rs:374-400). commit_is_blocked compares unblocked_commit against the needed serial (src/transactions.rs:417-419). Disabled means tardy or zero enable count (src/transactions.rs:421-423). Missing acks mark tardy, clear blockers and force the timeout path (src/transactions.rs:402-415).

The commit timeline and layout transaction meet in maybe_apply (src/ifs/wl_surface/commit_timeline.rs:632-638). The surface first records the serial, then parks on serial_waiter if still blocked. Publication calls serial_unblocked through WlSurfaceTransactionOp::UnblockCommitsUntil (src/ifs/wl_surface.rs:2696-2702), with waiter release in src/ifs/wl_surface/commit_timeline.rs:393-397.

Each serial group tick allocates one TreeSerial, commits transactions, then runs configure groups (src/tree_serial_groups.rs:18-30). Configures are grouped by serial (src/configurable.rs:241-280). schedule_configure also adds the surface to transactions (src/configurable.rs:91-105). Readiness counting releases whole groups (src/configurable.rs:170-189, src/configurable.rs:336-410). Flush sends the serial and records requested_serial (src/configurable.rs:218-239). The client ack must carry at least that serial before ready (src/ifs/wl_surface.rs:1307-1312, src/ifs/wl_surface.rs:1370-1374). The t0061 test asserts LiveTL moves first while RenderTL stays old until commit (src/it/tests/t0061_transactional_layout_animation.rs:35-47).

5.3 Tree

Node behavior is NodeBase (src/tree.rs:445-945) with dyn wrapper Node (src/tree.rs:947-968). Visitors are in src/tree/walker.rs:18-86. Timelines are LiveTL and RenderTL (src/tree.rs:976-981), with SplitView maps (src/tree.rs:984). Links carry per-timeline validity (src/tree.rs:985-1008).

Layers are ordered Display through OverlayStacked (src/tree.rs:343-365), with linked variants in src/tree.rs:367-385. Concrete types include DisplayNode (src/tree/display.rs:47), OutputNode (src/tree/output.rs:131), WorkspaceNode (src/tree/workspace.rs:76-98), containers, floats, toplevels and placeholders. Parents implement ContainingNode (src/tree/containing.rs:7-59). Toplevel behavior splits into ToplevelNodeBase (src/tree/toplevel.rs:366) and ToplevelNode (src/tree/toplevel.rs:81-101).

Rendering walks the same hierarchy through node_render and node_visit_children. Examples are output (src/tree/output.rs:2477), container (src/tree/container.rs:2953), workspace (src/tree/workspace.rs:559), float (src/tree/float.rs:972), display (src/tree/display.rs:285) and placeholder (src/tree/placeholder.rs:229). The renderer starts from outputs and workspaces, then stacked layers and exit frames (src/renderer.rs:250-303).

5.4 Multiphase animations

A phase is a MultiphasePhase with one action plus steps (src/presentation/multiphase.rs:204-209). Each step is classified as horizontal or vertical move or scale (src/presentation/multiphase.rs:1486-1509). Diagonal or size-plus-position changes cannot be one phase and force multiphase plans.

The planning entry is plan_no_overlap (src/presentation/multiphase.rs:354-356), with forward then reversed attempts (src/presentation/multiphase.rs:364-412). Strategies include SingleAction, SpaceThenOrthogonalGrowth, HierarchyOrderedScales, OrientationChange, SwapLanes and reversed plans (src/presentation/multiphase.rs:159-168), tried in that order (src/presentation/multiphase.rs:535-616). Independent motion groups split first (src/presentation/multiphase.rs:460-488).

Validation checks chaining, duplicates, action counts, unknown nodes, stale starts, intra-phase overlap and final rects (src/presentation/multiphase.rs:1280-1368). Overlap uses linear edge deltas with rational time intervals (src/presentation/multiphase.rs:1370-1484).

Runtime animations store multiple from to segments (src/presentation/layout_animation.rs:160-168). Total time is duration times phase count (src/presentation/layout_animation.rs:1292-1296). Position interpolates within the current phase with the configured curve (src/presentation/layout_animation.rs:1309-1319). Interrupts retarget from the current rect_at (src/presentation/layout_animation.rs:846-892, src/presentation/layout_animation.rs:894-952, src/presentation/layout_animation.rs:1112-1129). Path validation for reversed interrupts is in src/presentation/multiphase.rs:414-458. Appearing windows can wait behind existing phases (src/presentation/layout_animation.rs:976-1018).

Ticks are per-output LatchListeners (src/presentation/layout_animation.rs:257-260, src/presentation/layout_animation.rs:1945-1960). Each latch damages active rects and stops when idle (src/presentation/layout_animation.rs:1218-1248). Ticks attach to output.latch_event (src/presentation/layout_animation.rs:1250-1269) and detach in src/presentation/layout_animation.rs:1271-1289. The test asserts latch drives damage and that output disconnect or reconnect changes tick count (src/it/tests/t0061_transactional_layout_animation.rs:69-90).

Animations are transaction effects. The trait observes pre and post trees without mutating live state (src/transactions.rs:45-58). Scheduling adds effects (src/presentation/layout_animation.rs:561-738). Single moves capture the before rect plus current visual (src/presentation/layout_animation.rs:1472-1514). Batches capture whole container snapshots before and after (src/presentation/layout_animation.rs:1596-1664), with coalescing per container (src/presentation/layout_animation.rs:1666-1671). Direct manipulation first cancels animations then suppresses new ones for that transaction (src/presentation/layout_animation.rs:527-559, src/presentation/layout_animation.rs:1516-1522). The test asserts no animation before publication and a multiphase spawn after (src/it/tests/t0061_transactional_layout_animation.rs:92-127), plus no animation for direct manipulation (src/it/tests/t0061_transactional_layout_animation.rs:129-146).

Old pixels use retained captures. RetainedToplevel, RetainedSurface and RetainedContent hold textures or colors plus subsurface offsets (src/presentation/layout_animation.rs:107-153). Capture clones live buffers and subsurface structure (src/presentation/layout_animation.rs:1355-1469). Exits expose timed frames (src/presentation/layout_animation.rs:766-778). Spawns expose retained frames while resizing (src/presentation/layout_animation.rs:780-794). Close while blocked produces no exit until timeout publication (src/it/tests/t0061_transactional_layout_animation.rs:151-174). Closing mid-animation hands the presented rect to the exit (src/it/tests/t0061_transactional_layout_animation.rs:176-201).

5.5 layout_animation computation and render plug-in

It computes curve samples, spawn endpoints, roll edges, batch plans, per-node visual rects, damage and retained frames. Curves include linear and flattened cubic bezier (src/presentation/layout_animation.rs:281-323, src/presentation/layout_animation.rs:326-411). Spawn endpoints cover grow, fly and roll (src/presentation/layout_animation.rs:413-450), with edge helpers in src/presentation/layout_animation.rs:452-519. Batch planning filters to moving windows, partitions groups, then plans or falls back to direct interpolation (src/presentation/layout_animation.rs:954-1110).

Render integration reads visual_rect for fullscreen, container children and mono bodies (src/renderer.rs:311-330, src/renderer.rs:470-505, src/renderer.rs:571-589). Exit frames render above tiled or floating stacks (src/renderer.rs:256-274, src/renderer.rs:737-765). Spawns render retained content scaled from source to target size (src/renderer.rs:767-820, src/renderer.rs:822-860). Client damage follows the presented window through visual_damage (src/ifs/wl_surface.rs:1735-1752, src/presentation/layout_animation.rs:747-764).

5.6 Tree verify

verify_tree_invariants checks both timelines (src/tree/verify.rs:14-22) and runs after idle in integration tests (src/it/testrun.rs:198-201). It checks output identity and dummy status, active workspace membership, workspace-to-output links, toplevel parent and workspace pointers, float workspace and display stack membership, fullscreen uniqueness and placeholder linkage, scratchpad mapping and container snapshots (src/tree/verify.rs:72-108 and following).

Container checks include nonempty children, cached counts, child map identity, finite nonnegative factors, factor sum tolerance, focus history and backlinks, mono child placement, no pending layout and non-mono overlap (src/tree/verify.rs:671-853).

5.7 Unusual choices

  • Per-client commit queues plus per-surface timelines with explicit cross-timeline signals.
  • Shm upload, prime copy, dmabuf poll, fifo, commit time, restore, serial and unmap are treated uniformly as queue blockers.
  • The tardy path that discards blockers and relies on timeout is pragmatic.
  • A collision-free geometric planner with exact rational overlap checks drives window animation.
  • Retained textures render scaled from old source size to animated target size while clients resize.

6. Process model, config, tests, utilities

6.1 Startup

  • main() only calls cli::main() (src/main.rs:138-140). CLI parsing decides run vs control commands (src/cli.rs:312-356).
  • Non-run commands drop all process capabilities immediately (src/cli.rs:320-322).
  • Cmd::Run calls start_compositor (src/cli.rs:327). It resets signal handlers (src/compositor.rs:162-164), makes the process a child subreaper or re-execs under one (src/compositor.rs:164, src/utils/clone3.rs:87-115), snapshots capabilities (src/compositor.rs:165), and keeps only CAP_SYS_NICE for the compositor (src/pr_caps.rs:83-115).
  • If CAP_SYS_NICE survived, it elevates the scheduler and moves the privileged capability onto a dedicated helper thread (src/compositor.rs:166-172, src/pr_caps.rs:122-147). The reaper child also drops caps (src/utils/clone3.rs:116).
  • The forker is created next (src/compositor.rs:173-174), before logging, io_uring, threads, portal, or graphics. Later startup creates threads and io_uring state that must not be forked.
  • Startup then spawns the portal helper, sleeper, profiler, and logger (src/compositor.rs:174-184), then builds the full State in start_compositor2 (src/compositor.rs:253-536).
  • start_compositor2 installs the Wayland acceptor, IPC acceptor, forker environment, portal task, and compositor task, then runs io_uring (src/compositor.rs:495-535).
  • The forker child sanitizes its environment by forcing XDG_SESSION_TYPE=wayland and removing DISPLAY/WAYLAND_DISPLAY (src/forker.rs:343-348), renames itself (src/forker.rs:349), takes SIGKILL on parent death (src/forker.rs:350, src/forker.rs:584-592), ignores SIGCHLD (src/forker.rs:351-353), and collapses its fds so only the control socket survives (src/forker.rs:569-582).
  • start_compositor3 selects a backend, publishes sockets, loads config, creates the default seat, starts global handlers and Xwayland, then runs the backend (src/compositor.rs:538-588).
  • Backend order defaults to X11 then Metal (src/compositor.rs:755-758). Config is loaded after graphics/backend selection but before seats and Xwayland (src/compositor.rs:565-581).
  • RLIMIT_NOFILE is raised to the hard limit by default (src/compositor.rs:797-814).

6.2 Forker

  • ForkerProxy::create makes a control socketpair and calls double_fork (src/forker.rs:99-119). The parent gets a pidfd and the child enters Forker::handle (src/forker.rs:114-118).
  • double_fork uses pidfd-carrying socketpairs so both stages reliably learn child pidfds (src/utils/clone3.rs:24-48, src/utils/clone3.rs:50-85).
  • The compositor never calls fork/exec directly for clients. It queues ServerMessage::Spawn plus separately passed fds (src/forker.rs:206-225, src/forker.rs:276-290). The forker re-forks, remaps fds with collision handling, applies env deltas, and execs (src/forker.rs:449-546, src/forker.rs:594-616).
  • It is forked early because only an early, single-threaded, pre-graphics process can safely fork again later. It also owns the child environment via SetEnv (src/forker.rs:139-151, src/forker.rs:423-431) and reports exec failures and pidfds back (src/forker.rs:473-503, src/forker.rs:548-556).
  • Xwayland is one special spawn with fixed fds stderr=2, displayfd=3, listenfd=4, wm=5, wayland=6 (src/forker.rs:170-194).

6.3 Config

  • Current config is in-process and typed. ConfigProxy::default wires the compositor handler directly to wry_toml_config::configure through wry_config::_private::client::init (src/config.rs:255-259).
  • init takes a Weak<dyn ConfigServerEndpoint>, an InitMessage, and a configure function pointer (wry-config/src/_private/client.rs:242-246).
  • Traffic consists of cloned Rust enums such as ServerMessage, ClientMessage, and Response (wry-config/src/_private/ipc.rs:84-150, src/config/handler.rs:101-117). ConfigProxyHandler::send calls client.handle(msg.clone()) with no serialization (src/config/handler.rs:324-328).
  • No dlopen/libloading/config.so loading remains in src/config/handler.rs. The public docs still describe a cdylib plus config! workflow (wry-config/src/lib.rs:19-33), but wry-config/src/macros.rs:1-28 now only defines try_get!/get!, so that documentation looks stale.
  • The high-level wry-config API exposes seats, windows, workspaces, video/connectors, input, keyboard, theme, timers, tasks, exec, logging, and lifecycle calls (wry-config/src/lib.rs:61-79, wry-config/src/lib.rs:107-122).
  • reload() calls into the ambient config client (wry-config/src/lib.rs:117-122), becomes ClientMessage::Reload (src/config/handler.rs:3714), invokes state.reload_config() (src/config/handler.rs:523-525), and reissues configure(true) (src/state.rs:2497-2502).
  • Every configure sends feature negotiation then Configure { reload } (src/config.rs:240-253).
  • The TOML implementation preserves a PersistentState across reloads and clears ephemeral actions/rules/triggers/counters on unload (toml-config/src/lib.rs:2118-2145).
  • Unload clears timers, pollables, and client/window matchers (src/config/handler.rs:307-322), while ConfigProxy::destroy drops the client endpoint (src/config.rs:48-52) and Clear is available as an explicit message (src/config.rs:128-130).
  • There is a versioned VERSION = 1 and feature list (wry-config/src/_private.rs:21-29, wry-config/src/_private/ipc.rs:69-82).

6.4 Xwayland

  • Xwayland is started lazily. The manager allocates and listens on an X socket, exports DISPLAY, then waits for a readable connection attempt before launching Xwayland (src/xwayland.rs:106-133).
  • Launch uses fixed args including -terminate, -rootless, -displayfd 3, -listenfd 4, -wm 5, plus optional -enable-ei-portal after probing Xwayland -help (src/xwayland.rs:231-282).
  • The compositor creates a privileged Wayland client for the Xwayland process itself (src/xwayland.rs:188-203), waits for the displayfd signal (src/xwayland.rs:205), constructs the WM asynchronously (src/xwayland.rs:214-219), and supervises its pidfd (src/xwayland.rs:220-225).
  • Window management lives in src/xwayland/xwm.rs:1-60 and speaks X11 through the local xcon parser/formatter and generated wire_xcon bindings (src/xcon/parser.rs, src/xcon/formatter.rs, src/xcon/consts.rs, src/xwayland/xwm.rs:54-60).
  • Compositor-level Xwayland supervision is one async task installed once (src/state.rs:1630-1638), and stopping only clears the task when the server is not running (src/state.rs:1640-1645).

6.5 Integration tests

  • Tests are in-process and feature-gated (src/compositor.rs:205-218, src/cli.rs:145-151, src/cli.rs:350-351).
  • Each test boots a fresh compositor with TestBackend via start_compositor_for_test (src/it.rs:124-186, src/compositor.rs:747-754).
  • TestBackend provides an 800x600 virtual connector, keyboard, mouse, and test graphics context (src/it/test_backend.rs:97-120).
  • Virtual clients connect over a real Unix socket to the test acceptor and speak Wayland through TestTransport (src/it/testrun.rs:53-107, src/it/test_transport.rs:35-80).
  • Config is replaced by a thread-local TestConfig implementing the same typed config endpoint (src/it/test_config.rs:34-113, src/config.rs:261-264).
  • Test mode zeroes configure/transaction timeouts (src/compositor.rs:575-578). Each test runs as a Phase::Present future with a watchdog timeout (src/it.rs:156-172).
  • Tests run in parallel across worker threads, one isolated runtime dir per process (src/it.rs:46-52, src/it.rs:77-103).
  • There are about 73 src/it/tests/t*.rs cases, plus protocol-object helpers under src/it/test_ifs/ and utilities under src/it/test_utils/.
  • Assertions cover tree geometry and focus (src/it/tests/t0002_window.rs:9-28), protocol behavior, session-manager state, and golden pixels. The scaled-render test renders through the real screenshot path and byte-compares QOI output (src/it/tests/t0071_scaled_render.rs:22-60).

6.6 sm

  • sm is a SQLite-backed Wayland session manager (src/sm.rs:1-56, src/compositor.rs:287-292).
  • It models sessions, named toplevel sessions, owners, and asynchronous acquire/rename/update/disown jobs (src/sm.rs:66-144, src/sm.rs:209-228, src/sm.rs:359-384).
  • Toplevel state tracks output, workspace, floating rectangle, and fullscreen (src/sm.rs:178-186, src/sm.rs:484-539).
  • Persistence work is scheduled as SQLite jobs and flushed by a background task (src/sm.rs:464-475, src/sm.rs:681-705).

6.7 Keyboard and input

  • kbvm is an external crates.io dependency, version 0.1.7 with compose (Cargo.toml:64).
  • It supplies the XKB context, RMLVO/keymap compilation, state machine, and lookup tables (src/kbvm.rs:42-72, src/kbvm.rs:105-164, src/kbvm.rs:289-323).
  • Keymaps are sealed memfds, with a separate Xwayland-compatible rendering (src/kbvm.rs:210-231).
  • libinput is used through unsafe FFI, with no evdev reimplementation. The code binds libinput_path_create_context, dispatch, event, fd, logging, and ref functions (src/libinput.rs:15-26), supplies restricted open/close callbacks (src/libinput.rs:39-63), forwards libinput logs (src/libinput.rs:176-195), and unrefs on drop (src/libinput.rs:168-174).
  • Local evdev code is only a small EVIOCGBIT key-capability helper (src/evdev.rs:1-34).

6.8 Homegrown utilities

  • CloneCell (src/utils/clonecell.rs:9-55). UnsafeCell cell for cheaply cloneable Rc/handle types in single-threaded code.
  • CopyHashMap (src/utils/copyhashmap.rs:21-56). Interior-mutable hashbrown map with access counting and lockable hasher behavior.
  • NumCell (src/utils/numcell.rs:9-30). Cell numeric wrapper with fetch-add/sub and bit operations.
  • AsyncQueue (src/utils/queue.rs:16-54). Single-consumer async FIFO with waker support.
  • SyncQueue (src/utils/syncqueue.rs:10-37). Non-async interior-mutable deque.
  • Stack/AsyncStack (src/utils/stack.rs:15-48, src/utils/stack.rs:72-99). Interior-mutable Vec stacks, optionally awaitable for nonemptiness.
  • SmallMap (src/utils/smallmap.rs:11-58). Small inline map with interior mutability.
  • LinkedList (src/utils/linkedlist.rs:13-60). Intrusive-style doubly linked list with manual refcounts.
  • VecSet (src/utils/vecset.rs:4-41). Small vector-backed set used for pressed keys and similar sets.
  • RefCounted (src/utils/refcounted.rs:8-40). Manual refcount table with version bumping.
  • AsyncEvent (src/utils/asyncevent.rs:11-42). Counting one-shot async trigger.
  • rc_eq/RcEq (src/utils/rc_eq.rs:5-45). Pointer-identity equality for Rc.
  • ErrorFmt (src/utils/errorfmt.rs:5-22). Formats chained Error::source output.
  • oserror (src/utils/oserror.rs:9-60). Raw errno wrapper plus generated constructors/messages.
  • clone3 helpers (src/utils/clone3.rs:24-85). pidfd-aware fork/double-fork, reaper setup, and child supervision.
  • The workspace also contains its own TOML parser, PHF maps, and wire-format codegen crates (Cargo.toml:16, toml-config/src/toml/, toml-config/src/phf.rs).

6.9 Tracy

  • Profiling is behind the tracy Cargo feature (Cargo.toml:105).
  • Without it, zone/frame macros compile to no-ops (src/tracy.rs:1-16, src/tracy/tracy_noop.rs:21-51).
  • With it, zone names are cached in a global map and emitted through tracy-client-sys begin/end calls (src/tracy/tracy_impl.rs:30-80).
  • The profiler is enabled during normal startup (src/compositor.rs:176).

6.10 Unusual choices

  • Nearly everything is single-threaded Rc plus UnsafeCell interior mutability, with threads isolated to CPU work, SQLite, CAP_SYS_NICE, and test runners.
  • Spawning is centralized in a pre-thread double-forked helper that passes fds over a socket and remaps colliding targets.
  • Config, tests, and production share the same typed config traits rather than separate serialization boundaries.
  • The repo vendors protocol-adjacent machinery including X11 wire parsing, Wayland buffer handling, TOML parsing, PHF maps, and XML/wire codegen.
  • SQLite is a core runtime dependency for session state, not merely diagnostics.

7. Smithay extension points

Paths in this section are relative to shallow clones of smithay (023b78b, 2026-10-15) and calloop (0.14.5), prefixed smithay/ and calloop/.

7.1 Event loop

calloop 0.14.5 uses the polling 3.0.0 crate rather than direct epoll calls (calloop/Cargo.toml:26). Poll holds an Arc<Poller> (calloop/src/sys.rs:174) built with Poller::new() (calloop/src/sys.rs:207). Docs describe it as OS-provided polling with epoll on Linux (calloop/src/lib.rs:99). No io_uring string appears in calloop or smithay.

The futures executor is optional behind the executor feature (calloop/Cargo.toml:41). It is implemented as Executor<T> plus Scheduler<T> (calloop/src/sources/futures.rs:45), built on async-task runnables woken through a PingSource. The module docs state futures need their own wakeup means and point at LoopHandle::adapt_io (calloop/src/sources/futures.rs:17).

smithay does not hard-require calloop for wayland-server::Display. Anvil drives clients by wrapping the display in a generic fd source with Generic::new(display, Interest::READ, Mode::Level) then calling dispatch_clients and flush_clients (smithay/anvil/src/state.rs:682, smithay/anvil/src/udev.rs:544). The listening socket helper is itself an EventSource (smithay/src/wayland/socket.rs:95), and other helpers merely provide EventSource impls.

  • DmabufSource (smithay/src/backend/allocator/dmabuf.rs:631)
  • DrmDeviceNotifier (smithay/src/backend/drm/device/mod.rs:525)
  • UdevBackend (smithay/src/backend/udev.rs:130)
  • LibinputInputBackend (smithay/src/backend/libinput/mod.rs:707)
  • X11Backend (smithay/src/backend/x11/mod.rs:561)
  • WinitEventLoop (smithay/src/backend/winit/mod.rs:789)
  • DrmSyncPointSource (smithay/src/wayland/drm_syncobj/sync_point.rs:286)
  • SecurityContextListenerSource (smithay/src/wayland/security_context/listener_source.rs:36)
  • XWayland (smithay/src/xwayland/xserver.rs:279)
  • EiInput (smithay/src/backend/libei/mod.rs:163)

A compositor could poll the display fd from another loop and call dispatch_clients plus flush_clients itself, at the cost of those ready-made sources and the LoopHandle insertion helpers used across backends and xwayland.

7.2 Wayland server

smithay uses wayland-server 0.31.13 and wayland-backend 0.3.15 as optional deps enabled by wayland_frontend (smithay/Cargo.toml:64, smithay/Cargo.toml:66, smithay/Cargo.toml:115). The default is the pure Rust backend. The libwayland system backend is only selected by use_system_lib through wayland-backend/server_system plus wayland-sys (smithay/Cargo.toml:113). That feature also gates gbm?/import-wayland and the ImportEgl path.

Dispatch uses wayland-server Dispatch and GlobalDispatch, but smithay modules expose the simpler Dispatch2 and GlobalDispatch2 (smithay/src/wayland/dispatch2.rs:6, smithay/src/wayland/dispatch2.rs:25), with a delegate_dispatch2 macro (smithay/src/wayland/dispatch2.rs:45) that blanket-implements the upstream traits. Each protocol module documents *State plus *Handler plus delegate_dispatch2, for example compositor (smithay/src/wayland/compositor/mod.rs:63).

7.3 Renderer abstraction

Core traits live in smithay/src/backend/renderer/mod.rs.

  • Bind (:216) has bind and supported_formats.
  • Texture (:233) has size, width, height and format.
  • Frame (:270) has context_id, clear, draw_solid, render_texture_at, render_texture_from_to, transformation, output_size, wait and finish.
  • Renderer plus RendererSuper (:385, :402) have context_id, downscale_filter, upscale_filter, set_debug_flags, debug_flags, render, wait, cleanup_texture_cache and invalidate_caches.
  • Offscreen::create_buffer (:467).
  • ImportMem (:511) with import_memory, update_memory and mem_formats. ImportMemWl (:479) with import_shm_buffer and shm_formats.
  • ImportDma (:643) with dmabuf_formats, has_dmabuf_format and import_dmabuf. ImportDmaWl::import_dma_buffer (:618). ImportAll::import_buffer (:677).
  • ExportMem (:747) with copy_framebuffer, copy_texture, can_read_texture and map_texture.
  • Blit::blit (:808) and BlitFrame (:836) with blit_to and blit_from.

There is no Vulkan renderer in tree. src/backend/vulkan only has instance and physical device helpers. Its module docs state it does not provide logical devices, rendering or memory allocation (smithay/src/backend/vulkan/mod.rs:1). Vulkan allocation is separate in src/backend/allocator/vulkan.

Multigpu is generic over GraphicsApi and ApiDevice (smithay/src/backend/renderer/multigpu/mod.rs:728, :749). A device exposes renderer, renderer_mut, allocator returning a Dmabuf allocator, node, plus cross-device import hints. Target selection needs Bind<Dmabuf> (:328), cross rendering needs target-side ImportDma (:394), and early_import needs ImportMemWl plus ImportDmaWl plus ExportMem (:493).

OutputDamageTracker (smithay/src/backend/renderer/damage/mod.rs:193) exposes new, from_output, from_mode_source, mode, render_output, render_output_with_states and damage_output (:250, :279, :300, :317, :326, :382, :435). Rendering requires E: RenderElement<R> and R: Renderer.

Element (smithay/src/backend/renderer/element/mod.rs:518) requires id, current_commit, src, geometry plus defaulted location, transform, damage_since, opaque_regions, alpha, kind and is_framebuffer_effect. RenderElement (:572) requires draw and optionally underlying_storage plus capture_framebuffer.

7.4 DRM

DrmCompositor docs (smithay/src/backend/drm/compositor/mod.rs:1) describe walking elements front to back, assigning overlays, then trying the top fitting element for cursor, and compositing the rest on primary with the supplied renderer. Overlays are atomic-only, with legacy limited to primary, plus UnderlyingStorage export and atomic test commits. Scanout permission is controlled by FrameFlags (:1021) with ALLOW_PRIMARY_PLANE_SCANOUT, ALLOW_PRIMARY_PLANE_SCANOUT_ANY, ALLOW_OVERLAY_PLANE_SCANOUT, ALLOW_CURSOR_PLANE_SCANOUT, SKIP_CURSOR_ONLY_UPDATES, ALLOW_SCANOUT and DEFAULT.

Frame scheduling is caller-driven. render_frame (:1686) needs R: Renderer + Bind<Dmabuf> and prepares planes and damage. queue_frame (:2441) queues and submits when idle. commit_frame (:2487) commits without vblank generation. frame_submitted (:2618) retires the pending frame and submits a queued one. Empty frames must be rescheduled by the caller, typically with a retrace timer.

VRR exists on surfaces as vrr_supported, vrr_enabled and use_vrr backed by VRR_ENABLED (smithay/src/backend/drm/surface/atomic.rs:558, smithay/src/backend/drm/surface/gbm.rs:502), with VrrSupport values including RequiresModeset. No tearing or async flip control, no gamma or CTM or color pipeline properties, and no render-as-late-as-possible deadline API were found in src/backend/drm/compositor.

7.5 Surface commit handling

Commits use double-buffered cached state plus transactions and blockers. Blocker and BlockerState with Pending, Released and Cancelled are in smithay/src/wayland/compositor/transaction.rs:53. A Barrier helper is at :70. Transactions collect surfaces plus blockers (:111) and support add_blocker (:164), merge_into (:174), finalize (:207), global readiness in Transaction::state (:236), application in apply (:261), and per-client ordering in TransactionQueue (:280). The header comment (:1) describes synchronized subsurfaces merging into the parent pending transaction and future explicit sync blockers.

Public hooks are add_pre_commit_hook, remove_pre_commit_hook and add_blocker (smithay/src/wayland/compositor/mod.rs:454, :547), backed by PrivateSurfaceData (smithay/src/wayland/compositor/tree.rs:188). wl_surface.commit invokes pre-commit hooks then commits (smithay/src/wayland/compositor/handlers.rs:266).

Dmabuf readiness uses Dmabuf::generate_blocker returning DmabufBlocker plus DmabufSource (smithay/src/backend/allocator/dmabuf.rs:267), with the Blocker impl at :524. Explicit sync uses acquire and release points stored in DrmSyncobjSurfaceCachedState (smithay/src/wayland/drm_syncobj/mod.rs:96), validated as pairs (:242), installed by pre-commit hook (:330), and waited on through DrmSyncPoint::generate_blocker plus DrmSyncPointBlocker (smithay/src/wayland/drm_syncobj/sync_point.rs:240, :324). Multiple surfaces can join one transaction through PendingTransaction::merge_into and synchronized subsurface grouping.

7.6 Presentation and timing protocols

src/wayland contains presentation (presentation-time), fifo (fifo-v1), commit_timing (commit-timing-v1), drm_syncobj (linux-drm-syncobj-v1), content_type (content-type-v1) and alpha_modifier (alpha-modifier-v1) (smithay/src/wayland/mod.rs:50). No tearing_control or color_management modules exist, so tearing-control-v1 and color-management-v1 have no dedicated smithay helpers in this checkout.

7.7 Allocator

Common docs and traits are in smithay/src/backend/allocator/mod.rs:1, with submodules dmabuf, dumb, format, gbm, udmabuf, vulkan and swapchain (:18), and the Allocator plus Buffer traits (:44, :60). Concrete types are GbmAllocator (smithay/src/backend/allocator/gbm.rs:147), DumbBuffer (smithay/src/backend/allocator/dumb.rs:17), VulkanAllocator (smithay/src/backend/allocator/vulkan/mod.rs:123), VulkanImage (:382), Swapchain (smithay/src/backend/allocator/swapchain.rs:46) and Slot (:74), plus Dmabuf in dmabuf.rs. The Vulkan allocator targets up to Vulkan 1.3 and requires VK_EXT_image_drm_format_modifier, VK_EXT_external_memory_dmabuf and VK_KHR_external_memory_fd, with optional VK_EXT_4444_formats (smithay/src/backend/allocator/vulkan/mod.rs:1).