drmkit_csd/presenter.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! Getting a drawn decoration onto the screen.
5//!
6//! Three ways, in descending order of what the hardware will do for you:
7//!
8//! - **Plane** — each decoration on its own overlay. A hover repaints only
9//! that decoration's small buffer; nothing is recomposited.
10//! - **Composite** — blend them onto one canvas plane. Costs a canvas-sized
11//! blend per damaged region, and needs only one plane.
12//! - **Framebuffer** — no KMS plane at all. For an fbdev fallback, where the
13//! decoration is written into the same buffer as everything else.
14
15/// Which way a decoration reaches the screen.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum Tier {
19 /// One overlay plane per decoration.
20 Plane,
21 /// All of them blended onto one canvas plane.
22 Composite,
23 /// Into a framebuffer, with no plane involved.
24 Fb,
25}
26
27/// Pick a tier from what the hardware can offer.
28///
29/// `None` means neither a plane nor a canvas is available, and the caller
30/// should drop to its own framebuffer path — this function does not choose
31/// [`Tier::Fb`], because that tier is what a caller falls back *to* when KMS
32/// has nothing, not something to be selected among KMS options.
33///
34/// Zero desired decorations is never [`Tier::Plane`]: reserving no planes
35/// trivially "succeeds", and returning Plane on that basis would have a caller
36/// build a plane presenter with no slots.
37#[must_use]
38pub const fn choose_presenter_tier(
39 reservable: usize,
40 desired: usize,
41 has_canvas_plane: bool,
42) -> Option<Tier> {
43 if desired > 0 && reservable >= desired {
44 // Every window gets its own overlay, which is the arrangement worth
45 // having: a hover repaints one small buffer instead of a canvas.
46 return Some(Tier::Plane);
47 }
48 if has_canvas_plane {
49 // Plane-starved but not planeless: composite onto the primary.
50 return Some(Tier::Composite);
51 }
52 None
53}
54
55/// One property write, as [`compute_writes`](crate::compute_writes) and
56/// [`compute_canvas_writes`](crate::compute_canvas_writes) produce them.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[allow(
59 clippy::exhaustive_structs,
60 reason = "a value defined by its fields; there is nothing to add to it"
61)]
62pub struct PropertyWrite {
63 /// The object to write on.
64 pub object_id: u32,
65 /// Which property.
66 pub property_id: u32,
67 /// The value.
68 pub value: u64,
69}
70
71/// A decoration surface and where it goes.
72#[derive(Debug, Clone, Copy, Default)]
73#[allow(
74 clippy::exhaustive_structs,
75 reason = "built by callers with `..Default::default()`, which a new field does not break"
76)]
77pub struct SurfaceRef {
78 /// The framebuffer to scan out, or zero for nothing.
79 pub fb_id: u32,
80 /// Width in pixels.
81 pub width: u32,
82 /// Height in pixels.
83 pub height: u32,
84 /// Destination x.
85 pub x: i32,
86 /// Destination y.
87 pub y: i32,
88 /// The surface's content generation, for damage tracking.
89 pub generation: u64,
90}
91
92impl SurfaceRef {
93 /// Whether this reference names anything to draw.
94 ///
95 /// A zero framebuffer or a zero dimension is *nothing*, not an error: a
96 /// caller with fewer windows than slots passes these, and a caller mid-
97 /// resize passes them briefly.
98 #[must_use]
99 pub const fn is_armed(self) -> bool {
100 self.fb_id != 0 && self.width != 0 && self.height != 0
101 }
102}