Skip to main content

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}