breakwater_parser/framebuffer/
shared_memory.rs

1use core::slice;
2use std::{cell::UnsafeCell, pin::Pin};
3
4use color_eyre::eyre::{self, Context, bail};
5use shared_memory::{Shmem, ShmemConf, ShmemError};
6use tracing::{debug, info, instrument, warn};
7
8use super::FrameBuffer;
9use crate::framebuffer::FB_BYTES_PER_PIXEL;
10
11// Width and height, both of type u16.
12const HEADER_SIZE: usize = 2 * std::mem::size_of::<u16>();
13
14unsafe impl Send for SharedMemoryFrameBuffer {}
15unsafe impl Sync for SharedMemoryFrameBuffer {}
16
17pub struct SharedMemoryFrameBuffer {
18    width: usize,
19    height: usize,
20
21    bytes: usize,
22
23    // This owns the memory, but is never accessed
24    #[allow(unused)]
25    memory: MemoryType,
26
27    // This is a reference to the owned memory
28    // Safety: valid as long as memory won`t change/move/...
29    buffer: Pin<&'static [UnsafeCell<u8>]>,
30}
31
32// This owns the memory, but is never accessed
33#[allow(unused)]
34enum MemoryType {
35    Shared(Shmem),
36    Local(Pin<Box<[UnsafeCell<u8>]>>),
37}
38
39impl SharedMemoryFrameBuffer {
40    #[instrument]
41    pub fn new(
42        width: usize,
43        height: usize,
44        shared_memory_name: Option<&str>,
45    ) -> eyre::Result<Self> {
46        match shared_memory_name {
47            Some(shared_memory_name) => {
48                Self::new_from_shared_memory(width, height, shared_memory_name)
49            }
50            None => Self::new_with_local_memory(width, height),
51        }
52    }
53
54    #[instrument(skip_all)]
55    fn new_with_local_memory(width: usize, height: usize) -> eyre::Result<Self> {
56        let pixels = width * height;
57        let bytes = pixels * FB_BYTES_PER_PIXEL;
58
59        debug!("Using plain (non shared memory) framebuffer");
60
61        let memory: Pin<Box<[UnsafeCell<u8>]>> = Pin::new(
62            (0..(bytes))
63                .map(|_| UnsafeCell::new(0u8))
64                .collect::<Vec<_>>()
65                .into_boxed_slice(),
66        );
67        let buffer = unsafe {
68            std::mem::transmute::<Pin<&[UnsafeCell<u8>]>, Pin<&'static [UnsafeCell<u8>]>>(
69                memory.as_ref(),
70            )
71        };
72
73        Ok(Self {
74            width,
75            height,
76            bytes,
77            memory: MemoryType::Local(memory),
78            buffer,
79        })
80    }
81
82    #[instrument(skip_all)]
83    fn new_from_shared_memory(
84        width: usize,
85        height: usize,
86        shared_memory_name: &str,
87    ) -> eyre::Result<Self> {
88        let pixels = width * height;
89        let framebuffer_bytes = pixels * FB_BYTES_PER_PIXEL;
90        let target_size = HEADER_SIZE + framebuffer_bytes;
91
92        let mut shared_memory = match ShmemConf::new()
93            .os_id(shared_memory_name)
94            .size(target_size)
95            .create()
96        {
97            Ok(shared_memory) => shared_memory,
98            Err(ShmemError::LinkExists | ShmemError::MappingIdExists) => ShmemConf::new()
99                .os_id(shared_memory_name)
100                .open()
101                .with_context(|| {
102                    format!("failed to open existing shared memory \"{shared_memory_name}\"")
103                })?,
104            Err(err) => Err(err).with_context(|| {
105                format!("failed to create shared memory \"{shared_memory_name}\"")
106            })?,
107        };
108
109        // In case we crate the shared memory we are the owner. In that case `shared_memory` will
110        // delete the shared memory on `drop`. As we want to persist the framebuffer across
111        // restarts, we set the owner to false.
112        shared_memory.set_owner(false);
113
114        let actual_size = shared_memory.len();
115        if actual_size < target_size {
116            bail!(
117                "The shared memory is too small! Expected at least {target_size} bytes, \
118                        but it has {actual_size} bytes instead."
119            );
120        } else if actual_size > target_size {
121            warn!(
122                "The shared memory is too big! Expected at maximum {target_size} bytes, \
123                        but it has {actual_size} bytes instead."
124            );
125        }
126
127        info!(
128            actual_size,
129            name = shared_memory_name,
130            target_size,
131            "Shared memory loaded"
132        );
133        let size_ptr = shared_memory.as_ptr() as *mut u16;
134        unsafe {
135            *size_ptr = width.try_into().context("Framebuffer width too high")?;
136            *size_ptr.add(1) = height.try_into().context("Framebuffer height too high")?;
137        }
138
139        // We need to skip the 4 header bytes
140        let framebuffer_base_ptr = unsafe { shared_memory.as_ptr().add(HEADER_SIZE) };
141        let buffer = unsafe {
142            let data = framebuffer_base_ptr as *const UnsafeCell<u8>;
143            let slice = Pin::new(slice::from_raw_parts(data, framebuffer_bytes));
144            std::mem::transmute::<Pin<&[UnsafeCell<u8>]>, Pin<&'static [UnsafeCell<u8>]>>(slice)
145        };
146
147        Ok(Self {
148            width,
149            height,
150            bytes: framebuffer_bytes,
151            memory: MemoryType::Shared(shared_memory),
152            buffer,
153        })
154    }
155}
156
157impl FrameBuffer for SharedMemoryFrameBuffer {
158    #[inline(always)]
159    fn get_width(&self) -> usize {
160        self.width
161    }
162
163    #[inline(always)]
164    fn get_height(&self) -> usize {
165        self.height
166    }
167
168    #[inline(always)]
169    unsafe fn get_unchecked(&self, x: usize, y: usize) -> u32 {
170        debug_assert!(x < self.width);
171        debug_assert!(y < self.height);
172
173        let offset = (x + y * self.width) * FB_BYTES_PER_PIXEL;
174
175        let base_ptr = self.buffer.as_ptr() as *const u8;
176        let pixel_ptr = unsafe { base_ptr.add(offset) } as *const u32;
177
178        // The buffer coming from the shared memory might be unaligned!
179        unsafe { pixel_ptr.read_unaligned() }
180    }
181
182    #[inline(always)]
183    fn set(&self, x: usize, y: usize, rgba: u32) {
184        // See 'SimpleFrameBuffer::set' for performance consideration
185        if x < self.width && y < self.height {
186            let offset = (x + y * self.width) * FB_BYTES_PER_PIXEL;
187            let pixel_ptr = unsafe { self.buffer.get_unchecked(offset).get() } as *mut u32;
188
189            // The buffer coming from the shared memory might be unaligned!
190            unsafe { pixel_ptr.write_unaligned(rgba) }
191        }
192    }
193
194    #[inline(always)]
195    fn set_multi_from_start_index(&self, starting_index: usize, pixels: &[u8]) -> usize {
196        let num_pixels = pixels.len() / FB_BYTES_PER_PIXEL;
197
198        if starting_index + num_pixels > self.get_size() {
199            debug!(
200                starting_index,
201                num_pixels,
202                buffer_bytes = self.bytes,
203                "Ignoring invalid set_multi call, which would exceed the screen",
204            );
205            // We did not move
206            return 0;
207        }
208
209        let starting_ptr = unsafe {
210            self.buffer
211                .get_unchecked(starting_index * FB_BYTES_PER_PIXEL)
212        }
213        .get();
214        let target_slice = unsafe { slice::from_raw_parts_mut(starting_ptr, pixels.len()) };
215        target_slice.copy_from_slice(pixels);
216
217        num_pixels
218    }
219
220    #[inline(always)]
221    fn as_bytes(&self) -> &[u8] {
222        let base_ptr = self.buffer.as_ptr() as *const u8;
223        unsafe { slice::from_raw_parts(base_ptr as *mut u8, self.bytes) }
224    }
225}