smithay_client_toolkit/shm/
slot.rs

1//! A pool implementation based on buffer slots
2
3use std::io;
4use std::{
5    os::unix::io::{AsRawFd, OwnedFd},
6    sync::{
7        atomic::{AtomicU8, AtomicUsize, Ordering},
8        Arc, Mutex, Weak,
9    },
10};
11
12use wayland_client::{
13    protocol::{wl_buffer, wl_shm, wl_surface},
14    Proxy,
15};
16
17use crate::{globals::ProvidesBoundGlobal, shm::raw::RawPool, shm::CreatePoolError};
18
19#[derive(Debug, thiserror::Error)]
20pub enum CreateBufferError {
21    /// Slot creation error.
22    #[error(transparent)]
23    Io(#[from] io::Error),
24
25    /// Pool mismatch.
26    #[error("Incorrect pool for slot")]
27    PoolMismatch,
28
29    /// Slot size mismatch
30    #[error("Requested buffer size is too large for slot")]
31    SlotTooSmall,
32}
33
34#[derive(Debug, thiserror::Error)]
35pub enum ActivateSlotError {
36    /// Buffer was already active
37    #[error("Buffer was already active")]
38    AlreadyActive,
39}
40
41#[derive(Debug)]
42pub struct SlotPool {
43    pub(crate) inner: RawPool,
44    free_list: Arc<Mutex<Vec<FreelistEntry>>>,
45}
46
47#[derive(Debug)]
48struct FreelistEntry {
49    offset: usize,
50    len: usize,
51}
52
53/// A chunk of memory allocated from a [SlotPool]
54///
55/// Retaining this object is only required if you wish to resize or change the buffer's format
56/// without changing the contents of the backing memory.
57#[derive(Debug)]
58pub struct Slot {
59    inner: Arc<SlotInner>,
60}
61
62#[derive(Debug)]
63struct SlotInner {
64    free_list: Weak<Mutex<Vec<FreelistEntry>>>,
65    offset: usize,
66    len: usize,
67    active_buffers: AtomicUsize,
68    /// Count of all "real" references to this slot.  This includes all Slot objects and any
69    /// BufferData object that is not in the DEAD state.  When this reaches zero, the memory for
70    /// this slot will return to the free_list.  It is not possible for it to reach zero and have a
71    /// Slot or Buffer referring to it.
72    all_refs: AtomicUsize,
73}
74
75/// A wrapper around a [`wl_buffer::WlBuffer`] which has been allocated via a [SlotPool].
76///
77/// When this object is dropped, the buffer will be destroyed immediately if it is not active, or
78/// upon the server's release if it is.
79#[derive(Debug)]
80pub struct Buffer {
81    buffer: wl_buffer::WlBuffer,
82    height: i32,
83    stride: i32,
84    slot: Slot,
85}
86
87/// ObjectData for the WlBuffer
88#[derive(Debug)]
89struct BufferData {
90    inner: Arc<SlotInner>,
91    state: AtomicU8,
92}
93
94// These constants define the value of BufferData::state, since AtomicEnum does not exist.
95impl BufferData {
96    /// Buffer is counted in active_buffers list; will return to INACTIVE on Release.
97    const ACTIVE: u8 = 0;
98
99    /// Buffer is not counted in active_buffers list, but also has not been destroyed.
100    const INACTIVE: u8 = 1;
101
102    /// Buffer is counted in active_buffers list; will move to DEAD on Release
103    const DESTROY_ON_RELEASE: u8 = 2;
104
105    /// Buffer has been destroyed
106    const DEAD: u8 = 3;
107
108    /// Value that is ORed on buffer release to transition to the next state
109    const RELEASE_SET: u8 = 1;
110
111    /// Value that is ORed on buffer destroy to transition to the next state
112    const DESTROY_SET: u8 = 2;
113
114    /// Call after successfully transitioning the state to DEAD
115    fn record_death(&self) {
116        drop(Slot { inner: self.inner.clone() })
117    }
118}
119
120impl SlotPool {
121    pub fn new(
122        len: usize,
123        shm: &impl ProvidesBoundGlobal<wl_shm::WlShm, 1>,
124    ) -> Result<Self, CreatePoolError> {
125        let inner = RawPool::new(len, shm)?;
126        let free_list = Arc::new(Mutex::new(vec![FreelistEntry { offset: 0, len: inner.len() }]));
127        Ok(SlotPool { inner, free_list })
128    }
129
130    /// Create a new buffer in a new slot.
131    ///
132    /// This returns the buffer and the canvas.  The parameters are:
133    ///
134    /// - `width`: the width of this buffer (in pixels)
135    /// - `height`: the height of this buffer (in pixels)
136    /// - `stride`: distance (in bytes) between the beginning of a row and the next one
137    /// - `format`: the encoding format of the pixels. Using a format that was not
138    ///   advertised to the `wl_shm` global by the server is a protocol error and will
139    ///   terminate your connection.
140    ///
141    /// The [Slot] for this buffer will have exactly the size required for the data.  It can be
142    /// accessed via [Buffer::slot] to create additional buffers that point to the same data.  This
143    /// is required if you wish to change formats, buffer dimensions, or attach a canvas to
144    /// multiple surfaces.
145    ///
146    /// For more control over sizing, use [Self::new_slot] and [Self::create_buffer_in].
147    pub fn create_buffer(
148        &mut self,
149        width: i32,
150        height: i32,
151        stride: i32,
152        format: wl_shm::Format,
153    ) -> Result<(Buffer, &mut [u8]), CreateBufferError> {
154        let len = (height as usize) * (stride as usize);
155        let slot = self.new_slot(len)?;
156        let buffer = self.create_buffer_in(&slot, width, height, stride, format)?;
157        let canvas = self.raw_data_mut(&slot);
158        Ok((buffer, canvas))
159    }
160
161    /// Get the bytes corresponding to a given slot or buffer if drawing to the slot is permitted.
162    ///
163    /// Returns `None` if there are active buffers in the slot or if the slot does not correspond
164    /// to this pool.
165    pub fn canvas(&mut self, key: &impl CanvasKey) -> Option<&mut [u8]> {
166        key.canvas(self)
167    }
168
169    /// Returns the size, in bytes, of this pool.
170    #[allow(clippy::len_without_is_empty)]
171    pub fn len(&self) -> usize {
172        self.inner.len()
173    }
174
175    /// Resizes the memory pool, notifying the server the pool has changed in size.
176    ///
177    /// This is an optimization; the pool automatically resizes when you allocate new slots.
178    pub fn resize(&mut self, size: usize) -> io::Result<()> {
179        let old_len = self.inner.len();
180        self.inner.resize(size)?;
181        let new_len = self.inner.len();
182        if old_len == new_len {
183            return Ok(());
184        }
185        // add the new memory to the freelist
186        let mut free = self.free_list.lock().unwrap();
187        if let Some(FreelistEntry { offset, len }) = free.last_mut() {
188            if *offset + *len == old_len {
189                *len += new_len - old_len;
190                return Ok(());
191            }
192        }
193        free.push(FreelistEntry { offset: old_len, len: new_len - old_len });
194        Ok(())
195    }
196
197    fn alloc(&mut self, size: usize) -> io::Result<usize> {
198        let mut free = self.free_list.lock().unwrap();
199        for FreelistEntry { offset, len } in free.iter_mut() {
200            if *len >= size {
201                let rv = *offset;
202                *len -= size;
203                *offset += size;
204                return Ok(rv);
205            }
206        }
207        let mut rv = self.inner.len();
208        let mut pop_tail = false;
209        if let Some(FreelistEntry { offset, len }) = free.last() {
210            if offset + len == self.inner.len() {
211                rv -= len;
212                pop_tail = true;
213            }
214        }
215        // resize like Vec::reserve, always at least doubling
216        let target = std::cmp::max(rv + size, self.inner.len() * 2);
217        self.inner.resize(target)?;
218        // adjust the end of the freelist here
219        if pop_tail {
220            free.pop();
221        }
222        if target > rv + size {
223            free.push(FreelistEntry { offset: rv + size, len: target - rv - size });
224        }
225        Ok(rv)
226    }
227
228    fn free(free_list: &Mutex<Vec<FreelistEntry>>, mut offset: usize, mut len: usize) {
229        let mut free = free_list.lock().unwrap();
230        let mut nf = Vec::with_capacity(free.len() + 1);
231        for &FreelistEntry { offset: ioff, len: ilen } in free.iter() {
232            if ioff + ilen == offset {
233                offset = ioff;
234                len += ilen;
235                continue;
236            }
237            if ioff == offset + len {
238                len += ilen;
239                continue;
240            }
241            if ioff > offset + len && len != 0 {
242                nf.push(FreelistEntry { offset, len });
243                len = 0;
244            }
245            if ilen != 0 {
246                nf.push(FreelistEntry { offset: ioff, len: ilen });
247            }
248        }
249        if len != 0 {
250            nf.push(FreelistEntry { offset, len });
251        }
252        *free = nf;
253    }
254
255    /// Create a new slot with the given size in bytes.
256    pub fn new_slot(&mut self, mut len: usize) -> io::Result<Slot> {
257        len = (len + 63) & !63;
258        let offset = self.alloc(len)?;
259
260        Ok(Slot {
261            inner: Arc::new(SlotInner {
262                free_list: Arc::downgrade(&self.free_list),
263                offset,
264                len,
265                active_buffers: AtomicUsize::new(0),
266                all_refs: AtomicUsize::new(1),
267            }),
268        })
269    }
270
271    /// Get the bytes corresponding to a given slot.
272    ///
273    /// Note: prefer using [Self::canvas], which will prevent drawing to a buffer that has not been
274    /// released by the server.
275    ///
276    /// Returns an empty buffer if the slot does not belong to this pool.
277    pub fn raw_data_mut(&mut self, slot: &Slot) -> &mut [u8] {
278        if slot.inner.free_list.as_ptr() == Arc::as_ptr(&self.free_list) {
279            &mut self.inner.mmap()[slot.inner.offset..][..slot.inner.len]
280        } else {
281            &mut []
282        }
283    }
284
285    /// Create a new buffer corresponding to a slot.
286    ///
287    /// The parameters are:
288    ///
289    /// - `width`: the width of this buffer (in pixels)
290    /// - `height`: the height of this buffer (in pixels)
291    /// - `stride`: distance (in bytes) between the beginning of a row and the next one
292    /// - `format`: the encoding format of the pixels. Using a format that was not
293    ///   advertised to the `wl_shm` global by the server is a protocol error and will
294    ///   terminate your connection
295    pub fn create_buffer_in(
296        &mut self,
297        slot: &Slot,
298        width: i32,
299        height: i32,
300        stride: i32,
301        format: wl_shm::Format,
302    ) -> Result<Buffer, CreateBufferError> {
303        let offset = slot.inner.offset as i32;
304        let len = (height as usize) * (stride as usize);
305        if len > slot.inner.len {
306            return Err(CreateBufferError::SlotTooSmall);
307        }
308
309        if slot.inner.free_list.as_ptr() != Arc::as_ptr(&self.free_list) {
310            return Err(CreateBufferError::PoolMismatch);
311        }
312
313        let slot = slot.clone();
314        // take a ref for the BufferData, which will be destroyed by BufferData::record_death
315        slot.inner.all_refs.fetch_add(1, Ordering::Relaxed);
316        let data = Arc::new(BufferData {
317            inner: slot.inner.clone(),
318            state: AtomicU8::new(BufferData::INACTIVE),
319        });
320        let buffer = self.inner.create_buffer_raw(offset, width, height, stride, format, data);
321        Ok(Buffer { buffer, height, stride, slot })
322    }
323}
324
325impl Clone for Slot {
326    fn clone(&self) -> Self {
327        let inner = self.inner.clone();
328        inner.all_refs.fetch_add(1, Ordering::Relaxed);
329        Slot { inner }
330    }
331}
332
333impl Drop for Slot {
334    fn drop(&mut self) {
335        if self.inner.all_refs.fetch_sub(1, Ordering::Relaxed) == 1 {
336            if let Some(free_list) = self.inner.free_list.upgrade() {
337                SlotPool::free(&free_list, self.inner.offset, self.inner.len);
338            }
339        }
340    }
341}
342
343impl Drop for SlotInner {
344    fn drop(&mut self) {
345        debug_assert_eq!(*self.all_refs.get_mut(), 0);
346    }
347}
348
349/// A helper trait for [SlotPool::canvas].
350pub trait CanvasKey {
351    fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]>;
352}
353
354impl Slot {
355    /// Return true if there are buffers referencing this slot whose contents are being accessed
356    /// by the server.
357    pub fn has_active_buffers(&self) -> bool {
358        self.inner.active_buffers.load(Ordering::Relaxed) != 0
359    }
360
361    /// Returns the size, in bytes, of this slot.
362    #[allow(clippy::len_without_is_empty)]
363    pub fn len(&self) -> usize {
364        self.inner.len
365    }
366
367    /// Get the bytes corresponding to a given slot if drawing to the slot is permitted.
368    ///
369    /// Returns `None` if there are active buffers in the slot or if the slot does not correspond
370    /// to this pool.
371    pub fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
372        if self.has_active_buffers() {
373            return None;
374        }
375        if self.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) {
376            Some(&mut pool.inner.mmap()[self.inner.offset..][..self.inner.len])
377        } else {
378            None
379        }
380    }
381}
382
383impl CanvasKey for Slot {
384    fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
385        self.canvas(pool)
386    }
387}
388
389impl Buffer {
390    /// Attach a buffer to a surface.
391    ///
392    /// This marks the slot as active until the server releases the buffer, which will happen
393    /// automatically assuming the surface is committed without attaching a different buffer.
394    ///
395    /// Note: if you need to ensure that [`canvas()`](Buffer::canvas) calls never return data that
396    /// could be attached to a surface in a multi-threaded client, make this call while you have
397    /// exclusive access to the corresponding [`SlotPool`].
398    pub fn attach_to(&self, surface: &wl_surface::WlSurface) -> Result<(), ActivateSlotError> {
399        self.activate()?;
400        surface.attach(Some(&self.buffer), 0, 0);
401        Ok(())
402    }
403
404    /// Get the inner buffer.
405    pub fn wl_buffer(&self) -> &wl_buffer::WlBuffer {
406        &self.buffer
407    }
408
409    pub fn height(&self) -> i32 {
410        self.height
411    }
412
413    pub fn stride(&self) -> i32 {
414        self.stride
415    }
416
417    fn data(&self) -> Option<&BufferData> {
418        self.buffer.object_data()?.downcast_ref()
419    }
420
421    /// Get the bytes corresponding to this buffer if drawing is permitted.
422    ///
423    /// This may be smaller than the canvas associated with the slot.
424    pub fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
425        let len = (self.height as usize) * (self.stride as usize);
426        if self.slot.inner.active_buffers.load(Ordering::Relaxed) != 0 {
427            return None;
428        }
429        if self.slot.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) {
430            Some(&mut pool.inner.mmap()[self.slot.inner.offset..][..len])
431        } else {
432            None
433        }
434    }
435
436    /// Get the slot corresponding to this buffer.
437    pub fn slot(&self) -> Slot {
438        self.slot.clone()
439    }
440
441    /// Manually mark a buffer as active.
442    ///
443    /// An active buffer prevents drawing on its slot until a Release event is received or until
444    /// manually deactivated.
445    pub fn activate(&self) -> Result<(), ActivateSlotError> {
446        let data = self.data().expect("UserData type mismatch");
447
448        // This bitwise AND will transition INACTIVE -> ACTIVE, or do nothing if the buffer was
449        // already ACTIVE.  No other ordering is required, as the server will not send a Release
450        // until we send our attach after returning Ok.
451        match data.state.fetch_and(!BufferData::RELEASE_SET, Ordering::Relaxed) {
452            BufferData::INACTIVE => {
453                data.inner.active_buffers.fetch_add(1, Ordering::Relaxed);
454                Ok(())
455            }
456            BufferData::ACTIVE => Err(ActivateSlotError::AlreadyActive),
457            _ => unreachable!("Invalid state in BufferData"),
458        }
459    }
460
461    /// Manually mark a buffer as inactive.
462    ///
463    /// This should be used when the buffer was manually marked as active or when a buffer was
464    /// attached to a surface but not committed.  Calling this function on a buffer that was
465    /// committed to a surface risks making the surface contents undefined.
466    pub fn deactivate(&self) -> Result<(), ActivateSlotError> {
467        let data = self.data().expect("UserData type mismatch");
468
469        // Same operation as the Release event, but we know the Buffer was not dropped.
470        match data.state.fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed) {
471            BufferData::ACTIVE => {
472                data.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
473                Ok(())
474            }
475            BufferData::INACTIVE => Err(ActivateSlotError::AlreadyActive),
476            _ => unreachable!("Invalid state in BufferData"),
477        }
478    }
479}
480
481impl CanvasKey for Buffer {
482    fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
483        self.canvas(pool)
484    }
485}
486
487impl Drop for Buffer {
488    fn drop(&mut self) {
489        if let Some(data) = self.data() {
490            match data.state.fetch_or(BufferData::DESTROY_SET, Ordering::Relaxed) {
491                BufferData::ACTIVE => {
492                    // server is using the buffer, let ObjectData handle the destroy
493                }
494                BufferData::INACTIVE => {
495                    data.record_death();
496                    self.buffer.destroy();
497                }
498                _ => unreachable!("Invalid state in BufferData"),
499            }
500        }
501    }
502}
503
504impl wayland_client::backend::ObjectData for BufferData {
505    fn event(
506        self: Arc<Self>,
507        handle: &wayland_client::backend::Backend,
508        msg: wayland_backend::protocol::Message<wayland_backend::client::ObjectId, OwnedFd>,
509    ) -> Option<Arc<dyn wayland_backend::client::ObjectData>> {
510        debug_assert!(wayland_client::backend::protocol::same_interface(
511            msg.sender_id.interface(),
512            wl_buffer::WlBuffer::interface()
513        ));
514        debug_assert!(msg.opcode == 0);
515
516        match self.state.fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed) {
517            BufferData::ACTIVE => {
518                self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
519            }
520            BufferData::INACTIVE => {
521                // possible spurious release, or someone called deactivate incorrectly
522                log::debug!("Unexpected WlBuffer::Release on an inactive buffer");
523            }
524            BufferData::DESTROY_ON_RELEASE => {
525                self.record_death();
526                self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
527
528                // The Destroy message is identical to Release message (no args, same ID), so just reply
529                handle
530                    .send_request(msg.map_fd(|x| x.as_raw_fd()), None, None)
531                    .expect("Unexpected invalid ID");
532            }
533            BufferData::DEAD => {
534                // no-op, this object is already unusable
535            }
536            _ => unreachable!("Invalid state in BufferData"),
537        }
538
539        None
540    }
541
542    fn destroyed(&self, _: wayland_backend::client::ObjectId) {}
543}
544
545impl Drop for BufferData {
546    fn drop(&mut self) {
547        let state = *self.state.get_mut();
548        if state == BufferData::ACTIVE || state == BufferData::DESTROY_ON_RELEASE {
549            // Release the active-buffer count
550            self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
551        }
552
553        if state != BufferData::DEAD {
554            // nobody has ever transitioned state to DEAD, so we are responsible for freeing the
555            // extra reference
556            self.record_death();
557        }
558    }
559}