Skip to main content

wasdk_wgpu/
instance.rs

1//! The instance: the host's `gpu`, and the adapters it hands out.
2
3use std::pin::Pin;
4use std::sync::Arc;
5
6use wasdk::webgpu::{self, Gpu, GpuRequestAdapterOptions};
7use wgpu::custom::{
8    DispatchAdapter, DispatchSurface, EnumerateAdapterFuture, InstanceInterface,
9    RequestAdapterFuture,
10};
11use wgpu::{Backends, RequestAdapterError, WgslLanguageFeatures};
12
13use crate::adapter::WasdkAdapter;
14use crate::convert;
15use crate::gap::wasi_gap;
16use crate::surface::{SharedView, WasdkSurface, shared_view_of};
17
18/// The instance behind a [`wgpu::Instance`] from this backend:
19/// `instance.as_custom::<WasdkInstance>()`.
20#[derive(Debug)]
21pub struct WasdkInstance {
22    gpu: Arc<Gpu>,
23    /// What the program asked for, kept for the error an adapter request reports.
24    requested_backends: Backends,
25}
26
27impl WasdkInstance {
28    /// The wasi `gpu` behind the instance.
29    pub fn gpu(&self) -> &Gpu {
30        &self.gpu
31    }
32
33    /// The format the host draws its canvases in, which a view's surface offers.
34    pub(crate) fn preferred_canvas_format(&self) -> wgpu::TextureFormat {
35        convert::texture_format_from_wasi(self.gpu.get_preferred_canvas_format())
36    }
37}
38
39/// wasi:webgpu is WebGPU, so it answers to wgpu's WebGPU backend bit.
40const BACKEND: Backends = Backends::BROWSER_WEBGPU;
41
42const WGSL_LANGUAGE_FEATURES: &[(WgslLanguageFeatures, &str)] = &[
43    (
44        WgslLanguageFeatures::ReadOnlyAndReadWriteStorageTextures,
45        "readonly_and_readwrite_storage_textures",
46    ),
47    (
48        WgslLanguageFeatures::Packed4x8IntegerDotProduct,
49        "packed_4x8_integer_dot_product",
50    ),
51    (
52        WgslLanguageFeatures::UnrestrictedPointerParameters,
53        "unrestricted_pointer_parameters",
54    ),
55    (
56        WgslLanguageFeatures::PointerCompositeAccess,
57        "pointer_composite_access",
58    ),
59];
60
61// `create_surface` is `unsafe` in the trait, and reads the view its window handle points at.
62#[allow(unsafe_code)]
63impl InstanceInterface for WasdkInstance {
64    fn new(desc: wgpu::InstanceDescriptor) -> Self {
65        WasdkInstance {
66            gpu: Arc::new(webgpu::get_gpu()),
67            requested_backends: desc.backends,
68        }
69    }
70
71    /// A surface on the view a [`SharedView`]'s window handle points at, holding the view from
72    /// here on; any other handle is a gap (`surface`).
73    unsafe fn create_surface(
74        &self,
75        target: wgpu::SurfaceTargetUnsafe,
76    ) -> Result<DispatchSurface, wgpu::CreateSurfaceError> {
77        let wgpu::SurfaceTargetUnsafe::RawHandle {
78            raw_window_handle, ..
79        } = target
80        else {
81            wasi_gap("a surface from a target other than a window handle")
82        };
83        let pointer = shared_view_of(raw_window_handle);
84        // SAFETY: wgpu's contract for the target is that its window is alive for the call, and a
85        // `WebCanvas` handle in a component is only ever a `SharedView`'s, pointing at it.
86        let view: &SharedView = unsafe { pointer.as_ref() };
87        let surface = WasdkSurface::new(view, self.preferred_canvas_format());
88        Ok(DispatchSurface::custom(surface))
89    }
90
91    fn request_adapter(
92        &self,
93        options: &wgpu::RequestAdapterOptions<'_, '_>,
94    ) -> Pin<Box<dyn RequestAdapterFuture>> {
95        // Every adapter of the engine's draws every view, so a compatible surface narrows nothing.
96        let requested_backends = self.requested_backends;
97        let wasi_options = requested_backends
98            .contains(BACKEND)
99            .then(|| GpuRequestAdapterOptions {
100                feature_level: None,
101                power_preference: convert::power_preference_to_wasi(options.power_preference),
102                force_fallback_adapter: Some(options.force_fallback_adapter),
103                xr_compatible: None,
104            });
105        let gpu = Arc::clone(&self.gpu);
106        Box::pin(async move {
107            let adapter = match wasi_options {
108                Some(wasi_options) => gpu.request_adapter(Some(wasi_options)).await,
109                None => None,
110            };
111            adapter
112                .map(|adapter| DispatchAdapter::custom(WasdkAdapter::new(adapter)))
113                .ok_or_else(|| no_adapter(requested_backends))
114        })
115    }
116
117    fn poll_all_devices(&self, _force_wait: bool) -> bool {
118        // Devices make progress on their own; see `runtime`.
119        true
120    }
121
122    fn wgsl_language_features(&self) -> WgslLanguageFeatures {
123        let supported = self.gpu.wgsl_language_features();
124        WGSL_LANGUAGE_FEATURES
125            .iter()
126            .filter(|(_, name)| supported.has(name))
127            .fold(WgslLanguageFeatures::empty(), |features, (bit, _)| {
128                features | bit.clone()
129            })
130    }
131
132    fn enumerate_adapters(&self, backends: Backends) -> Pin<Box<dyn EnumerateAdapterFuture>> {
133        if !backends.contains(BACKEND) {
134            return Box::pin(async { Vec::new() });
135        }
136        let adapter = self.request_adapter(&wgpu::RequestAdapterOptions::default());
137        Box::pin(async move { adapter.await.into_iter().collect() })
138    }
139}
140
141fn no_adapter(requested_backends: Backends) -> RequestAdapterError {
142    RequestAdapterError::NotFound {
143        active_backends: BACKEND,
144        requested_backends,
145        supported_backends: BACKEND,
146        no_fallback_backends: Backends::empty(),
147        no_adapter_backends: BACKEND,
148        incompatible_surface_backends: Backends::empty(),
149    }
150}