1use std::{
2 error::Error,
3 fmt::{Display, Formatter},
4 fs::{write, OpenOptions},
5 mem::replace,
6 sync::mpsc::{
7 channel, sync_channel, Receiver, RecvTimeoutError, Sender, SyncSender, TryRecvError,
8 },
9 thread::{spawn, JoinHandle},
10 time::Duration,
11};
12
13use thread_priority::{set_current_thread_priority, ThreadPriority};
14
15use crate::{
16 canvas::{Canvas, PixelDesignator, PixelDesignatorMap},
17 chip::PiChip,
18 gpio::{Gpio, GpioInitializationError},
19 pixel_mapper::PixelMapper,
20 utils::{linux_has_isol_cpu, set_thread_affinity, FrameRateMonitor},
21 RGBMatrixConfig,
22};
23
24fn initialize_update_thread(chip: PiChip) {
25 let last_core_id = chip.num_cores() - 1;
27 set_thread_affinity(last_core_id);
28
29 if chip.num_cores() > 1 && !linux_has_isol_cpu(last_core_id) {
31 eprintln!(
32 "Suggestion: to slightly improve display update, add\n\tisolcpus={last_core_id}\nat \
33 the end of /boot/cmdline.txt and reboot"
34 );
35 }
36
37 if chip.num_cores() > 1 && write("/proc/sys/kernel/sched_rt_runtime_us", "999000").is_err() {
39 eprintln!("Could not disable realtime throttling");
40 }
41
42 if chip.num_cores() > 1
44 && write(
45 format!("/sys/devices/system/cpu/cpu{last_core_id}/cpufreq/scaling_governor"),
46 "performance",
47 )
48 .is_err()
49 {
50 eprintln!(
51 "Could not set core {} to performance mode.",
52 last_core_id + 1
53 );
54 }
55
56 if set_current_thread_priority(ThreadPriority::Max).is_err() {
58 eprintln!("Could not set thread priority. This might lead to reduced performance.",);
59 }
60}
61
62#[derive(Debug)]
63pub enum MatrixCreationError {
64 ChipDeterminationError,
65 TooManyParallelChains(usize),
66 InvalidDitherBits(usize),
67 ThreadTimedOut,
68 GpioError(GpioInitializationError),
69 MemoryAccessError,
70 PixelMapperError(String),
71}
72
73impl Error for MatrixCreationError {}
74
75impl Display for MatrixCreationError {
76 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
77 match self {
78 MatrixCreationError::ChipDeterminationError => {
79 f.write_str("Failed to automatically determine Raspberry Pi model.")
80 }
81 MatrixCreationError::TooManyParallelChains(max) => {
82 write!(f, "GPIO mapping only supports up to {max} parallel panels.")
83 }
84 MatrixCreationError::InvalidDitherBits(value) => {
85 write!(f, "Unsupported dither bits '{value}'.")
86 }
87 MatrixCreationError::ThreadTimedOut => {
88 f.write_str("The update thread did not return in time.")
89 }
90 MatrixCreationError::GpioError(error) => {
91 write!(f, "GPIO initialization error: {error}")
92 }
93 MatrixCreationError::MemoryAccessError => f.write_str(
94 "Failed to access the physical memory. Not running with root privileges?",
95 ),
96 MatrixCreationError::PixelMapperError(message) => {
97 write!(f, "Error in pixel mapper: {message}")
98 }
99 }
100 }
101}
102
103pub struct RGBMatrix {
104 thread_handle: Option<JoinHandle<()>>,
106 shutdown_sender: Sender<()>,
108 input_receiver: Receiver<u32>,
110 canvas_to_thread_sender: SyncSender<Box<Canvas>>,
112 canvas_from_thread_receiver: Receiver<Box<Canvas>>,
114 enabled_input_bits: u32,
116 frame_rate_monitor: FrameRateMonitor,
118}
119
120impl RGBMatrix {
121 pub fn new(
129 mut config: RGBMatrixConfig,
130 requested_inputs: u32,
131 ) -> Result<(Self, Box<Canvas>), MatrixCreationError> {
132 OpenOptions::new()
134 .read(true)
135 .write(true)
136 .open("/dev/mem")
137 .map_err(|_| MatrixCreationError::MemoryAccessError)?;
138
139 let chip = if let Some(chip) = config.pi_chip {
140 chip
141 } else {
142 PiChip::determine().ok_or(MatrixCreationError::ChipDeterminationError)?
143 };
144
145 let max_parallel = config.hardware_mapping.max_parallel_chains();
146 if config.parallel > max_parallel {
147 return Err(MatrixCreationError::TooManyParallelChains(max_parallel));
148 }
149
150 let multiplex_mapper = config.multiplexing.as_ref().map(|mapper_type| {
151 let mut mapper = mapper_type.create();
154 mapper.edit_rows_cols(&mut config.rows, &mut config.cols);
155 mapper
156 });
157
158 let pixel_designator = PixelDesignator::new(&config.hardware_mapping, config.led_sequence);
159 let width = config.cols * config.chain_length;
160 let height = config.rows * config.parallel;
161 let mut shared_mapper = PixelDesignatorMap::new(pixel_designator, width, height, &config);
162
163 if let Some(mapper) = multiplex_mapper {
165 let mapper = PixelMapper::Multiplex(mapper);
166 shared_mapper =
167 Self::apply_pixel_mapper(&shared_mapper, &mapper, &config, pixel_designator)?;
168 }
169
170 for mapper_type in config.pixelmapper.iter() {
172 let mapper = mapper_type.create(config.chain_length, config.parallel)?;
173 let mapper = PixelMapper::Named(mapper);
174 shared_mapper =
175 Self::apply_pixel_mapper(&shared_mapper, &mapper, &config, pixel_designator)?;
176 }
177
178 let dither_start_bits = match config.dither_bits {
179 0 => [0, 0, 0, 0],
180 1 => [0, 1, 0, 1],
181 2 => [0, 1, 2, 2],
182 _ => return Err(MatrixCreationError::InvalidDitherBits(config.dither_bits)),
183 };
184
185 let canvas = Box::new(Canvas::new(&config, shared_mapper));
188 let mut thread_canvas = canvas.clone();
189
190 let (canvas_to_thread_sender, canvas_to_thread_receiver) = sync_channel::<Box<Canvas>>(0);
191 let (canvas_from_thread_sender, canvas_from_thread_receiver) =
192 sync_channel::<Box<Canvas>>(1);
193 let (shutdown_sender, shutdown_receiver) = channel::<()>();
194 let (input_sender, input_receiver) = channel::<u32>();
195 let (thread_start_result_sender, thread_start_result_receiver) =
196 channel::<Result<u32, MatrixCreationError>>();
197
198 let thread_handle = spawn(move || {
199 initialize_update_thread(chip);
200
201 let mut address_setter = config.row_setter.create(&config);
202
203 let mut gpio = match Gpio::new(chip, &config, address_setter.as_ref()) {
204 Ok(gpio) => gpio,
205 Err(error) => {
206 thread_start_result_sender
207 .send(Err(MatrixCreationError::GpioError(error)))
208 .expect("Could not send to main thread.");
209 return;
210 }
211 };
212
213 if let Some(panel_type) = config.panel_type {
215 panel_type.run_init_sequence(&mut gpio, &config);
216 }
217
218 let mut last_gpio_inputs: u32 = 0;
219
220 let mut dither_low_bit_sequence = 0;
222
223 let frame_time_target_us = (1_000_000.0 / config.refresh_rate as f64) as u64;
224
225 let color_clk_mask = config
226 .hardware_mapping
227 .get_color_clock_mask(config.parallel);
228
229 let enabled_input_bits = gpio.request_enabled_inputs(requested_inputs);
230 thread_start_result_sender
231 .send(Ok(enabled_input_bits))
232 .expect("Could not send to main thread.");
233
234 'thread: loop {
235 let start_time = gpio.get_time();
236 loop {
237 if shutdown_receiver.try_recv() != Err(TryRecvError::Empty) {
239 break 'thread;
240 }
241 let new_inputs = gpio.read();
243 if new_inputs != last_gpio_inputs {
244 match input_sender.send(new_inputs) {
245 Ok(()) => {}
246 Err(_) => {
247 break 'thread;
248 }
249 }
250 last_gpio_inputs = new_inputs;
251 }
252 match canvas_to_thread_receiver.recv_timeout(Duration::from_millis(1)) {
254 Ok(new_canvas) => {
255 let old_canvas = replace(&mut thread_canvas, new_canvas);
256 match canvas_from_thread_sender.send(old_canvas) {
257 Ok(()) => break,
258 Err(_) => {
259 break 'thread;
260 }
261 };
262 }
263 Err(RecvTimeoutError::Disconnected) => {
264 break 'thread;
265 }
266 Err(RecvTimeoutError::Timeout) => {}
267 }
268 }
269
270 thread_canvas.dump_to_matrix(
271 &mut gpio,
272 &config.hardware_mapping,
273 address_setter.as_mut(),
274 dither_start_bits[dither_low_bit_sequence % dither_start_bits.len()],
275 color_clk_mask,
276 );
277 dither_low_bit_sequence += 1;
278
279 let now_time = gpio.get_time();
281 let end_time = start_time + frame_time_target_us;
282 if let Some(remaining_time) = end_time.checked_sub(now_time) {
283 gpio.sleep(remaining_time);
284 }
285 }
286
287 thread_canvas.fill(0, 0, 0);
289 thread_canvas.dump_to_matrix(
290 &mut gpio,
291 &config.hardware_mapping,
292 address_setter.as_mut(),
293 0,
294 color_clk_mask,
295 );
296 });
297
298 let enabled_input_bits = thread_start_result_receiver
299 .recv_timeout(Duration::from_secs(10))
300 .map_err(|_| MatrixCreationError::ThreadTimedOut)??;
301
302 let rgbmatrix = Self {
303 thread_handle: Some(thread_handle),
304 input_receiver,
305 shutdown_sender,
306 canvas_to_thread_sender,
307 canvas_from_thread_receiver,
308 enabled_input_bits,
309 frame_rate_monitor: FrameRateMonitor::new(),
310 };
311
312 Ok((rgbmatrix, canvas))
313 }
314
315 fn apply_pixel_mapper(
316 shared_mapper: &PixelDesignatorMap,
317 mapper: &PixelMapper,
318 config: &RGBMatrixConfig,
319 pixel_designator: PixelDesignator,
320 ) -> Result<PixelDesignatorMap, MatrixCreationError> {
321 let old_width = shared_mapper.width();
322 let old_height = shared_mapper.height();
323 let [new_width, new_height] = mapper.get_size_mapping(old_width, old_height)?;
324 let mut new_mapper =
325 PixelDesignatorMap::new(pixel_designator, new_width, new_height, config);
326 for y in 0..new_height {
327 for x in 0..new_width {
328 let [orig_x, orig_y] = mapper.map_visible_to_matrix(old_width, old_height, x, y);
329 if orig_x >= old_width || orig_y >= old_height {
330 return Err(MatrixCreationError::PixelMapperError(
331 "Invalid dimensions detected. This is likely a bug.".to_string(),
332 ));
333 }
334 let orig_designator = shared_mapper.get(orig_x, orig_y).unwrap();
335 *new_mapper.get_mut(x, y).unwrap() = *orig_designator;
336 }
337 }
338 Ok(new_mapper)
339 }
340
341 pub fn update_on_vsync(&mut self, canvas: Box<Canvas>) -> Box<Canvas> {
343 let Self {
344 canvas_to_thread_sender,
345 canvas_from_thread_receiver,
346 frame_rate_monitor,
347 ..
348 } = self;
349
350 canvas_to_thread_sender
351 .send(canvas)
352 .expect("Display update thread shut down unexpectedly.");
353
354 frame_rate_monitor.update();
355
356 canvas_from_thread_receiver
357 .recv()
358 .expect("Display update thread shut down unexpectedly.")
359 }
360
361 #[must_use]
363 pub fn enabled_input_bits(&self) -> u32 {
364 self.enabled_input_bits
365 }
366
367 pub fn receive_new_inputs(&mut self, timeout: Duration) -> Option<u32> {
369 self.input_receiver.recv_timeout(timeout).ok()
370 }
371
372 #[must_use]
374 pub fn get_framerate(&self) -> usize {
375 self.frame_rate_monitor.get_fps().round() as usize
376 }
377}
378
379impl Drop for RGBMatrix {
380 fn drop(&mut self) {
381 let Self {
382 thread_handle,
383 shutdown_sender,
384 ..
385 } = self;
386 if let Some(handle) = thread_handle.take() {
387 shutdown_sender.send(()).ok();
388 let _result = handle.join();
389 }
390 }
391}