Skip to main content

drmkit_csd/
surface.rs

1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! The buffer a decoration is drawn into.
5//!
6//! Always `ARGB8888`. A decoration is translucent by design — the panel is a
7//! gradient with alpha, the shadow is nothing but alpha — so a format without
8//! an alpha channel cannot represent one. Making it a constant rather than a
9//! parameter means a caller cannot ask for a format the renderer would then
10//! have to refuse.
11
12use drm::control::Device as ControlDevice;
13use drmkit_core::Device;
14use drmkit_dumb::{Buffer, Config, MapAccess, Mapping};
15
16/// How big a decoration surface is.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18#[allow(
19    clippy::exhaustive_structs,
20    reason = "built by callers with `..Default::default()`, which a new field does not break"
21)]
22pub struct SurfaceConfig {
23    /// Width in pixels. Non-zero.
24    pub width: u32,
25    /// Height in pixels. Non-zero.
26    pub height: u32,
27}
28
29/// Why a surface could not be created or used.
30#[derive(Debug, thiserror::Error)]
31#[non_exhaustive]
32pub enum SurfaceError {
33    /// A dimension was zero.
34    #[error("{field} must be non-zero")]
35    Invalid {
36        /// Which one.
37        field: &'static str,
38    },
39
40    /// The allocation failed.
41    #[error("allocating a decoration surface: {0}")]
42    Allocate(#[from] drmkit_dumb::DumbError),
43
44    /// The surface holds no buffer.
45    ///
46    /// A default surface, or one that has been forgotten. Distinct from an
47    /// allocation failure: nothing went wrong, there is simply nothing here.
48    #[error("this surface holds no buffer")]
49    Empty,
50}
51
52/// The one format a decoration is ever drawn in.
53pub const SURFACE_FOURCC: u32 = drmkit_fmt::fourcc::ARGB8888;
54
55/// A decoration's pixels, and the framebuffer they scan out through.
56#[derive(Debug, Default)]
57pub struct Surface {
58    buffer: Option<Buffer>,
59    /// Bumped on every `paint`, so the compositor can tell a decoration that
60    /// moved from one that was redrawn.
61    ///
62    /// Without it, a decoration at the same place with different pixels looks
63    /// unchanged to [`compute_damage`](crate::compute_damage) and is never
64    /// recomposited.
65    generation: u64,
66}
67
68impl Surface {
69    /// Allocate a decoration surface.
70    ///
71    /// # Errors
72    ///
73    /// [`SurfaceError::Invalid`] for a zero dimension — refused here rather
74    /// than passed to the kernel, which reports the same thing less clearly —
75    /// and [`SurfaceError::Allocate`] if the driver refuses it.
76    pub fn create(device: &Device, config: SurfaceConfig) -> Result<Self, SurfaceError> {
77        if config.width == 0 {
78            return Err(SurfaceError::Invalid { field: "width" });
79        }
80        if config.height == 0 {
81            return Err(SurfaceError::Invalid { field: "height" });
82        }
83
84        let buffer = Buffer::create(
85            device,
86            &Config {
87                width: config.width,
88                height: config.height,
89                fourcc: SURFACE_FOURCC,
90                ..Config::default()
91            },
92        )?;
93
94        Ok(Self {
95            buffer: Some(buffer),
96            generation: 0,
97        })
98    }
99
100    /// Whether this surface holds no buffer.
101    #[must_use]
102    pub const fn is_empty(&self) -> bool {
103        self.buffer.is_none()
104    }
105
106    /// The framebuffer id, or zero when empty.
107    ///
108    /// Zero rather than an error because that is what a presenter writes to
109    /// disarm a plane — an empty surface and a disarmed slot are the same
110    /// thing said twice.
111    #[must_use]
112    pub fn fb_id(&self) -> u32 {
113        self.buffer.as_ref().and_then(Buffer::fb_id).unwrap_or(0)
114    }
115
116    /// Width in pixels, or zero when empty.
117    #[must_use]
118    pub fn width(&self) -> u32 {
119        self.buffer.as_ref().map_or(0, Buffer::width)
120    }
121
122    /// Height in pixels, or zero when empty.
123    #[must_use]
124    pub fn height(&self) -> u32 {
125        self.buffer.as_ref().map_or(0, Buffer::height)
126    }
127
128    /// Bytes between rows, or zero when empty.
129    #[must_use]
130    pub fn stride(&self) -> u32 {
131        self.buffer.as_ref().map_or(0, Buffer::stride)
132    }
133
134    /// Always [`SURFACE_FOURCC`], empty or not.
135    ///
136    /// A constant rather than a stored field: an empty surface still answers,
137    /// because a caller laying out a decoration it has not allocated yet
138    /// needs to know what it will be.
139    #[must_use]
140    pub const fn format(&self) -> u32 {
141        SURFACE_FOURCC
142    }
143
144    /// How many times this surface has been painted.
145    #[must_use]
146    pub const fn generation(&self) -> u64 {
147        self.generation
148    }
149
150    /// Map the pixels for drawing, and count the paint.
151    ///
152    /// The generation is bumped whether or not anything is actually written:
153    /// a caller that mapped for writing intends to, and under-reporting a
154    /// change leaves a stale decoration on screen where over-reporting only
155    /// costs a recomposite.
156    ///
157    /// # Errors
158    ///
159    /// [`SurfaceError::Empty`] when there is no buffer.
160    pub fn paint(&mut self, access: MapAccess) -> Result<Mapping<'_>, SurfaceError> {
161        let buffer = self.buffer.as_mut().ok_or(SurfaceError::Empty)?;
162        if !matches!(access, MapAccess::Read) {
163            self.generation = self.generation.wrapping_add(1);
164        }
165        Ok(buffer.map(access))
166    }
167
168    /// Export the buffer as a DMA-BUF.
169    ///
170    /// # Errors
171    ///
172    /// [`SurfaceError::Empty`] when there is no buffer, and whatever the
173    /// export failed with otherwise.
174    pub fn export(&self, device: &Device) -> Result<std::os::fd::OwnedFd, SurfaceError> {
175        let buffer = self.buffer.as_ref().ok_or(SurfaceError::Empty)?;
176        let handle = buffer
177            .gem_handle()
178            .and_then(drm::control::from_u32)
179            .ok_or(SurfaceError::Empty)?;
180        device
181            .buffer_to_prime_fd(handle, 0)
182            .map_err(|_| SurfaceError::Empty)
183    }
184
185    /// Drop the buffer without issuing ioctls against its descriptor.
186    ///
187    /// For a session that lost its device: the handles are already dead, so
188    /// destroying them would fail, and the kernel reclaims them when the
189    /// descriptor closes. Forgetting an already-empty surface is a no-op,
190    /// which is what a caller tearing down twice does.
191    pub fn forget(&mut self) {
192        if let Some(mut buffer) = self.buffer.take() {
193            buffer.forget();
194        }
195    }
196}