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};
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 had the wrong size! Expected {target_size} bytes, \
118                        but it has {actual_size} bytes."
119            );
120        }
121
122        info!(
123            actual_size,
124            name = shared_memory_name,
125            target_size,
126            "Shared memory loaded"
127        );
128        let size_ptr = shared_memory.as_ptr() as *mut u16;
129        unsafe {
130            *size_ptr = width.try_into().context("Framebuffer width too high")?;
131            *size_ptr.add(1) = height.try_into().context("Framebuffer height too high")?;
132        }
133
134        // We need to skip the 4 header bytes
135        let framebuffer_base_ptr = unsafe { shared_memory.as_ptr().add(HEADER_SIZE) };
136        let buffer = unsafe {
137            let data = framebuffer_base_ptr as *const UnsafeCell<u8>;
138            let slice = Pin::new(slice::from_raw_parts(data, framebuffer_bytes));
139            std::mem::transmute::<Pin<&[UnsafeCell<u8>]>, Pin<&'static [UnsafeCell<u8>]>>(slice)
140        };
141
142        Ok(Self {
143            width,
144            height,
145            bytes: framebuffer_bytes,
146            memory: MemoryType::Shared(shared_memory),
147            buffer,
148        })
149    }
150}
151
152impl FrameBuffer for SharedMemoryFrameBuffer {
153    #[inline(always)]
154    fn get_width(&self) -> usize {
155        self.width
156    }
157
158    #[inline(always)]
159    fn get_height(&self) -> usize {
160        self.height
161    }
162
163    #[inline(always)]
164    unsafe fn get_unchecked(&self, x: usize, y: usize) -> u32 {
165        debug_assert!(x < self.width);
166        debug_assert!(y < self.height);
167
168        let offset = (x + y * self.width) * FB_BYTES_PER_PIXEL;
169
170        let base_ptr = self.buffer.as_ptr() as *const u8;
171        let pixel_ptr = unsafe { base_ptr.add(offset) } as *const u32;
172
173        // The buffer coming from the shared memory might be unaligned!
174        unsafe { pixel_ptr.read_unaligned() }
175    }
176
177    #[inline(always)]
178    fn set(&self, x: usize, y: usize, rgba: u32) {
179        // See 'SimpleFrameBuffer::set' for performance consideration
180        if x < self.width && y < self.height {
181            let offset = (x + y * self.width) * FB_BYTES_PER_PIXEL;
182            let pixel_ptr = unsafe { self.buffer.get_unchecked(offset).get() } as *mut u32;
183
184            // The buffer coming from the shared memory might be unaligned!
185            unsafe { pixel_ptr.write_unaligned(rgba) }
186        }
187    }
188
189    #[inline(always)]
190    fn set_multi_from_start_index(&self, starting_index: usize, pixels: &[u8]) -> usize {
191        let num_pixels = pixels.len() / FB_BYTES_PER_PIXEL;
192
193        if starting_index + num_pixels > self.get_size() {
194            debug!(
195                starting_index,
196                num_pixels,
197                buffer_bytes = self.bytes,
198                "Ignoring invalid set_multi call, which would exceed the screen",
199            );
200            // We did not move
201            return 0;
202        }
203
204        let starting_ptr = unsafe {
205            self.buffer
206                .get_unchecked(starting_index * FB_BYTES_PER_PIXEL)
207        }
208        .get();
209        let target_slice = unsafe { slice::from_raw_parts_mut(starting_ptr, pixels.len()) };
210        target_slice.copy_from_slice(pixels);
211
212        num_pixels
213    }
214
215    #[inline(always)]
216    fn as_bytes(&self) -> &[u8] {
217        let base_ptr = self.buffer.as_ptr() as *const u8;
218        unsafe { slice::from_raw_parts(base_ptr as *mut u8, self.bytes) }
219    }
220}