Before you start#
Read Wayland surfaces and buffers and the DRM userspace path. Weston 14.0.0 and wlroots 0.18.0 are the detailed examples; Mutter 46.4 and KWin 6.1.4 are separately versioned comparative surveys. These versions are study points, not a tested compatibility matrix.
Learning goals#
Trace a client commit into scene state and an output update, explain why rendering and direct scanout have different constraints, and describe how failed output state construction or commit affects object lifetime. Identify the separate architecture of Weston, wlroots, Mutter and KWin.
Source walkthrough#
- Client surface
Committed state supplies buffer and damage.
- Scene and output
Visible views intersect an output and produce damage.
- Renderer or plane
Composition and direct scanout have distinct constraints.
- Backend commit
A tested state can fail; completion controls resource reuse.
A Wayland compositor is both protocol server and policy engine. It receives client surface commits, decides placement and focus, builds a visible scene, and produces output updates through a backend. It also decides which clients may use extensions. The kernel does not implement this desktop scene graph. DRM/KMS supplies device-specific output objects and atomic operations when the chosen backend uses them; a nested or headless backend has different boundaries. A compositor can use a renderer, dedicated hardware planes, or both. Which combination is possible changes with output mode, format, modifier, transform, scaling, occlusion, color processing, synchronization, and hardware resources.
Weston's weston_surface holds compositor-side surface content and state; a weston_view positions an instance of a surface in the scene, with transforms and output association. An output represents a target region and has repaint scheduling. These are different objects: one surface can have views, and a view's position is not a client-controlled physical screen coordinate. Client commits make new surface state available; the compositor may coalesce changes and repaint later. A hidden surface can remain alive without being visible on any output. Consult Weston 14.0.0 libweston/compositor.c for this implementation, not as a universal compositor specification.
In weston_output_repaint, Weston conditionally rebuilds its view list before walking the output's paint nodes. Its repaint path computes output-specific work and calls the backend repaint function. The backend subsequently submits output state; the output's frame completion and repaint scheduling connect later work to a real or simulated output clock. A repaint is not necessarily one client commit: several commits may be combined, and one client's surface can contribute to more than one output. Coordinates, scale and transform must be mapped correctly before damage is used.
From Weston 14.0.0 libweston/compositor.c, weston_output_repaint, MIT-style file license; intervening code is not quoted:
if (ec->view_list_needs_rebuild)
weston_compositor_build_view_list(ec);
The flag records that the scene's view ordering needs rebuilding; the call refreshes the list before output painting relies on it. It does not render pixels by itself. The function then iterates paint nodes, calculates protection and damage-related state, and calls the output's repaint hook. View-list invalidation therefore needs to be resolved before a backend decides what is visible. Destroying a view or changing scene structure while an unrelated thread walks an unprotected list would violate that relationship; compositor code must follow its own event-loop and object lifetime rules, rather than assume Wayland protocol sequencing is a universal thread lock.
Weston DRM: scene to output state
The DRM backend in Weston 14.0.0 libweston/backend-drm/drm.c constructs backend output state and selects among composition and plane use. The renderer can composite visible surfaces into an output buffer, typically through a graphics API, while a plane may scan out a suitable buffer without that composition step. This is an optimization with constraints, not a property of every full-screen client. An incompatible modifier, scaling requirement, overlay, transform, color operation or unavailable plane can rule it out. A cursor may itself use a dedicated plane on a capable configuration.
A DRM atomic request groups property changes for a set of objects. The backend translates proposed output/plane state into properties and uses the kernel's atomic interface. Weston's backend-drm/kms.c contains the state-to-atomic-request path and commit handling. A test-only atomic check asks whether the proposed state is acceptable at that moment; it does not reserve hardware indefinitely or guarantee that a subsequent real commit succeeds. A failed test or commit must leave the implementation's retained buffers and pending state in a consistent condition. Whether and how it retries using renderer composition depends on the specific backend path and failure; never infer that every failure triggers an automatic fallback.
From Weston 14.0.0 libweston/backend-drm/kms.c, drm_pending_state_apply_atomic, MIT-style permission notice in the file header (surrounding code omitted):
switch (mode) {
case DRM_STATE_APPLY_SYNC:
flags = 0;
break;
case DRM_STATE_APPLY_ASYNC:
flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
break;
case DRM_STATE_TEST_ONLY:
flags = DRM_MODE_ATOMIC_TEST_ONLY;
break;
}
The switch chooses atomic flags before the request is submitted. The synchronous path uses no flags, the asynchronous path requests a page-flip event with a nonblocking commit, and the test-only mode sets DRM_MODE_ATOMIC_TEST_ONLY on the same submission path. A test is a flag on the request, not a different kind of object. The excerpt does not show how a failed request is retried, which planes were included, or whether a particular repaint uses the test mode.
- weston_output_repaintdrm_output_repaint
output->repaint hook
- drm_output_repaintdrm_pending_state_apply
pending state flushed (elided)
- drm_pending_state_applydrm_pending_state_apply_atomic
ASYNC: page-flip event, nonblocking
- drm_pending_state_apply_atomicdrmModeAtomicCommit
atomic request
- drmModeAtomicCommitdrm_pending_state_apply_atomic
Return. flip completion reaches atomic_flip_handler
Time runs downward. Dashed arrows show returns.
Read the text version
- weston_output_repaint → drm_output_repaint: output->repaint hook
- drm_output_repaint → drm_pending_state_apply: pending state flushed (elided)
- drm_pending_state_apply → drm_pending_state_apply_atomic: ASYNC: page-flip event, nonblocking
- drm_pending_state_apply_atomic → drmModeAtomicCommit: atomic request
- drmModeAtomicCommit → drm_pending_state_apply_atomic (return): flip completion reaches atomic_flip_handler
Synchronization is as important as geometry. A producer may still be rendering into a buffer when the compositor receives its handle. Depending on the stack, implicit reservation synchronization or negotiated explicit fences express when reading can start. An output fence or completion event can govern when the compositor can stop retaining a scanout buffer. Buffer release to the client is valid only after the compositor is done with that use. A page-flip completion, a client's frame callback, and presentation feedback have different consumers and semantics. Hardware reset, device removal, failed commits and output disable complicate cleanup; a robust compositor tracks outstanding references and does not free an in-flight buffer merely because a submit call returned.
wlroots builds and finishes state
wlroots is a library used by compositors, not the protocol server implementation for all desktops. Its compositor supplies policy around wlroots objects. The 0.18.0 scene API tracks nodes and scene outputs, damage, and output state. The backend and output APIs form another boundary; their ability to use DRM planes or rendering depends on the backend and implementation. The actual path is in wlroots 0.18.0 types/scene/wlr_scene.c and its output implementation.
From wlr_scene_output_commit in that scene file, wlroots 0.18.0, MIT project license, omitting initialization, early-return and cleanup lines:
if (!wlr_scene_output_build_state(scene_output, &state, options)) {
goto out;
}
ok = wlr_output_commit_state(scene_output->output, &state);
The first call attempts to construct an output state for the current scene; false branches to the common cleanup label. The second submits that state through the output implementation and records whether it succeeded. In the surrounding function wlr_output_state_finish runs at the label regardless of the result, so temporary state resources are finished on both paths. A failed build cannot be treated as a successful frame, and a successful build does not imply a successful commit. The early return when there is neither a needed frame nor pending commit damage means a caller must not interpret every true return as evidence of new pixels reaching a monitor.
- wlr_scene_output_commitwlr_scene_output_build_state
build state for the current scene
- wlr_scene_output_build_statewlr_scene_output_commit
Return. false: goto out, no commit
- wlr_scene_output_commitwlr_output_commit_state
commit only if the build succeeded
- wlr_output_commit_statewlr_scene_output_commit
Return. ok result recorded
- wlr_scene_output_commitwlr_output_state_finish
finish state at out, on both paths
Time runs downward. Dashed arrows show returns.
Read the text version
- wlr_scene_output_commit → wlr_scene_output_build_state: build state for the current scene
- wlr_scene_output_build_state → wlr_scene_output_commit (return): false: goto out, no commit
- wlr_scene_output_commit → wlr_output_commit_state: commit only if the build succeeded
- wlr_output_commit_state → wlr_scene_output_commit (return): ok result recorded
- wlr_scene_output_commit → wlr_output_state_finish: finish state at out, on both paths
The scene can use a direct-scanout candidate when conditions allow; otherwise an implementation can render the scene to an output buffer. The resulting candidate and temporary state must hold references for as long as their users need them. Output disable or destruction can race with application-level expectations of a future frame; event-loop callbacks should observe destruction and drop their references. Library API version matters: names and ownership contracts in 0.18.0 need not match later wlroots releases. A compositor's policy, not wlroots alone, chooses how to position windows, handle user input and manage protocol globals.
A stateful frame and a rejected candidate
Consider one output of 1920 by 1080 pixels. A client commits buffer A with damage covering a 100 by 50 rectangle. The compositor has a visible view intersecting that output. It transforms and clips damage into output coordinates, combines it with other damage and builds an output state. If the output is fully covered by a compatible A and all other constraints allow, a direct-scanout candidate may be considered; otherwise A can be sampled into a compositor-owned output buffer. An atomic test rejecting the candidate means that candidate must not be treated as displayed. A particular compositor may build and test a composited alternative; that is a conditional second proposal, not a guarantee derived from the kernel error.
If the compositor submits a valid state and later receives the relevant completion, it can retire the previous output buffer subject to its actual reference and fence rules. It may send a frame callback before A is released, for example when scheduling the client's next update while A is still in use. When A has no remaining compositor use, it can send the buffer release. If the output disappears during the sequence, pending work and resources must be unwound; the application's callback should not assume that a connected output will produce another presentation. This is a conceptual timeline, not a recorded Weston run.
Mutter and KWin are separate designs
GNOME Mutter combines a window manager and compositor with its own Clutter scene and platform backends. Its 46.4 Wayland surface implementation is an entry point for comparing how surface state belongs to Mutter objects; its KMS implementation and renderer have their own version-specific scheduling and synchronization. KDE KWin similarly has its own Qt-based architecture, scene and Wayland integration; its 6.1.4 surface implementation is a separate project. These observations are a survey, not a complete execution trace for either desktop. Neither should be described as a wlroots compositor merely because it serves Wayland clients.
Different compositors can offer the same core protocol while choosing different scheduling, decorations, input policy, scene structures, supported extensions and failure handling. A program must negotiate supported global and object versions, rather than equate one compositor's behavior with the protocol. Backends also change the trust and ownership picture: a nested compositor presents to another compositor; a headless backend can process virtual outputs; DRM backend ownership and session management are specific to its environment. None of these should be confused with a license to take control of a user's existing display session for an experiment.
Damage, pacing and retained output resources
Damage is a history of what needs to be recomputed, not simply the latest client's rectangle. If an output buffer is reused after several frames, it may lack changes from more than the most recent frame. A damage-tracking renderer must account for the buffer's age and accumulated changes, or repaint a larger region. Transformed surfaces, overlapping views, shadows and output scale can expand the affected area. A compositor also needs to repaint exposed areas when a window moves or disappears even though the newly exposed client sent no fresh damage. The exact algorithms depend on the renderer and compositor version; the rectangle exercise below isolates only clipping.
Output state usually holds references to objects whose backing memory outlives the call that submits it. A rendering job can still be reading a client buffer after a surface has received a later commit, while a display controller can still be scanning out the previous output buffer. Completion notifications and fences provide evidence about specific uses. Correct cleanup separates a failed proposal that never acquired a hardware use from a successful submission that must be retired later. A disconnect or output removal also needs an explicit path for releasing references whose expected completion event may no longer arrive normally. It is unsafe to free the storage just because a scene node was removed from a list.
The display backend's repaint loop is also a pacing boundary. Repainting immediately for every client commit could waste work and race ahead of the output, while never scheduling another repaint could leave visible changes stale. The compositor combines damage, chooses a scheduling point and reports appropriate callbacks under the protocol and backend contracts. A frame callback is not a measurement of the physical display's exact presentation time; an optional presentation-feedback event and hardware timestamp have narrower, extension-specific meanings. Different output refresh rates can require different scheduling for the same surface, and a nested compositor introduces an additional parent's pacing and buffer-lifetime boundary.
When a client commits another surface state while an output is waiting for presentation, the compositor must keep the two state transitions distinguishable. A release tells the client about buffer reuse under the relevant protocol; it is not necessarily proof that a particular frame was visible. A failed output commit can leave a different state from a completed presentation, so an investigation needs the commit result, the buffer reference and the output event rather than only a timestamp. This is a reasoning case, not a measured compositor trace.
Practice#
This isolated arithmetic experiment clips a damage rectangle to a conceptual 1920 by 1080 output. It contacts no compositor and changes no display state:
python3 - <<'PY'
output = (0, 0, 1920, 1080)
damage = (1900, 1060, 2000, 1110)
x1, y1 = max(output[0], damage[0]), max(output[1], damage[1])
x2, y2 = min(output[2], damage[2]), min(output[3], damage[3])
print('clipped:', (x1, y1, x2, y2), 'pixels:', max(0, x2-x1)*max(0, y2-y1))
PY
The expected rectangle is (1900, 1060, 1920, 1080), or 400 pixels. A rectangle wholly outside the output has zero intersection; the unmodified 100-by-50 area would overcount the visible portion. This models geometry, not the full region, transform, occlusion or repaint algorithm. As a source-only alternative, use the A timeline above: record candidate state, failed test, conditional second proposal, completion and release as separate entries. Deliver the clipped result and explain why a rejected candidate cannot be called a presented frame.
Knowledge check#
- What distinguishes a surface from a view and an output in Weston?
- Why does a successful atomic test not prove the real commit will succeed?
- What cleanup occurs when wlroots cannot build scene output state?
- Can a frame callback prove that a scanout buffer has been released?
- Are Mutter and KWin wlroots front ends?
Answers#
- A surface holds committed content and state, a view places an instance in the scene, and an output is a target receiving scene updates. Their lifetimes and coordinate systems differ.
- Testing validates a proposed state at a moment; it is not an indefinite reservation. Resources or device state can change before the real commit, which has its own result.
- The function branches to its common exit and finishes the initialized output state. Its false result cannot be counted as a successful presentation.
- No. A callback paces future work; release depends on the compositor finishing its use and any synchronization requirements.
- No. Each has its own implementation and policy; this chapter only surveys their pinned surface files.
Checkpoint#
Submit a diagram identifying client buffer, scene view, output state, selected renderer or plane, and the event that permits reuse. It passes if it separates failed test from commit, labels fallback conditional, and names at least one reference that must survive until completion. If not, trace the wlroots common exit and repeat the synthetic timeline. This is not certification.
Sources#
The detailed examples are Weston compositor, Weston DRM backend, Weston atomic handling, and wlroots scene. The separately pinned Mutter surface and KWin surface support the limited comparison.