Skip to main content

mew/
winitutils.rs

1//! Utilities for working with windows & surfaces.
2//!
3//! To bundle a
4//! [`winit::window::Window`](https://docs.rs/winit/latest/winit/window/struct.Window.html)
5//! with its
6//! [`wgpu::Surface`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html),
7//! you're looking for [`SurfaceWindow`].
8//!
9//! For an abstraction over WebGL's retardedness having to provide a surface
10//! before you can get a _wgpu_ adapter, see [`DeferredContext`].
11
12
13use std::{
14    cell::{
15        BorrowMutError,
16        Cell,
17        RefCell,
18        RefMut,
19        UnsafeCell,
20    },
21    sync::{
22        Arc,
23        mpsc::{
24            Receiver,
25        },
26    },
27};
28use thiserror::Error;
29use winit::{
30    dpi::PhysicalSize,
31    error::OsError,
32    window::{ Window, WindowAttributes, },
33    event_loop::ActiveEventLoop,
34};
35use crate::{ RenderContext, RenderContextBuilder, BuildContextError, };
36
37
38/// Error returned when waiting for a [`RenderContext`] to be built.
39#[derive(Clone, Debug, Error)]
40pub enum BuildDeferredContextError {
41    /// Something went wrong with the actual building of the [`RenderContext`].
42    #[error(transparent)]
43    BuildContext(#[from] BuildContextError),
44    /// The thread or async task dropped (only on web).
45    #[error("Context builder thread dropped before finishing")]
46    BuilderThreadDied,
47}
48
49/// Error returned trying to get the [`RenderContext`] from a [`DeferredContext`].
50#[derive(Clone, Debug, Error)]
51pub enum GetDeferredContextError {
52    /// Something went wrong actually building the [`RenderContext`].
53    /// This is a fatal error, if it fails you should probably just `panic!`.
54    #[error(transparent)]
55    Build(#[from] BuildDeferredContextError),
56    /// On web, you need to provide a canvas surface to create a
57    /// [`wgpu::Adapter`](https://docs.rs/wgpu/latest/wgpu/struct.Adapter.html)
58    /// before you can even get to the [`RenderContext`]. This is done automatically
59    /// when you create a new window through [`DeferredContext::create_window`].
60    #[error("No compatible surface has been provided yet to construct an Adapter")]
61    RequiresSurface,
62    /// On web, the async builder task hasn't returned a result yet. Building a
63    /// [`RenderContext`] should be quick though, so if you see this something might be
64    /// wrong.
65    #[error("No context available yet, waiting on builder thread to finish")]
66    StillBuilding,
67}
68
69
70/// Error returned when configuring a surface or making a texture view for the
71/// surface.
72#[derive(Clone, Debug, Error)]
73pub enum ConfigSurfaceError {
74    /// _wgpu_ could not create a surface, see
75    /// [`wgpu::Instance::create_surface`](https://docs.rs/wgpu/latest/wgpu/struct.Instance.html#method.create_surface).
76    #[error(transparent)]
77    CreateSurface(#[from] wgpu::CreateSurfaceError),
78    /// The surface already has an active view - a new one can't be created, nor can
79    /// the current one be reconfigured.
80    #[error("Surface is already active")]
81    InUse,
82    /// Can't reconfigure the surface because it hasn't been created or configured
83    /// yet. Non-fatal, but run [`SurfaceWindow::init_with_context`] first.
84    #[error("Surface hasn't been created or configured yet")]
85    NotInitialized,
86    /// Trying to use the surface failed. Non-fatal, but call
87    /// [`SurfaceWindow::drop_surface`] to re-initialize it. Also see
88    /// [`wgpu::SurfaceError`](https://docs.rs/wgpu/latest/wgpu/enum.SurfaceError.html).
89    #[error(transparent)]
90    SurfaceError(#[from] wgpu::SurfaceError),
91    /// The surface that tried to be configure wasn't supported by this adapter. Don't
92    /// mix & match resources between [`RenderContext`]s!
93    #[error("Surface not supported by adapter - don't mix & match between contexts!")]
94    Unsupported,
95}
96
97impl From<BorrowMutError> for ConfigSurfaceError {
98    fn from(_e: BorrowMutError) -> Self {
99        Self::InUse
100    }
101}
102
103
104/// Error returned when trying to create a window.
105#[derive(Debug, Error)]
106pub enum CreateWindowError {
107    /// The _winit_ event loop couldn't create a window for whatever reason, see
108    /// [`winit::ActiveEventLoop::create_window`](https://docs.rs/winit/latest/x86_64-unknown-linux-gnu/winit/event_loop/struct.ActiveEventLoop.html#method.create_window).
109    #[error(transparent)]
110    Window(#[from] OsError),
111    /// The window was created but _wgpu_ couldn't configure its surface.
112    #[error(transparent)]
113    ConfigSurface(#[from] ConfigSurfaceError),
114}
115
116impl From<wgpu::CreateSurfaceError> for CreateWindowError {
117    fn from(e: wgpu::CreateSurfaceError) -> Self {
118        Self::ConfigSurface(e.into())
119    }
120}
121
122
123fn clamp_config_size(config: &mut wgpu::SurfaceConfiguration, device: &wgpu::Device, mut size: PhysicalSize<u32>) {
124    let max_allowed_size = device.limits().max_texture_dimension_2d;
125    let max_config_size = size.width.max(size.height);
126    if max_config_size > max_allowed_size {
127        let ratio = max_allowed_size as f32 / max_config_size.max(1) as f32;
128        size.width = (size.width as f32 * ratio) as u32;
129        size.height = (size.height as f32 * ratio) as u32;
130    }
131
132    config.width = size.width.max(1);
133    config.height = size.height.max(1);
134}
135
136
137/// Persistent configuration for surfaces, which may be destroyed & re-created.
138/// Overrides defaults of [`wgpu::SurfaceConfiguration`](https://docs.rs/wgpu/latest/wgpu/type.SurfaceConfiguration.html).
139#[derive(Debug, Default)]
140pub struct SurfaceConfigOptions {
141    /// The surface's [`wgpu::PresentMode`](https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html).
142    pub present_mode: Option<wgpu::PresentMode>,
143    /// The surface's max frame latency.
144    pub frame_latency: Option<u32>,
145    /// The surface's [`wgpu::CompositeAlphaMode`](https://docs.rs/wgpu/latest/wgpu/enum.CompositeAlphaMode.html).
146    pub alpha_mode: Option<wgpu::CompositeAlphaMode>,
147}
148
149
150/// A combined
151/// [`winit::window::Window`](https://docs.rs/winit/latest/winit/window/struct.Window.html)
152/// &
153/// [`wgpu::Surface`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html).
154#[derive(Debug)]
155pub struct SurfaceWindow {
156    config_opts: SurfaceConfigOptions,
157    surface: RefCell<Option<Arc<wgpu::Surface<'static>>>>,
158    window: Arc<Window>,
159    target_size: Cell<Option<PhysicalSize<u32>>>,
160    reconfigure: Cell<bool>,
161}
162
163impl SurfaceWindow {
164    /// Create a new window under an
165    /// [`winit::ActiveEventLoop`](https://docs.rs/winit/latest/winit/event_loop/struct.ActiveEventLoop.html).
166    ///
167    /// Also requires a
168    /// [`winit::window::WindowAttributes`](https://docs.rs/winit/latest/winit/window/struct.WindowAttributes.html)
169    /// (though [`Default`] should work fine for most cases) and a [`SurfaceConfigOptions`]
170    /// ([`Default`] should be fine here too).
171    pub fn new(event_loop: &ActiveEventLoop, attributes: WindowAttributes, config_opts: SurfaceConfigOptions) -> Result<Self, OsError> {
172        let window = Arc::new(event_loop.create_window(attributes)?);
173        Ok(Self {
174            config_opts,
175            //config: OnceCell::new(),
176            surface: RefCell::new(None),
177            window,
178            target_size: Cell::new(None),
179            reconfigure: Cell::new(false),
180            //_config_lock: RefCell::new(()),
181        })
182    }
183
184    /*
185    /// Try to get the [`wgpu::Surface`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html),
186    /// if it's been built.
187    ///
188    /// This is marked `unsafe` because reconfiguring this surface while it has an
189    /// active view will cause _wgpu_ to panic. _wgpu_ doesn't enforce this so I
190    /// gotta.
191    pub unsafe fn try_get_surface(&self) -> Option<&wgpu::Surface<'static>> {
192        self.surface.get().map(|s| &**s)
193    }
194    */
195
196    /// Try to get the [`wgpu::SurfaceConfiguration`](https://docs.rs/wgpu/latest/wgpu/type.SurfaceConfiguration.html),
197    /// if the surface has been created.
198    pub fn try_get_config(&self) -> Option<wgpu::SurfaceConfiguration> {
199        unsafe { &*self.surface.as_ptr() }
200            .as_ref().and_then(|s| s.get_configuration())
201    }
202
203    /// Get a reference to the window.
204    pub fn window(&self) -> &Window {
205        &self.window
206    }
207
208    /*
209    /// Get an [`Arc`] to the window.
210    pub fn window_arc(&self) -> Arc<Window> {
211        self.window.clone()
212    }
213    */
214
215    /*
216    fn get_surface(&self, instance: &wgpu::Instance) -> Result<(&Arc<wgpu::Surface<'static>>, bool), wgpu::CreateSurfaceError> {
217        match self.surface.get() {
218            Some(s) => Ok((s, false)),
219            None => {
220                let surface = instance.create_surface(self.window.clone())
221                    .map(|s| s.into())?;
222                Ok((self.surface.get_or_init(|| surface), true))
223            },
224        }
225    }
226
227    fn get_config(&self, surface: &wgpu::Surface<'static>, adapter: &wgpu::Adapter, device: &wgpu::Device) -> Option<(&wgpu::SurfaceConfiguration, bool)> {
228        match self.config.get() {
229            Some(c) => Some((c, false)),
230            None => {
231                let size = self.get_size();
232
233                let mut config = surface.get_default_config(adapter, 0, 0)?;
234                clamp_config_size(&mut config, device, size);
235
236                self.config_opts.present_mode.map(|pm| config.present_mode = pm);
237                self.config_opts.frame_latency.map(|fl| config.desired_maximum_frame_latency = fl);
238                self.config_opts.alpha_mode.map(|am| config.alpha_mode = am);
239
240                Some((self.config.get_or_init(|| config), true))
241            },
242        }
243    }
244    */
245
246    /*
247    fn get_surface(&self, instance: &wgpu::Instance) -> Result<&Arc<wgpu::Surface<'static>>, ConfigSurfaceError> {
248        let mut lock = self.surface.try_borrow_mut()?;
249        match &*lock {
250            Some(s) => Ok(s),
251            None => {
252                let surface = self.make_surface(instance)?;
253                Ok(lock.insert(Arc::new(surface)))
254            },
255        }
256    }
257    */
258
259    fn make_surface(&self, instance: &wgpu::Instance) -> Result<wgpu::Surface<'static>, wgpu::CreateSurfaceError> {
260        instance.create_surface(self.window.clone())
261    }
262
263    fn make_config(&self, surface: &wgpu::Surface<'static>, adapter: &wgpu::Adapter, device: &wgpu::Device) -> Option<wgpu::SurfaceConfiguration> {
264        let mut config = surface.get_default_config(adapter, 0, 0)?;
265
266        self.config_opts.present_mode.map(|pm| config.present_mode = pm);
267        self.config_opts.frame_latency.map(|fl| config.desired_maximum_frame_latency = fl);
268        self.config_opts.alpha_mode.map(|am| config.alpha_mode = am);
269
270        let size = self.target_size.get()
271            .unwrap_or_else(|| self.get_size());
272        clamp_config_size(&mut config, device, size);
273
274        Some(config)
275    }
276
277    /// Opaquely create, configure, and get the
278    /// [`wgpu::SurfaceTexture`](https://docs.rs/wgpu/latest/wgpu/struct.SurfaceTexture.html)
279    /// &
280    /// [`wgpu::TextureView`](https://docs.rs/wgpu/latest/wgpu/struct.TextureView.html)
281    /// of the underlying
282    /// [`winit::window::Window`](https://docs.rs/winit/latest/winit/window/struct.Window.html).
283    ///
284    /// If the window's
285    /// [`wgpu::Surface`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html)
286    /// hasn't been created yet, it will do so now. If needed it'll make a new
287    /// [`wgpu::SurfaceConfiguration`](https://docs.rs/wgpu/latest/wgpu/type.SurfaceConfiguration.html)
288    /// using either the window's inner size or a manually provided one from
289    /// the last [`Self::resize`].
290    ///
291    /// Next, it'll try getting its
292    /// [`wgpu::SurfaceTexture`](https://docs.rs/wgpu/latest/wgpu/struct.SurfaceTexture.html)
293    /// with
294    /// [`wgpu::Surface::get_current_texture`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html#method.get_current_texture).
295    /// This may fail if the surface was old and needed to be refreshed, in which
296    /// case it'll need to fully recreate the surface. If getting the surface
297    /// texture fails a 2nd time, the error will be raised to the caller.
298    ///
299    /// Only one [`ActiveSurfaceWindow`] is allowed at any one time, this is
300    /// enforced during runtime.
301    pub fn init_surface(&self, instance: &wgpu::Instance, adapter: &wgpu::Adapter, device: &wgpu::Device) -> Result<ActiveSurfaceWindow<'_>, ConfigSurfaceError> {
302        let mut surface_lock = self.surface.try_borrow_mut()?;
303
304        /*
305        // See if surface is already created, configured, & doesn't need a reconfig,
306        // then just try to get its texture
307        let maybe_texture = surface_lock.as_ref()
308            .filter(|_| !self.reconfigure.get())
309            .filter(|s| s.get_configuration().is_some())
310            .and_then(|s| s.get_current_texture().ok());
311
312        // Surface may be borked, re-create it and then try to get its texture again
313        let (surface, texture) = match maybe_texture {
314            Some(t) => (RefMut::map(surface_lock, |sl| sl.as_mut().unwrap()), t),
315            None => {
316                let surface = match &*surface_lock {
317                    // Exists but needs configuration. If also borked will be fixed in next call
318                    Some(s) if s.get_configuration().is_none() || self.reconfigure.replace(false) => {
319                        RefMut::map(surface_lock, |sl| sl.as_mut().unwrap())
320                    },
321                    // Exists & is configured but borked & needs to be recreated, or doesn't exist
322                    _ => {
323                        let new_surface = self.make_surface(instance)?;
324                        RefMut::map(surface_lock, |sl| sl.insert(Arc::new(new_surface)))
325                    },
326                };
327                let config = self.make_config(&surface, adapter, device)
328                    .ok_or(ConfigSurfaceError::Unsupported)?;
329                surface.configure(device, &config);
330
331                let texture = surface.get_current_texture()?;
332
333                (surface, texture)
334            },
335        };
336        */
337
338        let reconfigure = self.reconfigure.replace(false);
339        let old_size = surface_lock.as_ref().and_then(|s| s.get_configuration()).map(|c| (c.width, c.height));
340        let mut resized = false;
341
342        // (Re)configure if surface exists & configuration is needed
343        if let Some(surface) = &*surface_lock && (surface.get_configuration().is_none() || reconfigure) {
344            let config = self.make_config(&surface, adapter, device)
345                .ok_or(ConfigSurfaceError::Unsupported)?;
346            surface.configure(device, &config);
347            resized = old_size != Some((config.width, config.height));
348        }
349
350        let texture = match surface_lock.as_ref().and_then(|s| s.get_current_texture().ok()) {
351            Some(t) => t,
352            None => {
353                // Surface borked, recreate everything
354                let new_surface = Arc::new(self.make_surface(instance)?);
355                let surface = surface_lock.insert(new_surface);
356
357                let config = self.make_config(&surface, adapter, device)
358                    .ok_or(ConfigSurfaceError::Unsupported)?;
359                surface.configure(device, &config);
360
361                resized = old_size != Some((config.width, config.height));
362
363                // Get texture of new surface, if this errors something is really borked
364                surface.get_current_texture()?
365            },
366        };
367
368        let view = texture.texture.create_view(&wgpu::TextureViewDescriptor {
369            format: Some(texture.texture.format()),
370            ..Default::default()
371        });
372
373        Ok(ActiveSurfaceWindow {
374            surface: RefMut::map(surface_lock, |sl| sl.as_mut().expect("Surface should have been created")),
375            window: &self.window,
376            texture,
377            view,
378            resized,
379        })
380    }
381
382    /// Get a usable [`ActiveSurfaceWindow`], which guarantees that the window
383    /// surface exists & is configured.
384    ///
385    /// See [`Self::init_surface`] for more details.
386    pub fn init_with_context(&self, context: &RenderContext) -> Result<ActiveSurfaceWindow<'_>, ConfigSurfaceError> {
387        self.init_surface(&context.instance, &context.adapter, &context.device)
388    }
389
390    /// Try to drop the surface.
391    pub fn try_drop_surface(&self) -> Result<(), BorrowMutError> {
392        let _ = self.surface.try_borrow_mut()?.take();
393        Ok(())
394    }
395
396    /// Drop the surface now.
397    pub fn drop_surface(&mut self) {
398        let _ = self.surface.get_mut().take();
399    }
400
401    /*
402    /// Try to reconfigure the surface, optionally specifying the surface's new size
403    /// or `None` to query the window's inner size. Call this on _winit_ window
404    /// resize events, after the surface has been created & initialized.
405    ///
406    /// Will return an error if no surface exists yet or if it hasn't already been
407    /// configured, though this isn't fatal - just run [`Self::init_with_context`]
408    /// first.  
409    /// [`wgpu::Surface`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html)s
410    /// also can't be reconfigured while they have an active view (while an
411    /// [`ActiveSurfaceWindow`] exists), so it'll return another error. It isn't
412    /// fatal either.
413    pub fn try_reconfigure(&self, device: &wgpu::Device, target_size: Option<PhysicalSize<u32>>) -> Result<(), ConfigSurfaceError> {
414        // Cannot reconfigure if an ActiveSurfaceWindow exists
415        let surface_lock = self.surface.try_borrow_mut()?;
416        let surface = surface_lock.as_ref().ok_or(ConfigSurfaceError::NotInitialized)?;
417        /*
418        let mut config = self.get_config.get_mut().ok_or(())?;
419        clamp_config_size(config, device, size);
420        */
421
422        let size = target_size
423            //.filter(|s| s.width != 0 && s.height != 0)
424            .unwrap_or_else(|| self.get_size());
425
426        let config = surface.get_configuration()
427            .map(|mut c| { clamp_config_size(&mut c, device, size); c })
428            .ok_or(ConfigSurfaceError::NotInitialized)?;
429
430        surface.configure(device, &config);
431        Ok(())
432    }
433    */
434
435    /// Set the target window size and trigger a reconfiguration.
436    ///
437    /// If you pass `None`, the size will be queried from the window, or you can
438    /// specify it from a _winit_ resize event. The surface will be reconfigured
439    /// when it's next initialized.
440    pub fn resize(&self, size: Option<PhysicalSize<u32>>) {
441        self.target_size.set(size);
442        self.reconfigure();
443    }
444
445    /// Mark the surface to be reconfigured when it's next initialized.
446    pub fn reconfigure(&self) {
447        self.reconfigure.set(true);
448    }
449
450    /// Convenience function to get the window's inner size.
451    pub fn get_size(&self) -> PhysicalSize<u32> {
452        self.window.inner_size()
453    }
454}
455
456
457/// A borrowed form of [`SurfaceWindow`] which guarantees that its surface
458/// exists & is configured, ready to be rendered to.
459///
460/// Only one instance is allowed at any time (per [`SurfaceWindow`]). _wgpu_
461/// panics if multiple
462/// [`wgpu::SurfaceTexture`](https://docs.rs/wgpu/latest/wgpu/struct.SurfaceTexture.html)s
463/// exist in specific conditions, so the safest thing to do is make sure only
464/// one can exist.
465#[must_use]
466pub struct ActiveSurfaceWindow<'w> {
467    //surface: &'w wgpu::Surface<'static>,
468    //config: &'w wgpu::SurfaceConfiguration,
469    surface: RefMut<'w, Arc<wgpu::Surface<'static>>>,
470    window: &'w Window,
471    texture: wgpu::SurfaceTexture,
472    view: wgpu::TextureView,
473    resized: bool,
474}
475
476impl ActiveSurfaceWindow<'_> {
477    /// Get the window's [`wgpu::Surface`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html).
478    ///
479    /// This is `unsafe` because reconfiguring this surface while it has an
480    /// active view will cause _wgpu_ to panic. _wgpu_ doesn't enforce this so I
481    /// gotta. In other words:  
482    /// ***ABSOLUTELY DO NOT CALL
483    /// [`wgpu::Surface::configure`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html#method.configure)
484    /// ON THIS SURFACE***
485    pub unsafe fn surface(&self) -> &wgpu::Surface<'static> {
486        &*self.surface
487    }
488
489    /// Get the surface's [`wgpu::SurfaceConfiguration`](https://docs.rs/wgpu/latest/wgpu/type.SurfaceConfiguration.html).
490    ///
491    /// Under the hood, `self.surface().get_configuration().unwrap()` - the surface
492    /// is guaranteed to be configured if it made it into this struct.
493    pub fn config(&self) -> wgpu::SurfaceConfiguration {
494        self.surface.get_configuration()
495            .expect("Surface should have been configured before constructing ActiveSurfaceWindow")
496    }
497
498    /// Get the [`winit::window::Window`](https://docs.rs/winit/latest/winit/window/struct.Window.html).
499    pub fn window(&self) -> &Window {
500        self.window
501    }
502
503    /// Convenience function to get the window's inner size.
504    pub fn get_size(&self) -> PhysicalSize<u32> {
505        self.window.inner_size()
506    }
507
508    /// Convenience function to get the configured size of the surface.
509    ///
510    /// Resize events may not be sent in time before a frame runs, leading to
511    /// desyncs. This may cause a panic on _wgpu_'s side if you have a depth, which
512    /// needs to always be kept the same size as the configured surface.  
513    /// See also [`Self::resized`].
514    pub fn get_config_size(&self) -> PhysicalSize<u32> {
515        let config = self.config();
516        PhysicalSize {
517            width: config.width,
518            height: config.height,
519        }
520    }
521
522    /// Get the surface's
523    /// [`wgpu::SurfaceTexture`](https://docs.rs/wgpu/latest/wgpu/struct.SurfaceTexture.html).
524    pub fn texture(&self) -> &wgpu::SurfaceTexture {
525        &self.texture
526    }
527
528    /// Get the surface texture's
529    /// [`wgpu::TextureView`](https://docs.rs/wgpu/latest/wgpu/struct.TextureView.html)
530    /// with default settings.
531    pub fn view(&self) -> &wgpu::TextureView {
532        &self.view
533    }
534
535    /// Returns `true` if the surface has just been resized (reconfigured or rebuilt
536    /// with a new size since last frame).
537    ///
538    /// Useful to keep a depth stencil buffer in-sync.
539    pub fn resized(&self) -> bool {
540        self.resized
541    }
542
543    /// Get a [`wgpu::RenderPassColorAttachment`](https://docs.rs/wgpu/latest/wgpu/struct.RenderPassColorAttachment.html)
544    /// to attach this surface's texture to a render pass, to draw to it using a
545    /// specified clear colour.
546    pub fn as_colour_attachment<'a>(&'a self, clear: Option<wgpu::Color>) -> wgpu::RenderPassColorAttachment<'a> {
547        wgpu::RenderPassColorAttachment {
548            view: &self.view,
549            depth_slice: None,
550            resolve_target: None,  // ???
551            ops: wgpu::Operations {
552                load: clear.map(|colour| wgpu::LoadOp::Clear(colour)).unwrap_or(wgpu::LoadOp::Load),
553                store: wgpu::StoreOp::Store,
554            },
555        }
556    }
557
558    /// Present the graphics operations to the surface. Consumes this struct, init a
559    /// new one for next frame.
560    pub fn present_texture(self) {
561        self.window.pre_present_notify();
562        self.texture.present();
563    }
564}
565
566
567#[derive(Debug)]
568#[allow(dead_code)]
569enum ContextState {
570    WaitingForSurface {
571        builder: RenderContextBuilder,
572    },
573    Building {
574        receiver: Receiver<Result<RenderContext, BuildContextError>>,
575    },
576    Resolved {
577        context: Result<RenderContext, BuildDeferredContextError>,
578    },
579}
580
581impl ContextState {
582    fn provide_surface(&mut self, surface: Arc<wgpu::Surface<'static>>) {
583        match self {
584            Self::WaitingForSurface { builder, } => {
585                builder.with_compatible_surface(Some(surface));
586                let builder =  // IF UNUSED, add feature `poll` or `webgl`
587                    builder.clone();
588                // If you're going to be using DeferredContext, you need to provide
589                // a polling method - either by adding feature `poll` (for native)
590                // or `webgl` (for web).
591
592                #[cfg(all(feature = "webgl", not(feature = "poll")))]
593                {
594                    use std::sync::mpsc::sync_channel;
595                    let (send, recv) = sync_channel::<Result<RenderContext, BuildContextError>>(1);
596                    wasm_bindgen_futures::spawn_local(async move {
597                        let context = builder.build().await;
598                        let _ = send.send(context);
599                    });
600
601                    *self = Self::Building { receiver: recv, };
602                }
603
604                #[cfg(all(not(feature = "webgl"), feature = "poll"))]
605                {
606                    let context = pollster::block_on(builder.build())
607                        .map(|c| c.into()).map_err(|e| e.into());
608                    *self = Self::Resolved { context, };
609                }
610
611                #[cfg(all(feature = "webgl", feature = "poll"))]
612                unreachable!("Ambiguous whether to start JS thread or poll, only one feature of `webgl` or `poll` should be enabled");
613
614                #[cfg(not(any(feature = "webgl", feature = "poll")))]
615                unreachable!("No way to poll for context, one feature of `webgl` or `poll` should be enabled");
616            },
617            _ => {},
618        }
619    }
620
621    fn poll_resolve_context(&mut self) -> Result<&RenderContext, GetDeferredContextError> {
622        match self {
623            Self::WaitingForSurface { .. } => Err(GetDeferredContextError::RequiresSurface),
624            Self::Building { receiver, } => {
625                use std::sync::mpsc::TryRecvError;
626
627                match receiver.try_recv() {
628                    Ok(context) => {
629                        let context = context.map_err(|e| e.into());
630                        *self = Self::Resolved { context, };
631                        let Self::Resolved { context, } = self else { unreachable!() };
632                        Ok(context.as_ref().map_err(|e| e.clone())?)
633                    },
634                    Err(TryRecvError::Disconnected) => {
635                        *self = Self::Resolved { context: Err(BuildDeferredContextError::BuilderThreadDied), };
636                        Err(BuildDeferredContextError::BuilderThreadDied.into())
637                    },
638                    Err(TryRecvError::Empty) => Err(GetDeferredContextError::StillBuilding),
639                }
640            },
641            Self::Resolved { context, } => Ok(context.as_ref().map_err(|e| e.clone())?),
642        }
643    }
644
645    /*
646    fn try_get_context(&self) -> Option<Rc<RenderContext>> {
647        match self {
648            Self::Resolved { context, } => context.as_ref().map(|c| c.ok()).flatten().clone(),
649            _ => None,
650        }
651    }
652    */
653}
654
655
656/// Defer the contstruction of the [`RenderContext`] until a surface is
657/// constructed.
658///
659/// WebGL really screws up the normal workflow of "first create your _wgpu_
660/// handles, then make windows and stuff", because creating the
661/// [`wgpu::Adapter`](https://docs.rs/wgpu/latest/wgpu/struct.Adapter.html)
662/// requires a
663/// [`wgpu::Surface`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html)
664/// to be passed in after creating it with a
665/// [`wgpu::Instance`](https://docs.rs/wgpu/latest/wgpu/struct.Instance.html)
666/// using the configuration of a
667/// [`winit::window::Window`](https://docs.rs/winit/latest/winit/window/struct.Window.html).
668/// To top it all off, this painful back-and-forth involves `async`.
669///
670/// On web, this struct caches a builder and constructs the [`RenderContext`]
671/// in a JS thread oppurtunistically when it is used to construct a
672/// [`SurfaceWindow`].  
673/// Native platforms block the thread when constructing, it's not an operation
674/// that should take very long. _pollster_ doesn't seem to get along with JS
675/// however.
676///
677/// See example usage below. There's a bit of jumping through hoops, but most of
678/// it is handled in the background.
679/// ```rust
680/// impl ApplicationHandler for YourApp {
681///     fn resume(&mut self, event_loop: &ActiveEventLoop) {
682///         // Create a window and provide the surface to the deferred context to start building.
683///         // In the worst case (web), at this point it will have an unconfigured surface and no config.
684///         let window = self.window.get_or_init(|| {
685///             let mut attributes = WindowAttributes::default();
686///             #[cfg(target_arch = "wasm32")]
687///             attributes.with_canvas(get_webgl_canvas());
688///             // On web, will provide the surface to the context builder so it can
689///             // start building the context. The surface isn't configured until it's needed.
690///             self.deferred_context.create_window(event_loop, attributes, Default::default())
691///                 .expect("Couldn't create window");
692///         });
693///
694///         // Can't do much else until we have a valid context, which may take until
695///         // the next function call to finish building. Move on to main loop.
696///         self.suspended = false;
697///     }
698///
699///     fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: _, event: _) {
700///         if self.suspended { return; }
701///
702///         let context = match self.defered_context.get_context() {
703///             Ok(c) => c,
704///             // Something went wrong building the context, abort.
705///             Err(GetDeferredContextError::Build(e)) => panic!("Error building context: {e}"),
706///             // resume() didn't run for some reason, RenderContext has no surface
707///             // to use to initialize with (on web).
708///             Err(GetDeferredContextError::RequiresSurface) => unreachable!("Surface should have been provided in resume()"),
709///             // Still building, will try again next window_event().
710///             Err(GetDeferredContextError::StillBuilding) => return,
711///         };
712///
713///         // Now that the context is ready, we can finish configuring the window surface.
714///         // init_surface() will create everything when needed and make sure it's configured.
715///         let active_window = self.window.get().expect("Window should have created in resume()")
716///             .init_with_context(&context).expect("Couldn't init window");
717///
718///         // Side-note: Vertex/shader buffers, pipelines, etc. are also dependent on the
719///         // RenderContext being configured (with a surface on web).
720///         // Put the buffers in an Option/OnceCell and generate them ASAP.
721///         let buffers_and_shit = self.buffers_and_shit.get_or_insert_with(|| init_buffers_and_shit(&context, active_window));
722///
723///         Self::finally_render(context, active_window);
724///     }
725///
726///     fn suspended(&mut self, event_loop: &ActiveEventLoop) {
727///         // Android requires surfaces be dropped on suspend. init_surface() above
728///         // will re-create and configure the window surface automatically when unsuspended.
729///         self.window.get_mut().map(|w| w.drop_surface());
730///         self.suspended = true;
731///     }
732/// }
733/// ```
734#[derive(Debug)]
735pub struct DeferredContext {
736    instance: wgpu::Instance,
737    context: UnsafeCell<ContextState>,
738}
739
740impl DeferredContext {
741    /// Create a new deferred builder from a regular builder, with the appropriate
742    /// method for the platform.
743    ///
744    /// On web, this will call [`Self::new_requiring_surface`]; on native with the
745    /// `poll` feature, [`Self::new_poll`].
746    #[cfg(any(feature = "webgl", feature = "poll", docs))]
747    pub fn new(builder: RenderContextBuilder) -> Self {
748        #[cfg(feature = "webgl")]
749        return Self::new_requiring_surface(builder);
750        #[cfg(feature = "poll")]
751        return Self::new_poll(builder);
752    }
753
754    /// Create a new deferred builder from a regular builder, in "waiting-for-surface"
755    /// mode. On native you don't have to use this unless you really want to validate
756    /// the surface format is compatible.
757    #[cfg(any(feature = "webgl", feature = "poll", docs))]
758    pub fn new_requiring_surface(mut builder: RenderContextBuilder) -> Self {
759        let instance = builder.get_instance();
760        Self {
761            instance,
762            context: ContextState::WaitingForSurface { builder, }.into(),
763        }
764    }
765
766    /// Create a future that immediately tries to `await` the finalization of a
767    /// [`RenderContextBuilder`].
768    ///
769    /// It's not really intended to use _mew_ in an async environment - the app as
770    /// a whole can contain async logic, sure, but rendering stuff deserves its own
771    /// system thread.
772    #[cfg(any(not(feature = "webgl"), docs))]
773    pub async fn new_async(mut builder: RenderContextBuilder) -> Self {
774        let instance = builder.get_instance();
775        let context = builder.build().await.map(|c| c.into()).map_err(|e| e.into());
776        Self {
777            instance,
778            context: ContextState::Resolved { context, }.into(),
779        }
780    }
781
782    /// Immediately try to finalize a [`RenderContextBuilder`], by blocking on
783    /// [`Self::new_async`].
784    ///
785    /// Realistically, it should not take long at all to build a [`RenderContext`].
786    /// Even if it does, it should be fine to block the main render thread because,
787    /// well, what are you going to render without a context?
788    #[cfg(any(all(not(feature = "webgl"), feature = "poll"), docs))]
789    pub fn new_poll(builder: RenderContextBuilder) -> Self {
790        pollster::block_on(Self::new_async(builder))
791    }
792
793    /// Create a [`SurfaceWindow`], and at the same time, provide the builder with
794    /// a WebGL surface so it can create a valid
795    /// [`wgpu::Adapter`](https://docs.rs/wgpu/latest/wgpu/struct.Adapter.html).
796    ///
797    /// In effect, you can almost ignore the whole initialization workflow - just
798    /// make a window in the event loop, and then get the context to draw to it.
799    /// If you can't get the context, try again until you can.
800    pub fn create_window(&self, event_loop: &ActiveEventLoop, attributes: WindowAttributes, config_opts: SurfaceConfigOptions) -> Result<SurfaceWindow, CreateWindowError> {
801        let mut window = SurfaceWindow::new(event_loop, attributes, config_opts)?;
802        let surface = Arc::new(window.make_surface(&self.instance)?);
803        let _ = window.surface.get_mut().insert(surface.clone());
804        unsafe { &mut *self.context.get() }.provide_surface(surface);
805        /*
806        if let Ok(context) = self.get_context() {
807            window.init_surface(&context.instance, &context.adapter, &context.device)?;
808        }
809        */
810        Ok(window)
811    }
812
813    /// Get a clone of the
814    /// [`wgpu::Instance`](https://docs.rs/wgpu/latest/wgpu/struct.Instance.html),
815    /// which may have been created automatically.
816    pub fn get_instance(&self) -> wgpu::Instance {
817        self.instance.clone()
818    }
819
820    /// Try to get the [`RenderContext`].
821    ///
822    /// If it hasn't been built yet, or if it ran into an error while building,
823    /// you'll get an `Err(_)` - try again next frame.
824    ///
825    /// On web, make sure to create a window with [`Self::create_window`], as you
826    /// can't get a
827    /// [`wgpu::Adapter`](https://docs.rs/wgpu/latest/wgpu/struct.Adapter.html)
828    /// before providing a
829    /// [`wgpu::Surface`](https://docs.rs/wgpu/latest/wgpu/struct.Surface.html).  
830    /// Makes sense, right? Totally not backwards? That's why I made this.
831    pub fn get_context(&self) -> Result<&RenderContext, GetDeferredContextError> {
832        let context_state = unsafe { &mut *self.context.get() };
833        context_state.poll_resolve_context()
834    }
835}