mew/
winitutils.rs

1use std::{
2    cell::OnceCell,
3    rc::Rc,
4    sync::{
5        Arc,
6        mpsc::{
7            Receiver,
8        },
9    },
10};
11use thiserror::Error;
12use winit::{
13    dpi::PhysicalSize,
14    error::OsError,
15    window::{ Window, WindowAttributes, },
16    event_loop::ActiveEventLoop,
17};
18use crate::{ RenderContext, RenderContextBuilder, BuildContextError, };
19
20
21#[derive(Clone, Debug, Error)]
22pub enum BuildDeferredContextError {
23    #[error(transparent)]
24    BuildContext(#[from] BuildContextError),
25    #[error("Context builder thread dropped before finishing")]
26    BuilderThreadDied,
27}
28
29#[derive(Clone, Debug, Error)]
30pub enum GetDeferredContextError {
31    #[error(transparent)]
32    Build(#[from] BuildDeferredContextError),
33    #[error("No compatible surface has been provided yet to construct an Instance")]
34    RequiresSurface,
35    #[error("No context available yet, waiting on builder thread to finish")]
36    StillBuilding,
37}
38
39
40#[derive(Clone, Debug, Error)]
41pub enum ConfigSurfaceError {
42    #[error(transparent)]
43    CreateSurface(#[from] wgpu::CreateSurfaceError),
44    #[error("Surface not supported by adapter - don't mix & match between contexts!")]
45    Unsupported,
46}
47
48
49#[derive(Debug, Error)]
50pub enum CreateWindowError {
51    #[error(transparent)]
52    Window(#[from] OsError),
53    #[error(transparent)]
54    ConfigSurface(#[from] ConfigSurfaceError),
55}
56
57impl From<wgpu::CreateSurfaceError> for CreateWindowError {
58    fn from(e: wgpu::CreateSurfaceError) -> Self {
59        Self::ConfigSurface(e.into())
60    }
61}
62
63
64#[derive(Debug, Default)]
65pub struct SurfaceConfigOptions {
66    pub present_mode: Option<wgpu::PresentMode>,
67    pub frame_latency: Option<u32>,
68    pub alpha_mode: Option<wgpu::CompositeAlphaMode>,
69}
70
71
72#[derive(Debug)]
73pub struct SurfaceWindow {
74    config_opts: SurfaceConfigOptions,
75    config: OnceCell<wgpu::SurfaceConfiguration>,
76    surface: OnceCell<Arc<wgpu::Surface<'static>>>,
77    pub window: Arc<Window>,
78}
79
80impl SurfaceWindow {
81    pub fn new(event_loop: &ActiveEventLoop, attributes: WindowAttributes, config_opts: SurfaceConfigOptions) -> Result<Self, OsError> {
82        let window = Arc::new(event_loop.create_window(attributes)?);
83        Ok(Self {
84            config_opts,
85            config: OnceCell::new(),
86            surface: OnceCell::new(),
87            window,
88        })
89    }
90
91    pub fn try_get_config(&self) -> Option<&wgpu::SurfaceConfiguration> {
92        self.config.get()
93    }
94
95    pub fn try_get_surface(&self) -> Option<&Arc<wgpu::Surface<'static>>> {
96        self.surface.get()
97    }
98
99    fn get_surface(&self, instance: &wgpu::Instance) -> Result<(&Arc<wgpu::Surface<'static>>, bool), wgpu::CreateSurfaceError> {
100        match self.surface.get() {
101            Some(s) => Ok((s, false)),
102            None => {
103                let surface = instance.create_surface(self.window.clone())
104                    .map(|s| s.into())?;
105                Ok((self.surface.get_or_init(|| surface), true))
106            },
107        }
108    }
109
110    fn get_config(&self, surface: &wgpu::Surface<'static>, adapter: &wgpu::Adapter) -> Option<(&wgpu::SurfaceConfiguration, bool)> {
111        match self.config.get() {
112            Some(c) => Some((c, false)),
113            None => {
114                let mut config = surface.get_default_config(adapter, 0, 0)?;
115                self.config_opts.present_mode.map(|pm| config.present_mode = pm);
116                self.config_opts.frame_latency.map(|fl| config.desired_maximum_frame_latency = fl);
117                self.config_opts.alpha_mode.map(|am| config.alpha_mode = am);
118
119                Some((self.config.get_or_init(|| config), true))
120            },
121        }
122    }
123
124    pub fn init_surface(&self, instance: &wgpu::Instance, adapter: &wgpu::Adapter, device: &wgpu::Device) -> Result<(&Arc<wgpu::Surface<'static>>, &wgpu::SurfaceConfiguration), ConfigSurfaceError> {
125        let (surface, is_surface_new) = self.get_surface(instance)?;
126        let (config, is_config_new) = self.get_config(surface, adapter).ok_or(ConfigSurfaceError::Unsupported)?;
127
128        if is_surface_new || is_config_new {
129            let size = self.get_size();
130            let mut config = config.clone();
131            config.width = size.width;
132            config.height = size.height;
133            surface.configure(device, &config);
134        }
135
136        Ok((surface, config))
137    }
138
139    pub fn init_with_context(&self, context: &RenderContext) -> Result<(&Arc<wgpu::Surface<'static>>, &wgpu::SurfaceConfiguration), ConfigSurfaceError> {
140        self.init_surface(&context.instance, &context.adapter, &context.device)
141    }
142
143    pub fn drop_surface(&mut self) {
144        let _ = self.surface.take();
145    }
146
147    pub fn try_reconfigure(&self, device: &wgpu::Device, size: PhysicalSize<u32>) -> Result<(), ()> {
148        let surface = self.try_get_surface().ok_or(())?;
149        let mut config = self.try_get_config().ok_or(())?.clone();
150        config.width = size.width;
151        config.height = size.height;
152        surface.configure(device, &config);
153        Ok(())
154    }
155
156    pub fn get_texture_view(&self) -> Option<(wgpu::SurfaceTexture, wgpu::TextureView)> {
157        // Assert surface is configured
158        let _ = self.try_get_config()?;
159        let texture = self.try_get_surface()?.get_current_texture().unwrap();
160        let view = texture.texture.create_view(&wgpu::TextureViewDescriptor::default());
161        Some((texture, view))
162    }
163
164    pub fn get_size(&self) -> PhysicalSize<u32> {
165        self.window.inner_size()
166    }
167}
168
169
170#[derive(Debug)]
171#[allow(dead_code)]
172enum ContextState {
173    WaitingForSurface {
174        builder: RenderContextBuilder,
175    },
176    Building {
177        receiver: Receiver<Result<RenderContext, BuildContextError>>,
178    },
179    Resolved {
180        context: Result<Rc<RenderContext>, BuildDeferredContextError>,
181    },
182}
183
184impl ContextState {
185    fn provide_surface(&mut self, surface: Arc<wgpu::Surface<'static>>) {
186        match self {
187            Self::WaitingForSurface { builder, } => {
188                builder.with_compatible_surface(Some(surface));
189                let builder = builder.clone();
190
191                #[cfg(all(feature = "webgl", not(feature = "poll")))]
192                {
193                    use std::sync::mpsc::sync_channel;
194                    let (send, recv) = sync_channel::<Result<RenderContext, BuildContextError>>(1);
195                    wasm_bindgen_futures::spawn_local(async move {
196                        let context = builder.build().await;
197                        let _ = send.send(context);
198                    });
199
200                    *self = Self::Building { receiver: recv, };
201                }
202
203                #[cfg(all(not(feature = "webgl"), feature = "poll"))]
204                {
205                    let context = pollster::block_on(builder.build())
206                        .map(|c| c.into()).map_err(|e| e.into());
207                    *self = Self::Resolved { context, };
208                }
209
210                #[cfg(all(feature = "webgl", feature = "poll"))]
211                unreachable!("Ambiguous whether to start JS thread or poll, only one feature of `webgl` or `poll` should be enabled");
212
213                #[cfg(not(any(feature = "webgl", feature = "poll")))]
214                unreachable!("No way to poll for context, one feature of `webgl` or `poll` should be enabled");
215            },
216            _ => {},
217        }
218    }
219
220    fn poll_resolve_context(&mut self) -> Result<Rc<RenderContext>, GetDeferredContextError> {
221        match self {
222            Self::WaitingForSurface { .. } => Err(GetDeferredContextError::RequiresSurface),
223            Self::Building { receiver, } => {
224                use std::sync::mpsc::TryRecvError;
225
226                match receiver.try_recv() {
227                    Ok(context) => {
228                        let context = context.map(|c| c.into()).map_err(|e| e.into());
229                        *self = Self::Resolved { context: context.clone(), };
230                        Ok(context?)
231                    },
232                    Err(TryRecvError::Disconnected) => {
233                        *self = Self::Resolved { context: Err(BuildDeferredContextError::BuilderThreadDied), };
234                        Err(BuildDeferredContextError::BuilderThreadDied.into())
235                    },
236                    Err(TryRecvError::Empty) => Err(GetDeferredContextError::StillBuilding),
237                }
238            },
239            Self::Resolved { context, } => Ok(context.clone()?),
240        }
241    }
242
243    /*
244    fn try_get_context(&self) -> Option<Rc<RenderContext>> {
245        match self {
246            Self::Resolved { context, } => context.as_ref().map(|c| c.ok()).flatten().clone(),
247            _ => None,
248        }
249    }
250    */
251}
252
253
254/// Defer the contstruction of the [`RenderContext`] until a surface is
255/// constructed.
256///
257/// WebGL really screws up the normal workflow of "first create your *wgpu*
258/// handles, then make windows and stuff", because creating the
259/// [`wgpu::Adapter`] requires a [`wgpu::Surface`] to be passed in from
260/// [`wgpu::Instance`] and [`winit::window::Window`]. To top it off, this
261/// annoying back-and-forth involves some `async` functions.
262///
263/// On *wasm*, this struct caches a builder and constructs the [`RenderContext`]
264/// in a JS thread oppurtunistically when it is used to construct a
265/// [`SurfaceWindow`].  
266/// Native platforms block the thread when constructing, *pollster* doesn't seem
267/// to get along with *wasm*.
268///
269/// See example usage below. There's a bit of jumping through hoops, but most of
270/// it is handled in the background.
271/// ```rust
272/// impl ApplicationHandler for YourApp {
273///     fn resume(&mut self, event_loop: &ActiveEventLoop) {
274///         // Create a window and provide the surface to the deferred context to start building.
275///         // In the worst case (wasm), at this point it will have an unconfigured surface and no config.
276///         let window = self.window.get_or_insert_with(|| {
277///             let attributes = WindowAttributes::default().with_canvas(get_webgl_canvas());
278///             // Will additionally try to configure surface on native.
279///             self.deferred_context.create_window(event_loop, attributes, Default::default())
280///                 .expect("Couldn't create window");
281///         });
282///
283///         // Can't do much else until we have a valid context, which may take until the next
284///         // function call to finish building. Move on to main loop.
285///         self.suspended = false;
286///     }
287///
288///     fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: _, event: _) {
289///         if self.suspended { return; }
290///
291///         let context = match self.defered_context.get_context() {
292///             // Something went wrong building the context, abort.
293///             GetDeferredContextError::Build(e) => panic!("Error building context: {e}"),
294///             // resume() didn't run for some reason, RenderContext has no surface to use to initialize with.
295///             GetDeferredContextError::RequiresSurface => unreachable!("Surface should have been provided in resume()"),
296///             // Still building, will try again next window_event()
297///             GetDeferredContextError::StillBuilding => return,
298///         };
299///
300///         // Now that context is ready, we can finish configuring the window surface.
301///         // init_surface() and init_with_context() will create everything when needed and make sure it's configured.
302///         let (surface, config) = self.window.as_ref().expect("Window should have created in resume()")
303///             .init_with_context(&context);
304///
305///         // Side-note: Vertex/shader buffers, pipelines, etc. are also dependent on the RenderContext being
306///         // configured with a surface. Keep them in an Option and generate them ASAP.
307///         let buffers_and_shit = self.buffers_and_shit.get_or_insert_with(|| init_buffers_and_shit(&context, config.format));
308///
309///         Self::finally_render(context, window, surface)
310///     }
311///
312///     fn suspended(&mut self, event_loop: &ActiveEventLoop) {
313///         // Android requires surfaces be dropped on suspend. init_surface() or init_with_context() above
314///         // will re-create and configure the window surface automatically when unsuspended.
315///         self.window.as_mut().map(|w| w.drop_surface());
316///         self.suspended = true;
317///     }
318/// }
319/// ```
320#[derive(Debug)]
321pub struct DeferredContext {
322    instance: wgpu::Instance,
323    context: ContextState,
324}
325
326impl DeferredContext {
327    #[cfg(any(feature = "webgl", feature = "poll"))]
328    pub fn new(builder: RenderContextBuilder) -> Self {
329        #[cfg(feature = "webgl")]
330        return Self::new_requiring_surface(builder);
331        #[cfg(feature = "poll")]
332        return Self::new_poll(builder);
333    }
334
335    #[cfg(any(feature = "webgl", feature = "poll"))]
336    pub fn new_requiring_surface(mut builder: RenderContextBuilder) -> Self {
337        let instance = builder.get_instance();
338        Self {
339            instance,
340            context: ContextState::WaitingForSurface { builder, },
341        }
342    }
343
344    #[cfg(not(feature = "webgl"))]
345    pub async fn new_async(mut builder: RenderContextBuilder) -> Self {
346        let instance = builder.get_instance();
347        let context = builder.build().await.map(|c| c.into()).map_err(|e| e.into());
348        Self {
349            instance,
350            context: ContextState::Resolved { context, },
351        }
352    }
353
354    #[cfg(all(not(feature = "webgl"), feature = "poll"))]
355    pub fn new_poll(builder: RenderContextBuilder) -> Self {
356        pollster::block_on(Self::new_async(builder))
357    }
358
359    pub fn create_window(&mut self, event_loop: &ActiveEventLoop, attributes: WindowAttributes, config_opts: SurfaceConfigOptions) -> Result<SurfaceWindow, CreateWindowError> {
360        let window = SurfaceWindow::new(event_loop, attributes, config_opts)?;
361        let surface = window.get_surface(&self.instance)?;
362        self.context.provide_surface(surface.0.clone());
363        if let Ok(context) = self.get_context() {
364            window.init_surface(&context.instance, &context.adapter, &context.device)?;
365        }
366        Ok(window)
367    }
368
369    pub fn get_instance(&self) -> wgpu::Instance {
370        self.instance.clone()
371    }
372
373    pub fn get_context(&mut self) -> Result<Rc<RenderContext>, GetDeferredContextError> {
374        self.context.poll_resolve_context()
375    }
376}