Skip to main content

pebble/rendering/
graphics_plugin.rs

1use crate::{
2    prelude::{
3        Backend, Commands, GPUSurfaceHandle, Plugin, PresentableWindow, Res, ResMut, SystemStage,
4        WindowResource,
5    },
6    rendering::{async_init::PendingBackend, sync::init_channel},
7};
8
9/// Plugin that initialises the GPU backend asynchronously and handles window
10/// resize events.
11///
12/// It calls [`Backend::init`] with the window handle and a one-shot sender
13/// once (see [`setup_gpu_async`]), then polls the receiver every
14/// [`PreRender`](SystemStage::PreRender) tick until the backend arrives. Once
15/// available it also forwards window size changes to [`Backend::resize`].
16pub struct GraphicsPlugin<B, W> {
17    _marker: std::marker::PhantomData<(B, W)>,
18}
19
20impl<B: Backend, W: PresentableWindow> GraphicsPlugin<B, W>
21where
22    W::Handle: GPUSurfaceHandle,
23{
24    pub fn new() -> Self {
25        Self {
26            _marker: std::marker::PhantomData,
27        }
28    }
29}
30
31impl<B: Backend, W: PresentableWindow> Plugin for GraphicsPlugin<B, W>
32where
33    W::Handle: GPUSurfaceHandle,
34{
35    fn build(&self, app: &mut crate::prelude::App) {
36        app.add_system(SystemStage::Startup, setup_gpu_async::<B, W>)
37            .add_system(SystemStage::PreRender, poll_backend_ready::<B>)
38            .add_system(SystemStage::PreRender, handle_resize_async::<B, W>);
39    }
40}
41
42struct LastWindowSize(u32, u32);
43
44/// Startup system: kicks off backend initialisation and stores the pending
45/// receiver. `WindowResource<W>` already exists by the time this first runs
46/// (inserted synchronously by `WindowPlugin::build`), so this always succeeds
47/// on its first invocation and is never invoked again.
48fn setup_gpu_async<B: Backend, W>(mut commands: Commands, window: Res<WindowResource<W>>) -> Option<()>
49where
50    W: PresentableWindow,
51    W::Handle: GPUSurfaceHandle,
52{
53    let (w, h) = W::size(&window.handle);
54    let (sender, receiver) = init_channel::<B>();
55    B::init(window.handle.clone(), w, h, sender);
56    commands.insert_resource(PendingBackend::<B> {
57        receiver: std::sync::Mutex::new(receiver),
58    });
59    Some(())
60}
61
62/// PreRender system: poll the one-shot channel; promote the backend to a
63/// resource and remove the pending marker once it arrives.
64fn poll_backend_ready<B: Backend>(mut commands: Commands, pending: Option<Res<PendingBackend<B>>>) {
65    if let Some(p) = pending {
66        let mut guard = match p.receiver.lock() {
67            Ok(g) => g,
68            Err(poisoned) => poisoned.into_inner(),
69        };
70
71        if let Ok(backend) = guard.try_recv() {
72            commands.insert_resource(backend);
73            commands.remove_resource::<PendingBackend<B>>();
74        }
75    }
76}
77
78/// PreRender system: forward the current window size to the backend so it can
79/// recreate the swapchain when the window is resized.
80///
81/// `Backend::resize` reconfigures the surface, which is expensive (it drains
82/// the GPU queue and recreates the swapchain), so this only calls it when the
83/// size has actually changed rather than unconditionally every frame.
84fn handle_resize_async<B: Backend, W: PresentableWindow>(
85    mut commands: Commands,
86    backend: Option<ResMut<B>>,
87    window: Res<WindowResource<W>>,
88    last_size: Option<Res<LastWindowSize>>,
89) where
90    W::Handle: GPUSurfaceHandle,
91{
92    let Some(mut backend) = backend else { return };
93    let (w, h) = W::size(&window.handle);
94    if w == 0 || h == 0 {
95        return;
96    }
97
98    if let Some(last_size) = &last_size {
99        if last_size.0 == w && last_size.1 == h {
100            return;
101        }
102    }
103
104    backend.resize(w, h);
105    commands.insert_resource(LastWindowSize(w, h));
106}