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 costs a small
9//! blit and one property write; 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 an `apply` produces.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[allow(
58 clippy::exhaustive_structs,
59 reason = "a value defined by its fields; there is nothing to add to it"
60)]
61pub struct PropertyWrite {
62 /// The object to write on.
63 pub object_id: u32,
64 /// Which property.
65 pub property_id: u32,
66 /// The value.
67 pub value: u64,
68}
69
70/// A decoration surface and where it goes.
71#[derive(Debug, Clone, Copy, Default)]
72#[allow(
73 clippy::exhaustive_structs,
74 reason = "built by callers with `..Default::default()`, which a new field does not break"
75)]
76pub struct SurfaceRef {
77 /// The framebuffer to scan out, or zero for nothing.
78 pub fb_id: u32,
79 /// Width in pixels.
80 pub width: u32,
81 /// Height in pixels.
82 pub height: u32,
83 /// Destination x.
84 pub x: i32,
85 /// Destination y.
86 pub y: i32,
87 /// The surface's content generation, for damage tracking.
88 pub generation: u64,
89}
90
91impl SurfaceRef {
92 /// Whether this reference names anything to draw.
93 ///
94 /// A zero framebuffer or a zero dimension is *nothing*, not an error: a
95 /// caller with fewer windows than slots passes these, and a caller mid-
96 /// resize passes them briefly.
97 #[must_use]
98 pub const fn is_armed(self) -> bool {
99 self.fb_id != 0 && self.width != 0 && self.height != 0
100 }
101}