softbuffer_window_renderer/
lib.rs

1//! An AnyRender WindowRenderer for rendering pixel buffers using the softbuffer crate
2
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5use anyrender::{ImageRenderer, WindowHandle, WindowRenderer};
6use debug_timer::debug_timer;
7use softbuffer::{Context, Surface};
8use std::{num::NonZero, sync::Arc};
9
10// Simple struct to hold the state of the renderer
11pub struct ActiveRenderState {
12    _context: Context<Arc<dyn WindowHandle>>,
13    surface: Surface<Arc<dyn WindowHandle>, Arc<dyn WindowHandle>>,
14}
15
16#[allow(clippy::large_enum_variant)]
17pub enum RenderState {
18    Active(ActiveRenderState),
19    Suspended,
20}
21
22pub struct SoftbufferWindowRenderer<Renderer: ImageRenderer> {
23    // The fields MUST be in this order, so that the surface is dropped before the window
24    // Window is cached even when suspended so that it can be reused when the app is resumed after being suspended
25    render_state: RenderState,
26    window_handle: Option<Arc<dyn WindowHandle>>,
27    renderer: Renderer,
28}
29
30impl<Renderer: ImageRenderer> SoftbufferWindowRenderer<Renderer> {
31    #[allow(clippy::new_without_default)]
32    pub fn new() -> Self {
33        Self::with_renderer(Renderer::new(0, 0))
34    }
35
36    pub fn with_renderer<R: ImageRenderer>(renderer: R) -> SoftbufferWindowRenderer<R> {
37        SoftbufferWindowRenderer {
38            render_state: RenderState::Suspended,
39            window_handle: None,
40            renderer,
41        }
42    }
43}
44
45impl<Renderer: ImageRenderer> WindowRenderer for SoftbufferWindowRenderer<Renderer> {
46    type ScenePainter<'a>
47        = Renderer::ScenePainter<'a>
48    where
49        Self: 'a;
50
51    fn is_active(&self) -> bool {
52        matches!(self.render_state, RenderState::Active(_))
53    }
54
55    fn resume(&mut self, window_handle: Arc<dyn WindowHandle>, width: u32, height: u32) {
56        let context = Context::new(window_handle.clone()).unwrap();
57        let surface = Surface::new(&context, window_handle.clone()).unwrap();
58        self.render_state = RenderState::Active(ActiveRenderState {
59            _context: context,
60            surface,
61        });
62        self.window_handle = Some(window_handle);
63
64        self.set_size(width, height);
65    }
66
67    fn suspend(&mut self) {
68        self.render_state = RenderState::Suspended;
69    }
70
71    fn set_size(&mut self, physical_width: u32, physical_height: u32) {
72        if let RenderState::Active(state) = &mut self.render_state {
73            state
74                .surface
75                .resize(
76                    NonZero::new(physical_width.max(1)).unwrap(),
77                    NonZero::new(physical_height.max(1)).unwrap(),
78                )
79                .unwrap();
80            self.renderer.resize(physical_width, physical_height);
81        };
82    }
83
84    fn render<F: FnOnce(&mut Renderer::ScenePainter<'_>)>(&mut self, draw_fn: F) {
85        let RenderState::Active(state) = &mut self.render_state else {
86            return;
87        };
88
89        debug_timer!(timer, feature = "log_frame_times");
90
91        let Ok(mut surface_buffer) = state.surface.buffer_mut() else {
92            return;
93        };
94        timer.record_time("buffer_mut");
95
96        // Paint
97        let mut vec = Vec::new();
98        self.renderer.render_to_vec(draw_fn, &mut vec);
99        timer.record_time("render");
100
101        let out = surface_buffer.as_mut();
102
103        // TODO: replace chunk_exacts with as_chunks once MSRV hits 1.88
104        let chunks = vec.chunks_exact(4);
105        assert_eq!(chunks.size_hint().0, out.len());
106        assert_eq!(chunks.remainder().len(), 0);
107
108        for (src, dest) in chunks.zip(out.iter_mut()) {
109            let [r, g, b, a]: [u8; 4] = src.try_into().unwrap();
110            if a == 0 {
111                *dest = u32::MAX;
112            } else {
113                *dest = (r as u32) << 16 | (g as u32) << 8 | b as u32;
114            }
115        }
116        timer.record_time("swizel");
117
118        surface_buffer.present().unwrap();
119        timer.record_time("present");
120        timer.print_times("softbuffer: ");
121
122        // Reset the renderer ready for the next render
123        self.renderer.reset();
124    }
125}