webgpu/
wgpu_thread.rs

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