breakwater_parser/framebuffer/
shared_memory.rs1use 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
11const 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 #[allow(unused)]
25 memory: MemoryType,
26
27 buffer: Pin<&'static [UnsafeCell<u8>]>,
30}
31
32#[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 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 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 unsafe { pixel_ptr.read_unaligned() }
180 }
181
182 #[inline(always)]
183 fn set(&self, x: usize, y: usize, rgba: u32) {
184 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 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 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}