Skip to main content

wgpu/backend/
webgpu.rs

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