Skip to main content

wgpu_context/
lib.rs

1// Copyright 2022 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Simple helpers for managing wgpu state and surfaces.
5
6use wgpu::{
7    Adapter, Device, Features, Instance, Limits, MemoryHints, Queue, Surface, SurfaceTarget,
8};
9
10mod buffer_renderer;
11mod error;
12mod surface_renderer;
13mod util;
14
15pub use buffer_renderer::{BufferRenderer, BufferRendererConfig};
16pub use error::WgpuContextError;
17pub use surface_renderer::{SurfaceRenderer, SurfaceRendererConfiguration, TextureConfiguration};
18pub use util::block_on_wgpu;
19
20/// A wgpu `Device`, it's associated `Queue`, and the `Adapter` and `Instance` used to create them
21#[derive(Clone, Debug)]
22pub struct DeviceHandle {
23    pub instance: Instance,
24    pub adapter: Adapter,
25    pub device: Device,
26    pub queue: Queue,
27}
28
29impl DeviceHandle {
30    /// Creates a `DeviceHandle` with `Device` that's compatible with the specified `Surface`
31    pub async fn new_from_compatible_surface(
32        instance: Instance,
33        compatible_surface: Option<&Surface<'_>>,
34        extra_features: Option<Features>,
35        override_limits: Option<Limits>,
36    ) -> Result<Self, WgpuContextError> {
37        let adapter =
38            wgpu::util::initialize_adapter_from_env_or_default(&instance, compatible_surface)
39                .await?;
40
41        // Determine features to request
42        // The user may request additional features
43        let requested_features = extra_features.unwrap_or(Features::empty());
44        let available_features = adapter.features();
45        let required_features = requested_features & available_features;
46
47        // Determine limits to request
48        // The user may override the limits
49        let required_limits = override_limits.clone().unwrap_or_else(|| Limits {
50            // Fix iOS simulator
51            max_inter_stage_shader_variables: 15,
52            ..Limits::default()
53        });
54
55        // Create the device and the queue
56        let descripter = wgpu::DeviceDescriptor {
57            label: None,
58            required_features,
59            required_limits,
60            memory_hints: MemoryHints::MemoryUsage,
61            trace: wgpu::Trace::default(),
62            experimental_features: wgpu::ExperimentalFeatures::default(),
63        };
64        let (device, queue) = adapter.request_device(&descripter).await?;
65
66        // Create the device handle and store in the pool
67        Ok(DeviceHandle {
68            instance,
69            adapter,
70            device,
71            queue,
72        })
73    }
74
75    /// Creates a new surface for the specified window and dimensions.
76    pub async fn create_surface<'w>(
77        &mut self,
78        window: impl Into<SurfaceTarget<'w>>,
79        surface_config: SurfaceRendererConfiguration,
80        intermediate_texture_config: Option<TextureConfiguration>,
81    ) -> Result<SurfaceRenderer<'w>, WgpuContextError> {
82        // Create a surface from the window handle
83        let surface = self.instance.create_surface(window.into())?;
84        SurfaceRenderer::new(
85            surface,
86            surface_config,
87            intermediate_texture_config,
88            self.clone(),
89        )
90    }
91}
92
93/// Simple render context that maintains wgpu state for rendering the pipeline.
94pub struct WGPUContext {
95    /// A WGPU `Instance`. This only needs to be created once per application.
96    pub instance: Instance,
97    /// A pool of already-created devices so that we can avoid recreating devices
98    /// when we already have a suitable one available
99    pub device_pool: Vec<DeviceHandle>,
100
101    // Config
102    extra_features: Option<Features>,
103    override_limits: Option<Limits>,
104}
105
106impl Default for WGPUContext {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112impl WGPUContext {
113    pub fn new() -> Self {
114        Self::with_features_and_limits(None, None)
115    }
116
117    pub fn with_features_and_limits(
118        extra_features: Option<Features>,
119        override_limits: Option<Limits>,
120    ) -> Self {
121        Self {
122            instance: Instance::new(wgpu::InstanceDescriptor {
123                backends: wgpu::Backends::from_env().unwrap_or_default(),
124                flags: wgpu::InstanceFlags::from_build_config().with_env(),
125                backend_options: wgpu::BackendOptions::from_env_or_default(),
126                memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
127
128                // TODO: support passing display handle
129                // Needed for opengl/webgl
130                display: None,
131            }),
132            device_pool: Vec::new(),
133            extra_features,
134            override_limits,
135        }
136    }
137
138    pub fn extra_features(&self) -> Option<Features> {
139        self.extra_features
140    }
141
142    pub fn override_limits(&self) -> Option<Limits> {
143        self.override_limits.clone()
144    }
145
146    /// Creates a new surface for the specified window and dimensions.
147    pub fn create_surface<'w>(
148        &self,
149        window: impl Into<SurfaceTarget<'w>>,
150    ) -> Result<Surface<'w>, WgpuContextError> {
151        Ok(self.instance.create_surface(window.into())?)
152    }
153
154    /// Creates a new surface for the specified window and dimensions.
155    pub async fn create_surface_renderer<'w>(
156        &mut self,
157        window: impl Into<SurfaceTarget<'w>>,
158        surface_config: SurfaceRendererConfiguration,
159        intermediate_texture_config: Option<TextureConfiguration>,
160    ) -> Result<SurfaceRenderer<'w>, WgpuContextError> {
161        // Create a surface from the window handle
162        let surface = self.create_surface(window.into())?;
163
164        // Find or create a suitable device for rendering to the surface
165        let dev_id = self
166            .find_or_create_device(Some(&surface))
167            .await
168            .or(Err(WgpuContextError::NoCompatibleDevice))?;
169        let device_handle = self.device_pool[dev_id].clone();
170
171        SurfaceRenderer::new(
172            surface,
173            surface_config,
174            intermediate_texture_config,
175            device_handle,
176        )
177    }
178
179    /// Creates a new `BufferRenderer` for the specified dimensions.
180    pub async fn create_buffer_renderer(
181        &mut self,
182        config: BufferRendererConfig,
183    ) -> Result<BufferRenderer, WgpuContextError> {
184        // Find or create a suitable device for rendering to the surface
185        let dev_id = self
186            .find_or_create_device(None)
187            .await
188            .or(Err(WgpuContextError::NoCompatibleDevice))?;
189        let device_handle = self.device_pool[dev_id].clone();
190
191        Ok(BufferRenderer::new(config, device_handle, dev_id))
192    }
193
194    /// Finds or creates a compatible device handle id.
195    pub async fn find_or_create_device(
196        &mut self,
197        compatible_surface: Option<&Surface<'_>>,
198    ) -> Result<usize, WgpuContextError> {
199        match self.find_existing_device_idx(compatible_surface) {
200            Some(device_id) => Ok(device_id),
201            None => self.create_device(compatible_surface).await,
202        }
203    }
204
205    /// Finds or creates a compatible device handle id.
206    pub fn find_compatible_device_handle(
207        &mut self,
208        compatible_surface: Option<&Surface<'_>>,
209    ) -> Option<DeviceHandle> {
210        self.find_existing_device_idx(compatible_surface)
211            .map(|idx| self.device_pool[idx].clone())
212    }
213
214    /// Finds  a compatible device handle id.
215    fn find_existing_device_idx(&self, compatible_surface: Option<&Surface<'_>>) -> Option<usize> {
216        match compatible_surface {
217            Some(s) => self
218                .device_pool
219                .iter()
220                .enumerate()
221                .find(|(_, d)| d.adapter.is_surface_supported(s))
222                .map(|(i, _)| i),
223            None => (!self.device_pool.is_empty()).then_some(0),
224        }
225    }
226
227    pub fn create_device_handle(
228        &self,
229        compatible_surface: Option<&Surface<'_>>,
230    ) -> impl Future<Output = Result<DeviceHandle, WgpuContextError>> {
231        DeviceHandle::new_from_compatible_surface(
232            self.instance.clone(),
233            compatible_surface,
234            self.extra_features,
235            self.override_limits.clone(),
236        )
237    }
238
239    /// Creates a compatible device handle id.
240    async fn create_device(
241        &mut self,
242        compatible_surface: Option<&Surface<'_>>,
243    ) -> Result<usize, WgpuContextError> {
244        let device_handle = self.create_device_handle(compatible_surface).await?;
245        self.device_pool.push(device_handle);
246        Ok(self.device_pool.len() - 1)
247    }
248}