Skip to main content

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