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