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}