Skip to main content

webgpu/
wgpu_thread.rs

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