webgpu/
wgpu_thread.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Data and main loop of WebGPU thread.
6
7use std::borrow::Cow;
8use std::slice;
9use std::sync::{Arc, Mutex};
10
11use base::id::PipelineId;
12use compositing_traits::{
13    CrossProcessCompositorApi, WebrenderExternalImageRegistry, WebrenderImageHandlerType,
14};
15use ipc_channel::ipc::{IpcReceiver, IpcSender, IpcSharedMemory};
16use log::{info, warn};
17use rustc_hash::FxHashMap;
18use servo_config::pref;
19use webgpu_traits::{
20    Adapter, ComputePassId, DeviceLostReason, Error, ErrorScope, Mapping, Pipeline, PopError,
21    RenderPassId, ShaderCompilationInfo, WebGPU, WebGPUAdapter, WebGPUContextId, WebGPUDevice,
22    WebGPUMsg, WebGPUQueue, WebGPURequest, apply_render_command,
23};
24use webrender_api::ExternalImageId;
25use wgc::command::{ComputePass, ComputePassDescriptor, RenderPass};
26use wgc::device::{DeviceDescriptor, ImplicitPipelineIds};
27use wgc::id;
28use wgc::id::DeviceId;
29use wgc::pipeline::ShaderModuleDescriptor;
30use wgc::resource::BufferMapOperation;
31use wgpu_core::command::RenderPassDescriptor;
32use wgpu_core::device::DeviceError;
33use wgpu_core::pipeline::{CreateComputePipelineError, CreateRenderPipelineError};
34use wgpu_core::resource::BufferAccessResult;
35use wgpu_types::MemoryHints;
36use wgt::InstanceDescriptor;
37pub use {wgpu_core as wgc, wgpu_types as wgt};
38
39use crate::canvas_context::WGPUImageMap;
40use crate::poll_thread::Poller;
41
42#[derive(Eq, Hash, PartialEq)]
43pub(crate) struct DeviceScope {
44    pub device_id: DeviceId,
45    pub pipeline_id: PipelineId,
46    /// <https://www.w3.org/TR/webgpu/#dom-gpudevice-errorscopestack-slot>
47    ///
48    /// Is `None` if device is lost
49    pub error_scope_stack: Option<Vec<ErrorScope>>,
50    // TODO:
51    // Queue for this device (to remove transmutes)
52    // queue_id: QueueId,
53    // Poller for this device
54    // poller: Poller,
55}
56
57impl DeviceScope {
58    pub fn new(device_id: DeviceId, pipeline_id: PipelineId) -> Self {
59        Self {
60            device_id,
61            pipeline_id,
62            error_scope_stack: Some(Vec::new()),
63        }
64    }
65}
66
67/// This roughly matches <https://www.w3.org/TR/2024/WD-webgpu-20240703/#encoder-state>
68#[derive(Debug, Default, Eq, PartialEq)]
69enum Pass<P> {
70    /// Pass is open (not ended)
71    Open {
72        /// Actual pass
73        pass: P,
74        /// we need to store valid field
75        /// because wgpu does not invalidate pass on error
76        valid: bool,
77    },
78    /// When pass is ended we need to drop it so we replace it with this
79    #[default]
80    Ended,
81}
82
83impl<P> Pass<P> {
84    /// Creates new open pass
85    fn new(pass: P, valid: bool) -> Self {
86        Self::Open { pass, valid }
87    }
88
89    /// Replaces pass with ended
90    fn take(&mut self) -> Self {
91        std::mem::take(self)
92    }
93}
94
95#[allow(clippy::upper_case_acronyms)] // Name of the library
96pub(crate) struct WGPU {
97    receiver: IpcReceiver<WebGPURequest>,
98    sender: IpcSender<WebGPURequest>,
99    pub(crate) script_sender: IpcSender<WebGPUMsg>,
100    pub(crate) global: Arc<wgc::global::Global>,
101    devices: Arc<Mutex<FxHashMap<DeviceId, DeviceScope>>>,
102    // TODO: Remove this (https://github.com/gfx-rs/wgpu/issues/867)
103    /// This stores first error on command encoder,
104    /// because wgpu does not invalidate command encoder object
105    /// (this is also reused for invalidation of command buffers)
106    error_command_encoders: FxHashMap<id::CommandEncoderId, String>,
107    pub(crate) compositor_api: CrossProcessCompositorApi,
108    pub(crate) external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
109    pub(crate) wgpu_image_map: WGPUImageMap,
110    /// Provides access to poller thread
111    pub(crate) poller: Poller,
112    /// Store compute passes
113    compute_passes: FxHashMap<ComputePassId, Pass<ComputePass>>,
114    /// Store render passes
115    render_passes: FxHashMap<RenderPassId, Pass<RenderPass>>,
116}
117
118impl WGPU {
119    pub(crate) fn new(
120        receiver: IpcReceiver<WebGPURequest>,
121        sender: IpcSender<WebGPURequest>,
122        script_sender: IpcSender<WebGPUMsg>,
123        compositor_api: CrossProcessCompositorApi,
124        external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
125        wgpu_image_map: WGPUImageMap,
126    ) -> Self {
127        let backend_pref = pref!(dom_webgpu_wgpu_backend);
128        let backends = if backend_pref.is_empty() {
129            wgt::Backends::PRIMARY
130        } else {
131            info!(
132                "Selecting backends based on dom.webgpu.wgpu_backend pref: {:?}",
133                backend_pref
134            );
135            wgt::Backends::from_comma_list(&backend_pref)
136        };
137        let global = Arc::new(wgc::global::Global::new(
138            "wgpu-core",
139            &InstanceDescriptor {
140                backends,
141                ..Default::default()
142            },
143        ));
144        WGPU {
145            poller: Poller::new(Arc::clone(&global)),
146            receiver,
147            sender,
148            script_sender,
149            global,
150            devices: Arc::new(Mutex::new(FxHashMap::default())),
151            error_command_encoders: FxHashMap::default(),
152            compositor_api,
153            external_images,
154            wgpu_image_map,
155            compute_passes: FxHashMap::default(),
156            render_passes: FxHashMap::default(),
157        }
158    }
159
160    pub(crate) fn run(&mut self) {
161        loop {
162            if let Ok(msg) = self.receiver.recv() {
163                log::trace!("recv: {msg:?}");
164                match msg {
165                    WebGPURequest::BufferMapAsync {
166                        sender,
167                        buffer_id,
168                        device_id,
169                        host_map,
170                        offset,
171                        size,
172                    } => {
173                        let glob = Arc::clone(&self.global);
174                        let resp_sender = sender.clone();
175                        let token = self.poller.token();
176                        let callback = Box::from(move |result: BufferAccessResult| {
177                            drop(token);
178                            let response = result.and_then(|_| {
179                                let global = &glob;
180                                let (slice_pointer, range_size) =
181                                    global.buffer_get_mapped_range(buffer_id, offset, size)?;
182                                // SAFETY: guarantee to be safe from wgpu
183                                let data = unsafe {
184                                    slice::from_raw_parts(
185                                        slice_pointer.as_ptr(),
186                                        range_size as usize,
187                                    )
188                                };
189
190                                Ok(Mapping {
191                                    data: IpcSharedMemory::from_bytes(data),
192                                    range: offset..offset + range_size,
193                                    mode: host_map,
194                                })
195                            });
196                            if let Err(e) = resp_sender.send(response) {
197                                warn!("Could not send BufferMapAsync Response ({})", e);
198                            }
199                        });
200
201                        let operation = BufferMapOperation {
202                            host: host_map,
203                            callback: Some(callback),
204                        };
205                        let global = &self.global;
206                        let result = global.buffer_map_async(buffer_id, offset, size, operation);
207                        self.poller.wake();
208                        // Per spec we also need to raise validation error here
209                        self.maybe_dispatch_wgpu_error(device_id, result.err());
210                    },
211                    WebGPURequest::CommandEncoderFinish {
212                        command_encoder_id,
213                        device_id,
214                        desc,
215                    } => {
216                        let global = &self.global;
217                        let result = if let Some(err) =
218                            self.error_command_encoders.get(&command_encoder_id)
219                        {
220                            Err(Error::Validation(err.clone()))
221                        } else if let Some(error) =
222                            global.command_encoder_finish(command_encoder_id, &desc).1
223                        {
224                            Err(Error::from_error(error))
225                        } else {
226                            Ok(())
227                        };
228
229                        // invalidate command buffer too
230                        self.encoder_record_error(command_encoder_id, &result);
231                        // dispatch validation error
232                        self.maybe_dispatch_error(device_id, result.err());
233                    },
234                    WebGPURequest::CopyBufferToBuffer {
235                        command_encoder_id,
236                        source_id,
237                        source_offset,
238                        destination_id,
239                        destination_offset,
240                        size,
241                    } => {
242                        let global = &self.global;
243                        let result = global.command_encoder_copy_buffer_to_buffer(
244                            command_encoder_id,
245                            source_id,
246                            source_offset,
247                            destination_id,
248                            destination_offset,
249                            Some(size),
250                        );
251                        self.encoder_record_error(command_encoder_id, &result);
252                    },
253                    WebGPURequest::CopyBufferToTexture {
254                        command_encoder_id,
255                        source,
256                        destination,
257                        copy_size,
258                    } => {
259                        let global = &self.global;
260                        let result = global.command_encoder_copy_buffer_to_texture(
261                            command_encoder_id,
262                            &source,
263                            &destination,
264                            &copy_size,
265                        );
266                        self.encoder_record_error(command_encoder_id, &result);
267                    },
268                    WebGPURequest::CopyTextureToBuffer {
269                        command_encoder_id,
270                        source,
271                        destination,
272                        copy_size,
273                    } => {
274                        let global = &self.global;
275                        let result = global.command_encoder_copy_texture_to_buffer(
276                            command_encoder_id,
277                            &source,
278                            &destination,
279                            &copy_size,
280                        );
281                        self.encoder_record_error(command_encoder_id, &result);
282                    },
283                    WebGPURequest::CopyTextureToTexture {
284                        command_encoder_id,
285                        source,
286                        destination,
287                        copy_size,
288                    } => {
289                        let global = &self.global;
290                        let result = global.command_encoder_copy_texture_to_texture(
291                            command_encoder_id,
292                            &source,
293                            &destination,
294                            &copy_size,
295                        );
296                        self.encoder_record_error(command_encoder_id, &result);
297                    },
298                    WebGPURequest::CreateBindGroup {
299                        device_id,
300                        bind_group_id,
301                        descriptor,
302                    } => {
303                        let global = &self.global;
304                        let (_, error) = global.device_create_bind_group(
305                            device_id,
306                            &descriptor,
307                            Some(bind_group_id),
308                        );
309                        self.maybe_dispatch_wgpu_error(device_id, error);
310                    },
311                    WebGPURequest::CreateBindGroupLayout {
312                        device_id,
313                        bind_group_layout_id,
314                        descriptor,
315                    } => {
316                        let global = &self.global;
317                        if let Some(desc) = descriptor {
318                            let (_, error) = global.device_create_bind_group_layout(
319                                device_id,
320                                &desc,
321                                Some(bind_group_layout_id),
322                            );
323
324                            self.maybe_dispatch_wgpu_error(device_id, error);
325                        }
326                    },
327                    WebGPURequest::CreateBuffer {
328                        device_id,
329                        buffer_id,
330                        descriptor,
331                    } => {
332                        let global = &self.global;
333                        let (_, error) =
334                            global.device_create_buffer(device_id, &descriptor, Some(buffer_id));
335
336                        self.maybe_dispatch_wgpu_error(device_id, error);
337                    },
338                    WebGPURequest::CreateCommandEncoder {
339                        device_id,
340                        command_encoder_id,
341                        desc,
342                    } => {
343                        let global = &self.global;
344                        let (_, error) = global.device_create_command_encoder(
345                            device_id,
346                            &desc,
347                            Some(command_encoder_id),
348                        );
349
350                        self.maybe_dispatch_wgpu_error(device_id, error);
351                    },
352                    WebGPURequest::CreateComputePipeline {
353                        device_id,
354                        compute_pipeline_id,
355                        descriptor,
356                        implicit_ids,
357                        async_sender: sender,
358                    } => {
359                        let global = &self.global;
360                        let bgls = implicit_ids
361                            .as_ref()
362                            .map_or(Vec::with_capacity(0), |(_, bgls)| {
363                                bgls.iter().map(|x| x.to_owned()).collect()
364                            });
365                        let implicit =
366                            implicit_ids
367                                .as_ref()
368                                .map(|(layout, _)| ImplicitPipelineIds {
369                                    root_id: *layout,
370                                    group_ids: bgls.as_slice(),
371                                });
372                        let (_, error) = global.device_create_compute_pipeline(
373                            device_id,
374                            &descriptor,
375                            Some(compute_pipeline_id),
376                            implicit,
377                        );
378                        if let Some(sender) = sender {
379                            let res = match error {
380                                // if device is lost we must return pipeline and not raise any error
381                                Some(CreateComputePipelineError::Device(DeviceError::Lost)) |
382                                None => Ok(Pipeline {
383                                    id: compute_pipeline_id,
384                                    label: descriptor.label.unwrap_or_default().to_string(),
385                                }),
386                                Some(e) => Err(Error::from_error(e)),
387                            };
388                            if let Err(e) = sender.send(res) {
389                                warn!("Failed sending WebGPUComputePipelineResponse {e:?}");
390                            }
391                        } else {
392                            self.maybe_dispatch_wgpu_error(device_id, error);
393                        }
394                    },
395                    WebGPURequest::CreatePipelineLayout {
396                        device_id,
397                        pipeline_layout_id,
398                        descriptor,
399                    } => {
400                        let global = &self.global;
401                        let (_, error) = global.device_create_pipeline_layout(
402                            device_id,
403                            &descriptor,
404                            Some(pipeline_layout_id),
405                        );
406                        self.maybe_dispatch_wgpu_error(device_id, error);
407                    },
408                    WebGPURequest::CreateRenderPipeline {
409                        device_id,
410                        render_pipeline_id,
411                        descriptor,
412                        implicit_ids,
413                        async_sender: sender,
414                    } => {
415                        let global = &self.global;
416                        let bgls = implicit_ids
417                            .as_ref()
418                            .map_or(Vec::with_capacity(0), |(_, bgls)| {
419                                bgls.iter().map(|x| x.to_owned()).collect()
420                            });
421                        let implicit =
422                            implicit_ids
423                                .as_ref()
424                                .map(|(layout, _)| ImplicitPipelineIds {
425                                    root_id: *layout,
426                                    group_ids: bgls.as_slice(),
427                                });
428                        let (_, error) = global.device_create_render_pipeline(
429                            device_id,
430                            &descriptor,
431                            Some(render_pipeline_id),
432                            implicit,
433                        );
434
435                        if let Some(sender) = sender {
436                            let res = match error {
437                                // if device is lost we must return pipeline and not raise any error
438                                Some(CreateRenderPipelineError::Device(DeviceError::Lost)) |
439                                None => Ok(Pipeline {
440                                    id: render_pipeline_id,
441                                    label: descriptor.label.unwrap_or_default().to_string(),
442                                }),
443                                Some(e) => Err(Error::from_error(e)),
444                            };
445                            if let Err(e) = sender.send(res) {
446                                warn!("Failed sending WebGPURenderPipelineResponse {e:?}");
447                            }
448                        } else {
449                            self.maybe_dispatch_wgpu_error(device_id, error);
450                        }
451                    },
452                    WebGPURequest::CreateSampler {
453                        device_id,
454                        sampler_id,
455                        descriptor,
456                    } => {
457                        let global = &self.global;
458                        let (_, error) =
459                            global.device_create_sampler(device_id, &descriptor, Some(sampler_id));
460                        self.maybe_dispatch_wgpu_error(device_id, error);
461                    },
462                    WebGPURequest::CreateShaderModule {
463                        device_id,
464                        program_id,
465                        program,
466                        label,
467                        sender,
468                    } => {
469                        let global = &self.global;
470                        let source =
471                            wgpu_core::pipeline::ShaderModuleSource::Wgsl(Cow::Borrowed(&program));
472                        let desc = ShaderModuleDescriptor {
473                            label: label.map(|s| s.into()),
474                            runtime_checks: wgt::ShaderRuntimeChecks::checked(),
475                        };
476                        let (_, error) = global.device_create_shader_module(
477                            device_id,
478                            &desc,
479                            source,
480                            Some(program_id),
481                        );
482                        if let Err(e) = sender.send(
483                            error
484                                .as_ref()
485                                .map(|e| ShaderCompilationInfo::from(e, &program)),
486                        ) {
487                            warn!("Failed to send CompilationInfo {e:?}");
488                        }
489                        self.maybe_dispatch_wgpu_error(device_id, error);
490                    },
491                    WebGPURequest::CreateContext {
492                        buffer_ids,
493                        size,
494                        sender,
495                    } => {
496                        let id = self
497                            .external_images
498                            .lock()
499                            .expect("Lock poisoned?")
500                            .next_id(WebrenderImageHandlerType::WebGPU);
501                        let image_key = self.compositor_api.generate_image_key_blocking().unwrap();
502                        let context_id = WebGPUContextId(id.0);
503                        if let Err(e) = sender.send((context_id, image_key)) {
504                            warn!("Failed to send ExternalImageId to new context ({})", e);
505                        };
506                        self.create_context(context_id, image_key, size, buffer_ids);
507                    },
508                    WebGPURequest::Present {
509                        context_id,
510                        pending_texture,
511                        size,
512                        canvas_epoch,
513                    } => {
514                        self.present(context_id, pending_texture, size, canvas_epoch);
515                    },
516                    WebGPURequest::GetImage {
517                        context_id,
518                        pending_texture,
519                        sender,
520                    } => self.get_image(context_id, pending_texture, sender),
521                    WebGPURequest::ValidateTextureDescriptor {
522                        device_id,
523                        texture_id,
524                        descriptor,
525                    } => {
526                        // https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-configure
527                        // validating TextureDescriptor by creating dummy texture
528                        let global = &self.global;
529                        let (_, error) =
530                            global.device_create_texture(device_id, &descriptor, Some(texture_id));
531                        global.texture_drop(texture_id);
532                        self.poller.wake();
533                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeTexture(texture_id))
534                        {
535                            warn!("Unable to send FreeTexture({:?}) ({:?})", texture_id, e);
536                        };
537                        self.maybe_dispatch_wgpu_error(device_id, error);
538                    },
539                    WebGPURequest::DestroyContext { context_id } => {
540                        self.destroy_context(context_id);
541                        self.external_images
542                            .lock()
543                            .expect("Lock poisoned?")
544                            .remove(&ExternalImageId(context_id.0));
545                    },
546                    WebGPURequest::CreateTexture {
547                        device_id,
548                        texture_id,
549                        descriptor,
550                    } => {
551                        let global = &self.global;
552                        let (_, error) =
553                            global.device_create_texture(device_id, &descriptor, Some(texture_id));
554                        self.maybe_dispatch_wgpu_error(device_id, error);
555                    },
556                    WebGPURequest::CreateTextureView {
557                        texture_id,
558                        texture_view_id,
559                        device_id,
560                        descriptor,
561                    } => {
562                        let global = &self.global;
563                        if let Some(desc) = descriptor {
564                            let (_, error) = global.texture_create_view(
565                                texture_id,
566                                &desc,
567                                Some(texture_view_id),
568                            );
569
570                            self.maybe_dispatch_wgpu_error(device_id, error);
571                        }
572                    },
573                    WebGPURequest::DestroyBuffer(buffer) => {
574                        let global = &self.global;
575                        global.buffer_destroy(buffer);
576                    },
577                    WebGPURequest::DestroyDevice(device) => {
578                        let global = &self.global;
579                        global.device_destroy(device);
580                        // Wake poller thread to trigger DeviceLostClosure
581                        self.poller.wake();
582                    },
583                    WebGPURequest::DestroyTexture(texture_id) => {
584                        let global = &self.global;
585                        global.texture_destroy(texture_id);
586                    },
587                    WebGPURequest::Exit(sender) => {
588                        if let Err(e) = sender.send(()) {
589                            warn!("Failed to send response to WebGPURequest::Exit ({})", e)
590                        }
591                        break;
592                    },
593                    WebGPURequest::DropCommandBuffer(id) => {
594                        self.error_command_encoders
595                            .remove(&id.into_command_encoder_id());
596                        let global = &self.global;
597                        global.command_buffer_drop(id);
598                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeCommandBuffer(id)) {
599                            warn!("Unable to send FreeCommandBuffer({:?}) ({:?})", id, e);
600                        };
601                    },
602                    WebGPURequest::DropDevice(device_id) => {
603                        let global = &self.global;
604                        global.device_drop(device_id);
605                        let device_scope = self
606                            .devices
607                            .lock()
608                            .unwrap()
609                            .remove(&device_id)
610                            .expect("Device should not be dropped by this point");
611                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeDevice {
612                            device_id,
613                            pipeline_id: device_scope.pipeline_id,
614                        }) {
615                            warn!("Unable to send FreeDevice({:?}) ({:?})", device_id, e);
616                        };
617                    },
618                    WebGPURequest::RenderBundleEncoderFinish {
619                        render_bundle_encoder,
620                        descriptor,
621                        render_bundle_id,
622                        device_id,
623                    } => {
624                        let global = &self.global;
625                        let (_, error) = global.render_bundle_encoder_finish(
626                            render_bundle_encoder,
627                            &descriptor,
628                            Some(render_bundle_id),
629                        );
630
631                        self.maybe_dispatch_wgpu_error(device_id, error);
632                    },
633                    WebGPURequest::RequestAdapter {
634                        sender,
635                        options,
636                        adapter_id,
637                    } => {
638                        let global = &self.global;
639                        let response = self
640                            .global
641                            .request_adapter(&options, wgt::Backends::all(), Some(adapter_id))
642                            .map(|adapter_id| {
643                                // TODO: can we do this lazily
644                                let adapter_info = global.adapter_get_info(adapter_id);
645                                let limits = global.adapter_limits(adapter_id);
646                                let features = global.adapter_features(adapter_id);
647                                Adapter {
648                                    adapter_info,
649                                    adapter_id: WebGPUAdapter(adapter_id),
650                                    features,
651                                    limits,
652                                    channel: WebGPU(self.sender.clone()),
653                                }
654                            })
655                            .map_err(|err| err.to_string());
656
657                        if let Err(e) = sender.send(Some(response)) {
658                            warn!(
659                                "Failed to send response to WebGPURequest::RequestAdapter ({})",
660                                e
661                            )
662                        }
663                    },
664                    WebGPURequest::RequestDevice {
665                        sender,
666                        adapter_id,
667                        descriptor,
668                        device_id,
669                        queue_id,
670                        pipeline_id,
671                    } => {
672                        let desc = DeviceDescriptor {
673                            label: descriptor.label.as_ref().map(crate::Cow::from),
674                            required_features: descriptor.required_features,
675                            required_limits: descriptor.required_limits.clone(),
676                            memory_hints: MemoryHints::MemoryUsage,
677                            trace: wgpu_types::Trace::Off,
678                        };
679                        let global = &self.global;
680                        let device = WebGPUDevice(device_id);
681                        let queue = WebGPUQueue(queue_id);
682                        let result = global
683                            .adapter_request_device(
684                                adapter_id.0,
685                                &desc,
686                                Some(device_id),
687                                Some(queue_id),
688                            )
689                            .map(|_| {
690                                {
691                                    self.devices.lock().unwrap().insert(
692                                        device_id,
693                                        DeviceScope::new(device_id, pipeline_id),
694                                    );
695                                }
696                                let script_sender = self.script_sender.clone();
697                                let devices = Arc::clone(&self.devices);
698                                let callback = Box::from(move |reason, msg| {
699                                    let reason = match reason {
700                                        wgt::DeviceLostReason::Unknown => DeviceLostReason::Unknown,
701                                        wgt::DeviceLostReason::Destroyed => {
702                                            DeviceLostReason::Destroyed
703                                        },
704                                    };
705                                    // make device lost by removing error scopes stack
706                                    let _ = devices
707                                        .lock()
708                                        .unwrap()
709                                        .get_mut(&device_id)
710                                        .expect("Device should not be dropped by this point")
711                                        .error_scope_stack
712                                        .take();
713                                    if let Err(e) = script_sender.send(WebGPUMsg::DeviceLost {
714                                        device,
715                                        pipeline_id,
716                                        reason,
717                                        msg,
718                                    }) {
719                                        warn!("Failed to send WebGPUMsg::DeviceLost: {e}");
720                                    }
721                                });
722                                global.device_set_device_lost_closure(device_id, callback);
723                                descriptor
724                            })
725                            .map_err(Into::into);
726                        if let Err(e) = sender.send((device, queue, result)) {
727                            warn!(
728                                "Failed to send response to WebGPURequest::RequestDevice ({})",
729                                e
730                            )
731                        }
732                    },
733                    WebGPURequest::BeginComputePass {
734                        command_encoder_id,
735                        compute_pass_id,
736                        label,
737                        device_id: _device_id,
738                    } => {
739                        let global = &self.global;
740                        let (pass, error) = global.command_encoder_begin_compute_pass(
741                            command_encoder_id,
742                            &ComputePassDescriptor {
743                                label,
744                                timestamp_writes: None,
745                            },
746                        );
747                        assert!(
748                            self.compute_passes
749                                .insert(compute_pass_id, Pass::new(pass, error.is_none()))
750                                .is_none(),
751                            "ComputePass should not exist yet."
752                        );
753                        // TODO: Command encoder state errors
754                        // self.maybe_dispatch_wgpu_error(device_id, error);
755                    },
756                    WebGPURequest::ComputePassSetPipeline {
757                        compute_pass_id,
758                        pipeline_id,
759                        device_id,
760                    } => {
761                        let pass = self
762                            .compute_passes
763                            .get_mut(&compute_pass_id)
764                            .expect("ComputePass should exists");
765                        if let Pass::Open { pass, valid } = pass {
766                            *valid &= self
767                                .global
768                                .compute_pass_set_pipeline(pass, pipeline_id)
769                                .is_ok();
770                        } else {
771                            self.maybe_dispatch_error(
772                                device_id,
773                                Some(Error::Validation("pass already ended".to_string())),
774                            );
775                        };
776                    },
777                    WebGPURequest::ComputePassSetBindGroup {
778                        compute_pass_id,
779                        index,
780                        bind_group_id,
781                        offsets,
782                        device_id,
783                    } => {
784                        let pass = self
785                            .compute_passes
786                            .get_mut(&compute_pass_id)
787                            .expect("ComputePass should exists");
788                        if let Pass::Open { pass, valid } = pass {
789                            *valid &= self
790                                .global
791                                .compute_pass_set_bind_group(
792                                    pass,
793                                    index,
794                                    Some(bind_group_id),
795                                    &offsets,
796                                )
797                                .is_ok();
798                        } else {
799                            self.maybe_dispatch_error(
800                                device_id,
801                                Some(Error::Validation("pass already ended".to_string())),
802                            );
803                        };
804                    },
805                    WebGPURequest::ComputePassDispatchWorkgroups {
806                        compute_pass_id,
807                        x,
808                        y,
809                        z,
810                        device_id,
811                    } => {
812                        let pass = self
813                            .compute_passes
814                            .get_mut(&compute_pass_id)
815                            .expect("ComputePass should exists");
816                        if let Pass::Open { pass, valid } = pass {
817                            *valid &= self
818                                .global
819                                .compute_pass_dispatch_workgroups(pass, x, y, z)
820                                .is_ok();
821                        } else {
822                            self.maybe_dispatch_error(
823                                device_id,
824                                Some(Error::Validation("pass already ended".to_string())),
825                            );
826                        };
827                    },
828                    WebGPURequest::ComputePassDispatchWorkgroupsIndirect {
829                        compute_pass_id,
830                        buffer_id,
831                        offset,
832                        device_id,
833                    } => {
834                        let pass = self
835                            .compute_passes
836                            .get_mut(&compute_pass_id)
837                            .expect("ComputePass should exists");
838                        if let Pass::Open { pass, valid } = pass {
839                            *valid &= self
840                                .global
841                                .compute_pass_dispatch_workgroups_indirect(pass, buffer_id, offset)
842                                .is_ok();
843                        } else {
844                            self.maybe_dispatch_error(
845                                device_id,
846                                Some(Error::Validation("pass already ended".to_string())),
847                            );
848                        };
849                    },
850                    WebGPURequest::EndComputePass {
851                        compute_pass_id,
852                        device_id,
853                        command_encoder_id,
854                    } => {
855                        // https://www.w3.org/TR/2024/WD-webgpu-20240703/#dom-gpucomputepassencoder-end
856                        let pass = self
857                            .compute_passes
858                            .get_mut(&compute_pass_id)
859                            .expect("ComputePass should exists");
860                        // TODO: Command encoder state error
861                        if let Pass::Open { mut pass, valid } = pass.take() {
862                            // `pass.end` does step 1-4
863                            // and if it returns ok we check the validity of the pass at step 5
864                            if self.global.compute_pass_end(&mut pass).is_ok() && !valid {
865                                self.encoder_record_error(
866                                    command_encoder_id,
867                                    &Err::<(), _>("Pass is invalid".to_string()),
868                                );
869                            }
870                        } else {
871                            self.dispatch_error(
872                                device_id,
873                                Error::Validation("pass already ended".to_string()),
874                            );
875                        };
876                    },
877                    WebGPURequest::BeginRenderPass {
878                        command_encoder_id,
879                        render_pass_id,
880                        label,
881                        color_attachments,
882                        depth_stencil_attachment,
883                        device_id: _device_id,
884                    } => {
885                        let global = &self.global;
886                        let desc = &RenderPassDescriptor {
887                            label,
888                            color_attachments: color_attachments.into(),
889                            depth_stencil_attachment: depth_stencil_attachment.as_ref(),
890                            timestamp_writes: None,
891                            occlusion_query_set: None,
892                        };
893                        let (pass, error) =
894                            global.command_encoder_begin_render_pass(command_encoder_id, desc);
895                        assert!(
896                            self.render_passes
897                                .insert(render_pass_id, Pass::new(pass, error.is_none()))
898                                .is_none(),
899                            "RenderPass should not exist yet."
900                        );
901                        // TODO: Command encoder state errors
902                        // self.maybe_dispatch_wgpu_error(device_id, error);
903                    },
904                    WebGPURequest::RenderPassCommand {
905                        render_pass_id,
906                        render_command,
907                        device_id,
908                    } => {
909                        let pass = self
910                            .render_passes
911                            .get_mut(&render_pass_id)
912                            .expect("RenderPass should exists");
913                        if let Pass::Open { pass, valid } = pass {
914                            *valid &=
915                                apply_render_command(&self.global, pass, render_command).is_ok();
916                        } else {
917                            self.maybe_dispatch_error(
918                                device_id,
919                                Some(Error::Validation("pass already ended".to_string())),
920                            );
921                        };
922                    },
923                    WebGPURequest::EndRenderPass {
924                        render_pass_id,
925                        device_id,
926                        command_encoder_id,
927                    } => {
928                        // https://www.w3.org/TR/2024/WD-webgpu-20240703/#dom-gpurenderpassencoder-end
929                        let pass = self
930                            .render_passes
931                            .get_mut(&render_pass_id)
932                            .expect("RenderPass should exists");
933                        // TODO: Command encoder state error
934                        if let Pass::Open { mut pass, valid } = pass.take() {
935                            // `pass.end` does step 1-4
936                            // and if it returns ok we check the validity of the pass at step 5
937                            if self.global.render_pass_end(&mut pass).is_ok() && !valid {
938                                self.encoder_record_error(
939                                    command_encoder_id,
940                                    &Err::<(), _>("Pass is invalid".to_string()),
941                                );
942                            }
943                        } else {
944                            self.dispatch_error(
945                                device_id,
946                                Error::Validation("Pass already ended".to_string()),
947                            );
948                        };
949                    },
950                    WebGPURequest::Submit {
951                        device_id,
952                        queue_id,
953                        command_buffers,
954                    } => {
955                        let global = &self.global;
956                        let cmd_id = command_buffers.iter().find(|id| {
957                            self.error_command_encoders
958                                .contains_key(&id.into_command_encoder_id())
959                        });
960                        let result = if cmd_id.is_some() {
961                            Err(Error::Validation(String::from(
962                                "Invalid command buffer submitted",
963                            )))
964                        } else {
965                            let _guard = self.poller.lock();
966                            global
967                                .queue_submit(queue_id, &command_buffers)
968                                .map_err(|(_, error)| Error::from_error(error))
969                        };
970                        self.maybe_dispatch_error(device_id, result.err());
971                    },
972                    WebGPURequest::UnmapBuffer { buffer_id, mapping } => {
973                        let global = &self.global;
974                        if let Some(mapping) = mapping {
975                            if let Ok((slice_pointer, range_size)) = global.buffer_get_mapped_range(
976                                buffer_id,
977                                mapping.range.start,
978                                Some(mapping.range.end - mapping.range.start),
979                            ) {
980                                unsafe {
981                                    slice::from_raw_parts_mut(
982                                        slice_pointer.as_ptr(),
983                                        range_size as usize,
984                                    )
985                                }
986                                .copy_from_slice(&mapping.data);
987                            }
988                        }
989                        // Ignore result because this operation always succeed from user perspective
990                        let _result = global.buffer_unmap(buffer_id);
991                    },
992                    WebGPURequest::WriteBuffer {
993                        device_id,
994                        queue_id,
995                        buffer_id,
996                        buffer_offset,
997                        data,
998                    } => {
999                        let global = &self.global;
1000                        let result = global.queue_write_buffer(
1001                            queue_id,
1002                            buffer_id,
1003                            buffer_offset as wgt::BufferAddress,
1004                            &data,
1005                        );
1006                        self.maybe_dispatch_wgpu_error(device_id, result.err());
1007                    },
1008                    WebGPURequest::WriteTexture {
1009                        device_id,
1010                        queue_id,
1011                        texture_cv,
1012                        data_layout,
1013                        size,
1014                        data,
1015                    } => {
1016                        let global = &self.global;
1017                        let _guard = self.poller.lock();
1018                        // TODO: Report result to content process
1019                        let result = global.queue_write_texture(
1020                            queue_id,
1021                            &texture_cv,
1022                            &data,
1023                            &data_layout,
1024                            &size,
1025                        );
1026                        drop(_guard);
1027                        self.maybe_dispatch_wgpu_error(device_id, result.err());
1028                    },
1029                    WebGPURequest::QueueOnSubmittedWorkDone { sender, queue_id } => {
1030                        let global = &self.global;
1031                        let token = self.poller.token();
1032                        let callback = Box::from(move || {
1033                            drop(token);
1034                            if let Err(e) = sender.send(()) {
1035                                warn!("Could not send SubmittedWorkDone Response ({})", e);
1036                            }
1037                        });
1038                        global.queue_on_submitted_work_done(queue_id, callback);
1039                        self.poller.wake();
1040                    },
1041                    WebGPURequest::DropTexture(id) => {
1042                        let global = &self.global;
1043                        global.texture_drop(id);
1044                        self.poller.wake();
1045                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeTexture(id)) {
1046                            warn!("Unable to send FreeTexture({:?}) ({:?})", id, e);
1047                        };
1048                    },
1049                    WebGPURequest::DropAdapter(id) => {
1050                        let global = &self.global;
1051                        global.adapter_drop(id);
1052                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeAdapter(id)) {
1053                            warn!("Unable to send FreeAdapter({:?}) ({:?})", id, e);
1054                        };
1055                    },
1056                    WebGPURequest::DropBuffer(id) => {
1057                        let global = &self.global;
1058                        global.buffer_drop(id);
1059                        self.poller.wake();
1060                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeBuffer(id)) {
1061                            warn!("Unable to send FreeBuffer({:?}) ({:?})", id, e);
1062                        };
1063                    },
1064                    WebGPURequest::DropPipelineLayout(id) => {
1065                        let global = &self.global;
1066                        global.pipeline_layout_drop(id);
1067                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreePipelineLayout(id)) {
1068                            warn!("Unable to send FreePipelineLayout({:?}) ({:?})", id, e);
1069                        };
1070                    },
1071                    WebGPURequest::DropComputePipeline(id) => {
1072                        let global = &self.global;
1073                        global.compute_pipeline_drop(id);
1074                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeComputePipeline(id))
1075                        {
1076                            warn!("Unable to send FreeComputePipeline({:?}) ({:?})", id, e);
1077                        };
1078                    },
1079                    WebGPURequest::DropComputePass(id) => {
1080                        // Pass might have already ended.
1081                        self.compute_passes.remove(&id);
1082                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeComputePass(id)) {
1083                            warn!("Unable to send FreeComputePass({:?}) ({:?})", id, e);
1084                        };
1085                    },
1086                    WebGPURequest::DropRenderPass(id) => {
1087                        self.render_passes
1088                            .remove(&id)
1089                            .expect("RenderPass should exists");
1090                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeRenderPass(id)) {
1091                            warn!("Unable to send FreeRenderPass({:?}) ({:?})", id, e);
1092                        };
1093                    },
1094                    WebGPURequest::DropRenderPipeline(id) => {
1095                        let global = &self.global;
1096                        global.render_pipeline_drop(id);
1097                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeRenderPipeline(id)) {
1098                            warn!("Unable to send FreeRenderPipeline({:?}) ({:?})", id, e);
1099                        };
1100                    },
1101                    WebGPURequest::DropBindGroup(id) => {
1102                        let global = &self.global;
1103                        global.bind_group_drop(id);
1104                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeBindGroup(id)) {
1105                            warn!("Unable to send FreeBindGroup({:?}) ({:?})", id, e);
1106                        };
1107                    },
1108                    WebGPURequest::DropBindGroupLayout(id) => {
1109                        let global = &self.global;
1110                        global.bind_group_layout_drop(id);
1111                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeBindGroupLayout(id))
1112                        {
1113                            warn!("Unable to send FreeBindGroupLayout({:?}) ({:?})", id, e);
1114                        };
1115                    },
1116                    WebGPURequest::DropTextureView(id) => {
1117                        let global = &self.global;
1118                        let _result = global.texture_view_drop(id);
1119                        self.poller.wake();
1120                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeTextureView(id)) {
1121                            warn!("Unable to send FreeTextureView({:?}) ({:?})", id, e);
1122                        };
1123                    },
1124                    WebGPURequest::DropSampler(id) => {
1125                        let global = &self.global;
1126                        global.sampler_drop(id);
1127                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeSampler(id)) {
1128                            warn!("Unable to send FreeSampler({:?}) ({:?})", id, e);
1129                        };
1130                    },
1131                    WebGPURequest::DropShaderModule(id) => {
1132                        let global = &self.global;
1133                        global.shader_module_drop(id);
1134                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeShaderModule(id)) {
1135                            warn!("Unable to send FreeShaderModule({:?}) ({:?})", id, e);
1136                        };
1137                    },
1138                    WebGPURequest::DropRenderBundle(id) => {
1139                        let global = &self.global;
1140                        global.render_bundle_drop(id);
1141                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeRenderBundle(id)) {
1142                            warn!("Unable to send FreeRenderBundle({:?}) ({:?})", id, e);
1143                        };
1144                    },
1145                    WebGPURequest::DropQuerySet(id) => {
1146                        let global = &self.global;
1147                        global.query_set_drop(id);
1148                        if let Err(e) = self.script_sender.send(WebGPUMsg::FreeQuerySet(id)) {
1149                            warn!("Unable to send FreeQuerySet({:?}) ({:?})", id, e);
1150                        };
1151                    },
1152                    WebGPURequest::PushErrorScope { device_id, filter } => {
1153                        // <https://www.w3.org/TR/webgpu/#dom-gpudevice-pusherrorscope>
1154                        let mut devices = self.devices.lock().unwrap();
1155                        let device_scope = devices
1156                            .get_mut(&device_id)
1157                            .expect("Device should not be dropped by this point");
1158                        if let Some(error_scope_stack) = &mut device_scope.error_scope_stack {
1159                            error_scope_stack.push(ErrorScope::new(filter));
1160                        } // else device is lost
1161                    },
1162                    WebGPURequest::DispatchError { device_id, error } => {
1163                        self.dispatch_error(device_id, error);
1164                    },
1165                    WebGPURequest::PopErrorScope { device_id, sender } => {
1166                        // <https://www.w3.org/TR/webgpu/#dom-gpudevice-poperrorscope>
1167                        let mut devices = self.devices.lock().unwrap();
1168                        let device_scope = devices
1169                            .get_mut(&device_id)
1170                            .expect("Device should not be dropped by this point");
1171                        let result =
1172                            if let Some(error_scope_stack) = &mut device_scope.error_scope_stack {
1173                                if let Some(error_scope) = error_scope_stack.pop() {
1174                                    Ok(
1175                                        // TODO: Do actual selection instead of selecting first error
1176                                        error_scope.errors.first().cloned(),
1177                                    )
1178                                } else {
1179                                    Err(PopError::Empty)
1180                                }
1181                            } else {
1182                                // This means the device has been lost.
1183                                Err(PopError::Lost)
1184                            };
1185                        if let Err(error) = sender.send(result) {
1186                            warn!("Error while sending PopErrorScope result: {error}");
1187                        }
1188                    },
1189                    WebGPURequest::ComputeGetBindGroupLayout {
1190                        device_id,
1191                        pipeline_id,
1192                        index,
1193                        id,
1194                    } => {
1195                        let global = &self.global;
1196                        let (_, error) = global.compute_pipeline_get_bind_group_layout(
1197                            pipeline_id,
1198                            index,
1199                            Some(id),
1200                        );
1201                        self.maybe_dispatch_wgpu_error(device_id, error);
1202                    },
1203                    WebGPURequest::RenderGetBindGroupLayout {
1204                        device_id,
1205                        pipeline_id,
1206                        index,
1207                        id,
1208                    } => {
1209                        let global = &self.global;
1210                        let (_, error) = global.render_pipeline_get_bind_group_layout(
1211                            pipeline_id,
1212                            index,
1213                            Some(id),
1214                        );
1215                        self.maybe_dispatch_wgpu_error(device_id, error);
1216                    },
1217                }
1218            }
1219        }
1220        if let Err(e) = self.script_sender.send(WebGPUMsg::Exit) {
1221            warn!("Failed to send WebGPUMsg::Exit to script ({})", e);
1222        }
1223    }
1224
1225    fn maybe_dispatch_wgpu_error<E: std::error::Error + 'static>(
1226        &mut self,
1227        device_id: id::DeviceId,
1228        error: Option<E>,
1229    ) {
1230        self.maybe_dispatch_error(device_id, error.map(|e| Error::from_error(e)))
1231    }
1232
1233    /// Dispatches error (if there is any)
1234    fn maybe_dispatch_error(&mut self, device_id: id::DeviceId, error: Option<Error>) {
1235        if let Some(error) = error {
1236            self.dispatch_error(device_id, error);
1237        }
1238    }
1239
1240    /// <https://www.w3.org/TR/webgpu/#abstract-opdef-dispatch-error>
1241    fn dispatch_error(&mut self, device_id: id::DeviceId, error: Error) {
1242        let mut devices = self.devices.lock().unwrap();
1243        let device_scope = devices
1244            .get_mut(&device_id)
1245            .expect("Device should not be dropped by this point");
1246        if let Some(error_scope_stack) = &mut device_scope.error_scope_stack {
1247            if let Some(error_scope) = error_scope_stack
1248                .iter_mut()
1249                .rev()
1250                .find(|error_scope| error_scope.filter == error.filter())
1251            {
1252                error_scope.errors.push(error);
1253            } else if self
1254                .script_sender
1255                .send(WebGPUMsg::UncapturedError {
1256                    device: WebGPUDevice(device_id),
1257                    pipeline_id: device_scope.pipeline_id,
1258                    error: error.clone(),
1259                })
1260                .is_err()
1261            {
1262                warn!("Failed to send WebGPUMsg::UncapturedError: {error:?}");
1263            }
1264        } // else device is lost
1265    }
1266
1267    fn encoder_record_error<U, T: std::fmt::Debug>(
1268        &mut self,
1269        encoder_id: id::CommandEncoderId,
1270        result: &Result<U, T>,
1271    ) {
1272        if let Err(e) = result {
1273            self.error_command_encoders
1274                .entry(encoder_id)
1275                .or_insert_with(|| format!("{:?}", e));
1276        }
1277    }
1278}