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}