drmkit_scene/lib.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Scene composition for drmkit.
5//!
6//! Port of `src/scene/` from drm-cxx @ `dc2915b`.
7//!
8//! A [`LayerScene`] owns one CRTC's stack of layers and the machinery that
9//! turns them into a frame: which plane each layer lands on, what to composite
10//! when there are more layers than planes, and when a buffer may go back to
11//! whoever produced it.
12//!
13//! # Driving a frame
14//!
15//! ```text
16//! let mut scene = LayerScene::new(crtc_id);
17//! let handle = scene.add_layer(Box::new(source));
18//! scene.layer_mut(handle)?.set_display(DisplayParams { .. });
19//!
20//! // per frame
21//! let mut build = scene.build_frame(®istry, crtc_index, kind, &mut committer)?;
22//! emit_frame(&mut request, &map, &mut build, modeset)?;
23//! arm_acquire_fences(&mut build, &mut request, &map, true)?;
24//! let result = request.commit(&device, flags);
25//! let report = scene.finalize_frame(build, classify(&result));
26//! // ... and when the page-flip event arrives
27//! scene.flip_landed();
28//! ```
29//!
30//! The split between `build_frame` and `finalize_frame` is the part worth
31//! understanding. `build_frame` acquires a buffer from every source, runs the
32//! allocator — which may issue `TEST_ONLY` commits through the committer to
33//! ask the kernel whether an arrangement is legal — and hands back a
34//! [`FrameBuild`] describing what to program. It does not commit: the caller
35//! owns the request, so it can add a modeset, arm fences, or refuse the frame.
36//! `finalize_frame` then takes the kernel's answer and settles the
37//! bookkeeping, which is what arms the flip and what decides which buffers are
38//! now safe to release.
39//!
40//! A `FrameBuild` dropped without `finalize_frame` leaks its acquisitions, and
41//! says so in a debug assertion rather than silently.
42//!
43//! # A source with nothing ready
44//!
45//! [`SourceError::WouldBlock`] from a source is not an error. It is the
46//! documented "no frame yet" signal — a V4L2 device before its first sample,
47//! an appsink mid-preroll — and the layer is *skipped* for that frame and
48//! counted in the report, keeping the plane it had and the framebuffer already
49//! on it. A frame does not fail because one producer was late, and a starved
50//! layer does not blink off screen.
51//!
52//! # The release contract
53//!
54//! Two of the four pinned invariants this crate carries are about *when* a
55//! buffer goes back to its source:
56//!
57//! - **Invariant 1** — a buffer is released only after the page flip that stops
58//! scanning it out completes, two commits deep, never when the ioctl returns.
59//! Releasing early lets a producer overwrite a buffer the display engine is
60//! still reading, which tears on hardware whose producer driver attaches no
61//! reservation fence.
62//! - **Invariant 2** — a *failed* commit releases that frame's acquisitions
63//! immediately, because no flip is in flight to gate them on, and a
64//! non-`EACCES` failure does not suspend the scene. Self-healing producer
65//! rings depend on being able to reuse their buffers after a rejection.
66//!
67//! [`AcquiredBuffer`] is move-only, so handing a buffer back early is a move
68//! and using it afterwards does not compile — the ownership half of invariant 1
69//! is enforced by the type. The timing half cannot be, and is pinned against a
70//! real device in `tests/release_invariants_vkms.rs`.
71
72mod canvas;
73mod commit;
74mod display;
75mod frame;
76mod lower;
77mod release;
78mod report;
79mod scene;
80mod signaling;
81mod source;
82
83pub use canvas::{
84 CompositeCanvas, CompositeRect, CompositeSrc, blend_into, canvas_format_for_plane,
85 canvas_output_bpp, clear_into, convert_row, format_supported,
86};
87pub use commit::{
88 DeviceCommitter, FenceAction, LayerWrites, Modeset, PlanePropertyMap, arm_acquire_fences,
89 classify, commit_detach, emit_disable, emit_frame, emit_frame_damaged, emit_layer,
90 fence_action,
91};
92pub use display::{ColorEncoding, ColorRange, DisplayParams, FixedRect, Rect, Ycbcr, to_16_16};
93pub use frame::{AcquireTally, CommitKind, FrameLifecycle, FrameOutcome, KernelResult};
94pub use lower::{LoweringInput, lower_layer};
95pub use release::{Acquisition, ReleaseQueue, ReleaseReason, Released, ScenePendingFlip};
96pub use report::{CommitReport, LayerPlacement, Placement};
97pub use scene::{
98 FrameBuild, IncompatibilityReason, LayerHandle, LayerIncompatibility, LayerScene,
99 PendingDetach, PlanePlan, RebindReport, SceneError, SceneLayer,
100};
101pub use signaling::{ColorPrimaries, OutputSignaling, derive_output_signaling, widest_gamut};
102#[allow(deprecated, reason = "the 0.1.0 names, kept for compatibility")]
103pub use signaling::{OutputSignalling, derive_output_signalling};
104pub use source::{
105 AcquiredBuffer, BindingModel, DamageRect, DmaBufDesc, LayerBufferSource, SourceError,
106 SourceFormat,
107};
108
109#[cfg(test)]
110mod tests;