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    /// 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}