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