Skip to main content

wgpu/backend/
webgpu.rs

1#![allow(clippy::type_complexity)]
2
3mod defined_non_null_js_value;
4mod ext_bindings;
5#[allow(clippy::allow_attributes)]
6pub(crate) mod webgpu_sys;
7
8use alloc::{
9    boxed::Box,
10    format,
11    rc::Rc,
12    string::{String, ToString as _},
13    sync::Arc,
14    vec,
15    vec::Vec,
16};
17use core::{
18    cell::{Cell, OnceCell, RefCell},
19    fmt,
20    future::Future,
21    ops::Range,
22    pin::Pin,
23    sync::atomic::{AtomicU8, Ordering},
24    task::{self, Poll},
25};
26use wgt::Backends;
27
28use js_sys::Promise;
29use wasm_bindgen::{prelude::*, JsCast};
30
31use crate::{
32    dispatch::{self, BlasCompactCallback},
33    Blas, SurfaceTargetUnsafe, Tlas, WriteOnly,
34};
35
36use defined_non_null_js_value::DefinedNonNullJsValue;
37
38// We need to mark various types as Send and Sync to satisfy the Rust type system.
39//
40// SAFETY: All webgpu handle types in wasm32 are internally a `JsValue`, and `JsValue` is neither
41// Send nor Sync.  Currently, wasm32 has no threading support by default, so implementing `Send` or
42// `Sync` for a type is harmless. However, nightly Rust supports compiling wasm with experimental
43// threading support via `--target-features`. If `wgpu` is being compiled with those features, we do
44// not implement `Send` and `Sync` on the webgpu handle types.
45macro_rules! impl_send_sync {
46    ($name:ty) => {
47        #[cfg(send_sync)]
48        unsafe impl Send for $name {}
49        #[cfg(send_sync)]
50        unsafe impl Sync for $name {}
51    };
52}
53
54#[derive(Clone)]
55pub struct ContextWebGpu {
56    /// `None` if browser does not advertise support for WebGPU.
57    gpu: Option<DefinedNonNullJsValue<webgpu_sys::Gpu>>,
58    /// Unique identifier for this context.
59    ident: crate::cmp::Identifier,
60    /// Backends requested in the [`crate::InstanceDescriptor`].
61    /// Remembered for error reporting even though this itself is strictly
62    /// [`Backends::BROWSER_WEBGPU`].
63    requested_backends: Backends,
64}
65
66impl fmt::Debug for ContextWebGpu {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.debug_struct("ContextWebGpu")
69            .field("type", &"Web")
70            .finish()
71    }
72}
73
74impl crate::Error {
75    fn from_js(js_error: js_sys::Object) -> Self {
76        let source = Box::<dyn core::error::Error + Send + Sync>::from("<WebGPU Error>");
77        if let Some(js_error) = js_error.dyn_ref::<webgpu_sys::GpuValidationError>() {
78            crate::Error::Validation {
79                source,
80                description: js_error.message(),
81            }
82        } else if js_error.has_type::<webgpu_sys::GpuOutOfMemoryError>() {
83            crate::Error::OutOfMemory { source }
84        } else {
85            panic!("Unexpected error");
86        }
87    }
88}
89
90/// A callback invoked when wgpu releases its reference to an externally
91/// owned WebGPU resource (e.g. a `GpuTexture` passed to
92/// [`crate::Device::create_texture_from_webgpu_handle`]).
93///
94/// This is the WebGPU counterpart of [`wgpu_hal::DropCallback`].
95pub type DropCallback = Box<dyn FnOnce() + 'static>;
96
97pub(crate) struct DropGuard {
98    callback: Option<DropCallback>,
99}
100
101impl DropGuard {
102    fn new(callback: Option<DropCallback>) -> Self {
103        Self { callback }
104    }
105}
106
107impl Drop for DropGuard {
108    fn drop(&mut self) {
109        if let Some(cb) = self.callback.take() {
110            cb();
111        }
112    }
113}
114
115impl fmt::Debug for DropGuard {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.debug_struct("DropGuard").finish()
118    }
119}
120
121#[derive(Debug, Clone)]
122pub struct WebShaderModule {
123    module: webgpu_sys::GpuShaderModule,
124    compilation_info: WebShaderCompilationInfo,
125    /// Unique identifier for this shader module.
126    ident: crate::cmp::Identifier,
127}
128
129#[derive(Debug, Clone)]
130enum WebShaderCompilationInfo {
131    /// WGSL shaders get their compilation info from a native WebGPU function.
132    /// We need the source to be able to do UTF16 to UTF8 location remapping.
133    Wgsl { source: String },
134    /// Transformed shaders get their compilation info from the transformer.
135    /// Further compilation errors are reported without a span.
136    Transformed {
137        compilation_info: crate::CompilationInfo,
138    },
139}
140
141fn map_utf16_to_utf8_offset(utf16_offset: u32, text: &str) -> u32 {
142    let mut utf16_i = 0;
143    for (utf8_index, c) in text.char_indices() {
144        if utf16_i >= utf16_offset {
145            return utf8_index as u32;
146        }
147        utf16_i += c.len_utf16() as u32;
148    }
149    if utf16_i >= utf16_offset {
150        text.len() as u32
151    } else {
152        log::error!("UTF16 offset {utf16_offset} is out of bounds for string {text}");
153        u32::MAX
154    }
155}
156
157impl crate::CompilationMessage {
158    fn from_js(
159        js_message: webgpu_sys::GpuCompilationMessage,
160        compilation_info: &WebShaderCompilationInfo,
161    ) -> Self {
162        let message_type = match js_message.type_() {
163            webgpu_sys::GpuCompilationMessageType::Error => crate::CompilationMessageType::Error,
164            webgpu_sys::GpuCompilationMessageType::Warning => {
165                crate::CompilationMessageType::Warning
166            }
167            webgpu_sys::GpuCompilationMessageType::Info => crate::CompilationMessageType::Info,
168            _ => crate::CompilationMessageType::Error,
169        };
170        let utf16_offset = js_message.offset() as u32;
171        let utf16_length = js_message.length() as u32;
172        let span = match compilation_info {
173            WebShaderCompilationInfo::Wgsl { .. } if utf16_offset == 0 && utf16_length == 0 => None,
174            WebShaderCompilationInfo::Wgsl { source } => {
175                let offset = map_utf16_to_utf8_offset(utf16_offset, source);
176                let length = map_utf16_to_utf8_offset(utf16_length, &source[offset as usize..]);
177                let line_number = js_message.line_num() as u32; // That's legal, because we're counting lines the same way
178
179                let prefix = &source[..offset as usize];
180                let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0) as u32;
181                let line_position = offset - line_start + 1; // Counting UTF-8 byte indices
182
183                Some(crate::SourceLocation {
184                    offset,
185                    length,
186                    line_number,
187                    line_position,
188                })
189            }
190            WebShaderCompilationInfo::Transformed { .. } => None,
191        };
192
193        crate::CompilationMessage {
194            message: js_message.message(),
195            message_type,
196            location: span,
197        }
198    }
199}
200
201// We need to assert that any future we return is Send to match the native API.
202//
203// This is safe on wasm32 *for now*, but similarly to the unsafe Send impls for the handle type
204// wrappers, the full story for threading on wasm32 is still unfolding.
205
206pub(crate) struct MakeSendFuture<F, M> {
207    future: F,
208    map: M,
209}
210
211impl<F: Future, M: Fn(F::Output) -> T, T> Future for MakeSendFuture<F, M> {
212    type Output = T;
213
214    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
215        // This is safe because we have no Drop implementation to violate the Pin requirements and
216        // do not provide any means of moving the inner future.
217        unsafe {
218            let this = self.get_unchecked_mut();
219            match Pin::new_unchecked(&mut this.future).poll(cx) {
220                task::Poll::Ready(value) => task::Poll::Ready((this.map)(value)),
221                task::Poll::Pending => task::Poll::Pending,
222            }
223        }
224    }
225}
226
227impl<F, M> MakeSendFuture<F, M> {
228    fn new(future: F, map: M) -> Self {
229        Self { future, map }
230    }
231}
232
233#[cfg(send_sync)]
234unsafe impl<F, M> Send for MakeSendFuture<F, M> {}
235
236fn map_texture_format(texture_format: wgt::TextureFormat) -> webgpu_sys::GpuTextureFormat {
237    use webgpu_sys::GpuTextureFormat as tf;
238    use wgt::TextureFormat;
239    match texture_format {
240        // 8-bit formats
241        TextureFormat::R8Unorm => tf::R8unorm,
242        TextureFormat::R8Snorm => tf::R8snorm,
243        TextureFormat::R8Uint => tf::R8uint,
244        TextureFormat::R8Sint => tf::R8sint,
245        // 16-bit formats
246        TextureFormat::R16Uint => tf::R16uint,
247        TextureFormat::R16Sint => tf::R16sint,
248        TextureFormat::R16Float => tf::R16float,
249        TextureFormat::Rg8Unorm => tf::Rg8unorm,
250        TextureFormat::Rg8Snorm => tf::Rg8snorm,
251        TextureFormat::Rg8Uint => tf::Rg8uint,
252        TextureFormat::Rg8Sint => tf::Rg8sint,
253        // 32-bit formats
254        TextureFormat::R32Uint => tf::R32uint,
255        TextureFormat::R32Sint => tf::R32sint,
256        TextureFormat::R32Float => tf::R32float,
257        TextureFormat::Rg16Uint => tf::Rg16uint,
258        TextureFormat::Rg16Sint => tf::Rg16sint,
259        TextureFormat::Rg16Float => tf::Rg16float,
260        TextureFormat::Rgba8Unorm => tf::Rgba8unorm,
261        TextureFormat::Rgba8UnormSrgb => tf::Rgba8unormSrgb,
262        TextureFormat::Rgba8Snorm => tf::Rgba8snorm,
263        TextureFormat::Rgba8Uint => tf::Rgba8uint,
264        TextureFormat::Rgba8Sint => tf::Rgba8sint,
265        TextureFormat::Bgra8Unorm => tf::Bgra8unorm,
266        TextureFormat::Bgra8UnormSrgb => tf::Bgra8unormSrgb,
267        // Packed 32-bit formats
268        TextureFormat::Rgb9e5Ufloat => tf::Rgb9e5ufloat,
269        TextureFormat::Rgb10a2Uint => tf::Rgb10a2uint,
270        TextureFormat::Rgb10a2Unorm => tf::Rgb10a2unorm,
271        TextureFormat::Rg11b10Ufloat => tf::Rg11b10ufloat,
272        // 64-bit formats
273        TextureFormat::Rg32Uint => tf::Rg32uint,
274        TextureFormat::Rg32Sint => tf::Rg32sint,
275        TextureFormat::Rg32Float => tf::Rg32float,
276        TextureFormat::Rgba16Uint => tf::Rgba16uint,
277        TextureFormat::Rgba16Sint => tf::Rgba16sint,
278        TextureFormat::Rgba16Float => tf::Rgba16float,
279        // 128-bit formats
280        TextureFormat::Rgba32Uint => tf::Rgba32uint,
281        TextureFormat::Rgba32Sint => tf::Rgba32sint,
282        TextureFormat::Rgba32Float => tf::Rgba32float,
283        // Depth/stencil formats
284        TextureFormat::Stencil8 => tf::Stencil8,
285        TextureFormat::Depth16Unorm => tf::Depth16unorm,
286        TextureFormat::Depth24Plus => tf::Depth24plus,
287        TextureFormat::Depth24PlusStencil8 => tf::Depth24plusStencil8,
288        TextureFormat::Depth32Float => tf::Depth32float,
289        // "depth32float-stencil8" feature
290        TextureFormat::Depth32FloatStencil8 => tf::Depth32floatStencil8,
291
292        TextureFormat::Bc1RgbaUnorm => tf::Bc1RgbaUnorm,
293        TextureFormat::Bc1RgbaUnormSrgb => tf::Bc1RgbaUnormSrgb,
294        TextureFormat::Bc2RgbaUnorm => tf::Bc2RgbaUnorm,
295        TextureFormat::Bc2RgbaUnormSrgb => tf::Bc2RgbaUnormSrgb,
296        TextureFormat::Bc3RgbaUnorm => tf::Bc3RgbaUnorm,
297        TextureFormat::Bc3RgbaUnormSrgb => tf::Bc3RgbaUnormSrgb,
298        TextureFormat::Bc4RUnorm => tf::Bc4RUnorm,
299        TextureFormat::Bc4RSnorm => tf::Bc4RSnorm,
300        TextureFormat::Bc5RgUnorm => tf::Bc5RgUnorm,
301        TextureFormat::Bc5RgSnorm => tf::Bc5RgSnorm,
302        TextureFormat::Bc6hRgbUfloat => tf::Bc6hRgbUfloat,
303        TextureFormat::Bc6hRgbFloat => tf::Bc6hRgbFloat,
304        TextureFormat::Bc7RgbaUnorm => tf::Bc7RgbaUnorm,
305        TextureFormat::Bc7RgbaUnormSrgb => tf::Bc7RgbaUnormSrgb,
306        TextureFormat::Etc2Rgb8Unorm => tf::Etc2Rgb8unorm,
307        TextureFormat::Etc2Rgb8UnormSrgb => tf::Etc2Rgb8unormSrgb,
308        TextureFormat::Etc2Rgb8A1Unorm => tf::Etc2Rgb8a1unorm,
309        TextureFormat::Etc2Rgb8A1UnormSrgb => tf::Etc2Rgb8a1unormSrgb,
310        TextureFormat::Etc2Rgba8Unorm => tf::Etc2Rgba8unorm,
311        TextureFormat::Etc2Rgba8UnormSrgb => tf::Etc2Rgba8unormSrgb,
312        TextureFormat::EacR11Unorm => tf::EacR11unorm,
313        TextureFormat::EacR11Snorm => tf::EacR11snorm,
314        TextureFormat::EacRg11Unorm => tf::EacRg11unorm,
315        TextureFormat::EacRg11Snorm => tf::EacRg11snorm,
316        TextureFormat::Astc { block, channel } => match channel {
317            wgt::AstcChannel::Unorm => match block {
318                wgt::AstcBlock::B4x4 => tf::Astc4x4Unorm,
319                wgt::AstcBlock::B5x4 => tf::Astc5x4Unorm,
320                wgt::AstcBlock::B5x5 => tf::Astc5x5Unorm,
321                wgt::AstcBlock::B6x5 => tf::Astc6x5Unorm,
322                wgt::AstcBlock::B6x6 => tf::Astc6x6Unorm,
323                wgt::AstcBlock::B8x5 => tf::Astc8x5Unorm,
324                wgt::AstcBlock::B8x6 => tf::Astc8x6Unorm,
325                wgt::AstcBlock::B8x8 => tf::Astc8x8Unorm,
326                wgt::AstcBlock::B10x5 => tf::Astc10x5Unorm,
327                wgt::AstcBlock::B10x6 => tf::Astc10x6Unorm,
328                wgt::AstcBlock::B10x8 => tf::Astc10x8Unorm,
329                wgt::AstcBlock::B10x10 => tf::Astc10x10Unorm,
330                wgt::AstcBlock::B12x10 => tf::Astc12x10Unorm,
331                wgt::AstcBlock::B12x12 => tf::Astc12x12Unorm,
332            },
333            wgt::AstcChannel::UnormSrgb => match block {
334                wgt::AstcBlock::B4x4 => tf::Astc4x4UnormSrgb,
335                wgt::AstcBlock::B5x4 => tf::Astc5x4UnormSrgb,
336                wgt::AstcBlock::B5x5 => tf::Astc5x5UnormSrgb,
337                wgt::AstcBlock::B6x5 => tf::Astc6x5UnormSrgb,
338                wgt::AstcBlock::B6x6 => tf::Astc6x6UnormSrgb,
339                wgt::AstcBlock::B8x5 => tf::Astc8x5UnormSrgb,
340                wgt::AstcBlock::B8x6 => tf::Astc8x6UnormSrgb,
341                wgt::AstcBlock::B8x8 => tf::Astc8x8UnormSrgb,
342                wgt::AstcBlock::B10x5 => tf::Astc10x5UnormSrgb,
343                wgt::AstcBlock::B10x6 => tf::Astc10x6UnormSrgb,
344                wgt::AstcBlock::B10x8 => tf::Astc10x8UnormSrgb,
345                wgt::AstcBlock::B10x10 => tf::Astc10x10UnormSrgb,
346                wgt::AstcBlock::B12x10 => tf::Astc12x10UnormSrgb,
347                wgt::AstcBlock::B12x12 => tf::Astc12x12UnormSrgb,
348            },
349            wgt::AstcChannel::Hdr => {
350                unimplemented!("Format {texture_format:?} has no WebGPU equivalent")
351            }
352        },
353        _ => unimplemented!("Format {texture_format:?} has no WebGPU equivalent"),
354    }
355}
356
357fn map_texture_component_type(
358    sample_type: wgt::TextureSampleType,
359) -> webgpu_sys::GpuTextureSampleType {
360    use webgpu_sys::GpuTextureSampleType as ts;
361    use wgt::TextureSampleType;
362    match sample_type {
363        TextureSampleType::Float { filterable: true } => ts::Float,
364        TextureSampleType::Float { filterable: false } => ts::UnfilterableFloat,
365        TextureSampleType::Sint => ts::Sint,
366        TextureSampleType::Uint => ts::Uint,
367        TextureSampleType::Depth => ts::Depth,
368    }
369}
370
371fn map_cull_mode(cull_mode: Option<wgt::Face>) -> webgpu_sys::GpuCullMode {
372    use webgpu_sys::GpuCullMode as cm;
373    use wgt::Face;
374    match cull_mode {
375        None => cm::None,
376        Some(Face::Front) => cm::Front,
377        Some(Face::Back) => cm::Back,
378    }
379}
380
381fn map_front_face(front_face: wgt::FrontFace) -> webgpu_sys::GpuFrontFace {
382    use webgpu_sys::GpuFrontFace as ff;
383    use wgt::FrontFace;
384    match front_face {
385        FrontFace::Ccw => ff::Ccw,
386        FrontFace::Cw => ff::Cw,
387    }
388}
389
390fn map_primitive_state(primitive: &wgt::PrimitiveState) -> webgpu_sys::GpuPrimitiveState {
391    use webgpu_sys::GpuPrimitiveTopology as pt;
392    use wgt::PrimitiveTopology;
393
394    let mapped = webgpu_sys::GpuPrimitiveState::new();
395    mapped.set_cull_mode(map_cull_mode(primitive.cull_mode));
396    mapped.set_front_face(map_front_face(primitive.front_face));
397
398    if let Some(format) = primitive.strip_index_format {
399        mapped.set_strip_index_format(map_index_format(format));
400    }
401
402    mapped.set_topology(match primitive.topology {
403        PrimitiveTopology::PointList => pt::PointList,
404        PrimitiveTopology::LineList => pt::LineList,
405        PrimitiveTopology::LineStrip => pt::LineStrip,
406        PrimitiveTopology::TriangleList => pt::TriangleList,
407        PrimitiveTopology::TriangleStrip => pt::TriangleStrip,
408    });
409
410    mapped.set_unclipped_depth(primitive.unclipped_depth);
411
412    match primitive.polygon_mode {
413        wgt::PolygonMode::Fill => {}
414        wgt::PolygonMode::Line => panic!(
415            "{:?} is not enabled for this backend",
416            wgt::Features::POLYGON_MODE_LINE
417        ),
418        wgt::PolygonMode::Point => panic!(
419            "{:?} is not enabled for this backend",
420            wgt::Features::POLYGON_MODE_POINT
421        ),
422    }
423
424    mapped
425}
426
427fn map_compare_function(compare_fn: wgt::CompareFunction) -> webgpu_sys::GpuCompareFunction {
428    use webgpu_sys::GpuCompareFunction as cf;
429    use wgt::CompareFunction;
430    match compare_fn {
431        CompareFunction::Never => cf::Never,
432        CompareFunction::Less => cf::Less,
433        CompareFunction::Equal => cf::Equal,
434        CompareFunction::LessEqual => cf::LessEqual,
435        CompareFunction::Greater => cf::Greater,
436        CompareFunction::NotEqual => cf::NotEqual,
437        CompareFunction::GreaterEqual => cf::GreaterEqual,
438        CompareFunction::Always => cf::Always,
439    }
440}
441
442fn map_stencil_operation(op: wgt::StencilOperation) -> webgpu_sys::GpuStencilOperation {
443    use webgpu_sys::GpuStencilOperation as so;
444    use wgt::StencilOperation;
445    match op {
446        StencilOperation::Keep => so::Keep,
447        StencilOperation::Zero => so::Zero,
448        StencilOperation::Replace => so::Replace,
449        StencilOperation::Invert => so::Invert,
450        StencilOperation::IncrementClamp => so::IncrementClamp,
451        StencilOperation::DecrementClamp => so::DecrementClamp,
452        StencilOperation::IncrementWrap => so::IncrementWrap,
453        StencilOperation::DecrementWrap => so::DecrementWrap,
454    }
455}
456
457fn map_stencil_state_face(desc: &wgt::StencilFaceState) -> webgpu_sys::GpuStencilFaceState {
458    let mapped = webgpu_sys::GpuStencilFaceState::new();
459    mapped.set_compare(map_compare_function(desc.compare));
460    mapped.set_depth_fail_op(map_stencil_operation(desc.depth_fail_op));
461    mapped.set_fail_op(map_stencil_operation(desc.fail_op));
462    mapped.set_pass_op(map_stencil_operation(desc.pass_op));
463    mapped
464}
465
466fn map_depth_stencil_state(desc: &wgt::DepthStencilState) -> webgpu_sys::GpuDepthStencilState {
467    let mapped = webgpu_sys::GpuDepthStencilState::new(map_texture_format(desc.format));
468    if let Some(compare) = desc.depth_compare {
469        mapped.set_depth_compare(map_compare_function(compare));
470    }
471    if let Some(write_enabled) = desc.depth_write_enabled {
472        mapped.set_depth_write_enabled(write_enabled);
473    }
474    mapped.set_depth_bias(desc.bias.constant);
475    mapped.set_depth_bias_clamp(desc.bias.clamp);
476    mapped.set_depth_bias_slope_scale(desc.bias.slope_scale);
477    mapped.set_stencil_back(&map_stencil_state_face(&desc.stencil.back));
478    mapped.set_stencil_front(&map_stencil_state_face(&desc.stencil.front));
479    mapped.set_stencil_read_mask(desc.stencil.read_mask);
480    mapped.set_stencil_write_mask(desc.stencil.write_mask);
481    mapped
482}
483
484fn map_blend_component(desc: &wgt::BlendComponent) -> webgpu_sys::GpuBlendComponent {
485    let mapped = webgpu_sys::GpuBlendComponent::new();
486    mapped.set_dst_factor(map_blend_factor(desc.dst_factor));
487    mapped.set_operation(map_blend_operation(desc.operation));
488    mapped.set_src_factor(map_blend_factor(desc.src_factor));
489    mapped
490}
491
492fn map_blend_factor(factor: wgt::BlendFactor) -> webgpu_sys::GpuBlendFactor {
493    use webgpu_sys::GpuBlendFactor as bf;
494    use wgt::BlendFactor;
495    match factor {
496        BlendFactor::Zero => bf::Zero,
497        BlendFactor::One => bf::One,
498        BlendFactor::Src => bf::Src,
499        BlendFactor::OneMinusSrc => bf::OneMinusSrc,
500        BlendFactor::SrcAlpha => bf::SrcAlpha,
501        BlendFactor::OneMinusSrcAlpha => bf::OneMinusSrcAlpha,
502        BlendFactor::Dst => bf::Dst,
503        BlendFactor::OneMinusDst => bf::OneMinusDst,
504        BlendFactor::DstAlpha => bf::DstAlpha,
505        BlendFactor::OneMinusDstAlpha => bf::OneMinusDstAlpha,
506        BlendFactor::SrcAlphaSaturated => bf::SrcAlphaSaturated,
507        BlendFactor::Constant => bf::Constant,
508        BlendFactor::OneMinusConstant => bf::OneMinusConstant,
509        BlendFactor::Src1 => bf::Src1,
510        BlendFactor::OneMinusSrc1 => bf::OneMinusSrc1,
511        BlendFactor::Src1Alpha => bf::Src1Alpha,
512        BlendFactor::OneMinusSrc1Alpha => bf::OneMinusSrc1Alpha,
513    }
514}
515
516fn map_blend_operation(op: wgt::BlendOperation) -> webgpu_sys::GpuBlendOperation {
517    use webgpu_sys::GpuBlendOperation as bo;
518    use wgt::BlendOperation;
519    match op {
520        BlendOperation::Add => bo::Add,
521        BlendOperation::Subtract => bo::Subtract,
522        BlendOperation::ReverseSubtract => bo::ReverseSubtract,
523        BlendOperation::Min => bo::Min,
524        BlendOperation::Max => bo::Max,
525    }
526}
527
528fn map_index_format(format: wgt::IndexFormat) -> webgpu_sys::GpuIndexFormat {
529    use webgpu_sys::GpuIndexFormat as f;
530    use wgt::IndexFormat;
531    match format {
532        IndexFormat::Uint16 => f::Uint16,
533        IndexFormat::Uint32 => f::Uint32,
534    }
535}
536
537fn map_vertex_format(format: wgt::VertexFormat) -> webgpu_sys::GpuVertexFormat {
538    use webgpu_sys::GpuVertexFormat as vf;
539    use wgt::VertexFormat;
540    match format {
541        VertexFormat::Uint8 => vf::Uint8,
542        VertexFormat::Uint8x2 => vf::Uint8x2,
543        VertexFormat::Uint8x4 => vf::Uint8x4,
544        VertexFormat::Sint8 => vf::Sint8,
545        VertexFormat::Sint8x2 => vf::Sint8x2,
546        VertexFormat::Sint8x4 => vf::Sint8x4,
547        VertexFormat::Unorm8 => vf::Unorm8,
548        VertexFormat::Unorm8x2 => vf::Unorm8x2,
549        VertexFormat::Unorm8x4 => vf::Unorm8x4,
550        VertexFormat::Snorm8 => vf::Snorm8,
551        VertexFormat::Snorm8x2 => vf::Snorm8x2,
552        VertexFormat::Snorm8x4 => vf::Snorm8x4,
553        VertexFormat::Uint16 => vf::Uint16,
554        VertexFormat::Uint16x2 => vf::Uint16x2,
555        VertexFormat::Uint16x4 => vf::Uint16x4,
556        VertexFormat::Sint16 => vf::Sint16,
557        VertexFormat::Sint16x2 => vf::Sint16x2,
558        VertexFormat::Sint16x4 => vf::Sint16x4,
559        VertexFormat::Unorm16 => vf::Unorm16,
560        VertexFormat::Unorm16x2 => vf::Unorm16x2,
561        VertexFormat::Unorm16x4 => vf::Unorm16x4,
562        VertexFormat::Snorm16 => vf::Snorm16,
563        VertexFormat::Snorm16x2 => vf::Snorm16x2,
564        VertexFormat::Snorm16x4 => vf::Snorm16x4,
565        VertexFormat::Float16 => vf::Float16,
566        VertexFormat::Float16x2 => vf::Float16x2,
567        VertexFormat::Float16x4 => vf::Float16x4,
568        VertexFormat::Float32 => vf::Float32,
569        VertexFormat::Float32x2 => vf::Float32x2,
570        VertexFormat::Float32x3 => vf::Float32x3,
571        VertexFormat::Float32x4 => vf::Float32x4,
572        VertexFormat::Uint32 => vf::Uint32,
573        VertexFormat::Uint32x2 => vf::Uint32x2,
574        VertexFormat::Uint32x3 => vf::Uint32x3,
575        VertexFormat::Uint32x4 => vf::Uint32x4,
576        VertexFormat::Sint32 => vf::Sint32,
577        VertexFormat::Sint32x2 => vf::Sint32x2,
578        VertexFormat::Sint32x3 => vf::Sint32x3,
579        VertexFormat::Sint32x4 => vf::Sint32x4,
580        VertexFormat::Unorm10_10_10_2 => vf::Unorm1010102,
581        VertexFormat::Unorm8x4Bgra => vf::Unorm8x4Bgra,
582        VertexFormat::Float64
583        | VertexFormat::Float64x2
584        | VertexFormat::Float64x3
585        | VertexFormat::Float64x4 => {
586            panic!("VERTEX_ATTRIBUTE_64BIT feature must be enabled to use Double formats")
587        }
588    }
589}
590
591fn map_vertex_step_mode(mode: wgt::VertexStepMode) -> webgpu_sys::GpuVertexStepMode {
592    use webgpu_sys::GpuVertexStepMode as sm;
593    use wgt::VertexStepMode;
594    match mode {
595        VertexStepMode::Vertex => sm::Vertex,
596        VertexStepMode::Instance => sm::Instance,
597    }
598}
599
600fn map_extent_3d(extent: wgt::Extent3d) -> webgpu_sys::GpuExtent3dDict {
601    let mapped = webgpu_sys::GpuExtent3dDict::new(extent.width);
602    mapped.set_height(extent.height);
603    mapped.set_depth_or_array_layers(extent.depth_or_array_layers);
604    mapped
605}
606
607fn map_origin_2d(extent: wgt::Origin2d) -> webgpu_sys::GpuOrigin2dDict {
608    let mapped = webgpu_sys::GpuOrigin2dDict::new();
609    mapped.set_x(extent.x);
610    mapped.set_y(extent.y);
611    mapped
612}
613
614fn map_origin_3d(origin: wgt::Origin3d) -> webgpu_sys::GpuOrigin3dDict {
615    let mapped = webgpu_sys::GpuOrigin3dDict::new();
616    mapped.set_x(origin.x);
617    mapped.set_y(origin.y);
618    mapped.set_z(origin.z);
619    mapped
620}
621
622fn map_texture_dimension(
623    texture_dimension: wgt::TextureDimension,
624) -> webgpu_sys::GpuTextureDimension {
625    match texture_dimension {
626        wgt::TextureDimension::D1 => webgpu_sys::GpuTextureDimension::N1d,
627        wgt::TextureDimension::D2 => webgpu_sys::GpuTextureDimension::N2d,
628        wgt::TextureDimension::D3 => webgpu_sys::GpuTextureDimension::N3d,
629    }
630}
631
632fn map_texture_view_dimension(
633    texture_view_dimension: wgt::TextureViewDimension,
634) -> webgpu_sys::GpuTextureViewDimension {
635    use webgpu_sys::GpuTextureViewDimension as tvd;
636    match texture_view_dimension {
637        wgt::TextureViewDimension::D1 => tvd::N1d,
638        wgt::TextureViewDimension::D2 => tvd::N2d,
639        wgt::TextureViewDimension::D2Array => tvd::N2dArray,
640        wgt::TextureViewDimension::Cube => tvd::Cube,
641        wgt::TextureViewDimension::CubeArray => tvd::CubeArray,
642        wgt::TextureViewDimension::D3 => tvd::N3d,
643    }
644}
645
646fn map_buffer_copy_view(
647    view: crate::TexelCopyBufferInfo<'_>,
648) -> webgpu_sys::GpuTexelCopyBufferInfo {
649    let buffer = view.buffer.inner.as_webgpu();
650    let mapped = webgpu_sys::GpuTexelCopyBufferInfo::new(&buffer.inner);
651    if let Some(bytes_per_row) = view.layout.bytes_per_row {
652        mapped.set_bytes_per_row(bytes_per_row);
653    }
654    if let Some(rows_per_image) = view.layout.rows_per_image {
655        mapped.set_rows_per_image(rows_per_image);
656    }
657    mapped.set_offset_f64(view.layout.offset as f64);
658    mapped
659}
660
661fn map_texture_copy_view(
662    view: crate::TexelCopyTextureInfo<'_>,
663) -> webgpu_sys::GpuTexelCopyTextureInfo {
664    let texture = view.texture.inner.as_webgpu();
665    let mapped = webgpu_sys::GpuTexelCopyTextureInfo::new(&texture.inner);
666    mapped.set_mip_level(view.mip_level);
667    mapped.set_origin_gpu_origin_3d_dict(&map_origin_3d(view.origin));
668    mapped.set_aspect(map_texture_aspect(view.aspect));
669    mapped
670}
671
672fn map_tagged_texture_copy_view(
673    view: crate::CopyExternalImageDestInfo<&crate::api::Texture>,
674) -> webgpu_sys::GpuCopyExternalImageDestInfo {
675    let texture = view.texture.inner.as_webgpu();
676    let mapped = webgpu_sys::GpuCopyExternalImageDestInfo::new(&texture.inner);
677    mapped.set_mip_level(view.mip_level);
678    mapped.set_origin_gpu_origin_3d_dict(&map_origin_3d(view.origin));
679    mapped.set_aspect(map_texture_aspect(view.aspect));
680    // mapped.set_color_space(map_color_space(view.color_space));
681    mapped.set_premultiplied_alpha(view.premultiplied_alpha);
682    mapped
683}
684
685fn map_external_texture_copy_view(
686    view: &crate::CopyExternalImageSourceInfo,
687) -> webgpu_sys::GpuCopyExternalImageSourceInfo {
688    let mapped = match &view.source {
689        crate::ExternalImageSource::ImageBitmap(bitmap) => {
690            webgpu_sys::GpuCopyExternalImageSourceInfo::new(bitmap)
691        }
692        crate::ExternalImageSource::HTMLImageElement(image) => {
693            webgpu_sys::GpuCopyExternalImageSourceInfo::new_with_html_image_element(image)
694        }
695        crate::ExternalImageSource::HTMLVideoElement(video) => {
696            webgpu_sys::GpuCopyExternalImageSourceInfo::new_with_html_video_element(video)
697        }
698        crate::ExternalImageSource::ImageData(image_data) => {
699            webgpu_sys::GpuCopyExternalImageSourceInfo::new_with_image_data(image_data)
700        }
701        crate::ExternalImageSource::HTMLCanvasElement(canvas) => {
702            webgpu_sys::GpuCopyExternalImageSourceInfo::new_with_html_canvas_element(canvas)
703        }
704        crate::ExternalImageSource::OffscreenCanvas(offscreen_canvas) => {
705            webgpu_sys::GpuCopyExternalImageSourceInfo::new_with_offscreen_canvas(offscreen_canvas)
706        }
707        crate::ExternalImageSource::VideoFrame(video_frame) => {
708            webgpu_sys::GpuCopyExternalImageSourceInfo::new_with_video_frame(video_frame)
709        }
710    };
711    mapped.set_origin_gpu_origin_2d_dict(&map_origin_2d(view.origin));
712    mapped.set_flip_y(view.flip_y);
713    mapped
714}
715
716fn map_texture_aspect(aspect: wgt::TextureAspect) -> webgpu_sys::GpuTextureAspect {
717    match aspect {
718        wgt::TextureAspect::All => webgpu_sys::GpuTextureAspect::All,
719        wgt::TextureAspect::StencilOnly => webgpu_sys::GpuTextureAspect::StencilOnly,
720        wgt::TextureAspect::DepthOnly => webgpu_sys::GpuTextureAspect::DepthOnly,
721        wgt::TextureAspect::Plane0 | wgt::TextureAspect::Plane1 | wgt::TextureAspect::Plane2 => {
722            panic!("multi-plane textures are not supported")
723        }
724    }
725}
726
727fn map_filter_mode(mode: wgt::FilterMode) -> webgpu_sys::GpuFilterMode {
728    match mode {
729        wgt::FilterMode::Nearest => webgpu_sys::GpuFilterMode::Nearest,
730        wgt::FilterMode::Linear => webgpu_sys::GpuFilterMode::Linear,
731    }
732}
733
734fn map_mipmap_filter_mode(mode: wgt::MipmapFilterMode) -> webgpu_sys::GpuMipmapFilterMode {
735    match mode {
736        wgt::MipmapFilterMode::Nearest => webgpu_sys::GpuMipmapFilterMode::Nearest,
737        wgt::MipmapFilterMode::Linear => webgpu_sys::GpuMipmapFilterMode::Linear,
738    }
739}
740
741fn map_address_mode(mode: wgt::AddressMode) -> webgpu_sys::GpuAddressMode {
742    match mode {
743        wgt::AddressMode::ClampToEdge => webgpu_sys::GpuAddressMode::ClampToEdge,
744        wgt::AddressMode::Repeat => webgpu_sys::GpuAddressMode::Repeat,
745        wgt::AddressMode::MirrorRepeat => webgpu_sys::GpuAddressMode::MirrorRepeat,
746        wgt::AddressMode::ClampToBorder => panic!("Clamp to border is not supported"),
747    }
748}
749
750fn map_color(color: wgt::Color) -> webgpu_sys::GpuColorDict {
751    webgpu_sys::GpuColorDict::new(color.a, color.b, color.g, color.r)
752}
753
754fn map_store_op(store: crate::StoreOp) -> webgpu_sys::GpuStoreOp {
755    match store {
756        crate::StoreOp::Store => webgpu_sys::GpuStoreOp::Store,
757        crate::StoreOp::Discard => webgpu_sys::GpuStoreOp::Discard,
758    }
759}
760
761fn map_map_mode(mode: crate::MapMode) -> u32 {
762    match mode {
763        crate::MapMode::Read => webgpu_sys::gpu_map_mode::READ,
764        crate::MapMode::Write => webgpu_sys::gpu_map_mode::WRITE,
765    }
766}
767
768const FEATURES_MAPPING: [(wgt::Features, webgpu_sys::GpuFeatureName); 16] = [
769    (
770        wgt::Features::DEPTH_CLIP_CONTROL,
771        webgpu_sys::GpuFeatureName::DepthClipControl,
772    ),
773    (
774        wgt::Features::DEPTH32FLOAT_STENCIL8,
775        webgpu_sys::GpuFeatureName::Depth32floatStencil8,
776    ),
777    (
778        wgt::Features::TEXTURE_COMPRESSION_BC,
779        webgpu_sys::GpuFeatureName::TextureCompressionBc,
780    ),
781    (
782        wgt::Features::TEXTURE_COMPRESSION_BC_SLICED_3D,
783        webgpu_sys::GpuFeatureName::TextureCompressionBcSliced3d,
784    ),
785    (
786        wgt::Features::TEXTURE_COMPRESSION_ETC2,
787        webgpu_sys::GpuFeatureName::TextureCompressionEtc2,
788    ),
789    (
790        wgt::Features::TEXTURE_COMPRESSION_ASTC,
791        webgpu_sys::GpuFeatureName::TextureCompressionAstc,
792    ),
793    (
794        wgt::Features::TEXTURE_COMPRESSION_ASTC_SLICED_3D,
795        webgpu_sys::GpuFeatureName::TextureCompressionAstcSliced3d,
796    ),
797    (
798        wgt::Features::TIMESTAMP_QUERY,
799        webgpu_sys::GpuFeatureName::TimestampQuery,
800    ),
801    (
802        wgt::Features::INDIRECT_FIRST_INSTANCE,
803        webgpu_sys::GpuFeatureName::IndirectFirstInstance,
804    ),
805    (
806        wgt::Features::SHADER_F16,
807        webgpu_sys::GpuFeatureName::ShaderF16,
808    ),
809    (
810        wgt::Features::RG11B10UFLOAT_RENDERABLE,
811        webgpu_sys::GpuFeatureName::Rg11b10ufloatRenderable,
812    ),
813    (
814        wgt::Features::BGRA8UNORM_STORAGE,
815        webgpu_sys::GpuFeatureName::Bgra8unormStorage,
816    ),
817    (
818        wgt::Features::FLOAT32_FILTERABLE,
819        webgpu_sys::GpuFeatureName::Float32Filterable,
820    ),
821    (
822        wgt::Features::FLOAT32_BLENDABLE,
823        webgpu_sys::GpuFeatureName::Float32Blendable,
824    ),
825    (
826        wgt::Features::DUAL_SOURCE_BLENDING,
827        webgpu_sys::GpuFeatureName::DualSourceBlending,
828    ),
829    (
830        wgt::Features::CLIP_DISTANCES,
831        webgpu_sys::GpuFeatureName::ClipDistances,
832    ),
833];
834
835fn map_wgt_features(supported_features: webgpu_sys::GpuSupportedFeatures) -> wgt::Features {
836    let mut features = wgt::Features::empty();
837    for (wgpu_feat, web_feat) in FEATURES_MAPPING {
838        match wasm_bindgen::JsValue::from(web_feat).as_string() {
839            Some(value) if supported_features.has(&value) => features |= wgpu_feat,
840            _ => {}
841        }
842    }
843    features
844}
845
846fn map_wgt_limits(limits: webgpu_sys::GpuSupportedLimits) -> wgt::Limits {
847    wgt::Limits {
848        max_texture_dimension_1d: limits.max_texture_dimension_1d(),
849        max_texture_dimension_2d: limits.max_texture_dimension_2d(),
850        max_texture_dimension_3d: limits.max_texture_dimension_3d(),
851        max_texture_array_layers: limits.max_texture_array_layers(),
852        max_bind_groups: limits.max_bind_groups(),
853        max_bind_groups_plus_vertex_buffers: limits.max_bind_groups_plus_vertex_buffers(),
854        max_bindings_per_bind_group: limits.max_bindings_per_bind_group(),
855        max_dynamic_uniform_buffers_per_pipeline_layout: limits
856            .max_dynamic_uniform_buffers_per_pipeline_layout(),
857        max_dynamic_storage_buffers_per_pipeline_layout: limits
858            .max_dynamic_storage_buffers_per_pipeline_layout(),
859        max_sampled_textures_per_shader_stage: limits.max_sampled_textures_per_shader_stage(),
860        max_samplers_per_shader_stage: limits.max_samplers_per_shader_stage(),
861        max_storage_buffers_per_shader_stage: limits.max_storage_buffers_per_shader_stage(),
862        max_storage_textures_per_shader_stage: limits.max_storage_textures_per_shader_stage(),
863        max_uniform_buffers_per_shader_stage: limits.max_uniform_buffers_per_shader_stage(),
864        max_binding_array_elements_per_shader_stage: 0,
865        max_binding_array_sampler_elements_per_shader_stage: 0,
866        max_binding_array_acceleration_structure_elements_per_shader_stage: 0,
867        max_uniform_buffer_binding_size: limits.max_uniform_buffer_binding_size() as u64,
868        max_storage_buffer_binding_size: limits.max_storage_buffer_binding_size() as u64,
869        max_vertex_buffers: limits.max_vertex_buffers(),
870        max_buffer_size: limits.max_buffer_size() as u64,
871        max_vertex_attributes: limits.max_vertex_attributes(),
872        max_vertex_buffer_array_stride: limits.max_vertex_buffer_array_stride(),
873        max_inter_stage_shader_variables: limits.max_inter_stage_shader_variables(),
874        min_uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment(),
875        min_storage_buffer_offset_alignment: limits.min_storage_buffer_offset_alignment(),
876        max_color_attachments: limits.max_color_attachments(),
877        max_color_attachment_bytes_per_sample: limits.max_color_attachment_bytes_per_sample(),
878        max_compute_workgroup_storage_size: limits.max_compute_workgroup_storage_size(),
879        max_compute_invocations_per_workgroup: limits.max_compute_invocations_per_workgroup(),
880        max_compute_workgroup_size_x: limits.max_compute_workgroup_size_x(),
881        max_compute_workgroup_size_y: limits.max_compute_workgroup_size_y(),
882        max_compute_workgroup_size_z: limits.max_compute_workgroup_size_z(),
883        max_compute_workgroups_per_dimension: limits.max_compute_workgroups_per_dimension(),
884        max_immediate_size: wgt::Limits::default().max_immediate_size,
885        max_non_sampler_bindings: wgt::Limits::default().max_non_sampler_bindings,
886
887        max_task_workgroup_total_count: wgt::Limits::default().max_task_workgroup_total_count,
888        max_task_workgroups_per_dimension: wgt::Limits::default().max_task_workgroups_per_dimension,
889        max_mesh_workgroup_total_count: wgt::Limits::default().max_mesh_workgroup_total_count,
890        max_mesh_workgroups_per_dimension: wgt::Limits::default().max_mesh_workgroups_per_dimension,
891        max_task_invocations_per_workgroup: wgt::Limits::default()
892            .max_task_invocations_per_workgroup,
893        max_task_invocations_per_dimension: wgt::Limits::default()
894            .max_task_invocations_per_dimension,
895        max_mesh_invocations_per_workgroup: wgt::Limits::default()
896            .max_mesh_invocations_per_workgroup,
897        max_mesh_invocations_per_dimension: wgt::Limits::default()
898            .max_mesh_invocations_per_dimension,
899        max_task_payload_size: wgt::Limits::default().max_task_payload_size,
900        max_mesh_output_vertices: wgt::Limits::default().max_mesh_output_vertices,
901        max_mesh_output_primitives: wgt::Limits::default().max_mesh_output_primitives,
902        max_mesh_output_layers: wgt::Limits::default().max_mesh_output_layers,
903        max_mesh_multiview_view_count: wgt::Limits::default().max_mesh_multiview_view_count,
904
905        max_blas_primitive_count: wgt::Limits::default().max_blas_primitive_count,
906        max_blas_geometry_count: wgt::Limits::default().max_blas_geometry_count,
907        max_tlas_instance_count: wgt::Limits::default().max_tlas_instance_count,
908        max_acceleration_structures_per_shader_stage: wgt::Limits::default()
909            .max_acceleration_structures_per_shader_stage,
910        max_buffers_and_acceleration_structures_per_shader_stage: wgt::Limits::default()
911            .max_buffers_and_acceleration_structures_per_shader_stage,
912
913        max_multiview_view_count: wgt::Limits::default().max_multiview_view_count,
914
915        max_ray_dispatch_count: wgt::Limits::default().max_ray_dispatch_count,
916        max_ray_recursion_depth: wgt::Limits::default().max_ray_recursion_depth,
917    }
918}
919
920fn map_adapter_info(adapter_info: &webgpu_sys::GpuAdapterInfo) -> wgt::AdapterInfo {
921    // TODO(https://github.com/gfx-rs/wgpu/issues/8819): populate more fields if/when possible
922    wgt::AdapterInfo {
923        name: adapter_info.description().to_string(),
924        vendor: 0,
925        device: 0,
926        device_type: if adapter_info.is_fallback_adapter() {
927            wgt::DeviceType::Cpu
928        } else {
929            wgt::DeviceType::Other
930        },
931        device_pci_bus_id: String::new(),
932        driver: String::new(),
933        driver_info: String::new(),
934        backend: wgt::Backend::BrowserWebGpu,
935        subgroup_min_size: adapter_info.subgroup_min_size(),
936        subgroup_max_size: adapter_info.subgroup_max_size(),
937        transient_saves_memory: None,
938        limit_bucket: None,
939    }
940}
941
942fn map_js_sys_limits(limits: &wgt::Limits) -> js_sys::Object<js_sys::Number> {
943    let object = js_sys::Object::<js_sys::Number>::new_typed();
944
945    macro_rules! set_properties {
946        (($from:expr) => ($on:expr) : $(($js_ident:ident, $rs_ident:ident)),* $(,)?) => {
947            $(
948                ::js_sys::Reflect::set(
949                    &$on,
950                    &::wasm_bindgen::JsValue::from(stringify!($js_ident)),
951                    // Numbers may be u64, however using `from` on a u64 yields
952                    // errors on the wasm side, since it uses an unsupported api.
953                    // Wasm sends us things that need to fit into u64s by sending
954                    // us f64s instead. So we just send them f64s back.
955                    &::wasm_bindgen::JsValue::from($from.$rs_ident as f64)
956                )
957                    .expect("Setting Object properties should never fail.");
958            )*
959        }
960    }
961
962    // https://gpuweb.github.io/gpuweb/#gpusupportedlimits
963    set_properties![
964        (limits) => (object):
965        (maxTextureDimension1D, max_texture_dimension_1d),
966        (maxTextureDimension2D, max_texture_dimension_2d),
967        (maxTextureDimension3D, max_texture_dimension_3d),
968        (maxTextureArrayLayers, max_texture_array_layers),
969        (maxBindGroups, max_bind_groups),
970        (maxBindGroupsPlusVertexBuffers, max_bind_groups_plus_vertex_buffers),
971        (maxBindingsPerBindGroup, max_bindings_per_bind_group),
972        (maxDynamicUniformBuffersPerPipelineLayout, max_dynamic_uniform_buffers_per_pipeline_layout),
973        (maxDynamicStorageBuffersPerPipelineLayout, max_dynamic_storage_buffers_per_pipeline_layout),
974        (maxSampledTexturesPerShaderStage, max_sampled_textures_per_shader_stage),
975        (maxSamplersPerShaderStage, max_samplers_per_shader_stage),
976        (maxStorageBuffersPerShaderStage, max_storage_buffers_per_shader_stage),
977        (maxStorageTexturesPerShaderStage, max_storage_textures_per_shader_stage),
978        (maxUniformBuffersPerShaderStage, max_uniform_buffers_per_shader_stage),
979        (maxUniformBufferBindingSize, max_uniform_buffer_binding_size),
980        (maxStorageBufferBindingSize, max_storage_buffer_binding_size),
981        (minUniformBufferOffsetAlignment, min_uniform_buffer_offset_alignment),
982        (minStorageBufferOffsetAlignment, min_storage_buffer_offset_alignment),
983        (maxVertexBuffers, max_vertex_buffers),
984        (maxBufferSize, max_buffer_size),
985        (maxVertexAttributes, max_vertex_attributes),
986        (maxVertexBufferArrayStride, max_vertex_buffer_array_stride),
987        (maxInterStageShaderVariables, max_inter_stage_shader_variables),
988        (maxColorAttachments, max_color_attachments),
989        (maxColorAttachmentBytesPerSample, max_color_attachment_bytes_per_sample),
990        (maxComputeWorkgroupStorageSize, max_compute_workgroup_storage_size),
991        (maxComputeInvocationsPerWorkgroup, max_compute_invocations_per_workgroup),
992        (maxComputeWorkgroupSizeX, max_compute_workgroup_size_x),
993        (maxComputeWorkgroupSizeY, max_compute_workgroup_size_y),
994        (maxComputeWorkgroupSizeZ, max_compute_workgroup_size_z),
995        (maxComputeWorkgroupsPerDimension, max_compute_workgroups_per_dimension),
996    ];
997
998    object
999}
1000
1001fn future_request_adapter(
1002    result: Result<js_sys::JsNullable<webgpu_sys::GpuAdapter>, wasm_bindgen::JsValue>,
1003    requested_backends: Backends,
1004) -> Result<dispatch::DispatchAdapter, wgt::RequestAdapterError> {
1005    result
1006        .map_err(|_| request_adapter_null_error(requested_backends))
1007        .and_then(|adapter| match adapter.into_option() {
1008            Some(adapter) => Ok(WebAdapter {
1009                inner: adapter,
1010                ident: crate::cmp::Identifier::create(),
1011            }
1012            .into()),
1013            None => Err(request_adapter_null_error(requested_backends)),
1014        })
1015}
1016
1017// Translate WebGPU’s null return into our error.
1018fn request_adapter_null_error(requested_backends: Backends) -> wgt::RequestAdapterError {
1019    wgt::RequestAdapterError::NotFound {
1020        active_backends: Backends::BROWSER_WEBGPU,
1021        requested_backends,
1022        // TODO: supported_backends should also include wgpu-core-based backends,
1023        // if they were compiled in.
1024        supported_backends: Backends::BROWSER_WEBGPU,
1025        no_fallback_backends: Backends::empty(),
1026        no_adapter_backends: Backends::BROWSER_WEBGPU,
1027        incompatible_surface_backends: Backends::empty(),
1028    }
1029}
1030
1031fn future_request_device(
1032    result: Result<webgpu_sys::GpuDevice, wasm_bindgen::JsValue>,
1033) -> Result<(dispatch::DispatchDevice, dispatch::DispatchQueue), crate::RequestDeviceError> {
1034    result
1035        .map(|device| {
1036            let queue = device.queue();
1037
1038            // A `GPUDevice` is the first point at which we can empirically probe
1039            // which canvas formats this browser actually supports (see
1040            // `probe_rgba16float_canvas_support`). Run it once here so that
1041            // `Surface::get_capabilities` can report `Rgba16Float` truthfully.
1042            rgba16float_probe::probe_rgba16float_canvas_support(&device);
1043
1044            (
1045                WebDevice {
1046                    inner: device,
1047                    ident: crate::cmp::Identifier::create(),
1048                    error_scope_count: Rc::new(Cell::new(0)),
1049                    pipeline_discovery: Rc::default(),
1050                }
1051                .into(),
1052                WebQueue {
1053                    inner: queue,
1054                    ident: crate::cmp::Identifier::create(),
1055                }
1056                .into(),
1057            )
1058        })
1059        .map_err(|error_value| crate::RequestDeviceError {
1060            // wasm-bindgen provides a reasonable error stringification via `Debug` impl
1061            inner: crate::RequestDeviceErrorKind::WebGpu(format!("{error_value:?}")),
1062        })
1063}
1064
1065fn future_pop_error_scope(
1066    result: Result<js_sys::JsNullable<webgpu_sys::GpuError>, wasm_bindgen::JsValue>,
1067) -> Option<crate::Error> {
1068    Some(crate::Error::from_js(result.ok()?.into_option()?.into()))
1069}
1070
1071fn future_compilation_info(
1072    result: Result<webgpu_sys::GpuCompilationInfo, wasm_bindgen::JsValue>,
1073    base_compilation_info: &WebShaderCompilationInfo,
1074) -> crate::CompilationInfo {
1075    let base_messages = match base_compilation_info {
1076        WebShaderCompilationInfo::Transformed { compilation_info } => {
1077            compilation_info.messages.iter().cloned()
1078        }
1079        _ => [].iter().cloned(),
1080    };
1081
1082    let messages =
1083        match result {
1084            Ok(info) => base_messages
1085                .chain(info.messages().into_iter().map(|message| {
1086                    crate::CompilationMessage::from_js(message, base_compilation_info)
1087                }))
1088                .collect(),
1089            Err(_v) => base_messages
1090                .chain(core::iter::once(crate::CompilationMessage {
1091                    message: "Getting compilation info failed".to_string(),
1092                    message_type: crate::CompilationMessageType::Error,
1093                    location: None,
1094                }))
1095                .collect(),
1096        };
1097
1098    crate::CompilationInfo { messages }
1099}
1100
1101/// Calls `callback(success_value)` when the promise completes successfully, calls `callback(failure_value)`
1102/// when the promise completes unsuccessfully.
1103fn register_then_closures<F, T>(
1104    promise: &Promise<js_sys::Undefined>,
1105    callback: F,
1106    success_value: T,
1107    failure_value: T,
1108) where
1109    F: FnOnce(T) + 'static,
1110    T: 'static,
1111{
1112    // Both the 'success' and 'rejected' closures need access to callback, but only one
1113    // of them will ever run. We have them both hold a reference to a `Rc<RefCell<Option<impl FnOnce...>>>`,
1114    // and then take ownership of callback when invoked.
1115    //
1116    // We also only need Rc's because these will only ever be called on our thread.
1117    //
1118    // We also store the actual closure types inside this Rc, as the closures need to be kept alive
1119    // until they are actually called by the callback. It is valid to drop a closure inside of a callback.
1120    // This allows us to keep the closures alive without leaking them.
1121    let rc_callback: Rc<RefCell<Option<(_, _, F)>>> = Rc::new(RefCell::new(None));
1122
1123    let rc_callback_clone1 = rc_callback.clone();
1124    let rc_callback_clone2 = rc_callback.clone();
1125
1126    let closure_success = wasm_bindgen::closure::Closure::once(move |_| {
1127        let (success_closure, rejection_closure, callback) =
1128            rc_callback_clone1.borrow_mut().take().unwrap();
1129        callback(success_value);
1130        // drop the closures, including ourselves, which will free any captured memory.
1131        drop((success_closure, rejection_closure));
1132        Ok(())
1133    });
1134    let closure_rejected = wasm_bindgen::closure::Closure::once(move |_| {
1135        let (success_closure, rejection_closure, callback) =
1136            rc_callback_clone2.borrow_mut().take().unwrap();
1137        callback(failure_value);
1138        // drop the closures, including ourselves, which will free any captured memory.
1139        drop((success_closure, rejection_closure));
1140        Ok(())
1141    });
1142
1143    // Calling then before setting the value in the Rc seems like a race, but it isn't
1144    // because the promise callback will run on this thread, so there is no race.
1145    let _ = promise.then_with_reject(&closure_success, &closure_rejected);
1146
1147    *rc_callback.borrow_mut() = Some((closure_success, closure_rejected, callback));
1148}
1149
1150impl ContextWebGpu {
1151    /// Common portion of the internal branches of the public `instance_create_surface` function.
1152    ///
1153    /// Note: Analogous code also exists in the WebGL2 backend at
1154    /// `wgpu_hal::gles::web::Instance`.
1155    fn create_surface_from_context(
1156        &self,
1157        canvas: Canvas,
1158        context_result: Result<Option<js_sys::Object>, wasm_bindgen::JsValue>,
1159    ) -> Result<dispatch::DispatchSurface, crate::CreateSurfaceError> {
1160        let context: js_sys::Object = match context_result {
1161            Ok(Some(context)) => context,
1162            Ok(None) => {
1163                // <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev>
1164                // A getContext() call “returns null if contextId is not supported, or if the
1165                // canvas has already been initialized with another context type”. Additionally,
1166                // “not supported” could include “insufficient GPU resources” or “the GPU process
1167                // previously crashed”. So, we must return it as an `Err` since it could occur
1168                // for circumstances outside the application author's control.
1169                return Err(crate::CreateSurfaceError {
1170                    inner: crate::CreateSurfaceErrorKind::Web(
1171                        String::from(
1172                            "canvas.getContext() returned null; webgpu not available or canvas already in use"
1173                        )
1174                    )
1175                });
1176            }
1177            Err(js_error) => {
1178                // <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext>
1179                // A thrown exception indicates misuse of the canvas state.
1180                return Err(crate::CreateSurfaceError {
1181                    inner: crate::CreateSurfaceErrorKind::Web(format!(
1182                        "canvas.getContext() threw exception {js_error:?}",
1183                    )),
1184                });
1185            }
1186        };
1187
1188        /* Use unchecked_into instead of dyn_into: the instanceof check that dyn_into
1189         * performs for GpuCanvasContext fails in some wasm-bindgen configurations even
1190         * when the object is correct (e.g. certain browser extensions or cross-realm
1191         * scenarios interfere with the JS prototype chain). getContext("webgpu") already
1192         * guarantees the returned object is a GPUCanvasContext when it succeeds, so the
1193         * instanceof check is redundant and only causes spurious surface creation errors. */
1194        let context: webgpu_sys::GpuCanvasContext = context.unchecked_into();
1195
1196        Ok(WebSurface {
1197            gpu: self.gpu.clone(),
1198            context,
1199            canvas,
1200            configure_failed: Cell::new(false),
1201            ident: crate::cmp::Identifier::create(),
1202        }
1203        .into())
1204    }
1205}
1206
1207// Represents the global object in the JavaScript context.
1208// It can be cast to from `webgpu_sys::global` and exposes two getters `window` and `worker` of which only one is defined depending on the caller's context.
1209// When called from the UI thread only `window` is defined whereas `worker` is only defined within a web worker context.
1210// See: https://github.com/rustwasm/gloo/blob/2c9e776701ecb90c53e62dec1abd19c2b70e47c7/crates/timers/src/callback.rs#L8-L40
1211#[wasm_bindgen]
1212extern "C" {
1213    type Global;
1214
1215    #[wasm_bindgen(method, getter, js_name = Window)]
1216    fn window(this: &Global) -> JsValue;
1217
1218    #[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
1219    fn worker(this: &Global) -> JsValue;
1220}
1221
1222#[derive(Debug, Clone)]
1223pub enum Canvas {
1224    Canvas(web_sys::HtmlCanvasElement),
1225    Offscreen(web_sys::OffscreenCanvas),
1226}
1227
1228#[derive(Debug, Clone, Copy)]
1229pub struct BrowserGpuPropertyInaccessible;
1230
1231/// Returns the browser's gpu object or `Err(BrowserGpuPropertyInaccessible)` if
1232/// the current context is neither the main thread nor a dedicated worker.
1233///
1234/// If WebGPU is not supported, the Gpu property may (!) be `undefined`,
1235/// and so this function will return `Ok(None)`.
1236/// Note that this check is insufficient to determine whether WebGPU is
1237/// supported, as the browser may define the Gpu property, but be unable to
1238/// create any WebGPU adapters.
1239/// To detect whether WebGPU is supported, use the [`crate::utils::is_browser_webgpu_supported`] function.
1240///
1241/// See:
1242/// * <https://developer.mozilla.org/en-US/docs/Web/API/Navigator/gpu>
1243/// * <https://developer.mozilla.org/en-US/docs/Web/API/WorkerNavigator/gpu>
1244pub fn get_browser_gpu_property(
1245) -> Result<Option<DefinedNonNullJsValue<webgpu_sys::Gpu>>, BrowserGpuPropertyInaccessible> {
1246    let global: Global = js_sys::global().unchecked_into();
1247
1248    let maybe_undefined_gpu: webgpu_sys::Gpu = if !global.window().is_undefined() {
1249        let navigator = global.unchecked_into::<web_sys::Window>().navigator();
1250        ext_bindings::NavigatorGpu::gpu(&navigator)
1251    } else if !global.worker().is_undefined() {
1252        let navigator = global
1253            .unchecked_into::<web_sys::WorkerGlobalScope>()
1254            .navigator();
1255        ext_bindings::NavigatorGpu::gpu(&navigator)
1256    } else {
1257        return Err(BrowserGpuPropertyInaccessible);
1258    };
1259    Ok(DefinedNonNullJsValue::new(maybe_undefined_gpu))
1260}
1261
1262#[derive(Debug, Clone)]
1263pub struct WebAdapter {
1264    pub(crate) inner: webgpu_sys::GpuAdapter,
1265    /// Unique identifier for this Adapter.
1266    ident: crate::cmp::Identifier,
1267}
1268
1269#[derive(Debug, Clone)]
1270pub struct WebDevice {
1271    pub(crate) inner: webgpu_sys::GpuDevice,
1272    /// Unique identifier for this Device.
1273    ident: crate::cmp::Identifier,
1274    /// Current number of error scopes that have been pushed on the device.
1275    error_scope_count: Rc<Cell<u32>>,
1276    /// The `createRenderPipelineAsync` promises of the pipelines created
1277    /// while pipeline discovery is on, or `None` while it is off.
1278    pipeline_discovery: Rc<RefCell<Option<js_sys::Array>>>,
1279}
1280
1281#[derive(Debug, Clone)]
1282pub struct WebQueue {
1283    pub(crate) inner: webgpu_sys::GpuQueue,
1284    /// Unique identifier for this Queue.
1285    ident: crate::cmp::Identifier,
1286}
1287
1288#[derive(Debug, Clone)]
1289pub struct WebBindGroupLayout {
1290    pub(crate) inner: webgpu_sys::GpuBindGroupLayout,
1291    /// Unique identifier for this BindGroupLayout.
1292    ident: crate::cmp::Identifier,
1293}
1294
1295#[derive(Debug, Clone)]
1296pub struct WebBindGroup {
1297    pub(crate) inner: webgpu_sys::GpuBindGroup,
1298    /// Unique identifier for this BindGroup.
1299    ident: crate::cmp::Identifier,
1300}
1301
1302#[derive(Debug, Clone)]
1303pub struct WebTextureView {
1304    pub(crate) inner: webgpu_sys::GpuTextureView,
1305    /// Unique identifier for this TextureView.
1306    ident: crate::cmp::Identifier,
1307}
1308
1309#[derive(Debug, Clone)]
1310pub struct WebSampler {
1311    pub(crate) inner: webgpu_sys::GpuSampler,
1312    /// Unique identifier for this Sampler.
1313    ident: crate::cmp::Identifier,
1314}
1315
1316/// Remembers which portion of a buffer has been mapped, along with a reference
1317/// to the mapped portion.
1318#[derive(Debug, Clone)]
1319struct WebBufferMapState {
1320    /// The mapped memory of the buffer.
1321    pub mapped_buffer: Option<js_sys::ArrayBuffer>,
1322    /// The total range which has been mapped in the buffer overall.
1323    pub range: Range<wgt::BufferAddress>,
1324}
1325
1326/// Stores the state of a GPU buffer and a reference to its mapped `ArrayBuffer` (if any).
1327/// The WebGPU specification forbids calling `getMappedRange` on a `webgpu_sys::GpuBuffer` more than
1328/// once, so this struct stores the initial mapped range and re-uses it, allowing for multiple `get_mapped_range`
1329/// calls on the Rust-side.
1330#[derive(Debug, Clone)]
1331pub struct WebBuffer {
1332    /// The associated GPU buffer.
1333    pub(crate) inner: webgpu_sys::GpuBuffer,
1334    /// The mapped array buffer and mapped range.
1335    mapping: Rc<RefCell<WebBufferMapState>>,
1336    /// Unique identifier for this Buffer.
1337    ident: crate::cmp::Identifier,
1338}
1339
1340impl WebBuffer {
1341    /// Creates a new web buffer for the given Javascript object and description.
1342    fn new(inner: webgpu_sys::GpuBuffer, desc: &crate::BufferDescriptor<'_>) -> Self {
1343        Self {
1344            inner,
1345            mapping: Rc::new(RefCell::new(WebBufferMapState {
1346                mapped_buffer: None,
1347                range: 0..desc.size,
1348            })),
1349            ident: crate::cmp::Identifier::create(),
1350        }
1351    }
1352
1353    /// Obtains a reference to the re-usable buffer mapping as a Javascript array view.
1354    fn get_mapped_range(
1355        &self,
1356        sub_range: Range<wgt::BufferAddress>,
1357    ) -> Result<js_sys::Uint8Array, crate::MapRangeError> {
1358        let mut mapping = self.mapping.borrow_mut();
1359        let range = mapping.range.clone();
1360        if mapping.mapped_buffer.is_none() {
1361            let buffer = self
1362                .inner
1363                .get_mapped_range_with_f64_and_f64(
1364                    range.start as f64,
1365                    (range.end - range.start) as f64,
1366                )
1367                .map_err(|e| crate::MapRangeError(format!("{e:?}")))?;
1368            mapping.mapped_buffer = Some(buffer);
1369        }
1370        let array_buffer = mapping.mapped_buffer.as_ref().unwrap();
1371        Ok(js_sys::Uint8Array::new_with_byte_offset_and_length(
1372            array_buffer,
1373            (sub_range.start - range.start) as u32,
1374            (sub_range.end - sub_range.start) as u32,
1375        ))
1376    }
1377
1378    /// Sets the range of the buffer which is presently mapped.
1379    fn set_mapped_range(&self, range: Range<wgt::BufferAddress>) {
1380        self.mapping.borrow_mut().range = range;
1381    }
1382}
1383
1384#[derive(Debug, Clone)]
1385pub struct WebTexture {
1386    pub(crate) inner: webgpu_sys::GpuTexture,
1387    /// Lifetime management for the underlying `GpuTexture`.
1388    ///
1389    /// - `None` means wgpu owns the handle: [`Self::destroy`] forwards to
1390    ///   `GpuTexture.destroy()`. (This is the case for textures created via
1391    ///   `device.createTexture` or `surface.getCurrentTexture`.)
1392    /// - `Some(_)` means the handle is externally owned (wrapped via
1393    ///   [`WebDevice::wrap_external_texture`]). `destroy()` is a no-op, and
1394    ///   the wrapped [`DropCallback`], if any, fires when the last clone of
1395    ///   this `WebTexture` is dropped, signalling that wgpu is done with the
1396    ///   handle.
1397    drop_guard: Option<Rc<DropGuard>>,
1398    /// Unique identifier for this Texture.
1399    ident: crate::cmp::Identifier,
1400}
1401
1402#[derive(Debug, Clone)]
1403pub struct WebExternalTexture {
1404    /// Unique identifier for this ExternalTexture.
1405    ident: crate::cmp::Identifier,
1406}
1407
1408#[derive(Debug, Clone)]
1409pub struct WebBlas {
1410    /// Unique identifier for this Blas.
1411    ident: crate::cmp::Identifier,
1412}
1413
1414#[derive(Debug, Clone)]
1415pub struct WebTlas {
1416    /// Unique identifier for this Blas.
1417    ident: crate::cmp::Identifier,
1418}
1419
1420#[derive(Debug, Clone)]
1421pub struct WebQuerySet {
1422    pub(crate) inner: webgpu_sys::GpuQuerySet,
1423    /// Unique identifier for this QuerySet.
1424    ident: crate::cmp::Identifier,
1425}
1426
1427#[derive(Debug, Clone)]
1428pub struct WebPipelineLayout {
1429    pub(crate) inner: webgpu_sys::GpuPipelineLayout,
1430    /// Unique identifier for this PipelineLayout.
1431    ident: crate::cmp::Identifier,
1432}
1433
1434#[derive(Debug, Clone)]
1435pub struct WebRenderPipeline {
1436    /// `None` for a pipeline created during pipeline discovery, which never
1437    /// draws.
1438    pub(crate) inner: Option<webgpu_sys::GpuRenderPipeline>,
1439    /// Unique identifier for this RenderPipeline.
1440    ident: crate::cmp::Identifier,
1441}
1442
1443#[derive(Debug, Clone)]
1444pub struct WebComputePipeline {
1445    pub(crate) inner: webgpu_sys::GpuComputePipeline,
1446    /// Unique identifier for this ComputePipeline.
1447    ident: crate::cmp::Identifier,
1448}
1449
1450#[derive(Debug, Clone)]
1451pub struct WebPipelineCache {
1452    /// Unique identifier for this PipelineCache.
1453    ident: crate::cmp::Identifier,
1454}
1455
1456#[derive(Debug, Clone)]
1457pub struct WebCommandEncoder {
1458    pub(crate) inner: webgpu_sys::GpuCommandEncoder,
1459    /// The device's pipeline discovery, which a pass begun while it is on
1460    /// records no draw for.
1461    pipeline_discovery: Rc<RefCell<Option<js_sys::Array>>>,
1462    /// Unique identifier for this CommandEncoder.
1463    ident: crate::cmp::Identifier,
1464}
1465
1466#[derive(Debug)]
1467pub struct WebComputePassEncoder {
1468    pub(crate) inner: webgpu_sys::GpuComputePassEncoder,
1469    /// Unique identifier for this ComputePassEncoder.
1470    ident: crate::cmp::Identifier,
1471}
1472
1473#[derive(Debug)]
1474pub struct WebRenderPassEncoder {
1475    pub(crate) inner: webgpu_sys::GpuRenderPassEncoder,
1476    /// Draws left out: the pass began during pipeline discovery, or its
1477    /// pipeline was created during it.
1478    draws: PipelineDraws,
1479    /// Unique identifier for this RenderPassEncoder.
1480    ident: crate::cmp::Identifier,
1481}
1482
1483#[derive(Debug)]
1484pub struct WebCommandBuffer {
1485    pub(crate) inner: webgpu_sys::GpuCommandBuffer,
1486    /// Unique identifier for this CommandBuffer.
1487    ident: crate::cmp::Identifier,
1488}
1489
1490#[derive(Debug, Clone)]
1491pub struct WebRenderBundleEncoder {
1492    pub(crate) inner: webgpu_sys::GpuRenderBundleEncoder,
1493    /// Draws left out, as a render pass leaves them out.
1494    draws: PipelineDraws,
1495    /// Unique identifier for this RenderBundleEncoder.
1496    ident: crate::cmp::Identifier,
1497}
1498
1499#[derive(Debug, Clone)]
1500pub struct WebRenderBundle {
1501    pub(crate) inner: webgpu_sys::GpuRenderBundle,
1502    /// Unique identifier for this RenderBundle.
1503    ident: crate::cmp::Identifier,
1504}
1505
1506#[derive(Debug, Clone)]
1507pub struct WebSurface {
1508    gpu: Option<DefinedNonNullJsValue<webgpu_sys::Gpu>>,
1509    canvas: Canvas,
1510    context: webgpu_sys::GpuCanvasContext,
1511    /// Set when the most recent [`configure`](Self::configure) call failed, e.g.
1512    /// because the browser rejected the requested canvas format. While set,
1513    /// `get_current_texture` reports [`SurfaceStatus::Lost`] instead of letting
1514    /// the JS exception turn into a panic — there is no `catch_unwind` on wasm,
1515    /// so a panic would be an uncatchable abort the application cannot recover
1516    /// from.
1517    ///
1518    /// [`SurfaceStatus::Lost`]: crate::SurfaceStatus::Lost
1519    configure_failed: Cell<bool>,
1520    /// Unique identifier for this Surface.
1521    ident: crate::cmp::Identifier,
1522}
1523
1524#[derive(Debug, Clone)]
1525pub struct WebSurfaceOutputDetail {
1526    /// Unique identifier for this SurfaceOutputDetail.
1527    ident: crate::cmp::Identifier,
1528}
1529
1530#[derive(Debug)]
1531pub struct WebQueueWriteBuffer {
1532    inner: Box<[u8]>,
1533    /// Unique identifier for this QueueWriteBuffer.
1534    ident: crate::cmp::Identifier,
1535}
1536
1537#[derive(Debug)]
1538pub struct WebBufferMappedRange {
1539    actual_mapping: js_sys::Uint8Array,
1540    /// Copy of actual_mapping that lives in the Rust/Wasm heap instead of JS. This
1541    /// is done only when accessed for the first time to avoid unnecessary allocations.
1542    temporary_mapping: OnceCell<Vec<u8>>,
1543    /// Whether `temporary_mapping` has possibly been written to and needs to be written back to JS.
1544    temporary_mapping_modified: bool,
1545    /// Unique identifier for this BufferMappedRange.
1546    ident: crate::cmp::Identifier,
1547}
1548
1549impl_send_sync!(ContextWebGpu);
1550impl_send_sync!(WebAdapter);
1551impl_send_sync!(WebDevice);
1552impl_send_sync!(WebQueue);
1553impl_send_sync!(WebShaderModule);
1554impl_send_sync!(WebBindGroupLayout);
1555impl_send_sync!(WebBindGroup);
1556impl_send_sync!(WebTextureView);
1557impl_send_sync!(WebSampler);
1558impl_send_sync!(WebBuffer);
1559impl_send_sync!(WebTexture);
1560impl_send_sync!(WebExternalTexture);
1561impl_send_sync!(WebBlas);
1562impl_send_sync!(WebTlas);
1563impl_send_sync!(WebQuerySet);
1564impl_send_sync!(WebPipelineLayout);
1565impl_send_sync!(WebRenderPipeline);
1566impl_send_sync!(WebComputePipeline);
1567impl_send_sync!(WebPipelineCache);
1568impl_send_sync!(WebCommandEncoder);
1569impl_send_sync!(WebComputePassEncoder);
1570impl_send_sync!(WebRenderPassEncoder);
1571impl_send_sync!(WebCommandBuffer);
1572impl_send_sync!(WebRenderBundleEncoder);
1573impl_send_sync!(WebRenderBundle);
1574impl_send_sync!(WebSurface);
1575impl_send_sync!(WebSurfaceOutputDetail);
1576impl_send_sync!(WebQueueWriteBuffer);
1577impl_send_sync!(WebBufferMappedRange);
1578
1579crate::cmp::impl_eq_ord_hash_proxy!(ContextWebGpu => .ident);
1580crate::cmp::impl_eq_ord_hash_proxy!(WebAdapter => .ident);
1581crate::cmp::impl_eq_ord_hash_proxy!(WebDevice => .ident);
1582crate::cmp::impl_eq_ord_hash_proxy!(WebQueue => .ident);
1583crate::cmp::impl_eq_ord_hash_proxy!(WebShaderModule => .ident);
1584crate::cmp::impl_eq_ord_hash_proxy!(WebBindGroupLayout => .ident);
1585crate::cmp::impl_eq_ord_hash_proxy!(WebBindGroup => .ident);
1586crate::cmp::impl_eq_ord_hash_proxy!(WebTextureView => .ident);
1587crate::cmp::impl_eq_ord_hash_proxy!(WebSampler => .ident);
1588crate::cmp::impl_eq_ord_hash_proxy!(WebBuffer => .ident);
1589crate::cmp::impl_eq_ord_hash_proxy!(WebTexture => .ident);
1590crate::cmp::impl_eq_ord_hash_proxy!(WebExternalTexture => .ident);
1591crate::cmp::impl_eq_ord_hash_proxy!(WebBlas => .ident);
1592crate::cmp::impl_eq_ord_hash_proxy!(WebTlas => .ident);
1593crate::cmp::impl_eq_ord_hash_proxy!(WebQuerySet => .ident);
1594crate::cmp::impl_eq_ord_hash_proxy!(WebPipelineLayout => .ident);
1595crate::cmp::impl_eq_ord_hash_proxy!(WebRenderPipeline => .ident);
1596crate::cmp::impl_eq_ord_hash_proxy!(WebComputePipeline => .ident);
1597crate::cmp::impl_eq_ord_hash_proxy!(WebPipelineCache => .ident);
1598crate::cmp::impl_eq_ord_hash_proxy!(WebCommandEncoder => .ident);
1599crate::cmp::impl_eq_ord_hash_proxy!(WebComputePassEncoder => .ident);
1600crate::cmp::impl_eq_ord_hash_proxy!(WebRenderPassEncoder => .ident);
1601crate::cmp::impl_eq_ord_hash_proxy!(WebCommandBuffer => .ident);
1602crate::cmp::impl_eq_ord_hash_proxy!(WebRenderBundleEncoder => .ident);
1603crate::cmp::impl_eq_ord_hash_proxy!(WebRenderBundle => .ident);
1604crate::cmp::impl_eq_ord_hash_proxy!(WebSurface => .ident);
1605crate::cmp::impl_eq_ord_hash_proxy!(WebSurfaceOutputDetail => .ident);
1606crate::cmp::impl_eq_ord_hash_proxy!(WebQueueWriteBuffer => .ident);
1607crate::cmp::impl_eq_ord_hash_proxy!(WebBufferMappedRange => .ident);
1608
1609impl dispatch::InstanceInterface for ContextWebGpu {
1610    fn new(desc: crate::InstanceDescriptor) -> Self
1611    where
1612        Self: Sized,
1613    {
1614        let Ok(gpu) = get_browser_gpu_property() else {
1615            panic!(
1616                "Accessing the GPU is only supported on the main thread or from a dedicated worker"
1617            );
1618        };
1619
1620        ContextWebGpu {
1621            gpu,
1622            requested_backends: desc.backends,
1623            ident: crate::cmp::Identifier::create(),
1624        }
1625    }
1626
1627    unsafe fn create_surface(
1628        &self,
1629        target: crate::SurfaceTargetUnsafe,
1630    ) -> Result<dispatch::DispatchSurface, crate::CreateSurfaceError> {
1631        match target {
1632            SurfaceTargetUnsafe::RawHandle {
1633                raw_display_handle: _,
1634                raw_window_handle,
1635            } => {
1636                let canvas_element: web_sys::HtmlCanvasElement = match raw_window_handle {
1637                    raw_window_handle::RawWindowHandle::Web(handle) => {
1638                        let canvas_node: wasm_bindgen::JsValue = web_sys::window()
1639                            .and_then(|win| win.document())
1640                            .and_then(|doc| {
1641                                doc.query_selector_all(&format!(
1642                                    "[data-raw-handle=\"{}\"]",
1643                                    handle.id
1644                                ))
1645                                .ok()
1646                            })
1647                            .and_then(|nodes| nodes.get(0))
1648                            .expect("expected to find single canvas")
1649                            .into();
1650                        canvas_node.into()
1651                    }
1652                    raw_window_handle::RawWindowHandle::WebCanvas(handle) => {
1653                        let value: &JsValue = unsafe { handle.obj.cast().as_ref() };
1654                        value.clone().unchecked_into()
1655                    }
1656                    raw_window_handle::RawWindowHandle::WebOffscreenCanvas(handle) => {
1657                        let value: &JsValue = unsafe { handle.obj.cast().as_ref() };
1658                        let canvas: web_sys::OffscreenCanvas = value.clone().unchecked_into();
1659                        let context_result = canvas.get_context("webgpu");
1660
1661                        return self.create_surface_from_context(
1662                            Canvas::Offscreen(canvas),
1663                            context_result,
1664                        );
1665                    }
1666                    _ => panic!("expected valid handle for canvas"),
1667                };
1668
1669                let context_result = canvas_element.get_context("webgpu");
1670                self.create_surface_from_context(Canvas::Canvas(canvas_element), context_result)
1671            }
1672        }
1673    }
1674
1675    fn request_adapter(
1676        &self,
1677        options: &crate::RequestAdapterOptions<'_, '_>,
1678    ) -> Pin<Box<dyn dispatch::RequestAdapterFuture>> {
1679        let requested_backends = self.requested_backends;
1680
1681        //TODO: support this check, return `None` if the flag is not set.
1682        // It's not trivial, since we need the Future logic to have this check,
1683        // and currently the Future here has no room for extra parameter `backends`.
1684        if !(requested_backends.contains(wgt::Backends::BROWSER_WEBGPU)) {
1685            return Box::pin(core::future::ready(Err(
1686                wgt::RequestAdapterError::NotFound {
1687                    active_backends: Backends::BROWSER_WEBGPU,
1688                    requested_backends,
1689                    // TODO: supported_backends should also include wgpu-core-based backends,
1690                    // if they were compiled in.
1691                    supported_backends: Backends::BROWSER_WEBGPU,
1692                    no_fallback_backends: Backends::default(),
1693                    no_adapter_backends: Backends::default(),
1694                    incompatible_surface_backends: Backends::default(),
1695                },
1696            )));
1697        }
1698        let mapped_options = webgpu_sys::GpuRequestAdapterOptions::new();
1699        let mapped_power_preference = match options.power_preference {
1700            wgt::PowerPreference::None => None,
1701            wgt::PowerPreference::LowPower => Some(webgpu_sys::GpuPowerPreference::LowPower),
1702            wgt::PowerPreference::HighPerformance => {
1703                Some(webgpu_sys::GpuPowerPreference::HighPerformance)
1704            }
1705        };
1706        if let Some(mapped_pref) = mapped_power_preference {
1707            mapped_options.set_power_preference(mapped_pref);
1708        }
1709
1710        if let Some(gpu) = &self.gpu {
1711            let adapter_promise = gpu.request_adapter_with_options(&mapped_options);
1712            Box::pin(MakeSendFuture::new(
1713                wasm_bindgen_futures::JsFuture::from(adapter_promise),
1714                move |result| future_request_adapter(result, requested_backends),
1715            ))
1716        } else {
1717            // Gpu is undefined; WebGPU is not supported in this browser.
1718            // Treat this exactly like requestAdapter() returned null.
1719            Box::pin(core::future::ready(Err(request_adapter_null_error(
1720                requested_backends,
1721            ))))
1722        }
1723    }
1724    fn enumerate_adapters(
1725        &self,
1726        _backends: crate::Backends,
1727    ) -> Pin<Box<dyn dispatch::EnumerateAdapterFuture>> {
1728        let future = self.request_adapter(&crate::RequestAdapterOptions::default());
1729        let enumerate_future = async move {
1730            let adapter = future.await;
1731            match adapter {
1732                Ok(a) => vec![a],
1733                Err(_) => vec![],
1734            }
1735        };
1736        Box::pin(enumerate_future)
1737    }
1738
1739    fn poll_all_devices(&self, _force_wait: bool) -> bool {
1740        // Devices are automatically polled.
1741        true
1742    }
1743
1744    #[cfg(feature = "wgsl")]
1745    fn wgsl_language_features(&self) -> crate::WgslLanguageFeatures {
1746        let mut wgsl_language_features = crate::WgslLanguageFeatures::empty();
1747        if let Some(gpu) = &self.gpu {
1748            gpu.wgsl_language_features()
1749                .keys()
1750                .into_iter()
1751                .map(|wlf| wlf.expect("`WgslLanguageFeatures` elements should be valid"))
1752                .map(|wlf| {
1753                    wlf.as_string()
1754                        .expect("`WgslLanguageFeatures` should be string set")
1755                })
1756                .filter_map(|wlf| match wlf.as_str() {
1757                    "readonly_and_readwrite_storage_textures" => {
1758                        Some(crate::WgslLanguageFeatures::ReadOnlyAndReadWriteStorageTextures)
1759                    }
1760                    "packed_4x8_integer_dot_product" => {
1761                        Some(crate::WgslLanguageFeatures::Packed4x8IntegerDotProduct)
1762                    }
1763                    "unrestricted_pointer_parameters" => {
1764                        Some(crate::WgslLanguageFeatures::UnrestrictedPointerParameters)
1765                    }
1766                    "pointer_composite_access" => {
1767                        Some(crate::WgslLanguageFeatures::PointerCompositeAccess)
1768                    }
1769                    _ => None,
1770                })
1771                .for_each(|wlf| {
1772                    wgsl_language_features |= wlf;
1773                })
1774        }
1775        wgsl_language_features
1776    }
1777}
1778
1779impl Drop for ContextWebGpu {
1780    fn drop(&mut self) {
1781        // no-op
1782    }
1783}
1784
1785impl dispatch::AdapterInterface for WebAdapter {
1786    fn request_device(
1787        &self,
1788        desc: &crate::DeviceDescriptor<'_>,
1789    ) -> Pin<Box<dyn dispatch::RequestDeviceFuture>> {
1790        if !matches!(desc.trace, wgt::Trace::Off) {
1791            log::warn!("The `trace` parameter is not supported on the WebGPU backend.");
1792        }
1793
1794        let mapped_desc = webgpu_sys::GpuDeviceDescriptor::new();
1795
1796        let required_limits = map_js_sys_limits(&desc.required_limits);
1797        mapped_desc.set_required_limits(&required_limits);
1798
1799        let required_features = FEATURES_MAPPING
1800            .iter()
1801            .copied()
1802            .flat_map(|(flag, value)| {
1803                if desc.required_features.contains(flag) {
1804                    Some(
1805                        wasm_bindgen::JsValue::from(value)
1806                            .dyn_into::<js_sys::JsString>()
1807                            .unwrap(),
1808                    )
1809                } else {
1810                    None
1811                }
1812            })
1813            .collect::<Vec<js_sys::JsString>>();
1814        mapped_desc.set_required_features(&required_features);
1815
1816        if let Some(label) = desc.label {
1817            mapped_desc.set_label(label);
1818        }
1819
1820        let device_promise = self.inner.request_device_with_descriptor(&mapped_desc);
1821
1822        Box::pin(MakeSendFuture::new(
1823            wasm_bindgen_futures::JsFuture::from(device_promise),
1824            future_request_device,
1825        ))
1826    }
1827
1828    fn is_surface_supported(&self, _surface: &dispatch::DispatchSurface) -> bool {
1829        // All surfaces are inherently supported.
1830        true
1831    }
1832
1833    fn features(&self) -> crate::Features {
1834        map_wgt_features(self.inner.features())
1835    }
1836
1837    fn limits(&self) -> crate::Limits {
1838        map_wgt_limits(self.inner.limits())
1839    }
1840
1841    fn downlevel_capabilities(&self) -> crate::DownlevelCapabilities {
1842        // WebGPU is assumed to be fully compliant
1843        crate::DownlevelCapabilities::default()
1844    }
1845
1846    fn get_info(&self) -> crate::AdapterInfo {
1847        map_adapter_info(&self.inner.info())
1848    }
1849
1850    fn get_texture_format_features(
1851        &self,
1852        format: crate::TextureFormat,
1853    ) -> crate::TextureFormatFeatures {
1854        format.guaranteed_format_features(dispatch::AdapterInterface::features(self))
1855    }
1856
1857    fn get_presentation_timestamp(&self) -> crate::PresentationTimestamp {
1858        crate::PresentationTimestamp::INVALID_TIMESTAMP
1859    }
1860
1861    fn cooperative_matrix_properties(&self) -> Vec<wgt::CooperativeMatrixProperties> {
1862        Vec::new()
1863    }
1864}
1865impl Drop for WebAdapter {
1866    fn drop(&mut self) {
1867        // no-op
1868    }
1869}
1870
1871/// Whether an encoder records the draws that follow its last pipeline.
1872#[derive(Debug, Clone, Copy)]
1873struct PipelineDraws {
1874    /// The encoder began during pipeline discovery: it records no draw.
1875    discovering: bool,
1876    /// Its last pipeline was created during pipeline discovery.
1877    skipping: bool,
1878}
1879
1880impl PipelineDraws {
1881    fn new(discovering: bool) -> Self {
1882        Self {
1883            discovering,
1884            skipping: false,
1885        }
1886    }
1887
1888    /// The JS pipeline to set, or `None` when the draws that follow are
1889    /// left out.
1890    fn set<'a>(
1891        &mut self,
1892        pipeline: &'a dispatch::DispatchRenderPipeline,
1893    ) -> Option<&'a webgpu_sys::GpuRenderPipeline> {
1894        let pipeline = pipeline.as_webgpu().inner.as_ref().filter(|_| !self.discovering);
1895        self.skipping = pipeline.is_none();
1896        pipeline
1897    }
1898}
1899
1900/// The pipelines a pipeline discovery compiled, kept so the browser keeps
1901/// them cached until they are created again.
1902#[derive(Debug)]
1903pub struct DiscoveredPipelines(#[allow(dead_code)] JsValue);
1904
1905impl WebDevice {
1906    /// Starts pipeline discovery: see `Device::begin_pipeline_discovery`.
1907    pub(crate) fn begin_pipeline_discovery(&self) {
1908        *self.pipeline_discovery.borrow_mut() = Some(js_sys::Array::new());
1909    }
1910
1911    /// Ends pipeline discovery: see `Device::end_pipeline_discovery`.
1912    pub(crate) fn end_pipeline_discovery(
1913        &self,
1914    ) -> impl Future<Output = DiscoveredPipelines> + 'static {
1915        let pending = self
1916            .pipeline_discovery
1917            .borrow_mut()
1918            .take()
1919            .unwrap_or_else(js_sys::Array::new);
1920        let settled = wasm_bindgen_futures::JsFuture::from(Promise::all_settled(&pending));
1921        async move { DiscoveredPipelines(settled.await.unwrap_or_else(|error| error)) }
1922    }
1923
1924    /// Wrap a foreign `webgpu_sys::GpuTexture` (e.g. from a canvas `getCurrentTexture()`) as a
1925    /// dispatch-level `WebTexture` without calling `device.createTexture(...)`.
1926    ///
1927    /// The resulting texture is *external*: its `destroy()` is a no-op, since
1928    /// the JS handle's lifetime is the caller's responsibility.
1929    ///
1930    /// If `drop_callback` is `Some`, it fires when the last clone of the
1931    /// returned `WebTexture` is dropped, signalling that wgpu is done with
1932    /// the handle (e.g. so the caller can call `GpuTexture.destroy()`
1933    /// themselves, release a pool slot, etc.).
1934    ///
1935    /// The caller is responsible for asserting that `texture` was produced by
1936    /// the same underlying `GpuDevice` as `self`.
1937    pub(crate) fn wrap_external_texture(
1938        &self,
1939        texture: webgpu_sys::GpuTexture,
1940        drop_callback: Option<DropCallback>,
1941    ) -> dispatch::DispatchTexture {
1942        WebTexture {
1943            inner: texture,
1944            drop_guard: Some(Rc::new(DropGuard::new(drop_callback))),
1945            ident: crate::cmp::Identifier::create(),
1946        }
1947        .into()
1948    }
1949}
1950
1951impl dispatch::DeviceInterface for WebDevice {
1952    fn features(&self) -> crate::Features {
1953        map_wgt_features(self.inner.features())
1954    }
1955
1956    fn limits(&self) -> crate::Limits {
1957        map_wgt_limits(self.inner.limits())
1958    }
1959
1960    fn adapter_info(&self) -> crate::AdapterInfo {
1961        map_adapter_info(&self.inner.adapter_info())
1962    }
1963
1964    fn create_shader_module(
1965        &self,
1966        desc: crate::ShaderModuleDescriptor<'_>,
1967        _shader_runtime_checks: crate::ShaderRuntimeChecks,
1968    ) -> dispatch::DispatchShaderModule {
1969        let shader_module_result = match desc.source {
1970            #[cfg(feature = "spirv")]
1971            crate::ShaderSource::SpirV(ref spv) => {
1972                use naga::front;
1973
1974                let options = naga::front::spv::Options {
1975                    adjust_coordinate_space: false,
1976                    strict_capabilities: true,
1977                    block_ctx_dump_prefix: None,
1978                };
1979                let spv_parser = front::spv::Frontend::new(spv.iter().cloned(), &options);
1980                spv_parser
1981                    .parse()
1982                    .map_err(|inner| {
1983                        crate::CompilationInfo::from(naga::error::ShaderError {
1984                            source: String::new(),
1985                            label: desc.label.map(|s| s.to_string()),
1986                            inner: Box::new(inner),
1987                        })
1988                    })
1989                    .and_then(|spv_module| {
1990                        validate_transformed_shader_module(&spv_module, "", &desc).map(|v| {
1991                            (
1992                                v,
1993                                WebShaderCompilationInfo::Transformed {
1994                                    compilation_info: crate::CompilationInfo { messages: vec![] },
1995                                },
1996                            )
1997                        })
1998                    })
1999            }
2000            #[cfg(feature = "glsl")]
2001            crate::ShaderSource::Glsl {
2002                ref shader,
2003                stage,
2004                defines,
2005            } => {
2006                use naga::front;
2007
2008                // Parse the given shader code and store its representation.
2009                let options = front::glsl::Options {
2010                    stage,
2011                    defines: defines
2012                        .iter()
2013                        .map(|&(key, value)| (String::from(key), String::from(value)))
2014                        .collect(),
2015                };
2016                let mut parser = front::glsl::Frontend::default();
2017                parser
2018                    .parse(&options, shader)
2019                    .map_err(|inner| {
2020                        crate::CompilationInfo::from(naga::error::ShaderError {
2021                            source: shader.to_string(),
2022                            label: desc.label.map(|s| s.to_string()),
2023                            inner: Box::new(inner),
2024                        })
2025                    })
2026                    .and_then(|glsl_module| {
2027                        validate_transformed_shader_module(&glsl_module, shader, &desc).map(|v| {
2028                            (
2029                                v,
2030                                WebShaderCompilationInfo::Transformed {
2031                                    compilation_info: crate::CompilationInfo { messages: vec![] },
2032                                },
2033                            )
2034                        })
2035                    })
2036            }
2037            #[cfg(feature = "wgsl")]
2038            crate::ShaderSource::Wgsl(ref code) => {
2039                let shader_module = webgpu_sys::GpuShaderModuleDescriptor::new(code);
2040                Ok((
2041                    shader_module,
2042                    WebShaderCompilationInfo::Wgsl {
2043                        source: code.to_string(),
2044                    },
2045                ))
2046            }
2047            #[cfg(feature = "naga-ir")]
2048            crate::ShaderSource::Naga(ref module) => {
2049                validate_transformed_shader_module(module, "", &desc).map(|v| {
2050                    (
2051                        v,
2052                        WebShaderCompilationInfo::Transformed {
2053                            compilation_info: crate::CompilationInfo { messages: vec![] },
2054                        },
2055                    )
2056                })
2057            }
2058            crate::ShaderSource::Dummy(_) => {
2059                panic!("found `ShaderSource::Dummy`")
2060            }
2061        };
2062
2063        #[cfg(naga)]
2064        fn validate_transformed_shader_module(
2065            module: &naga::Module,
2066            source: &str,
2067            desc: &crate::ShaderModuleDescriptor<'_>,
2068        ) -> Result<webgpu_sys::GpuShaderModuleDescriptor, crate::CompilationInfo> {
2069            use naga::{back, valid};
2070            let mut validator =
2071                valid::Validator::new(valid::ValidationFlags::all(), valid::Capabilities::all());
2072            let module_info = validator.validate(module).map_err(|err| {
2073                crate::CompilationInfo::from(naga::error::ShaderError {
2074                    source: source.to_string(),
2075                    label: desc.label.map(|s| s.to_string()),
2076                    inner: Box::new(err),
2077                })
2078            })?;
2079
2080            let writer_flags = naga::back::wgsl::WriterFlags::empty();
2081            let wgsl_text = back::wgsl::write_string(module, &module_info, writer_flags).unwrap();
2082            Ok(webgpu_sys::GpuShaderModuleDescriptor::new(
2083                wgsl_text.as_str(),
2084            ))
2085        }
2086        let (descriptor, compilation_info) = match shader_module_result {
2087            Ok(v) => v,
2088            Err(compilation_info) => (
2089                webgpu_sys::GpuShaderModuleDescriptor::new(""),
2090                WebShaderCompilationInfo::Transformed { compilation_info },
2091            ),
2092        };
2093        if let Some(label) = desc.label {
2094            descriptor.set_label(label);
2095        }
2096        WebShaderModule {
2097            module: self.inner.create_shader_module(&descriptor),
2098            compilation_info,
2099            ident: crate::cmp::Identifier::create(),
2100        }
2101        .into()
2102    }
2103
2104    unsafe fn create_shader_module_passthrough(
2105        &self,
2106        desc: &crate::ShaderModuleDescriptorPassthrough<'_>,
2107    ) -> dispatch::DispatchShaderModule {
2108        let shader_module_result = if let Some(ref code) = desc.wgsl {
2109            let shader_module = webgpu_sys::GpuShaderModuleDescriptor::new(code);
2110            Ok((
2111                shader_module,
2112                WebShaderCompilationInfo::Wgsl {
2113                    source: code.to_string(),
2114                },
2115            ))
2116        } else {
2117            Err(crate::CompilationInfo {
2118                messages: vec![crate::CompilationMessage {
2119                    message:
2120                        "Passthrough shader not compiled for WGSL on WebGPU backend (wgpu error)"
2121                            .to_string(),
2122                    location: None,
2123                    message_type: crate::CompilationMessageType::Error,
2124                }],
2125            })
2126        };
2127        let (descriptor, compilation_info) = match shader_module_result {
2128            Ok(v) => v,
2129            Err(compilation_info) => (
2130                webgpu_sys::GpuShaderModuleDescriptor::new(""),
2131                WebShaderCompilationInfo::Transformed { compilation_info },
2132            ),
2133        };
2134        if let Some(label) = desc.label {
2135            descriptor.set_label(label);
2136        }
2137        WebShaderModule {
2138            module: self.inner.create_shader_module(&descriptor),
2139            compilation_info,
2140            ident: crate::cmp::Identifier::create(),
2141        }
2142        .into()
2143    }
2144
2145    fn create_bind_group_layout(
2146        &self,
2147        desc: &crate::BindGroupLayoutDescriptor<'_>,
2148    ) -> dispatch::DispatchBindGroupLayout {
2149        let mapped_bindings = desc
2150            .entries
2151            .iter()
2152            .map(|bind| {
2153                let mapped_entry =
2154                    webgpu_sys::GpuBindGroupLayoutEntry::new(bind.binding, bind.visibility.bits());
2155
2156                match bind.ty {
2157                    wgt::BindingType::Buffer {
2158                        ty,
2159                        has_dynamic_offset,
2160                        min_binding_size,
2161                    } => {
2162                        let buffer = webgpu_sys::GpuBufferBindingLayout::new();
2163                        buffer.set_has_dynamic_offset(has_dynamic_offset);
2164                        if let Some(size) = min_binding_size {
2165                            buffer.set_min_binding_size_f64(size.get() as f64);
2166                        }
2167                        buffer.set_type(match ty {
2168                            wgt::BufferBindingType::Uniform => {
2169                                webgpu_sys::GpuBufferBindingType::Uniform
2170                            }
2171                            wgt::BufferBindingType::Storage { read_only: false } => {
2172                                webgpu_sys::GpuBufferBindingType::Storage
2173                            }
2174                            wgt::BufferBindingType::Storage { read_only: true } => {
2175                                webgpu_sys::GpuBufferBindingType::ReadOnlyStorage
2176                            }
2177                        });
2178                        mapped_entry.set_buffer(&buffer);
2179                    }
2180                    wgt::BindingType::Sampler(ty) => {
2181                        let sampler = webgpu_sys::GpuSamplerBindingLayout::new();
2182                        sampler.set_type(match ty {
2183                            wgt::SamplerBindingType::NonFiltering => {
2184                                webgpu_sys::GpuSamplerBindingType::NonFiltering
2185                            }
2186                            wgt::SamplerBindingType::Filtering => {
2187                                webgpu_sys::GpuSamplerBindingType::Filtering
2188                            }
2189                            wgt::SamplerBindingType::Comparison => {
2190                                webgpu_sys::GpuSamplerBindingType::Comparison
2191                            }
2192                        });
2193                        mapped_entry.set_sampler(&sampler);
2194                    }
2195                    wgt::BindingType::Texture {
2196                        multisampled,
2197                        sample_type,
2198                        view_dimension,
2199                    } => {
2200                        let texture = webgpu_sys::GpuTextureBindingLayout::new();
2201                        texture.set_multisampled(multisampled);
2202                        texture.set_sample_type(map_texture_component_type(sample_type));
2203                        texture.set_view_dimension(map_texture_view_dimension(view_dimension));
2204                        mapped_entry.set_texture(&texture);
2205                    }
2206                    wgt::BindingType::StorageTexture {
2207                        access,
2208                        format,
2209                        view_dimension,
2210                    } => {
2211                        let mapped_access = match access {
2212                            wgt::StorageTextureAccess::WriteOnly => {
2213                                webgpu_sys::GpuStorageTextureAccess::WriteOnly
2214                            }
2215                            wgt::StorageTextureAccess::ReadOnly => {
2216                                webgpu_sys::GpuStorageTextureAccess::ReadOnly
2217                            }
2218                            wgt::StorageTextureAccess::ReadWrite => {
2219                                webgpu_sys::GpuStorageTextureAccess::ReadWrite
2220                            }
2221                            wgt::StorageTextureAccess::Atomic => {
2222                                // Validated out by `BindGroupLayoutEntryError::StorageTextureAtomic`
2223                                unreachable!()
2224                            }
2225                        };
2226                        let storage_texture = webgpu_sys::GpuStorageTextureBindingLayout::new(
2227                            map_texture_format(format),
2228                        );
2229                        storage_texture.set_access(mapped_access);
2230                        storage_texture
2231                            .set_view_dimension(map_texture_view_dimension(view_dimension));
2232                        mapped_entry.set_storage_texture(&storage_texture);
2233                    }
2234                    wgt::BindingType::AccelerationStructure { .. } => todo!(),
2235                    wgt::BindingType::ExternalTexture => {
2236                        mapped_entry.set_external_texture(
2237                            &webgpu_sys::GpuExternalTextureBindingLayout::new(),
2238                        );
2239                    }
2240                }
2241
2242                mapped_entry
2243            })
2244            .collect::<Vec<webgpu_sys::GpuBindGroupLayoutEntry>>();
2245
2246        let mapped_desc = webgpu_sys::GpuBindGroupLayoutDescriptor::new(&mapped_bindings);
2247        if let Some(label) = desc.label {
2248            mapped_desc.set_label(label);
2249        }
2250        let bind_group_layout = self.inner.create_bind_group_layout(&mapped_desc).unwrap();
2251
2252        WebBindGroupLayout {
2253            inner: bind_group_layout,
2254            ident: crate::cmp::Identifier::create(),
2255        }
2256        .into()
2257    }
2258
2259    fn create_bind_group(
2260        &self,
2261        desc: &crate::BindGroupDescriptor<'_>,
2262    ) -> dispatch::DispatchBindGroup {
2263        let mapped_entries = desc
2264            .entries
2265            .iter()
2266            .map(|binding| match binding.resource {
2267                crate::BindingResource::Buffer(crate::BufferBinding {
2268                    buffer,
2269                    offset,
2270                    size,
2271                }) => {
2272                    let buffer = buffer.inner.as_webgpu();
2273                    let mapped_buffer_binding = webgpu_sys::GpuBufferBinding::new(&buffer.inner);
2274                    mapped_buffer_binding.set_offset_f64(offset as f64);
2275                    if let Some(s) = size {
2276                        mapped_buffer_binding.set_size_f64(s.get() as f64);
2277                    }
2278
2279                    webgpu_sys::GpuBindGroupEntry::new_with_gpu_buffer_binding(
2280                        binding.binding,
2281                        &mapped_buffer_binding,
2282                    )
2283                }
2284                crate::BindingResource::BufferArray(..) => {
2285                    panic!("Web backend does not support arrays of buffers")
2286                }
2287                crate::BindingResource::Sampler(sampler) => {
2288                    let sampler = &sampler.inner.as_webgpu().inner;
2289                    webgpu_sys::GpuBindGroupEntry::new(binding.binding, sampler)
2290                }
2291                crate::BindingResource::SamplerArray(..) => {
2292                    panic!("Web backend does not support arrays of samplers")
2293                }
2294                crate::BindingResource::TextureView(texture_view) => {
2295                    let texture_view = &texture_view.inner.as_webgpu().inner;
2296                    webgpu_sys::GpuBindGroupEntry::new_with_gpu_texture_view(
2297                        binding.binding,
2298                        texture_view,
2299                    )
2300                }
2301                crate::BindingResource::TextureViewArray(..) => {
2302                    panic!("Web backend does not support BINDING_INDEXING extension")
2303                }
2304                crate::BindingResource::AccelerationStructure(_) => {
2305                    unimplemented!("Raytracing not implemented for web")
2306                }
2307                crate::BindingResource::AccelerationStructureArray(_) => {
2308                    unimplemented!("Raytracing not implemented for web")
2309                }
2310                crate::BindingResource::ExternalTexture(_) => {
2311                    unimplemented!("ExternalTexture not implemented for web")
2312                }
2313            })
2314            .collect::<Vec<webgpu_sys::GpuBindGroupEntry>>();
2315
2316        let bgl = &desc.layout.inner.as_webgpu().inner;
2317        let mapped_desc = webgpu_sys::GpuBindGroupDescriptor::new(&mapped_entries, bgl);
2318        if let Some(label) = desc.label {
2319            mapped_desc.set_label(label);
2320        }
2321        let bind_group = self.inner.create_bind_group(&mapped_desc);
2322
2323        WebBindGroup {
2324            inner: bind_group,
2325            ident: crate::cmp::Identifier::create(),
2326        }
2327        .into()
2328    }
2329
2330    fn create_pipeline_layout(
2331        &self,
2332        desc: &crate::PipelineLayoutDescriptor<'_>,
2333    ) -> dispatch::DispatchPipelineLayout {
2334        let temp_layouts = desc
2335            .bind_group_layouts
2336            .iter()
2337            .map(|bgl| match bgl {
2338                Some(bgl) => js_sys::JsNullable::wrap(bgl.inner.as_webgpu().inner.clone()),
2339                None => js_sys::JsNullable::new(),
2340            })
2341            .collect::<Vec<js_sys::JsNullable<webgpu_sys::GpuBindGroupLayout>>>();
2342        let mapped_desc = webgpu_sys::GpuPipelineLayoutDescriptor::new(&temp_layouts);
2343        if let Some(label) = desc.label {
2344            mapped_desc.set_label(label);
2345        }
2346
2347        let pipeline_layout = self.inner.create_pipeline_layout(&mapped_desc);
2348
2349        WebPipelineLayout {
2350            inner: pipeline_layout,
2351            ident: crate::cmp::Identifier::create(),
2352        }
2353        .into()
2354    }
2355
2356    fn create_render_pipeline(
2357        &self,
2358        desc: &crate::RenderPipelineDescriptor<'_>,
2359    ) -> dispatch::DispatchRenderPipeline {
2360        let module = desc.vertex.module.inner.as_webgpu();
2361        let mapped_vertex_state = webgpu_sys::GpuVertexState::new(&module.module);
2362        insert_constants_map(
2363            &mapped_vertex_state,
2364            desc.vertex.compilation_options.constants,
2365        );
2366        if let Some(ep) = desc.vertex.entry_point {
2367            mapped_vertex_state.set_entry_point(ep);
2368        }
2369
2370        let buffers = desc
2371            .vertex
2372            .buffers
2373            .iter()
2374            .map(|vbuf| match vbuf {
2375                Some(vbuf) => {
2376                    let mapped_attributes = vbuf
2377                        .attributes
2378                        .iter()
2379                        .map(|attr| {
2380                            webgpu_sys::GpuVertexAttribute::new_with_f64(
2381                                map_vertex_format(attr.format),
2382                                attr.offset as f64,
2383                                attr.shader_location,
2384                            )
2385                        })
2386                        .collect::<Vec<webgpu_sys::GpuVertexAttribute>>();
2387
2388                    let mapped_vbuf = webgpu_sys::GpuVertexBufferLayout::new_with_f64(
2389                        vbuf.array_stride as f64,
2390                        &mapped_attributes,
2391                    );
2392                    mapped_vbuf.set_step_mode(map_vertex_step_mode(vbuf.step_mode));
2393                    js_sys::JsNullable::wrap(mapped_vbuf)
2394                }
2395                None => js_sys::JsNullable::new(),
2396            })
2397            .collect::<Vec<js_sys::JsNullable<webgpu_sys::GpuVertexBufferLayout>>>();
2398
2399        mapped_vertex_state.set_buffers(&buffers);
2400
2401        let mapped_desc = match desc.layout {
2402            Some(layout) => webgpu_sys::GpuRenderPipelineDescriptor::new(
2403                &layout.inner.as_webgpu().inner,
2404                &mapped_vertex_state,
2405            ),
2406            None => webgpu_sys::GpuRenderPipelineDescriptor::new_with_gpu_auto_layout_mode(
2407                webgpu_sys::GpuAutoLayoutMode::Auto,
2408                &mapped_vertex_state,
2409            ),
2410        };
2411
2412        if let Some(label) = desc.label {
2413            mapped_desc.set_label(label);
2414        }
2415
2416        if let Some(ref depth_stencil) = desc.depth_stencil {
2417            mapped_desc.set_depth_stencil(&map_depth_stencil_state(depth_stencil));
2418        }
2419
2420        if let Some(ref frag) = desc.fragment {
2421            let targets = frag
2422                .targets
2423                .iter()
2424                .map(|target| match target {
2425                    Some(target) => {
2426                        let mapped_format = map_texture_format(target.format);
2427                        let mapped_color_state =
2428                            webgpu_sys::GpuColorTargetState::new(mapped_format);
2429                        if let Some(ref bs) = target.blend {
2430                            let alpha = map_blend_component(&bs.alpha);
2431                            let color = map_blend_component(&bs.color);
2432                            let mapped_blend_state = webgpu_sys::GpuBlendState::new(&alpha, &color);
2433                            mapped_color_state.set_blend(&mapped_blend_state);
2434                        }
2435                        mapped_color_state.set_write_mask(target.write_mask.bits());
2436                        js_sys::JsNullable::wrap(mapped_color_state)
2437                    }
2438                    None => js_sys::JsNullable::new(),
2439                })
2440                .collect::<Vec<js_sys::JsNullable<webgpu_sys::GpuColorTargetState>>>();
2441            let module = frag.module.inner.as_webgpu();
2442            let mapped_fragment_desc = webgpu_sys::GpuFragmentState::new(&module.module, &targets);
2443            insert_constants_map(&mapped_fragment_desc, frag.compilation_options.constants);
2444            if let Some(ep) = frag.entry_point {
2445                mapped_fragment_desc.set_entry_point(ep);
2446            }
2447            mapped_desc.set_fragment(&mapped_fragment_desc);
2448        }
2449
2450        let mapped_multisample = webgpu_sys::GpuMultisampleState::new();
2451        mapped_multisample.set_count(desc.multisample.count);
2452        mapped_multisample.set_mask(desc.multisample.mask as u32);
2453        mapped_multisample
2454            .set_alpha_to_coverage_enabled(desc.multisample.alpha_to_coverage_enabled);
2455        mapped_desc.set_multisample(&mapped_multisample);
2456
2457        let mapped_primitive = map_primitive_state(&desc.primitive);
2458        mapped_desc.set_primitive(&mapped_primitive);
2459
2460        // During pipeline discovery the browser compiles the pipeline in the
2461        // background, beside the others, and this one never draws.
2462        let discovered = self.pipeline_discovery.borrow().as_ref().map(|pending| {
2463            pending.push(&self.inner.create_render_pipeline_async(&mapped_desc));
2464        });
2465        let render_pipeline = match discovered {
2466            Some(()) => None,
2467            None => Some(self.inner.create_render_pipeline(&mapped_desc).unwrap()),
2468        };
2469
2470        WebRenderPipeline {
2471            inner: render_pipeline,
2472            ident: crate::cmp::Identifier::create(),
2473        }
2474        .into()
2475    }
2476
2477    fn create_mesh_pipeline(
2478        &self,
2479        _desc: &crate::MeshPipelineDescriptor<'_>,
2480    ) -> dispatch::DispatchRenderPipeline {
2481        panic!("MESH_SHADER feature must be enabled to call create_mesh_pipeline")
2482    }
2483
2484    fn create_compute_pipeline(
2485        &self,
2486        desc: &crate::ComputePipelineDescriptor<'_>,
2487    ) -> dispatch::DispatchComputePipeline {
2488        let shader_module = desc.module.inner.as_webgpu();
2489        let mapped_compute_stage = webgpu_sys::GpuProgrammableStage::new(&shader_module.module);
2490        insert_constants_map(&mapped_compute_stage, desc.compilation_options.constants);
2491        if let Some(ep) = desc.entry_point {
2492            mapped_compute_stage.set_entry_point(ep);
2493        }
2494        let mapped_desc = match desc.layout {
2495            Some(layout) => webgpu_sys::GpuComputePipelineDescriptor::new(
2496                &layout.inner.as_webgpu().inner,
2497                &mapped_compute_stage,
2498            ),
2499            None => webgpu_sys::GpuComputePipelineDescriptor::new_with_gpu_auto_layout_mode(
2500                webgpu_sys::GpuAutoLayoutMode::Auto,
2501                &mapped_compute_stage,
2502            ),
2503        };
2504
2505        if let Some(label) = desc.label {
2506            mapped_desc.set_label(label);
2507        }
2508
2509        let compute_pipeline = self.inner.create_compute_pipeline(&mapped_desc);
2510
2511        WebComputePipeline {
2512            inner: compute_pipeline,
2513            ident: crate::cmp::Identifier::create(),
2514        }
2515        .into()
2516    }
2517
2518    unsafe fn create_pipeline_cache(
2519        &self,
2520        _desc: &crate::PipelineCacheDescriptor<'_>,
2521    ) -> dispatch::DispatchPipelineCache {
2522        WebPipelineCache {
2523            ident: crate::cmp::Identifier::create(),
2524        }
2525        .into()
2526    }
2527
2528    fn create_buffer(&self, desc: &crate::BufferDescriptor<'_>) -> dispatch::DispatchBuffer {
2529        let mapped_desc =
2530            webgpu_sys::GpuBufferDescriptor::new_with_f64(desc.size as f64, desc.usage.bits());
2531        mapped_desc.set_mapped_at_creation(desc.mapped_at_creation);
2532        if let Some(label) = desc.label {
2533            mapped_desc.set_label(label);
2534        }
2535        WebBuffer::new(self.inner.create_buffer(&mapped_desc).unwrap(), desc).into()
2536    }
2537
2538    fn create_texture(&self, desc: &crate::TextureDescriptor<'_>) -> dispatch::DispatchTexture {
2539        let mapped_desc = webgpu_sys::GpuTextureDescriptor::new_with_gpu_extent_3d_dict(
2540            map_texture_format(desc.format),
2541            &map_extent_3d(desc.size),
2542            desc.usage.bits(),
2543        );
2544        if let Some(label) = desc.label {
2545            mapped_desc.set_label(label);
2546        }
2547        mapped_desc.set_dimension(map_texture_dimension(desc.dimension));
2548        mapped_desc.set_mip_level_count(desc.mip_level_count);
2549        mapped_desc.set_sample_count(desc.sample_count);
2550        let mapped_view_formats = desc
2551            .view_formats
2552            .iter()
2553            .map(|format| {
2554                wasm_bindgen::JsValue::from(map_texture_format(*format))
2555                    .dyn_into::<js_sys::JsString>()
2556                    .unwrap()
2557            })
2558            .collect::<Vec<js_sys::JsString>>();
2559        mapped_desc.set_view_formats(&mapped_view_formats);
2560
2561        let texture = self.inner.create_texture(&mapped_desc).unwrap();
2562        WebTexture {
2563            inner: texture,
2564            drop_guard: None,
2565            ident: crate::cmp::Identifier::create(),
2566        }
2567        .into()
2568    }
2569
2570    fn create_external_texture(
2571        &self,
2572        _desc: &crate::ExternalTextureDescriptor<'_>,
2573        _planes: &[&crate::TextureView],
2574    ) -> dispatch::DispatchExternalTexture {
2575        unimplemented!("ExternalTexture not implemented for web");
2576    }
2577
2578    fn create_blas(
2579        &self,
2580        _desc: &crate::CreateBlasDescriptor<'_>,
2581        _sizes: crate::BlasGeometrySizeDescriptors,
2582    ) -> (Option<u64>, dispatch::DispatchBlas) {
2583        unimplemented!("Raytracing not implemented for web");
2584    }
2585
2586    fn create_tlas(&self, _desc: &crate::CreateTlasDescriptor<'_>) -> dispatch::DispatchTlas {
2587        unimplemented!("Raytracing not implemented for web");
2588    }
2589
2590    fn create_sampler(&self, desc: &crate::SamplerDescriptor<'_>) -> dispatch::DispatchSampler {
2591        let mapped_desc = webgpu_sys::GpuSamplerDescriptor::new();
2592        mapped_desc.set_address_mode_u(map_address_mode(desc.address_mode_u));
2593        mapped_desc.set_address_mode_v(map_address_mode(desc.address_mode_v));
2594        mapped_desc.set_address_mode_w(map_address_mode(desc.address_mode_w));
2595        if let Some(compare) = desc.compare {
2596            mapped_desc.set_compare(map_compare_function(compare));
2597        }
2598        mapped_desc.set_lod_max_clamp(desc.lod_max_clamp);
2599        mapped_desc.set_lod_min_clamp(desc.lod_min_clamp);
2600        mapped_desc.set_mag_filter(map_filter_mode(desc.mag_filter));
2601        mapped_desc.set_min_filter(map_filter_mode(desc.min_filter));
2602        mapped_desc.set_mipmap_filter(map_mipmap_filter_mode(desc.mipmap_filter));
2603        mapped_desc.set_max_anisotropy(desc.anisotropy_clamp);
2604        if let Some(label) = desc.label {
2605            mapped_desc.set_label(label);
2606        }
2607
2608        let sampler = self.inner.create_sampler_with_descriptor(&mapped_desc);
2609
2610        WebSampler {
2611            inner: sampler,
2612            ident: crate::cmp::Identifier::create(),
2613        }
2614        .into()
2615    }
2616
2617    fn create_query_set(&self, desc: &crate::QuerySetDescriptor<'_>) -> dispatch::DispatchQuerySet {
2618        let ty = match desc.ty {
2619            wgt::QueryType::Occlusion => webgpu_sys::GpuQueryType::Occlusion,
2620            wgt::QueryType::Timestamp => webgpu_sys::GpuQueryType::Timestamp,
2621            wgt::QueryType::PipelineStatistics(_) => unreachable!(),
2622        };
2623        let mapped_desc = webgpu_sys::GpuQuerySetDescriptor::new(desc.count, ty);
2624        if let Some(label) = desc.label {
2625            mapped_desc.set_label(label);
2626        }
2627
2628        let query_set = self.inner.create_query_set(&mapped_desc).unwrap();
2629
2630        WebQuerySet {
2631            inner: query_set,
2632            ident: crate::cmp::Identifier::create(),
2633        }
2634        .into()
2635    }
2636
2637    fn create_command_encoder(
2638        &self,
2639        desc: &crate::CommandEncoderDescriptor<'_>,
2640    ) -> dispatch::DispatchCommandEncoder {
2641        let mapped_desc = webgpu_sys::GpuCommandEncoderDescriptor::new();
2642        if let Some(label) = desc.label {
2643            mapped_desc.set_label(label);
2644        }
2645
2646        let command_encoder = self
2647            .inner
2648            .create_command_encoder_with_descriptor(&mapped_desc);
2649
2650        WebCommandEncoder {
2651            inner: command_encoder,
2652            pipeline_discovery: Rc::clone(&self.pipeline_discovery),
2653            ident: crate::cmp::Identifier::create(),
2654        }
2655        .into()
2656    }
2657
2658    fn create_render_bundle_encoder(
2659        &self,
2660        desc: &crate::RenderBundleEncoderDescriptor<'_>,
2661    ) -> dispatch::DispatchRenderBundleEncoder {
2662        let mapped_color_formats = desc
2663            .color_formats
2664            .iter()
2665            .map(|cf| match cf {
2666                Some(cf) => js_sys::JsNullable::wrap(
2667                    wasm_bindgen::JsValue::from(map_texture_format(*cf))
2668                        .dyn_into::<js_sys::JsString>()
2669                        .unwrap(),
2670                ),
2671                None => js_sys::JsNullable::new(),
2672            })
2673            .collect::<Vec<js_sys::JsNullable<js_sys::JsString>>>();
2674        let mapped_desc = webgpu_sys::GpuRenderBundleEncoderDescriptor::new(&mapped_color_formats);
2675        if let Some(label) = desc.label {
2676            mapped_desc.set_label(label);
2677        }
2678        if let Some(ds) = desc.depth_stencil {
2679            mapped_desc.set_depth_stencil_format(map_texture_format(ds.format));
2680            mapped_desc.set_depth_read_only(ds.depth_read_only);
2681            mapped_desc.set_stencil_read_only(ds.stencil_read_only);
2682        }
2683        mapped_desc.set_sample_count(desc.sample_count);
2684
2685        let render_bundle_encoder = self
2686            .inner
2687            .create_render_bundle_encoder(&mapped_desc)
2688            .unwrap();
2689
2690        WebRenderBundleEncoder {
2691            inner: render_bundle_encoder,
2692            draws: PipelineDraws::new(self.pipeline_discovery.borrow().is_some()),
2693            ident: crate::cmp::Identifier::create(),
2694        }
2695        .into()
2696    }
2697
2698    fn set_device_lost_callback(&self, device_lost_callback: dispatch::BoxDeviceLostCallback) {
2699        let closure = Closure::once(move |info: webgpu_sys::GpuDeviceLostInfo| {
2700            device_lost_callback(
2701                match info.reason() {
2702                    webgpu_sys::GpuDeviceLostReason::Destroyed => {
2703                        crate::DeviceLostReason::Destroyed
2704                    }
2705                    webgpu_sys::GpuDeviceLostReason::Unknown => crate::DeviceLostReason::Unknown,
2706                    _ => crate::DeviceLostReason::Unknown,
2707                },
2708                info.message(),
2709            );
2710        });
2711        let _ = self.inner.lost().then(&closure);
2712        // Release memory management of this closure from Rust to the JS GC.
2713        // TODO: This will leak if weak references is not supported.
2714        closure.forget();
2715    }
2716
2717    fn on_uncaptured_error(&self, handler: Arc<dyn crate::UncapturedErrorHandler>) {
2718        let f = Closure::wrap(Box::new(move |event: webgpu_sys::GpuUncapturedErrorEvent| {
2719            let error = crate::Error::from_js(event.error().value_of());
2720            handler(error);
2721        }) as Box<dyn FnMut(_)>);
2722        self.inner
2723            .set_onuncapturederror(Some(f.as_ref().unchecked_ref()));
2724        // Release memory management of this closure from Rust to the JS GC.
2725        // TODO: This will leak if weak references is not supported.
2726        f.forget();
2727    }
2728
2729    fn push_error_scope(&self, filter: crate::ErrorFilter) -> u32 {
2730        let index = self.error_scope_count.get();
2731        self.error_scope_count.set(
2732            index
2733                .checked_add(1)
2734                .expect("Greater than 2^32 nested error scopes"),
2735        );
2736        self.inner.push_error_scope(match filter {
2737            crate::ErrorFilter::OutOfMemory => webgpu_sys::GpuErrorFilter::OutOfMemory,
2738            crate::ErrorFilter::Validation => webgpu_sys::GpuErrorFilter::Validation,
2739            crate::ErrorFilter::Internal => webgpu_sys::GpuErrorFilter::Internal,
2740        });
2741        index
2742    }
2743
2744    fn pop_error_scope(&self, index: u32) -> Pin<Box<dyn dispatch::PopErrorScopeFuture>> {
2745        let current_scope_count = self.error_scope_count.get();
2746        let is_panicking = crate::util::is_panicking();
2747        if current_scope_count == 0 && !is_panicking {
2748            panic!("Mismatched pop_error_scope call: no error scope for this thread. Error scopes are thread-local.");
2749        }
2750        if index + 1 != current_scope_count && !is_panicking {
2751            panic!(
2752                "Mismatched pop_error_scope call: error scopes must be popped in reverse order."
2753            );
2754        }
2755        // Decrement the error scope count. We've asserted that the current
2756        // size is `index + 1` above.
2757        self.error_scope_count.set(index);
2758
2759        let error_promise = self.inner.pop_error_scope();
2760        Box::pin(MakeSendFuture::new(
2761            wasm_bindgen_futures::JsFuture::from(error_promise),
2762            future_pop_error_scope,
2763        ))
2764    }
2765
2766    unsafe fn start_graphics_debugger_capture(&self) {
2767        // No capturing api in webgpu
2768    }
2769
2770    unsafe fn stop_graphics_debugger_capture(&self) {
2771        // No capturing api in webgpu
2772    }
2773
2774    fn poll(&self, _poll_type: wgt::PollType<u64>) -> Result<crate::PollStatus, crate::PollError> {
2775        // Device is polled automatically
2776        Ok(crate::PollStatus::QueueEmpty)
2777    }
2778
2779    fn get_internal_counters(&self) -> crate::InternalCounters {
2780        crate::InternalCounters::default()
2781    }
2782
2783    fn generate_allocator_report(&self) -> Option<wgt::AllocatorReport> {
2784        None
2785    }
2786
2787    fn destroy(&self) {
2788        self.inner.destroy();
2789    }
2790}
2791
2792impl Drop for WebDevice {
2793    fn drop(&mut self) {
2794        // no-op
2795    }
2796}
2797
2798impl dispatch::QueueInterface for WebQueue {
2799    fn write_buffer(
2800        &self,
2801        buffer: &dispatch::DispatchBuffer,
2802        offset: crate::BufferAddress,
2803        data: &[u8],
2804    ) {
2805        let buffer = buffer.as_webgpu();
2806        self.inner
2807            .write_buffer_with_f64_and_u8_slice_and_f64_and_f64(
2808                &buffer.inner,
2809                offset as f64,
2810                data,
2811                0f64,
2812                data.len() as f64,
2813            )
2814            .unwrap();
2815    }
2816
2817    fn create_staging_buffer(
2818        &self,
2819        size: crate::BufferSize,
2820    ) -> Option<dispatch::DispatchQueueWriteBuffer> {
2821        Some(
2822            WebQueueWriteBuffer {
2823                inner: vec![0; size.get() as usize].into_boxed_slice(),
2824                ident: crate::cmp::Identifier::create(),
2825            }
2826            .into(),
2827        )
2828    }
2829
2830    fn validate_write_buffer(
2831        &self,
2832        buffer: &dispatch::DispatchBuffer,
2833        offset: wgt::BufferAddress,
2834        size: wgt::BufferSize,
2835    ) -> Option<()> {
2836        let buffer = buffer.as_webgpu();
2837
2838        let usage = wgt::BufferUsages::from_bits_truncate(buffer.inner.usage());
2839        // TODO: actually send this down the error scope
2840        if !usage.contains(wgt::BufferUsages::COPY_DST) {
2841            log::error!("Destination buffer is missing the `COPY_DST` usage flag");
2842            return None;
2843        }
2844        let write_size = u64::from(size);
2845        if !write_size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
2846            log::error!("Copy size {size} does not respect `COPY_BUFFER_ALIGNMENT`");
2847            return None;
2848        }
2849        if !offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
2850            log::error!(
2851                "Buffer offset {offset} is not aligned to block size or `COPY_BUFFER_ALIGNMENT`"
2852            );
2853            return None;
2854        }
2855        if write_size + offset > buffer.inner.size() as u64 {
2856            log::error!("copy of {}..{} would end up overrunning the bounds of the destination buffer of size {}", offset, offset + write_size, buffer.inner.size());
2857            return None;
2858        }
2859        Some(())
2860    }
2861
2862    fn write_staging_buffer(
2863        &self,
2864        buffer: &dispatch::DispatchBuffer,
2865        offset: crate::BufferAddress,
2866        staging_buffer: &dispatch::DispatchQueueWriteBuffer,
2867    ) {
2868        let staging_buffer = staging_buffer.as_webgpu();
2869
2870        dispatch::QueueInterface::write_buffer(self, buffer, offset, &staging_buffer.inner)
2871    }
2872
2873    fn write_texture(
2874        &self,
2875        texture: crate::TexelCopyTextureInfo<'_>,
2876        data: &[u8],
2877        data_layout: crate::TexelCopyBufferLayout,
2878        size: crate::Extent3d,
2879    ) {
2880        let mapped_data_layout = webgpu_sys::GpuTexelCopyBufferLayout::new();
2881        if let Some(bytes_per_row) = data_layout.bytes_per_row {
2882            mapped_data_layout.set_bytes_per_row(bytes_per_row);
2883        }
2884        if let Some(rows_per_image) = data_layout.rows_per_image {
2885            mapped_data_layout.set_rows_per_image(rows_per_image);
2886        }
2887        mapped_data_layout.set_offset_f64(data_layout.offset as f64);
2888
2889        self.inner
2890            .write_texture_with_u8_slice_and_gpu_extent_3d_dict(
2891                &map_texture_copy_view(texture),
2892                data,
2893                &mapped_data_layout,
2894                &map_extent_3d(size),
2895            )
2896            .unwrap();
2897    }
2898
2899    fn copy_external_image_to_texture(
2900        &self,
2901        source: &crate::CopyExternalImageSourceInfo,
2902        dest: crate::CopyExternalImageDestInfo<&crate::api::Texture>,
2903        size: crate::Extent3d,
2904    ) {
2905        self.inner
2906            .copy_external_image_to_texture_with_gpu_extent_3d_dict(
2907                &map_external_texture_copy_view(source),
2908                &map_tagged_texture_copy_view(dest),
2909                &map_extent_3d(size),
2910            )
2911            .unwrap();
2912    }
2913
2914    fn submit(
2915        &self,
2916        command_buffers: &mut dyn Iterator<Item = dispatch::DispatchCommandBuffer>,
2917    ) -> u64 {
2918        let temp_command_buffers = command_buffers.collect::<Vec<_>>();
2919
2920        let array = temp_command_buffers
2921            .iter()
2922            .map(|buffer| buffer.as_webgpu().inner.clone())
2923            .collect::<Vec<webgpu_sys::GpuCommandBuffer>>();
2924
2925        self.inner.submit(&array);
2926
2927        0
2928    }
2929
2930    fn get_timestamp_period(&self) -> f32 {
2931        // Timestamp values are always in nanoseconds, see https://gpuweb.github.io/gpuweb/#timestamp
2932        1.0
2933    }
2934
2935    fn on_submitted_work_done(&self, callback: dispatch::BoxSubmittedWorkDoneCallback) {
2936        let promise = self.inner.on_submitted_work_done();
2937        wasm_bindgen_futures::spawn_local(async move {
2938            match wasm_bindgen_futures::JsFuture::from(promise).await {
2939                Ok(_) => callback(),
2940                Err(error) => {
2941                    log::error!("on_submitted_work_done promise failed: {error:?}");
2942                    callback();
2943                }
2944            }
2945        });
2946    }
2947
2948    fn compact_blas(
2949        &self,
2950        _blas: &dispatch::DispatchBlas,
2951    ) -> (Option<u64>, dispatch::DispatchBlas) {
2952        unimplemented!("Raytracing not implemented for web")
2953    }
2954
2955    fn present(&self, _detail: &dispatch::DispatchSurfaceOutputDetail) {
2956        // Swapchain is presented automatically on the web.
2957    }
2958}
2959impl Drop for WebQueue {
2960    fn drop(&mut self) {
2961        // no-op
2962    }
2963}
2964
2965impl dispatch::ShaderModuleInterface for WebShaderModule {
2966    fn get_compilation_info(&self) -> Pin<Box<dyn dispatch::ShaderCompilationInfoFuture>> {
2967        let compilation_info_promise = self.module.get_compilation_info();
2968        let map_future = Box::new({
2969            let compilation_info = self.compilation_info.clone();
2970            move |result| future_compilation_info(result, &compilation_info)
2971        });
2972        Box::pin(MakeSendFuture::new(
2973            wasm_bindgen_futures::JsFuture::from(compilation_info_promise),
2974            map_future,
2975        ))
2976    }
2977}
2978impl Drop for WebShaderModule {
2979    fn drop(&mut self) {
2980        // no-op
2981    }
2982}
2983
2984impl dispatch::BindGroupLayoutInterface for WebBindGroupLayout {}
2985impl Drop for WebBindGroupLayout {
2986    fn drop(&mut self) {
2987        // no-op
2988    }
2989}
2990
2991impl dispatch::BindGroupInterface for WebBindGroup {}
2992impl Drop for WebBindGroup {
2993    fn drop(&mut self) {
2994        // no-op
2995    }
2996}
2997
2998impl dispatch::TextureViewInterface for WebTextureView {}
2999impl Drop for WebTextureView {
3000    fn drop(&mut self) {
3001        // no-op
3002    }
3003}
3004
3005impl dispatch::SamplerInterface for WebSampler {}
3006impl Drop for WebSampler {
3007    fn drop(&mut self) {
3008        // no-op
3009    }
3010}
3011
3012impl dispatch::BufferInterface for WebBuffer {
3013    fn map_async(
3014        &self,
3015        mode: crate::MapMode,
3016        range: Range<crate::BufferAddress>,
3017        callback: dispatch::BufferMapCallback,
3018    ) {
3019        let map_promise = self.inner.map_async_with_f64_and_f64(
3020            map_map_mode(mode),
3021            range.start as f64,
3022            (range.end - range.start) as f64,
3023        );
3024
3025        self.set_mapped_range(range);
3026
3027        register_then_closures(&map_promise, callback, Ok(()), Err(crate::BufferAsyncError));
3028    }
3029
3030    fn get_mapped_range(
3031        &self,
3032        sub_range: Range<crate::BufferAddress>,
3033    ) -> Result<dispatch::DispatchBufferMappedRange, crate::MapRangeError> {
3034        let actual_mapping = self.get_mapped_range(sub_range)?;
3035        Ok(WebBufferMappedRange {
3036            actual_mapping,
3037            temporary_mapping: OnceCell::new(),
3038            temporary_mapping_modified: false,
3039            ident: crate::cmp::Identifier::create(),
3040        }
3041        .into())
3042    }
3043
3044    fn unmap(&self) {
3045        self.inner.unmap();
3046        self.mapping.borrow_mut().mapped_buffer = None;
3047    }
3048
3049    fn destroy(&self) {
3050        self.inner.destroy();
3051    }
3052}
3053impl Drop for WebBuffer {
3054    fn drop(&mut self) {
3055        // no-op
3056    }
3057}
3058
3059impl dispatch::TextureInterface for WebTexture {
3060    fn create_view(
3061        &self,
3062        desc: &crate::TextureViewDescriptor<'_>,
3063    ) -> dispatch::DispatchTextureView {
3064        let mapped = webgpu_sys::GpuTextureViewDescriptor::new();
3065        if let Some(dim) = desc.dimension {
3066            mapped.set_dimension(map_texture_view_dimension(dim));
3067        }
3068        if let Some(format) = desc.format {
3069            mapped.set_format(map_texture_format(format));
3070        }
3071        mapped.set_aspect(map_texture_aspect(desc.aspect));
3072        mapped.set_base_array_layer(desc.base_array_layer);
3073        if let Some(count) = desc.array_layer_count {
3074            mapped.set_array_layer_count(count);
3075        }
3076        mapped.set_base_mip_level(desc.base_mip_level);
3077        if let Some(count) = desc.mip_level_count {
3078            mapped.set_mip_level_count(count);
3079        }
3080        if let Some(label) = desc.label {
3081            mapped.set_label(label);
3082        }
3083        mapped.set_usage(desc.usage.unwrap_or(wgt::TextureUsages::empty()).bits());
3084
3085        let view = self.inner.create_view_with_descriptor(&mapped).unwrap();
3086
3087        WebTextureView {
3088            inner: view,
3089            ident: crate::cmp::Identifier::create(),
3090        }
3091        .into()
3092    }
3093
3094    fn destroy(&self) {
3095        if self.drop_guard.is_none() {
3096            self.inner.destroy();
3097        }
3098    }
3099}
3100impl Drop for WebTexture {
3101    fn drop(&mut self) {
3102        // The drop callback for external textures is fired by `DropGuard`'s
3103        // own `Drop` impl when the last `Rc<DropGuard>` clone is released.
3104    }
3105}
3106
3107impl dispatch::ExternalTextureInterface for WebExternalTexture {
3108    fn destroy(&self) {
3109        unimplemented!("ExternalTexture not implemented for web");
3110    }
3111}
3112impl Drop for WebExternalTexture {
3113    fn drop(&mut self) {
3114        unimplemented!("ExternalTexture not implemented for web");
3115    }
3116}
3117
3118impl dispatch::BlasInterface for WebBlas {
3119    fn prepare_compact_async(&self, _callback: BlasCompactCallback) {
3120        unimplemented!("Raytracing not implemented for web")
3121    }
3122    fn ready_for_compaction(&self) -> bool {
3123        unimplemented!("Raytracing not implemented for web")
3124    }
3125}
3126impl Drop for WebBlas {
3127    fn drop(&mut self) {
3128        // no-op
3129    }
3130}
3131
3132impl dispatch::TlasInterface for WebTlas {}
3133impl Drop for WebTlas {
3134    fn drop(&mut self) {
3135        // no-op
3136    }
3137}
3138
3139impl dispatch::QuerySetInterface for WebQuerySet {
3140    fn destroy(&self) {
3141        self.inner.destroy();
3142    }
3143}
3144
3145impl Drop for WebQuerySet {
3146    fn drop(&mut self) {
3147        // no-op
3148    }
3149}
3150
3151impl dispatch::PipelineLayoutInterface for WebPipelineLayout {}
3152impl Drop for WebPipelineLayout {
3153    fn drop(&mut self) {
3154        // no-op
3155    }
3156}
3157
3158impl dispatch::RenderPipelineInterface for WebRenderPipeline {
3159    fn get_bind_group_layout(&self, index: u32) -> dispatch::DispatchBindGroupLayout {
3160        let bind_group_layout = self
3161            .inner
3162            .as_ref()
3163            .expect("a pipeline created during pipeline discovery has no bind group layouts")
3164            .get_bind_group_layout(index);
3165
3166        WebBindGroupLayout {
3167            inner: bind_group_layout,
3168            ident: crate::cmp::Identifier::create(),
3169        }
3170        .into()
3171    }
3172}
3173impl Drop for WebRenderPipeline {
3174    fn drop(&mut self) {
3175        // no-op
3176    }
3177}
3178
3179impl dispatch::ComputePipelineInterface for WebComputePipeline {
3180    fn get_bind_group_layout(&self, index: u32) -> dispatch::DispatchBindGroupLayout {
3181        let bind_group_layout = self.inner.get_bind_group_layout(index);
3182
3183        WebBindGroupLayout {
3184            inner: bind_group_layout,
3185            ident: crate::cmp::Identifier::create(),
3186        }
3187        .into()
3188    }
3189}
3190impl Drop for WebComputePipeline {
3191    fn drop(&mut self) {
3192        // no-op
3193    }
3194}
3195
3196impl dispatch::CommandEncoderInterface for WebCommandEncoder {
3197    fn copy_buffer_to_buffer(
3198        &self,
3199        source: &dispatch::DispatchBuffer,
3200        source_offset: crate::BufferAddress,
3201        destination: &dispatch::DispatchBuffer,
3202        destination_offset: crate::BufferAddress,
3203        copy_size: Option<crate::BufferAddress>,
3204    ) {
3205        let source = source.as_webgpu();
3206        let destination = destination.as_webgpu();
3207
3208        if let Some(size) = copy_size {
3209            self.inner
3210                .copy_buffer_to_buffer_with_f64_and_f64_and_f64(
3211                    &source.inner,
3212                    source_offset as f64,
3213                    &destination.inner,
3214                    destination_offset as f64,
3215                    size as f64,
3216                )
3217                .unwrap();
3218        } else {
3219            self.inner
3220                .copy_buffer_to_buffer_with_f64_and_f64(
3221                    &source.inner,
3222                    source_offset as f64,
3223                    &destination.inner,
3224                    destination_offset as f64,
3225                )
3226                .unwrap();
3227        }
3228    }
3229
3230    fn copy_buffer_to_texture(
3231        &self,
3232        source: crate::TexelCopyBufferInfo<'_>,
3233        destination: crate::TexelCopyTextureInfo<'_>,
3234        copy_size: crate::Extent3d,
3235    ) {
3236        self.inner
3237            .copy_buffer_to_texture_with_gpu_extent_3d_dict(
3238                &map_buffer_copy_view(source),
3239                &map_texture_copy_view(destination),
3240                &map_extent_3d(copy_size),
3241            )
3242            .unwrap();
3243    }
3244
3245    fn copy_texture_to_buffer(
3246        &self,
3247        source: crate::TexelCopyTextureInfo<'_>,
3248        destination: crate::TexelCopyBufferInfo<'_>,
3249        copy_size: crate::Extent3d,
3250    ) {
3251        self.inner
3252            .copy_texture_to_buffer_with_gpu_extent_3d_dict(
3253                &map_texture_copy_view(source),
3254                &map_buffer_copy_view(destination),
3255                &map_extent_3d(copy_size),
3256            )
3257            .unwrap();
3258    }
3259
3260    fn copy_texture_to_texture(
3261        &self,
3262        source: crate::TexelCopyTextureInfo<'_>,
3263        destination: crate::TexelCopyTextureInfo<'_>,
3264        copy_size: crate::Extent3d,
3265    ) {
3266        self.inner
3267            .copy_texture_to_texture_with_gpu_extent_3d_dict(
3268                &map_texture_copy_view(source),
3269                &map_texture_copy_view(destination),
3270                &map_extent_3d(copy_size),
3271            )
3272            .unwrap();
3273    }
3274
3275    fn begin_compute_pass(
3276        &self,
3277        desc: &crate::ComputePassDescriptor<'_>,
3278    ) -> dispatch::DispatchComputePass {
3279        let mapped_desc = webgpu_sys::GpuComputePassDescriptor::new();
3280        if let Some(label) = desc.label {
3281            mapped_desc.set_label(label);
3282        }
3283
3284        if let Some(ref timestamp_writes) = desc.timestamp_writes {
3285            let query_set = timestamp_writes.query_set.inner.as_webgpu();
3286            let writes = webgpu_sys::GpuComputePassTimestampWrites::new(&query_set.inner);
3287            if let Some(index) = timestamp_writes.beginning_of_pass_write_index {
3288                writes.set_beginning_of_pass_write_index(index);
3289            }
3290            if let Some(index) = timestamp_writes.end_of_pass_write_index {
3291                writes.set_end_of_pass_write_index(index);
3292            }
3293            mapped_desc.set_timestamp_writes(&writes);
3294        }
3295
3296        let compute_pass = self.inner.begin_compute_pass_with_descriptor(&mapped_desc);
3297
3298        WebComputePassEncoder {
3299            inner: compute_pass,
3300            ident: crate::cmp::Identifier::create(),
3301        }
3302        .into()
3303    }
3304
3305    fn begin_render_pass(
3306        &self,
3307        desc: &crate::RenderPassDescriptor<'_>,
3308    ) -> dispatch::DispatchRenderPass {
3309        let mapped_color_attachments = desc
3310            .color_attachments
3311            .iter()
3312            .map(|attachment| match attachment {
3313                Some(ca) => {
3314                    let (load_op, clear_value) = match ca.ops.load {
3315                        crate::LoadOp::Clear(color) => {
3316                            (webgpu_sys::GpuLoadOp::Clear, Some(map_color(color)))
3317                        }
3318                        crate::LoadOp::DontCare(_token) => {
3319                            // WebGPU can't safely have a ClearOp::DontCare, so we clear to black
3320                            // which is ideal for most GPUs.
3321                            (
3322                                webgpu_sys::GpuLoadOp::Clear,
3323                                Some(map_color(crate::Color::BLACK)),
3324                            )
3325                        }
3326                        crate::LoadOp::Load => (webgpu_sys::GpuLoadOp::Load, None),
3327                    };
3328
3329                    let view = &ca.view.inner.as_webgpu().inner;
3330
3331                    let mapped_color_attachment =
3332                        webgpu_sys::GpuRenderPassColorAttachment::new_with_gpu_texture_view(
3333                            load_op,
3334                            map_store_op(ca.ops.store),
3335                            view,
3336                        );
3337                    if let Some(cv) = clear_value {
3338                        mapped_color_attachment.set_clear_value_gpu_color_dict(&cv);
3339                    }
3340                    if let Some(rt) = ca.resolve_target {
3341                        let resolve_target_view = &rt.inner.as_webgpu().inner;
3342                        mapped_color_attachment
3343                            .set_resolve_target_gpu_texture_view(resolve_target_view);
3344                    }
3345                    mapped_color_attachment.set_store_op(map_store_op(ca.ops.store));
3346
3347                    js_sys::JsNullable::wrap(mapped_color_attachment)
3348                }
3349                None => js_sys::JsNullable::new(),
3350            })
3351            .collect::<Vec<js_sys::JsNullable<webgpu_sys::GpuRenderPassColorAttachment>>>();
3352
3353        let mapped_desc = webgpu_sys::GpuRenderPassDescriptor::new(&mapped_color_attachments);
3354
3355        if let Some(label) = desc.label {
3356            mapped_desc.set_label(label);
3357        }
3358
3359        if let Some(dsa) = &desc.depth_stencil_attachment {
3360            let depth_stencil_attachment = &dsa.view.inner.as_webgpu().inner;
3361            let mapped_depth_stencil_attachment =
3362                webgpu_sys::GpuRenderPassDepthStencilAttachment::new_with_gpu_texture_view(
3363                    depth_stencil_attachment,
3364                );
3365            if let Some(ref ops) = dsa.depth_ops {
3366                let load_op = match ops.load {
3367                    crate::LoadOp::Clear(v) => {
3368                        mapped_depth_stencil_attachment.set_depth_clear_value(v);
3369                        webgpu_sys::GpuLoadOp::Clear
3370                    }
3371                    crate::LoadOp::DontCare(_token) => {
3372                        // WebGPU can't safely have a ClearOp::DontCare, so we clear to 1.0
3373                        mapped_depth_stencil_attachment.set_depth_clear_value(1.0);
3374                        webgpu_sys::GpuLoadOp::Clear
3375                    }
3376                    crate::LoadOp::Load => webgpu_sys::GpuLoadOp::Load,
3377                };
3378                mapped_depth_stencil_attachment.set_depth_load_op(load_op);
3379                mapped_depth_stencil_attachment.set_depth_store_op(map_store_op(ops.store));
3380            }
3381            mapped_depth_stencil_attachment.set_depth_read_only(dsa.depth_ops.is_none());
3382            if let Some(ref ops) = dsa.stencil_ops {
3383                let load_op = match ops.load {
3384                    crate::LoadOp::Clear(v) => {
3385                        mapped_depth_stencil_attachment.set_stencil_clear_value(v);
3386                        webgpu_sys::GpuLoadOp::Clear
3387                    }
3388                    crate::LoadOp::DontCare(_token) => {
3389                        // WebGPU can't safely have a ClearOp::DontCare, so we clear to 0
3390                        mapped_depth_stencil_attachment.set_stencil_clear_value(0);
3391                        webgpu_sys::GpuLoadOp::Clear
3392                    }
3393                    crate::LoadOp::Load => webgpu_sys::GpuLoadOp::Load,
3394                };
3395                mapped_depth_stencil_attachment.set_stencil_load_op(load_op);
3396                mapped_depth_stencil_attachment.set_stencil_store_op(map_store_op(ops.store));
3397            }
3398            mapped_depth_stencil_attachment.set_stencil_read_only(dsa.stencil_ops.is_none());
3399            mapped_desc.set_depth_stencil_attachment(&mapped_depth_stencil_attachment);
3400        }
3401
3402        if let Some(ref timestamp_writes) = desc.timestamp_writes {
3403            let query_set = &timestamp_writes.query_set.inner.as_webgpu().inner;
3404            let writes = webgpu_sys::GpuRenderPassTimestampWrites::new(query_set);
3405            if let Some(index) = timestamp_writes.beginning_of_pass_write_index {
3406                writes.set_beginning_of_pass_write_index(index);
3407            }
3408            if let Some(index) = timestamp_writes.end_of_pass_write_index {
3409                writes.set_end_of_pass_write_index(index);
3410            }
3411            mapped_desc.set_timestamp_writes(&writes);
3412        }
3413
3414        let render_pass = self.inner.begin_render_pass(&mapped_desc).unwrap();
3415
3416        WebRenderPassEncoder {
3417            inner: render_pass,
3418            draws: PipelineDraws::new(self.pipeline_discovery.borrow().is_some()),
3419            ident: crate::cmp::Identifier::create(),
3420        }
3421        .into()
3422    }
3423
3424    fn finish(&mut self) -> dispatch::DispatchCommandBuffer {
3425        let label = self.inner.label();
3426        let buffer = if label.is_empty() {
3427            self.inner.finish()
3428        } else {
3429            let mapped_desc = webgpu_sys::GpuCommandBufferDescriptor::new();
3430            mapped_desc.set_label(&label);
3431
3432            self.inner.finish_with_descriptor(&mapped_desc)
3433        };
3434
3435        WebCommandBuffer {
3436            inner: buffer,
3437            ident: crate::cmp::Identifier::create(),
3438        }
3439        .into()
3440    }
3441
3442    fn clear_texture(
3443        &self,
3444        _texture: &dispatch::DispatchTexture,
3445        _subresource_range: &crate::ImageSubresourceRange,
3446    ) {
3447        unimplemented!("clear_texture is not yet implemented");
3448    }
3449
3450    fn clear_buffer(
3451        &self,
3452        buffer: &dispatch::DispatchBuffer,
3453        offset: crate::BufferAddress,
3454        size: Option<crate::BufferAddress>,
3455    ) {
3456        let buffer = buffer.as_webgpu();
3457
3458        match size {
3459            Some(size) => {
3460                self.inner
3461                    .clear_buffer_with_f64_and_f64(&buffer.inner, offset as f64, size as f64)
3462            }
3463            None => self
3464                .inner
3465                .clear_buffer_with_f64(&buffer.inner, offset as f64),
3466        }
3467    }
3468
3469    fn insert_debug_marker(&self, label: &str) {
3470        self.inner.insert_debug_marker(label)
3471    }
3472
3473    fn push_debug_group(&self, group_label: &str) {
3474        self.inner.push_debug_group(group_label)
3475    }
3476
3477    fn pop_debug_group(&self) {
3478        self.inner.pop_debug_group()
3479    }
3480
3481    fn write_timestamp(&self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32) {
3482        // Not available on WebGPU.
3483        // This was part of the spec originally but got removed, see https://github.com/gpuweb/gpuweb/pull/4370
3484        panic!("TIMESTAMP_QUERY_INSIDE_ENCODERS feature must be enabled to call write_timestamp on a command encoder.")
3485    }
3486
3487    fn resolve_query_set(
3488        &self,
3489        query_set: &dispatch::DispatchQuerySet,
3490        first_query: u32,
3491        query_count: u32,
3492        destination: &dispatch::DispatchBuffer,
3493        destination_offset: crate::BufferAddress,
3494    ) {
3495        let query_set = &query_set.as_webgpu().inner;
3496        let destination = &destination.as_webgpu().inner;
3497
3498        self.inner.resolve_query_set_with_u32(
3499            query_set,
3500            first_query,
3501            query_count,
3502            destination,
3503            destination_offset as u32,
3504        );
3505    }
3506
3507    fn mark_acceleration_structures_built<'a>(
3508        &self,
3509        _blas: &mut dyn Iterator<Item = &'a Blas>,
3510        _tlas: &mut dyn Iterator<Item = &'a Tlas>,
3511    ) {
3512        unimplemented!("Raytracing not implemented for web");
3513    }
3514
3515    fn build_acceleration_structures<'a>(
3516        &self,
3517        _blas: &mut dyn Iterator<Item = &'a crate::BlasBuildEntry<'a>>,
3518        _tlas: &mut dyn Iterator<Item = &'a crate::Tlas>,
3519    ) {
3520        unimplemented!("Raytracing not implemented for web");
3521    }
3522
3523    fn transition_resources<'a>(
3524        &mut self,
3525        _buffer_transitions: &mut dyn Iterator<
3526            Item = wgt::BufferTransition<&'a dispatch::DispatchBuffer>,
3527        >,
3528        _texture_transitions: &mut dyn Iterator<
3529            Item = wgt::TextureTransition<&'a dispatch::DispatchTexture>,
3530        >,
3531    ) {
3532        // no-op
3533    }
3534}
3535impl Drop for WebCommandEncoder {
3536    fn drop(&mut self) {
3537        // no-op
3538    }
3539}
3540
3541impl dispatch::PipelineCacheInterface for WebPipelineCache {
3542    fn get_data(&self) -> Option<Vec<u8>> {
3543        todo!()
3544    }
3545}
3546impl Drop for WebPipelineCache {
3547    fn drop(&mut self) {
3548        // no-op
3549    }
3550}
3551
3552impl dispatch::ComputePassInterface for WebComputePassEncoder {
3553    fn set_pipeline(&mut self, pipeline: &dispatch::DispatchComputePipeline) {
3554        let pipeline = &pipeline.as_webgpu().inner;
3555        self.inner.set_pipeline(pipeline);
3556    }
3557
3558    fn set_bind_group(
3559        &mut self,
3560        index: u32,
3561        bind_group: Option<&dispatch::DispatchBindGroup>,
3562        offsets: &[crate::DynamicOffset],
3563    ) {
3564        let bind_group = bind_group.map(|bind_group| &bind_group.as_webgpu().inner);
3565
3566        if offsets.is_empty() {
3567            self.inner.set_bind_group(index, bind_group);
3568        } else {
3569            self.inner
3570                .set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length(
3571                    index,
3572                    bind_group,
3573                    offsets,
3574                    0f64,
3575                    offsets.len() as u32,
3576                )
3577                .unwrap();
3578        }
3579    }
3580
3581    fn set_immediates(&mut self, _offset: u32, _data: &[u8]) {
3582        panic!("IMMEDIATES feature must be enabled to call set_immediates")
3583    }
3584
3585    fn insert_debug_marker(&mut self, label: &str) {
3586        self.inner.insert_debug_marker(label);
3587    }
3588
3589    fn push_debug_group(&mut self, group_label: &str) {
3590        self.inner.push_debug_group(group_label);
3591    }
3592
3593    fn pop_debug_group(&mut self) {
3594        self.inner.pop_debug_group();
3595    }
3596
3597    fn write_timestamp(&mut self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32) {
3598        panic!("TIMESTAMP_QUERY_INSIDE_PASSES feature must be enabled to call write_timestamp in a compute pass.")
3599    }
3600
3601    fn begin_pipeline_statistics_query(
3602        &mut self,
3603        _query_set: &dispatch::DispatchQuerySet,
3604        _query_index: u32,
3605    ) {
3606        // Not available in gecko yet
3607    }
3608
3609    fn end_pipeline_statistics_query(&mut self) {
3610        // Not available in gecko yet
3611    }
3612
3613    fn dispatch_workgroups(&mut self, x: u32, y: u32, z: u32) {
3614        self.inner
3615            .dispatch_workgroups_with_workgroup_count_y_and_workgroup_count_z(x, y, z);
3616    }
3617
3618    fn dispatch_workgroups_indirect(
3619        &mut self,
3620        indirect_buffer: &dispatch::DispatchBuffer,
3621        indirect_offset: crate::BufferAddress,
3622    ) {
3623        let indirect_buffer = indirect_buffer.as_webgpu();
3624
3625        self.inner
3626            .dispatch_workgroups_indirect_with_f64(&indirect_buffer.inner, indirect_offset as f64);
3627    }
3628
3629    fn transition_resources<'a>(
3630        &mut self,
3631        _buffer_transitions: &mut dyn Iterator<
3632            Item = wgt::BufferTransition<&'a dispatch::DispatchBuffer>,
3633        >,
3634        _texture_transitions: &mut dyn Iterator<
3635            Item = wgt::TextureTransition<&'a dispatch::DispatchTextureView>,
3636        >,
3637    ) {
3638        // noop
3639    }
3640}
3641impl Drop for WebComputePassEncoder {
3642    fn drop(&mut self) {
3643        self.inner.end();
3644    }
3645}
3646
3647impl dispatch::RenderPassInterface for WebRenderPassEncoder {
3648    fn set_pipeline(&mut self, pipeline: &dispatch::DispatchRenderPipeline) {
3649        if let Some(pipeline) = self.draws.set(pipeline) {
3650            self.inner.set_pipeline(pipeline);
3651        }
3652    }
3653
3654    fn set_bind_group(
3655        &mut self,
3656        index: u32,
3657        bind_group: Option<&dispatch::DispatchBindGroup>,
3658        offsets: &[crate::DynamicOffset],
3659    ) {
3660        let bind_group = bind_group.map(|bind_group| &bind_group.as_webgpu().inner);
3661
3662        if offsets.is_empty() {
3663            self.inner.set_bind_group(index, bind_group);
3664        } else {
3665            self.inner
3666                .set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length(
3667                    index,
3668                    bind_group,
3669                    offsets,
3670                    0f64,
3671                    offsets.len() as u32,
3672                )
3673                .unwrap();
3674        }
3675    }
3676
3677    fn set_index_buffer(
3678        &mut self,
3679        buffer: &dispatch::DispatchBuffer,
3680        index_format: crate::IndexFormat,
3681        offset: crate::BufferAddress,
3682        size: Option<crate::BufferSize>,
3683    ) {
3684        let buffer = buffer.as_webgpu();
3685        let index_format = map_index_format(index_format);
3686
3687        if let Some(size) = size {
3688            self.inner.set_index_buffer_with_f64_and_f64(
3689                &buffer.inner,
3690                index_format,
3691                offset as f64,
3692                size.get() as f64,
3693            );
3694        } else {
3695            self.inner
3696                .set_index_buffer_with_f64(&buffer.inner, index_format, offset as f64);
3697        }
3698    }
3699
3700    fn set_vertex_buffer(
3701        &mut self,
3702        slot: u32,
3703        buffer: Option<&dispatch::DispatchBuffer>,
3704        offset: crate::BufferAddress,
3705        size: Option<crate::BufferSize>,
3706    ) {
3707        let buffer = buffer.map(|buffer| &buffer.as_webgpu().inner);
3708
3709        if let Some(size) = size {
3710            self.inner.set_vertex_buffer_with_f64_and_f64(
3711                slot,
3712                buffer,
3713                offset as f64,
3714                size.get() as f64,
3715            );
3716        } else {
3717            self.inner
3718                .set_vertex_buffer_with_f64(slot, buffer, offset as f64);
3719        }
3720    }
3721
3722    fn set_immediates(&mut self, _offset: u32, _data: &[u8]) {
3723        panic!("IMMEDIATES feature must be enabled to call set_immediates")
3724    }
3725
3726    fn set_blend_constant(&mut self, color: crate::Color) {
3727        self.inner
3728            .set_blend_constant_with_gpu_color_dict(&map_color(color))
3729            .unwrap();
3730    }
3731
3732    fn set_scissor_rect(&mut self, x: u32, y: u32, width: u32, height: u32) {
3733        self.inner.set_scissor_rect(x, y, width, height);
3734    }
3735
3736    fn set_viewport(
3737        &mut self,
3738        x: f32,
3739        y: f32,
3740        width: f32,
3741        height: f32,
3742        min_depth: f32,
3743        max_depth: f32,
3744    ) {
3745        self.inner
3746            .set_viewport(x, y, width, height, min_depth, max_depth);
3747    }
3748
3749    fn set_stencil_reference(&mut self, reference: u32) {
3750        self.inner.set_stencil_reference(reference);
3751    }
3752
3753    fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
3754        if self.draws.skipping {
3755            return;
3756        }
3757        self.inner
3758            .draw_with_instance_count_and_first_vertex_and_first_instance(
3759                vertices.end - vertices.start,
3760                instances.end - instances.start,
3761                vertices.start,
3762                instances.start,
3763            );
3764    }
3765
3766    fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
3767        if self.draws.skipping {
3768            return;
3769        }
3770        self.inner
3771            .draw_indexed_with_instance_count_and_first_index_and_base_vertex_and_first_instance(
3772                indices.end - indices.start,
3773                instances.end - instances.start,
3774                indices.start,
3775                base_vertex,
3776                instances.start,
3777            )
3778    }
3779
3780    fn draw_mesh_tasks(&mut self, _group_count_x: u32, _group_count_y: u32, _group_count_z: u32) {
3781        panic!("MESH_SHADER feature must be enabled to call draw_mesh_tasks")
3782    }
3783
3784    fn draw_indirect(
3785        &mut self,
3786        indirect_buffer: &dispatch::DispatchBuffer,
3787        indirect_offset: crate::BufferAddress,
3788    ) {
3789        if self.draws.skipping {
3790            return;
3791        }
3792        let buffer = indirect_buffer.as_webgpu();
3793        self.inner
3794            .draw_indirect_with_f64(&buffer.inner, indirect_offset as f64);
3795    }
3796
3797    fn draw_indexed_indirect(
3798        &mut self,
3799        indirect_buffer: &dispatch::DispatchBuffer,
3800        indirect_offset: crate::BufferAddress,
3801    ) {
3802        if self.draws.skipping {
3803            return;
3804        }
3805        let buffer = indirect_buffer.as_webgpu();
3806        self.inner
3807            .draw_indexed_indirect_with_f64(&buffer.inner, indirect_offset as f64);
3808    }
3809
3810    fn draw_mesh_tasks_indirect(
3811        &mut self,
3812        _indirect_buffer: &dispatch::DispatchBuffer,
3813        _indirect_offset: crate::BufferAddress,
3814    ) {
3815        panic!("MESH_SHADER feature must be enabled to call draw_mesh_tasks_indirect")
3816    }
3817
3818    fn multi_draw_indirect(
3819        &mut self,
3820        indirect_buffer: &dispatch::DispatchBuffer,
3821        indirect_offset: crate::BufferAddress,
3822        count: u32,
3823    ) {
3824        if self.draws.skipping {
3825            return;
3826        }
3827        let buffer = indirect_buffer.as_webgpu();
3828
3829        for i in 0..count {
3830            let offset = indirect_offset + i as crate::BufferAddress * 16;
3831            self.inner
3832                .draw_indirect_with_f64(&buffer.inner, offset as f64);
3833        }
3834    }
3835
3836    fn multi_draw_indexed_indirect(
3837        &mut self,
3838        indirect_buffer: &dispatch::DispatchBuffer,
3839        indirect_offset: crate::BufferAddress,
3840        count: u32,
3841    ) {
3842        if self.draws.skipping {
3843            return;
3844        }
3845        let buffer = indirect_buffer.as_webgpu();
3846
3847        for i in 0..count {
3848            let offset = indirect_offset + i as crate::BufferAddress * 20;
3849            self.inner
3850                .draw_indexed_indirect_with_f64(&buffer.inner, offset as f64);
3851        }
3852    }
3853
3854    fn multi_draw_mesh_tasks_indirect(
3855        &mut self,
3856        _indirect_buffer: &dispatch::DispatchBuffer,
3857        _indirect_offset: crate::BufferAddress,
3858        _count: u32,
3859    ) {
3860        panic!("MESH_SHADER feature must be enabled to call multi_draw_mesh_tasks_indirect")
3861    }
3862
3863    fn multi_draw_indirect_count(
3864        &mut self,
3865        _indirect_buffer: &dispatch::DispatchBuffer,
3866        _indirect_offset: crate::BufferAddress,
3867        _count_buffer: &dispatch::DispatchBuffer,
3868        _count_buffer_offset: crate::BufferAddress,
3869        _max_count: u32,
3870    ) {
3871        if self.draws.skipping {
3872            return;
3873        }
3874        panic!(
3875            "MULTI_DRAW_INDIRECT_COUNT feature must be enabled to call multi_draw_indirect_count"
3876        )
3877    }
3878
3879    fn multi_draw_indexed_indirect_count(
3880        &mut self,
3881        _indirect_buffer: &dispatch::DispatchBuffer,
3882        _indirect_offset: crate::BufferAddress,
3883        _count_buffer: &dispatch::DispatchBuffer,
3884        _count_buffer_offset: crate::BufferAddress,
3885        _max_count: u32,
3886    ) {
3887        if self.draws.skipping {
3888            return;
3889        }
3890        panic!("MULTI_DRAW_INDIRECT_COUNT feature must be enabled to call multi_draw_indexed_indirect_count")
3891    }
3892
3893    fn multi_draw_mesh_tasks_indirect_count(
3894        &mut self,
3895        _indirect_buffer: &dispatch::DispatchBuffer,
3896        _indirect_offset: crate::BufferAddress,
3897        _count_buffer: &dispatch::DispatchBuffer,
3898        _count_buffer_offset: crate::BufferAddress,
3899        _max_count: u32,
3900    ) {
3901        panic!("MESH_SHADER feature must be enabled to call multi_draw_mesh_tasks_indirect_count")
3902    }
3903
3904    fn insert_debug_marker(&mut self, label: &str) {
3905        self.inner.insert_debug_marker(label);
3906    }
3907
3908    fn push_debug_group(&mut self, group_label: &str) {
3909        self.inner.push_debug_group(group_label);
3910    }
3911
3912    fn pop_debug_group(&mut self) {
3913        self.inner.pop_debug_group();
3914    }
3915
3916    fn write_timestamp(&mut self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32) {
3917        panic!("TIMESTAMP_QUERY_INSIDE_PASSES feature must be enabled to call write_timestamp in a render pass.")
3918    }
3919
3920    fn begin_occlusion_query(&mut self, query_index: u32) {
3921        self.inner.begin_occlusion_query(query_index);
3922    }
3923
3924    fn end_occlusion_query(&mut self) {
3925        self.inner.end_occlusion_query();
3926    }
3927
3928    fn begin_pipeline_statistics_query(
3929        &mut self,
3930        _query_set: &dispatch::DispatchQuerySet,
3931        _query_index: u32,
3932    ) {
3933        // Removed from WebGPU in https://github.com/gpuweb/gpuweb/pull/2296
3934    }
3935
3936    fn end_pipeline_statistics_query(&mut self) {
3937        // Removed from WebGPU https://github.com/gpuweb/gpuweb/pull/2296
3938    }
3939
3940    fn execute_bundles(
3941        &mut self,
3942        render_bundles: &mut dyn Iterator<Item = &dispatch::DispatchRenderBundle>,
3943    ) {
3944        let mapped = render_bundles
3945            .map(|bundle| bundle.as_webgpu().inner.clone())
3946            .collect::<Vec<webgpu_sys::GpuRenderBundle>>();
3947        self.inner.execute_bundles(&mapped);
3948    }
3949}
3950impl Drop for WebRenderPassEncoder {
3951    fn drop(&mut self) {
3952        self.inner.end();
3953    }
3954}
3955
3956impl dispatch::CommandBufferInterface for WebCommandBuffer {}
3957impl Drop for WebCommandBuffer {
3958    fn drop(&mut self) {
3959        // no-op
3960    }
3961}
3962
3963impl dispatch::RenderBundleEncoderInterface for WebRenderBundleEncoder {
3964    fn set_pipeline(&mut self, pipeline: &dispatch::DispatchRenderPipeline) {
3965        if let Some(pipeline) = self.draws.set(pipeline) {
3966            self.inner.set_pipeline(pipeline);
3967        }
3968    }
3969
3970    fn set_bind_group(
3971        &mut self,
3972        index: u32,
3973        bind_group: Option<&dispatch::DispatchBindGroup>,
3974        offsets: &[crate::DynamicOffset],
3975    ) {
3976        let bind_group = bind_group.map(|bind_group| &bind_group.as_webgpu().inner);
3977
3978        if offsets.is_empty() {
3979            self.inner.set_bind_group(index, bind_group);
3980        } else {
3981            self.inner
3982                .set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length(
3983                    index,
3984                    bind_group,
3985                    offsets,
3986                    0f64,
3987                    offsets.len() as u32,
3988                )
3989                .unwrap();
3990        }
3991    }
3992
3993    fn set_index_buffer(
3994        &mut self,
3995        buffer: &dispatch::DispatchBuffer,
3996        index_format: crate::IndexFormat,
3997        offset: crate::BufferAddress,
3998        size: Option<crate::BufferSize>,
3999    ) {
4000        let buffer = buffer.as_webgpu();
4001        let index_format = map_index_format(index_format);
4002
4003        if let Some(size) = size {
4004            self.inner.set_index_buffer_with_f64_and_f64(
4005                &buffer.inner,
4006                index_format,
4007                offset as f64,
4008                size.get() as f64,
4009            );
4010        } else {
4011            self.inner
4012                .set_index_buffer_with_f64(&buffer.inner, index_format, offset as f64);
4013        }
4014    }
4015
4016    fn set_vertex_buffer(
4017        &mut self,
4018        slot: u32,
4019        buffer: Option<&dispatch::DispatchBuffer>,
4020        offset: crate::BufferAddress,
4021        size: Option<crate::BufferSize>,
4022    ) {
4023        let buffer = buffer.map(|buffer| &buffer.as_webgpu().inner);
4024
4025        if let Some(size) = size {
4026            self.inner.set_vertex_buffer_with_f64_and_f64(
4027                slot,
4028                buffer,
4029                offset as f64,
4030                size.get() as f64,
4031            );
4032        } else {
4033            self.inner
4034                .set_vertex_buffer_with_f64(slot, buffer, offset as f64);
4035        }
4036    }
4037
4038    fn set_immediates(&mut self, _offset: u32, _data: &[u8]) {
4039        panic!("IMMEDIATES feature must be enabled to call set_immediates")
4040    }
4041
4042    fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
4043        if self.draws.skipping {
4044            return;
4045        }
4046        self.inner
4047            .draw_with_instance_count_and_first_vertex_and_first_instance(
4048                vertices.end - vertices.start,
4049                instances.end - instances.start,
4050                vertices.start,
4051                instances.start,
4052            );
4053    }
4054
4055    fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
4056        if self.draws.skipping {
4057            return;
4058        }
4059        self.inner
4060            .draw_indexed_with_instance_count_and_first_index_and_base_vertex_and_first_instance(
4061                indices.end - indices.start,
4062                instances.end - instances.start,
4063                indices.start,
4064                base_vertex,
4065                instances.start,
4066            )
4067    }
4068
4069    fn draw_indirect(
4070        &mut self,
4071        indirect_buffer: &dispatch::DispatchBuffer,
4072        indirect_offset: crate::BufferAddress,
4073    ) {
4074        if self.draws.skipping {
4075            return;
4076        }
4077        let buffer = indirect_buffer.as_webgpu();
4078        self.inner
4079            .draw_indirect_with_f64(&buffer.inner, indirect_offset as f64);
4080    }
4081
4082    fn draw_indexed_indirect(
4083        &mut self,
4084        indirect_buffer: &dispatch::DispatchBuffer,
4085        indirect_offset: crate::BufferAddress,
4086    ) {
4087        if self.draws.skipping {
4088            return;
4089        }
4090        let buffer = indirect_buffer.as_webgpu();
4091        self.inner
4092            .draw_indexed_indirect_with_f64(&buffer.inner, indirect_offset as f64);
4093    }
4094
4095    fn finish(self, desc: &crate::RenderBundleDescriptor<'_>) -> dispatch::DispatchRenderBundle
4096    where
4097        Self: Sized,
4098    {
4099        let bundle = match desc.label {
4100            Some(label) => {
4101                let mapped_desc = webgpu_sys::GpuRenderBundleDescriptor::new();
4102                mapped_desc.set_label(label);
4103                self.inner.finish_with_descriptor(&mapped_desc)
4104            }
4105            None => self.inner.finish(),
4106        };
4107
4108        WebRenderBundle {
4109            inner: bundle,
4110            ident: crate::cmp::Identifier::create(),
4111        }
4112        .into()
4113    }
4114
4115    #[cfg(custom)]
4116    fn finish_boxed(
4117        self: Box<Self>,
4118        desc: &crate::RenderBundleDescriptor<'_>,
4119    ) -> dispatch::DispatchRenderBundle {
4120        (*self).finish(desc)
4121    }
4122}
4123impl Drop for WebRenderBundleEncoder {
4124    fn drop(&mut self) {
4125        // no-op
4126    }
4127}
4128
4129impl dispatch::RenderBundleInterface for WebRenderBundle {}
4130impl Drop for WebRenderBundle {
4131    fn drop(&mut self) {
4132        // no-op
4133    }
4134}
4135
4136/// Evaluates a CSS media query, or `None` if there is no [`web_sys::Window`]
4137/// (Worker / `OffscreenCanvas` contexts, where `matchMedia` is absent).
4138fn match_media_query(query: &str) -> Option<bool> {
4139    let list = web_sys::window()?.match_media(query).ok().flatten()?;
4140    Some(list.matches())
4141}
4142
4143/// Classifies the display's gamut from CSS `color-gamut`, widest bucket first.
4144/// `None` if there is no `Window` or the browser reports nothing.
4145fn environment_color_gamut() -> Option<wgt::DisplayGamut> {
4146    if match_media_query("(color-gamut: rec2020)") == Some(true) {
4147        Some(wgt::DisplayGamut::Rec2020)
4148    } else if match_media_query("(color-gamut: p3)") == Some(true) {
4149        Some(wgt::DisplayGamut::DisplayP3)
4150    } else if match_media_query("(color-gamut: srgb)") == Some(true) {
4151        Some(wgt::DisplayGamut::Srgb)
4152    } else {
4153        None
4154    }
4155}
4156
4157/// Probing and caching of whether this browser can configure an `rgba16float`
4158/// WebGPU canvas. Only the reader functions are visible outside this module; the
4159/// cached state and the probe machinery are private to it.
4160mod rgba16float_probe {
4161    use super::*;
4162
4163    /// Cached result of probing whether this browser can configure a WebGPU canvas
4164    /// with the `rgba16float` format: [`PROBE_UNKNOWN`] until first probed,
4165    /// otherwise [`PROBE_SUPPORTED`]/[`PROBE_UNSUPPORTED`].
4166    ///
4167    /// Canvas-format support is process-wide and immutable at runtime, so a single
4168    /// probe suffices. The probe (see [`probe_rgba16float_canvas_support`]) is
4169    /// empirical (it actually calls `configure()` rather than checking a
4170    /// browser/version), so it starts reporting supported automatically once a
4171    /// browser (e.g. Firefox, <https://bugzilla.mozilla.org/show_bug.cgi?id=1834395>)
4172    /// adds support, with no wgpu change required.
4173    static RGBA16FLOAT_CANVAS_SUPPORT: AtomicU8 = AtomicU8::new(PROBE_UNKNOWN);
4174    const PROBE_UNKNOWN: u8 = 0;
4175    const PROBE_UNSUPPORTED: u8 = 1;
4176    const PROBE_SUPPORTED: u8 = 2;
4177
4178    /// Whether `Surface::get_capabilities` should advertise `Rgba16Float`.
4179    ///
4180    /// Optimistic (advertised) until the probe has determined it is unsupported:
4181    /// the no-panic handling in [`WebSurface::configure`]/
4182    /// [`WebSurface::get_current_texture`] recovers if an app selects it before a
4183    /// device exists on a browser that rejects it.
4184    pub(super) fn rgba16float_canvas_supported() -> bool {
4185        RGBA16FLOAT_CANVAS_SUPPORT.load(Ordering::Relaxed) != PROBE_UNSUPPORTED
4186    }
4187
4188    /// Probe (once, then cache) whether this browser can configure a `rgba16float`
4189    /// WebGPU canvas, by actually configuring a throwaway 1x1 `OffscreenCanvas`.
4190    ///
4191    /// Some browsers list `rgba16float` as a context format but throw from
4192    /// `configure` (current Firefox), which on wasm would otherwise be an
4193    /// uncatchable panic. Detecting it empirically (instead of sniffing the user
4194    /// agent) means the result self-corrects when the browser ships support.
4195    pub(super) fn probe_rgba16float_canvas_support(device: &webgpu_sys::GpuDevice) {
4196        if RGBA16FLOAT_CANVAS_SUPPORT.load(Ordering::Relaxed) != PROBE_UNKNOWN {
4197            return;
4198        }
4199        // If the probe can't run (e.g. no `OffscreenCanvas`), leave the support
4200        // state unknown so we keep advertising the format optimistically.
4201        let Some(supported) = try_configure_rgba16float_canvas(device) else {
4202            return;
4203        };
4204        RGBA16FLOAT_CANVAS_SUPPORT.store(
4205            if supported {
4206                PROBE_SUPPORTED
4207            } else {
4208                PROBE_UNSUPPORTED
4209            },
4210            Ordering::Relaxed,
4211        );
4212    }
4213
4214    /// Returns `Some(true)`/`Some(false)` if a `rgba16float` canvas could/couldn't
4215    /// be configured, or `None` if the probe itself couldn't be set up.
4216    fn try_configure_rgba16float_canvas(device: &webgpu_sys::GpuDevice) -> Option<bool> {
4217        let canvas = web_sys::OffscreenCanvas::new(1, 1).ok()?;
4218        let context = canvas.get_context("webgpu").ok()??;
4219        let context: webgpu_sys::GpuCanvasContext = context.unchecked_into();
4220        let config = webgpu_sys::GpuCanvasConfiguration::new(
4221            device,
4222            map_texture_format(wgt::TextureFormat::Rgba16Float),
4223        );
4224        let supported = context.configure(&config).is_ok();
4225        if supported {
4226            // Leave the throwaway context unconfigured before it's dropped.
4227            context.unconfigure();
4228        }
4229        Some(supported)
4230    }
4231}
4232
4233impl dispatch::SurfaceInterface for WebSurface {
4234    fn get_capabilities(&self, _adapter: &dispatch::DispatchAdapter) -> wgt::SurfaceCapabilities {
4235        let mut formats = vec![
4236            wgt::TextureFormat::Rgba8Unorm,
4237            wgt::TextureFormat::Bgra8Unorm,
4238        ];
4239        // Only advertise `Rgba16Float` where the browser can actually configure
4240        // it as a canvas (some, e.g. current Firefox, throw from `configure`).
4241        if rgba16float_probe::rgba16float_canvas_supported() {
4242            formats.push(wgt::TextureFormat::Rgba16Float);
4243        }
4244        let mut mapped_formats = formats.iter().map(|format| map_texture_format(*format));
4245        // Preferred canvas format will only be either "rgba8unorm" or "bgra8unorm".
4246        // https://www.w3.org/TR/webgpu/#dom-gpu-getpreferredcanvasformat
4247        let preferred_format = self
4248            .gpu
4249            .as_ref()
4250            .expect("Caller could not have created an adapter if gpu is undefined.")
4251            .get_preferred_canvas_format();
4252        if let Some(index) = mapped_formats.position(|format| format == preferred_format) {
4253            formats.swap(0, index);
4254        }
4255
4256        wgt::SurfaceCapabilities {
4257            format_capabilities: formats
4258                .iter()
4259                .map(|&format| {
4260                    // Every WebGPU implementation supports the "srgb" and
4261                    // "display-p3" canvas color spaces.
4262                    // https://gpuweb.github.io/gpuweb/#canvas-configuration
4263                    let mut color_spaces =
4264                        wgt::SurfaceColorSpaces::SRGB | wgt::SurfaceColorSpaces::DISPLAY_P3;
4265                    // An fp16 canvas with "extended" tone mapping holds
4266                    // encoded extended-range values (the nonlinear sRGB OETF,
4267                    // continued beyond [0, 1]). "extended" tone mapping is a
4268                    // configuration the canvas always accepts on an fp16 surface,
4269                    // independent of the display: an SDR display clamps the
4270                    // out-of-range values rather than rejecting the config (and
4271                    // `configure` here never gates on display HDR state either).
4272                    // So these spaces are gated on fp16-canvas support alone —
4273                    // the same condition that puts `Rgba16Float` in `formats` —
4274                    // not on whether the display is currently HDR, which is what
4275                    // `display_hdr_info` reports. WebGPU has no linear canvas
4276                    // color space, so the web backend advertises the encoded
4277                    // `ExtendedSrgb` and, for the "display-p3" canvas, the
4278                    // wide-gamut `ExtendedDisplayP3` — not the linear
4279                    // `ExtendedSrgbLinear`.
4280                    if format == wgt::TextureFormat::Rgba16Float {
4281                        color_spaces |= wgt::SurfaceColorSpaces::EXTENDED_SRGB
4282                            | wgt::SurfaceColorSpaces::EXTENDED_DISPLAY_P3;
4283                    }
4284                    wgt::SurfaceFormatCapabilities {
4285                        format,
4286                        color_spaces,
4287                    }
4288                })
4289                .collect(),
4290            // https://gpuweb.github.io/gpuweb/#supported-context-formats
4291            formats,
4292            // Doesn't really have meaning on the web.
4293            present_modes: vec![wgt::PresentMode::Fifo],
4294            alpha_modes: vec![wgt::CompositeAlphaMode::Opaque],
4295            // A canvas texture takes any of these usages:
4296            // https://gpuweb.github.io/gpuweb/#dom-gpucanvasconfiguration-usage
4297            usages: wgt::TextureUsages::RENDER_ATTACHMENT
4298                | wgt::TextureUsages::COPY_SRC
4299                | wgt::TextureUsages::COPY_DST
4300                | wgt::TextureUsages::TEXTURE_BINDING,
4301        }
4302    }
4303
4304    fn display_hdr_info(&self, _adapter: &dispatch::DispatchAdapter) -> wgt::DisplayHdrInfo {
4305        // The web exposes only coarse boolean dynamic-range + gamut buckets (CSS
4306        // media queries), no numeric luminance, so every numeric field stays
4307        // `None`. `(dynamic-range: high)` reports capability, not whether HDR is
4308        // active. Worker / `OffscreenCanvas` contexts have no `Window` (no
4309        // `matchMedia`), so this falls back to `default()`.
4310        let high_dynamic_range = match_media_query("(dynamic-range: high)");
4311        let gamut = environment_color_gamut();
4312
4313        let coarse = if high_dynamic_range.is_some() || gamut.is_some() {
4314            Some(wgt::DisplayCoarseRange {
4315                high_dynamic_range,
4316                gamut,
4317            })
4318        } else {
4319            None
4320        };
4321        wgt::DisplayHdrInfo {
4322            luminance: None,
4323            headroom: None,
4324            chromaticity: None,
4325            coarse,
4326            bits_per_color: None,
4327        }
4328    }
4329
4330    fn configure(&self, device: &dispatch::DispatchDevice, config: &crate::SurfaceConfiguration) {
4331        let device = device.as_webgpu();
4332
4333        match self.canvas {
4334            Canvas::Canvas(ref canvas) => {
4335                canvas.set_width(config.width);
4336                canvas.set_height(config.height);
4337            }
4338            Canvas::Offscreen(ref canvas) => {
4339                canvas.set_width(config.width);
4340                canvas.set_height(config.height);
4341            }
4342        }
4343
4344        if let wgt::PresentMode::Mailbox | wgt::PresentMode::Immediate = config.present_mode {
4345            panic!("Only FIFO/Auto* is supported on web");
4346        }
4347        if let wgt::CompositeAlphaMode::PostMultiplied | wgt::CompositeAlphaMode::Inherit =
4348            config.alpha_mode
4349        {
4350            panic!("Only Opaque/Auto or PreMultiplied alpha mode are supported on web");
4351        }
4352        let alpha_mode = match config.alpha_mode {
4353            wgt::CompositeAlphaMode::PreMultiplied => webgpu_sys::GpuCanvasAlphaMode::Premultiplied,
4354            _ => webgpu_sys::GpuCanvasAlphaMode::Opaque,
4355        };
4356        let mapped = webgpu_sys::GpuCanvasConfiguration::new(
4357            &device.inner,
4358            map_texture_format(config.format),
4359        );
4360        mapped.set_usage(config.usage.bits());
4361        mapped.set_alpha_mode(alpha_mode);
4362        match config.color_space {
4363            // `Auto` intentionally keeps the browser defaults (colorSpace
4364            // "srgb", standard tone mapping) even for fp16 formats, matching
4365            // wgpu's historical behavior on the web.
4366            wgt::SurfaceColorSpace::Auto | wgt::SurfaceColorSpace::Srgb => {}
4367            wgt::SurfaceColorSpace::DisplayP3 => {
4368                // The vendored bindings have no `colorSpace` setter, so set
4369                // the dictionary member by reflection.
4370                js_sys::Reflect::set(
4371                    &mapped,
4372                    &JsValue::from_str("colorSpace"),
4373                    &JsValue::from_str("display-p3"),
4374                )
4375                .expect("Setting the canvas configuration color space should never fail");
4376            }
4377            wgt::SurfaceColorSpace::ExtendedSrgb => {
4378                // The canvas keeps the default "srgb" color space; "extended"
4379                // tone mapping disables clamping to [0, 1], so an fp16 canvas
4380                // holds sRGB-encoded extended-range values (the nonlinear sRGB
4381                // OETF, continued beyond [0, 1]). This is the W3C HDR-canvas
4382                // mechanism shipped in Chrome 129+.
4383                let tone_mapping = webgpu_sys::GpuCanvasToneMapping::new();
4384                tone_mapping.set_mode(webgpu_sys::GpuCanvasToneMappingMode::Extended);
4385                mapped.set_tone_mapping(&tone_mapping);
4386            }
4387            wgt::SurfaceColorSpace::ExtendedDisplayP3 => {
4388                // Wide-gamut HDR: the "display-p3" canvas color space combined
4389                // with "extended" tone mapping holds Display-P3-encoded
4390                // extended-range values. Set both dictionary members (colorSpace
4391                // by reflection, as for `DisplayP3`).
4392                js_sys::Reflect::set(
4393                    &mapped,
4394                    &JsValue::from_str("colorSpace"),
4395                    &JsValue::from_str("display-p3"),
4396                )
4397                .expect("Setting the canvas configuration color space should never fail");
4398                let tone_mapping = webgpu_sys::GpuCanvasToneMapping::new();
4399                tone_mapping.set_mode(webgpu_sys::GpuCanvasToneMappingMode::Extended);
4400                mapped.set_tone_mapping(&tone_mapping);
4401            }
4402            cs @ (wgt::SurfaceColorSpace::ExtendedSrgbLinear
4403            | wgt::SurfaceColorSpace::Bt2100Pq
4404            | wgt::SurfaceColorSpace::Bt2100Hlg) => {
4405                // Not representable on a WebGPU canvas: `ExtendedSrgbLinear`
4406                // needs a linear-transfer canvas (WebGPU has none), and
4407                // `Bt2100Pq`/`Bt2100Hlg` need PQ/HLG canvas signaling (browsers expose
4408                // none). `get_capabilities` never advertises these, but an app
4409                // may still request one without checking; record the failure and
4410                // report the surface as lost (as for a rejected `configure`)
4411                // rather than panicking, since there is no `catch_unwind` on
4412                // wasm to recover an abort.
4413                self.configure_failed.set(true);
4414                log::error!(
4415                    "Surface color space {cs:?} is not supported on the WebGPU backend; \
4416                     the surface will report as lost. Check `get_capabilities` before \
4417                     configuring."
4418                );
4419                return;
4420            }
4421        }
4422        let mapped_view_formats = config
4423            .view_formats
4424            .iter()
4425            .map(|format| {
4426                wasm_bindgen::JsValue::from(map_texture_format(*format))
4427                    .dyn_into::<js_sys::JsString>()
4428                    .unwrap()
4429            })
4430            .collect::<Vec<js_sys::JsString>>();
4431        mapped.set_view_formats(&mapped_view_formats);
4432        // `configure` can throw (e.g. the browser doesn't support the requested
4433        // canvas format). There is no `catch_unwind` on wasm, so unwrapping here
4434        // would be an uncatchable abort. Instead, record the failure and report
4435        // the surface as lost from `get_current_texture`, which the application
4436        // can already handle and recover from (e.g. by selecting a different
4437        // format reported by `get_capabilities`).
4438        //
4439        // This isn't WebGPU behavior we have to support forever: some browsers
4440        // (current Firefox) list a context format and then reject it from
4441        // `configure`. Once they stop doing that, this handling can go away.
4442        match self.context.configure(&mapped) {
4443            Ok(()) => self.configure_failed.set(false),
4444            Err(err) => {
4445                self.configure_failed.set(true);
4446                log::error!(
4447                    "Surface configuration failed: {err:?}. The browser may not support \
4448                     this canvas format (for example, Firefox does not yet support \
4449                     `rgba16float` canvases). The surface will report as lost until it \
4450                     is successfully reconfigured."
4451                );
4452            }
4453        }
4454    }
4455
4456    fn get_current_texture(
4457        &self,
4458    ) -> (
4459        Option<dispatch::DispatchTexture>,
4460        crate::SurfaceStatus,
4461        dispatch::DispatchSurfaceOutputDetail,
4462    ) {
4463        let detail = WebSurfaceOutputDetail {
4464            ident: crate::cmp::Identifier::create(),
4465        };
4466
4467        // If the last `configure` failed, the context is not usable; report the
4468        // surface as lost rather than panicking (see `configure`).
4469        if self.configure_failed.get() {
4470            return (None, crate::SurfaceStatus::Lost, detail.into());
4471        }
4472
4473        let surface_texture = match self.context.get_current_texture() {
4474            Ok(surface_texture) => surface_texture,
4475            Err(err) => {
4476                log::error!("`getCurrentTexture` failed: {err:?}");
4477                return (None, crate::SurfaceStatus::Lost, detail.into());
4478            }
4479        };
4480
4481        let web_surface_texture = WebTexture {
4482            inner: surface_texture,
4483            drop_guard: None,
4484            ident: crate::cmp::Identifier::create(),
4485        };
4486
4487        (
4488            Some(web_surface_texture.into()),
4489            crate::SurfaceStatus::Good,
4490            detail.into(),
4491        )
4492    }
4493}
4494impl Drop for WebSurface {
4495    fn drop(&mut self) {
4496        // no-op
4497    }
4498}
4499
4500impl dispatch::SurfaceOutputDetailInterface for WebSurfaceOutputDetail {
4501    fn texture_discard(&self) {
4502        // Can't really discard the texture on the web.
4503    }
4504
4505    fn texture_release(&self) {
4506        // Can't really discard the texture on the web, so there's no point of releasing too
4507    }
4508}
4509impl Drop for WebSurfaceOutputDetail {
4510    fn drop(&mut self) {
4511        // no-op
4512    }
4513}
4514
4515impl WebBufferMappedRange {
4516    fn get_temporary_mapping(&self) -> &[u8] {
4517        self.temporary_mapping
4518            .get_or_init(|| self.actual_mapping.to_vec())
4519    }
4520}
4521impl dispatch::BufferMappedRangeInterface for WebBufferMappedRange {
4522    fn len(&self) -> usize {
4523        self.get_temporary_mapping().len()
4524    }
4525
4526    #[inline]
4527    unsafe fn read_slice(&self) -> &[u8] {
4528        self.get_temporary_mapping()
4529    }
4530
4531    #[inline]
4532    unsafe fn write_slice(&mut self) -> WriteOnly<'_, [u8]> {
4533        self.temporary_mapping_modified = true;
4534        self.get_temporary_mapping();
4535        let t: &mut Vec<u8> = self.temporary_mapping.get_mut().unwrap();
4536        WriteOnly::from_mut(t)
4537    }
4538
4539    #[inline]
4540    fn as_uint8array(&self) -> &js_sys::Uint8Array {
4541        &self.actual_mapping
4542    }
4543}
4544impl Drop for WebBufferMappedRange {
4545    fn drop(&mut self) {
4546        if !self.temporary_mapping_modified {
4547            // For efficiency, skip the copy if it is not needed.
4548            // This is also how we skip copying back on *read-only* mappings.
4549            return;
4550        }
4551
4552        // Copy from the temporary mapping back into the array buffer that was
4553        // originally provided by the browser
4554        let temporary_mapping_slice = self.temporary_mapping.get().unwrap().as_slice();
4555        unsafe {
4556            // Note: no allocations can happen between `view` and `set`, or this
4557            // will break
4558            self.actual_mapping
4559                .set(&js_sys::Uint8Array::view(temporary_mapping_slice), 0);
4560        }
4561    }
4562}
4563
4564impl dispatch::QueueWriteBufferInterface for WebQueueWriteBuffer {
4565    #[inline]
4566    fn len(&self) -> usize {
4567        self.inner.len()
4568    }
4569
4570    #[inline]
4571    unsafe fn write_slice(&mut self) -> WriteOnly<'_, [u8]> {
4572        WriteOnly::from_mut(&mut *self.inner)
4573    }
4574}
4575impl Drop for WebQueueWriteBuffer {
4576    fn drop(&mut self) {
4577        // The api struct calls write_staging_buffer
4578
4579        // no-op
4580    }
4581}
4582
4583/// Adds the constants map to the given pipeline descriptor if the map is nonempty.
4584/// Panics if the map cannot be set.
4585///
4586/// This function is necessary because the constants array is not currently
4587/// exposed by `wasm-bindgen`. See the following issues for details:
4588/// - [gfx-rs/wgpu#5688](https://github.com/gfx-rs/wgpu/pull/5688)
4589/// - [rustwasm/wasm-bindgen#3587](https://github.com/rustwasm/wasm-bindgen/issues/3587)
4590fn insert_constants_map(target: &JsValue, map: &[(&str, f64)]) {
4591    if !map.is_empty() {
4592        js_sys::Reflect::set(
4593            target,
4594            &JsValue::from_str("constants"),
4595            &hashmap_to_jsvalue(map),
4596        )
4597        .expect("Setting the values in a Javascript pipeline descriptor should never fail");
4598    }
4599}
4600
4601/// Converts a hashmap to a Javascript object.
4602fn hashmap_to_jsvalue(map: &[(&str, f64)]) -> JsValue {
4603    let obj = js_sys::Object::new();
4604
4605    for &(key, v) in map.iter() {
4606        js_sys::Reflect::set(&obj, &JsValue::from_str(key), &JsValue::from_f64(v))
4607            .expect("Setting the values in a Javascript map should never fail");
4608    }
4609
4610    JsValue::from(obj)
4611}