Skip to main content

wgpu/backend/
webgpu.rs

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