drmkit_csd/presenter_plane.rs
1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! One decoration per overlay plane.
5
6use crate::{PropertyWrite, SurfaceRef};
7
8/// The property ids for one reserved plane, and what to write to the ones
9/// whose values do not come from the surface.
10///
11/// Ids rather than names because the caller resolves them once; a zero id
12/// means the plane does not expose that property, which is why the
13/// optional ones are checked rather than assumed.
14#[derive(Debug, Clone, Copy, Default)]
15#[non_exhaustive]
16pub struct PlaneSlot {
17 /// The plane.
18 pub plane_id: u32,
19 /// The CRTC to bind it to when armed.
20 pub crtc_id: u32,
21 /// `FB_ID`.
22 pub fb_id_prop: u32,
23 /// `CRTC_ID`.
24 pub crtc_id_prop: u32,
25 /// `CRTC_X`.
26 pub crtc_x_prop: u32,
27 /// `CRTC_Y`.
28 pub crtc_y_prop: u32,
29 /// `CRTC_W`.
30 pub crtc_w_prop: u32,
31 /// `CRTC_H`.
32 pub crtc_h_prop: u32,
33 /// `SRC_X`.
34 pub src_x_prop: u32,
35 /// `SRC_Y`.
36 pub src_y_prop: u32,
37 /// `SRC_W`.
38 pub src_w_prop: u32,
39 /// `SRC_H`.
40 pub src_h_prop: u32,
41 /// `pixel blend mode`, zero where absent.
42 pub blend_mode_prop: u32,
43 /// What to write to it.
44 pub blend_mode_value: u64,
45 /// `alpha`, zero where absent.
46 pub alpha_prop: u32,
47 /// `zpos`, zero where absent.
48 pub zpos_prop: u32,
49 /// What to write to it.
50 pub zpos_value: u64,
51}
52
53/// Why the writes could not be computed.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
55#[non_exhaustive]
56pub enum PlaneError {
57 /// More decorations than reserved planes.
58 ///
59 /// Refused rather than truncated: dropping the surfaces past the end would
60 /// leave windows silently undecorated, and the caller reserved the planes
61 /// knowing how many it wanted.
62 #[error("{surfaces} decorations for {slots} reserved planes")]
63 TooManySurfaces {
64 /// How many were passed.
65 surfaces: usize,
66 /// How many planes are reserved.
67 slots: usize,
68 },
69}
70
71/// Convert `surfaces` to a full set of property writes over `slots`.
72///
73/// Positional: surface `i` goes on slot `i`. A slot with no surface — because
74/// there were fewer, or because the surface is empty — is **disarmed**, not
75/// left alone. A plane left with last frame's framebuffer goes on scanning it
76/// out, so a window that closed would leave its decoration on screen.
77///
78/// The size is fixed at 16.16 for `SRC_W`/`SRC_H`, which is what KMS expects,
79/// and the surface is never scaled: a decoration drawn at one size and
80/// stretched to another is a decoration with the wrong corner radius.
81///
82/// # Errors
83///
84/// [`PlaneError::TooManySurfaces`] when there are more surfaces than slots.
85pub fn compute_writes(
86 slots: &[PlaneSlot],
87 surfaces: &[SurfaceRef],
88) -> Result<Vec<PropertyWrite>, PlaneError> {
89 if surfaces.len() > slots.len() {
90 return Err(PlaneError::TooManySurfaces {
91 surfaces: surfaces.len(),
92 slots: slots.len(),
93 });
94 }
95
96 let mut writes = Vec::with_capacity(slots.len() * 12);
97 let mut add = |object_id: u32, property_id: u32, value: u64| {
98 writes.push(PropertyWrite {
99 object_id,
100 property_id,
101 value,
102 });
103 };
104
105 for (index, slot) in slots.iter().enumerate() {
106 let armed = surfaces.get(index).copied().filter(|s| s.is_armed());
107 let Some(surface) = armed else {
108 // Two writes are enough to take a plane down, and writing the rest
109 // would be describing geometry for a framebuffer that is not there.
110 add(slot.plane_id, slot.fb_id_prop, 0);
111 add(slot.plane_id, slot.crtc_id_prop, 0);
112 continue;
113 };
114
115 add(slot.plane_id, slot.fb_id_prop, u64::from(surface.fb_id));
116 add(slot.plane_id, slot.crtc_id_prop, u64::from(slot.crtc_id));
117 // Two's complement, which is how KMS reads a signed CRTC_X: a
118 // decoration may sit partly off the left or top edge, and the kernel
119 // clips it.
120 add(
121 slot.plane_id,
122 slot.crtc_x_prop,
123 i64::from(surface.x).cast_unsigned(),
124 );
125 add(
126 slot.plane_id,
127 slot.crtc_y_prop,
128 i64::from(surface.y).cast_unsigned(),
129 );
130 add(slot.plane_id, slot.crtc_w_prop, u64::from(surface.width));
131 add(slot.plane_id, slot.crtc_h_prop, u64::from(surface.height));
132 add(slot.plane_id, slot.src_x_prop, 0);
133 add(slot.plane_id, slot.src_y_prop, 0);
134 add(slot.plane_id, slot.src_w_prop, to_16_16(surface.width));
135 add(slot.plane_id, slot.src_h_prop, to_16_16(surface.height));
136
137 // The optional three. A zero id means the plane does not expose the
138 // property, and writing to property zero is an EINVAL that takes the
139 // whole commit down rather than just this plane.
140 if slot.blend_mode_prop != 0 {
141 add(slot.plane_id, slot.blend_mode_prop, slot.blend_mode_value);
142 }
143 if slot.alpha_prop != 0 {
144 // Fully opaque: the decoration's own alpha is in its pixels, and
145 // a per-plane alpha on top would fade the whole thing.
146 add(slot.plane_id, slot.alpha_prop, 0xFFFF);
147 }
148 if slot.zpos_prop != 0 {
149 add(slot.plane_id, slot.zpos_prop, slot.zpos_value);
150 }
151 }
152
153 Ok(writes)
154}
155
156/// Convert a pixel count to KMS's 16.16 fixed point.
157const fn to_16_16(value: u32) -> u64 {
158 (value as u64) << 16
159}