floem_renderer/
gpu_resources.rs

1//! Asynchronous GPU resource acquisition for rendering with wgpu.
2//!
3//! To support WebGPU on WASM, the GPU resources need to be acquired asynchronously because
4//! the wgpu library provides only asynchronous methods for requesting adapters and devices.
5//! In WASM, blocking the main thread is not an option, as the JavaScript
6//! execution model does not support thread blocking. Consequently, we must use asynchronous
7//! execution (via `wasm_bindgen_futures`) to handle these operations.
8//!
9//! Based on a [code snippet by Luke Petherbridge](https://github.com/rust-windowing/winit/issues/3560#issuecomment-2085754164).
10
11use std::{future::Future, sync::Arc};
12
13#[cfg(feature = "crossbeam")]
14use crossbeam::channel::{bounded as sync_channel, Receiver};
15#[cfg(not(feature = "crossbeam"))]
16use std::sync::mpsc::{sync_channel, Receiver};
17use wgpu::Backends;
18
19use winit::window::{Window, WindowId};
20
21/// The acquired GPU resources needed for rendering with wgpu.
22pub struct GpuResources {
23    /// The rendering surface, representing the window or screen where the graphics will be displayed.
24    /// It is the interface between wgpu and the platform's windowing system, enabling rendering
25    /// onto the screen.
26    pub surface: wgpu::Surface<'static>,
27
28    /// The adapter that represents the GPU or a rendering backend. It provides information about
29    /// the capabilities of the hardware and is used to request a logical device (`wgpu::Device`).
30    pub adapter: wgpu::Adapter,
31
32    /// The logical device that serves as an interface to the GPU. It is responsible for creating
33    /// resources such as buffers, textures, and pipelines, and manages the execution of commands.
34    /// The `device` provides a connection to the physical hardware represented by the `adapter`.
35    pub device: wgpu::Device,
36
37    /// The command queue that manages the submission of command buffers to the GPU for execution.
38    /// It is used to send rendering and computation commands to the device. The `queue` ensures
39    /// that commands are executed in the correct order and manages synchronization.
40    pub queue: wgpu::Queue,
41}
42
43impl GpuResources {
44    /// Request GPU resources
45    ///
46    /// # Parameters
47    /// - `on_result`: Function to notify upon completion or error.
48    /// - `window`: The window to associate with the created surface.
49    pub fn request<F: Fn(WindowId) + 'static>(
50        on_result: F,
51        window: Arc<dyn Window>,
52    ) -> Receiver<Result<Self, GpuResourceError>> {
53        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
54            backends: wgpu::util::backend_bits_from_env().unwrap_or(Backends::all()),
55            ..Default::default()
56        });
57        // Channel passing to do async out-of-band within the winit event_loop since wasm can't
58        // execute futures with a return value
59        let (tx, rx) = sync_channel(1);
60
61        spawn({
62            async move {
63                let surface = match instance.create_surface(Arc::clone(&window)) {
64                    Ok(surface) => surface,
65                    Err(err) => {
66                        tx.send(Err(GpuResourceError::SurfaceCreationError(err)))
67                            .unwrap();
68                        on_result(window.id());
69                        return;
70                    }
71                };
72
73                let Some(adapter) = instance
74                    .request_adapter(&wgpu::RequestAdapterOptions {
75                        power_preference: wgpu::PowerPreference::default(),
76                        compatible_surface: Some(&surface),
77                        force_fallback_adapter: false,
78                    })
79                    .await
80                else {
81                    tx.send(Err(GpuResourceError::AdapterNotFoundError))
82                        .unwrap();
83                    on_result(window.id());
84                    return;
85                };
86
87                tx.send(
88                    adapter
89                        .request_device(
90                            &wgpu::DeviceDescriptor {
91                                label: None,
92                                ..Default::default()
93                            },
94                            None,
95                        )
96                        .await
97                        .map_err(GpuResourceError::DeviceRequestError)
98                        .map(|(device, queue)| Self {
99                            surface,
100                            adapter,
101                            device,
102                            queue,
103                        }),
104                )
105                .unwrap();
106                on_result(window.id());
107            }
108        });
109        rx
110    }
111}
112
113/// Possible errors during GPU resource setup.
114#[derive(Debug)]
115pub enum GpuResourceError {
116    SurfaceCreationError(wgpu::CreateSurfaceError),
117    AdapterNotFoundError,
118    DeviceRequestError(wgpu::RequestDeviceError),
119}
120
121impl std::fmt::Display for GpuResourceError {
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        match self {
124            GpuResourceError::SurfaceCreationError(err) => {
125                write!(f, "Surface creation error: {}", err)
126            }
127            GpuResourceError::AdapterNotFoundError => {
128                write!(f, "Failed to find a suitable GPU adapter")
129            }
130            GpuResourceError::DeviceRequestError(err) => write!(f, "Device request error: {}", err),
131        }
132    }
133}
134
135/// Spawns a future for execution, adapting to the target environment.
136///
137/// On WASM (`wasm32`), it uses `wasm_bindgen_futures::spawn_local` to avoid blocking
138/// the main thread. On other targets, it uses `pollster::block_on` to synchronously
139/// wait for the future to complete.
140pub fn spawn<F>(future: F)
141where
142    F: Future<Output = ()> + 'static,
143{
144    #[cfg(target_arch = "wasm32")]
145    wasm_bindgen_futures::spawn_local(future);
146    #[cfg(not(target_arch = "wasm32"))]
147    futures::executor::block_on(future)
148}