Skip to main content

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