Skip to main content

wasdk_wgpu/
device.rs

1//! The device: every object it creates, its error scopes, and what happens to it.
2
3use std::pin::Pin;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicU32, Ordering};
6
7use futures::StreamExt;
8use wasdk::webgpu::GpuDevice;
9use wgpu::custom::{
10    BoxDeviceLostCallback, DeviceInterface, DispatchBindGroup, DispatchBindGroupLayout,
11    DispatchBlas, DispatchBuffer, DispatchCommandEncoder, DispatchComputePipeline, DispatchDevice,
12    DispatchExternalTexture, DispatchPipelineCache, DispatchPipelineLayout, DispatchQuerySet,
13    DispatchRenderBundleEncoder, DispatchRenderPipeline, DispatchSampler, DispatchShaderModule,
14    DispatchTexture, DispatchTlas, PopErrorScopeFuture,
15};
16use wgpu::{AdapterInfo, Features, Limits};
17
18use crate::gap::wasi_gap;
19use crate::{
20    binding, buffer, command, convert, pass, pipeline, query, runtime, sampler, shader, texture,
21};
22
23/// The device behind a [`wgpu::Device`] from this backend: `device.as_custom::<WasdkDevice>()`.
24#[derive(Debug)]
25pub struct WasdkDevice {
26    device: Arc<GpuDevice>,
27    /// How many error scopes are open, so a pop out of order is caught here.
28    open_error_scopes: AtomicU32,
29}
30
31downcast!(WasdkDevice: api wgpu::Device, dispatch DispatchDevice);
32
33impl WasdkDevice {
34    pub(crate) fn new(device: GpuDevice) -> Self {
35        WasdkDevice {
36            device: Arc::new(device),
37            open_error_scopes: AtomicU32::new(0),
38        }
39    }
40
41    /// The wasi device, which a view's canvas context is configured against and a hardware
42    /// buffer is imported on as a texture.
43    pub fn gpu_device(&self) -> &GpuDevice {
44        &self.device
45    }
46}
47
48// `create_shader_module_passthrough`, `create_pipeline_cache` and the debugger-capture pair
49// are `unsafe` in the trait; nothing they do here is.
50#[allow(unsafe_code)]
51impl DeviceInterface for WasdkDevice {
52    fn features(&self) -> Features {
53        convert::features_from_wasi(&self.device.features())
54    }
55
56    fn limits(&self) -> Limits {
57        convert::limits_from_wasi(&self.device.limits())
58    }
59
60    fn adapter_info(&self) -> AdapterInfo {
61        convert::adapter_info_from_wasi(&self.device.adapter_info())
62    }
63
64    fn create_shader_module(
65        &self,
66        desc: wgpu::ShaderModuleDescriptor<'_>,
67        _runtime_checks: wgpu::ShaderRuntimeChecks,
68    ) -> DispatchShaderModule {
69        shader::create_shader_module(&self.device, &desc)
70    }
71
72    unsafe fn create_shader_module_passthrough(
73        &self,
74        desc: &wgpu::ShaderModuleDescriptorPassthrough<'_>,
75    ) -> DispatchShaderModule {
76        shader::create_shader_module_passthrough(&self.device, desc)
77    }
78
79    fn create_bind_group_layout(
80        &self,
81        desc: &wgpu::BindGroupLayoutDescriptor<'_>,
82    ) -> DispatchBindGroupLayout {
83        binding::create_bind_group_layout(&self.device, desc)
84    }
85
86    fn create_bind_group(&self, desc: &wgpu::BindGroupDescriptor<'_>) -> DispatchBindGroup {
87        binding::create_bind_group(&self.device, desc)
88    }
89
90    fn create_pipeline_layout(
91        &self,
92        desc: &wgpu::PipelineLayoutDescriptor<'_>,
93    ) -> DispatchPipelineLayout {
94        pipeline::create_pipeline_layout(&self.device, desc)
95    }
96
97    fn create_render_pipeline(
98        &self,
99        desc: &wgpu::RenderPipelineDescriptor<'_>,
100    ) -> DispatchRenderPipeline {
101        pipeline::create_render_pipeline(&self.device, desc)
102    }
103
104    fn create_mesh_pipeline(
105        &self,
106        _desc: &wgpu::MeshPipelineDescriptor<'_>,
107    ) -> DispatchRenderPipeline {
108        wasi_gap("mesh shaders (Device::create_mesh_pipeline)")
109    }
110
111    fn create_compute_pipeline(
112        &self,
113        desc: &wgpu::ComputePipelineDescriptor<'_>,
114    ) -> DispatchComputePipeline {
115        pipeline::create_compute_pipeline(&self.device, desc)
116    }
117
118    unsafe fn create_pipeline_cache(
119        &self,
120        _desc: &wgpu::PipelineCacheDescriptor<'_>,
121    ) -> DispatchPipelineCache {
122        wasi_gap("pipeline caches (Device::create_pipeline_cache)")
123    }
124
125    fn create_buffer(&self, desc: &wgpu::BufferDescriptor<'_>) -> DispatchBuffer {
126        buffer::create_buffer(&self.device, desc)
127    }
128
129    fn create_texture(&self, desc: &wgpu::TextureDescriptor<'_>) -> DispatchTexture {
130        texture::create_texture(&self.device, desc)
131    }
132
133    fn create_external_texture(
134        &self,
135        _desc: &wgpu::ExternalTextureDescriptor<'_>,
136        _planes: &[&wgpu::TextureView],
137    ) -> DispatchExternalTexture {
138        wasi_gap("external textures (Device::create_external_texture)")
139    }
140
141    fn create_blas(
142        &self,
143        _desc: &wgpu::CreateBlasDescriptor<'_>,
144        _sizes: wgpu::BlasGeometrySizeDescriptors,
145    ) -> (Option<u64>, DispatchBlas) {
146        wasi_gap("ray tracing (Device::create_blas)")
147    }
148
149    fn create_tlas(&self, _desc: &wgpu::CreateTlasDescriptor<'_>) -> DispatchTlas {
150        wasi_gap("ray tracing (Device::create_tlas)")
151    }
152
153    fn create_sampler(&self, desc: &wgpu::SamplerDescriptor<'_>) -> DispatchSampler {
154        sampler::create_sampler(&self.device, desc)
155    }
156
157    fn create_query_set(&self, desc: &wgpu::QuerySetDescriptor<'_>) -> DispatchQuerySet {
158        query::create_query_set(&self.device, desc)
159    }
160
161    fn create_command_encoder(
162        &self,
163        desc: &wgpu::CommandEncoderDescriptor<'_>,
164    ) -> DispatchCommandEncoder {
165        command::create_command_encoder(&self.device, desc)
166    }
167
168    fn create_render_bundle_encoder(
169        &self,
170        desc: &wgpu::RenderBundleEncoderDescriptor<'_>,
171    ) -> DispatchRenderBundleEncoder {
172        pass::create_render_bundle_encoder(&self.device, desc)
173    }
174
175    fn set_device_lost_callback(&self, callback: BoxDeviceLostCallback) {
176        let lost = self.device.lost();
177        runtime::spawn(async move {
178            let info = lost.await;
179            callback(
180                convert::device_lost_reason_from_wasi(info.reason()),
181                info.message(),
182            );
183        });
184    }
185
186    fn on_uncaptured_error(&self, handler: Arc<dyn wgpu::UncapturedErrorHandler>) {
187        let mut errors = self.device.on_uncaptured_error().into_stream();
188        runtime::spawn(async move {
189            while let Some(error) = errors.next().await {
190                handler(convert::error_from_wasi(&error));
191            }
192        });
193    }
194
195    fn push_error_scope(&self, filter: wgpu::ErrorFilter) -> u32 {
196        let index = self.open_error_scopes.fetch_add(1, Ordering::Relaxed);
197        self.device
198            .push_error_scope(convert::error_filter_to_wasi(filter));
199        index
200    }
201
202    fn pop_error_scope(&self, index: u32) -> Pin<Box<dyn PopErrorScopeFuture>> {
203        let open = self.open_error_scopes.load(Ordering::Relaxed);
204        assert!(
205            open == index + 1,
206            "wasdk-wgpu: error scope {index} popped with {open} open; scopes pop in reverse order"
207        );
208        self.open_error_scopes.store(index, Ordering::Relaxed);
209        let device = Arc::clone(&self.device);
210        Box::pin(async move {
211            // The only wasi error is "no scope to pop", which the count above rules out.
212            let error = device.pop_error_scope().await.ok().flatten()?;
213            Some(convert::error_from_wasi(&error))
214        })
215    }
216
217    unsafe fn start_graphics_debugger_capture(&self) {
218        // WebGPU has no capture API.
219    }
220
221    unsafe fn stop_graphics_debugger_capture(&self) {
222        // WebGPU has no capture API.
223    }
224
225    fn poll(
226        &self,
227        _poll_type: wgpu::wgt::PollType<u64>,
228    ) -> Result<wgpu::PollStatus, wgpu::PollError> {
229        // The host drives the device; callbacks land when the program yields (see `runtime`).
230        Ok(wgpu::PollStatus::QueueEmpty)
231    }
232
233    fn get_internal_counters(&self) -> wgpu::InternalCounters {
234        wgpu::InternalCounters::default()
235    }
236
237    fn generate_allocator_report(&self) -> Option<wgpu::AllocatorReport> {
238        None
239    }
240
241    fn destroy(&self) {
242        self.device.destroy();
243    }
244}