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 /// The kernel refused to export the buffer as a DMA-BUF. Carries its
52 /// errno, as the reference reports it.
53 #[error("exporting a decoration surface: {0}")]
54 Export(rustix::io::Errno),
55}
56
57/// The one format a decoration is ever drawn in.
58pub const SURFACE_FOURCC: u32 = drmkit_fmt::fourcc::ARGB8888;
59
60/// A decoration's pixels, and the framebuffer they scan out through.
61#[derive(Debug, Default)]
62pub struct Surface {
63 buffer: Option<Buffer>,
64 /// Bumped on every `paint` except a read-only ([`MapAccess::Read`]) one,
65 /// so the compositor can tell a decoration that moved from one that was
66 /// redrawn.
67 ///
68 /// Without it, a decoration at the same place with different pixels looks
69 /// unchanged to [`compute_damage`](crate::compute_damage) and is never
70 /// recomposited.
71 generation: u64,
72}
73
74impl Surface {
75 /// Allocate a decoration surface.
76 ///
77 /// # Errors
78 ///
79 /// [`SurfaceError::Invalid`] for a zero dimension — refused here rather
80 /// than passed to the kernel, which reports the same thing less clearly —
81 /// and [`SurfaceError::Allocate`] if the driver refuses it.
82 pub fn create(device: &Device, config: SurfaceConfig) -> Result<Self, SurfaceError> {
83 if config.width == 0 {
84 return Err(SurfaceError::Invalid { field: "width" });
85 }
86 if config.height == 0 {
87 return Err(SurfaceError::Invalid { field: "height" });
88 }
89
90 let buffer = Buffer::create(
91 device,
92 &Config {
93 width: config.width,
94 height: config.height,
95 fourcc: SURFACE_FOURCC,
96 ..Config::default()
97 },
98 )?;
99
100 Ok(Self {
101 buffer: Some(buffer),
102 generation: 0,
103 })
104 }
105
106 /// Whether this surface holds no buffer.
107 #[must_use]
108 pub const fn is_empty(&self) -> bool {
109 self.buffer.is_none()
110 }
111
112 /// The framebuffer id, or zero when empty.
113 ///
114 /// Zero rather than an error because that is what a presenter writes to
115 /// disarm a plane — an empty surface and a disarmed slot are the same
116 /// thing said twice.
117 #[must_use]
118 pub fn fb_id(&self) -> u32 {
119 self.buffer.as_ref().and_then(Buffer::fb_id).unwrap_or(0)
120 }
121
122 /// Width in pixels, or zero when empty.
123 #[must_use]
124 pub fn width(&self) -> u32 {
125 self.buffer.as_ref().map_or(0, Buffer::width)
126 }
127
128 /// Height in pixels, or zero when empty.
129 #[must_use]
130 pub fn height(&self) -> u32 {
131 self.buffer.as_ref().map_or(0, Buffer::height)
132 }
133
134 /// Bytes between rows, or zero when empty.
135 #[must_use]
136 pub fn stride(&self) -> u32 {
137 self.buffer.as_ref().map_or(0, Buffer::stride)
138 }
139
140 /// Always [`SURFACE_FOURCC`], empty or not.
141 ///
142 /// A constant rather than a stored field: an empty surface still answers,
143 /// because a caller laying out a decoration it has not allocated yet
144 /// needs to know what it will be.
145 #[must_use]
146 pub const fn format(&self) -> u32 {
147 SURFACE_FOURCC
148 }
149
150 /// How many times this surface has been painted — mapped by
151 /// [`paint`](Self::paint) for anything other than [`MapAccess::Read`].
152 #[must_use]
153 pub const fn generation(&self) -> u64 {
154 self.generation
155 }
156
157 /// Map the pixels for drawing, and count the paint.
158 ///
159 /// The generation is bumped for any map that allows writing, whether or
160 /// not anything is actually written: a caller that mapped for writing
161 /// intends to, and under-reporting a change leaves a stale decoration on
162 /// screen where over-reporting only costs a recomposite. A read-only
163 /// ([`MapAccess::Read`]) map does not bump it.
164 ///
165 /// # Errors
166 ///
167 /// [`SurfaceError::Empty`] when there is no buffer.
168 pub fn paint(&mut self, access: MapAccess) -> Result<Mapping<'_>, SurfaceError> {
169 let buffer = self.buffer.as_mut().ok_or(SurfaceError::Empty)?;
170 if !matches!(access, MapAccess::Read) {
171 self.generation = self.generation.wrapping_add(1);
172 }
173 Ok(buffer.map(access))
174 }
175
176 /// Export the buffer as a DMA-BUF.
177 ///
178 /// The descriptor is close-on-exec, as the reference exports it, so a
179 /// compositor that spawns a client does not hand it the decoration.
180 ///
181 /// # Errors
182 ///
183 /// [`SurfaceError::Empty`] when there is no buffer, and
184 /// [`SurfaceError::Export`] with the kernel's errno when the export is
185 /// refused.
186 pub fn export(&self, device: &Device) -> Result<std::os::fd::OwnedFd, SurfaceError> {
187 let buffer = self.buffer.as_ref().ok_or(SurfaceError::Empty)?;
188 let handle = buffer
189 .gem_handle()
190 .and_then(drm::control::from_u32)
191 .ok_or(SurfaceError::Empty)?;
192 device
193 .buffer_to_prime_fd(handle, rustix::fs::OFlags::CLOEXEC.bits())
194 .map_err(|error| {
195 SurfaceError::Export(
196 rustix::io::Errno::from_io_error(&error).unwrap_or(rustix::io::Errno::IO),
197 )
198 })
199 }
200
201 /// Drop the buffer without issuing ioctls against its descriptor.
202 ///
203 /// For a session that lost its device: the handles are already dead, so
204 /// destroying them would fail, and the kernel reclaims them when the
205 /// descriptor closes. Forgetting an already-empty surface is a no-op,
206 /// which is what a caller tearing down twice does.
207 pub fn forget(&mut self) {
208 if let Some(mut buffer) = self.buffer.take() {
209 buffer.forget();
210 }
211 }
212}