#![allow(clippy::type_complexity)]

mod defined_non_null_js_value;
mod ext_bindings;
#[allow(clippy::allow_attributes)]
mod webgpu_sys;

use alloc::{
    boxed::Box,
    format,
    rc::Rc,
    string::{String, ToString as _},
    sync::Arc,
    vec,
    vec::Vec,
};
use core::{
    cell::OnceCell,
    cell::RefCell,
    fmt,
    future::Future,
    ops::Range,
    pin::Pin,
    task::{self, Poll},
};
use wgt::Backends;

use js_sys::Promise;
use wasm_bindgen::{prelude::*, JsCast};

use crate::{
    dispatch::{self, BlasCompactCallback},
    Blas, SurfaceTargetUnsafe, Tlas,
};

use defined_non_null_js_value::DefinedNonNullJsValue;

// We need to mark various types as Send and Sync to satisfy the Rust type system.
//
// SAFETY: All webgpu handle types in wasm32 are internally a `JsValue`, and `JsValue` is neither
// Send nor Sync.  Currently, wasm32 has no threading support by default, so implementing `Send` or
// `Sync` for a type is harmless. However, nightly Rust supports compiling wasm with experimental
// threading support via `--target-features`. If `wgpu` is being compiled with those features, we do
// not implement `Send` and `Sync` on the webgpu handle types.
macro_rules! impl_send_sync {
    ($name:ty) => {
        #[cfg(send_sync)]
        unsafe impl Send for $name {}
        #[cfg(send_sync)]
        unsafe impl Sync for $name {}
    };
}

pub struct ContextWebGpu {
    /// `None` if browser does not advertise support for WebGPU.
    gpu: Option<DefinedNonNullJsValue<webgpu_sys::Gpu>>,
    /// Unique identifier for this context.
    ident: crate::cmp::Identifier,
    /// Backends requested in the [`crate::InstanceDescriptor`].
    /// Remembered for error reporting even though this itself is strictly
    /// [`Backends::BROWSER_WEBGPU`].
    requested_backends: Backends,
}

impl fmt::Debug for ContextWebGpu {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ContextWebGpu")
            .field("type", &"Web")
            .finish()
    }
}

impl crate::Error {
    fn from_js(js_error: js_sys::Object) -> Self {
        let source = Box::<dyn core::error::Error + Send + Sync>::from("<WebGPU Error>");
        if let Some(js_error) = js_error.dyn_ref::<webgpu_sys::GpuValidationError>() {
            crate::Error::Validation {
                source,
                description: js_error.message(),
            }
        } else if js_error.has_type::<webgpu_sys::GpuOutOfMemoryError>() {
            crate::Error::OutOfMemory { source }
        } else {
            panic!("Unexpected error");
        }
    }
}

#[derive(Debug)]
pub struct WebShaderModule {
    module: webgpu_sys::GpuShaderModule,
    compilation_info: WebShaderCompilationInfo,
    /// Unique identifier for this shader module.
    ident: crate::cmp::Identifier,
}

#[derive(Debug, Clone)]
enum WebShaderCompilationInfo {
    /// WGSL shaders get their compilation info from a native WebGPU function.
    /// We need the source to be able to do UTF16 to UTF8 location remapping.
    Wgsl { source: String },
    /// Transformed shaders get their compilation info from the transformer.
    /// Further compilation errors are reported without a span.
    Transformed {
        compilation_info: crate::CompilationInfo,
    },
}

fn map_utf16_to_utf8_offset(utf16_offset: u32, text: &str) -> u32 {
    let mut utf16_i = 0;
    for (utf8_index, c) in text.char_indices() {
        if utf16_i >= utf16_offset {
            return utf8_index as u32;
        }
        utf16_i += c.len_utf16() as u32;
    }
    if utf16_i >= utf16_offset {
        text.len() as u32
    } else {
        log::error!("UTF16 offset {utf16_offset} is out of bounds for string {text}");
        u32::MAX
    }
}

impl crate::CompilationMessage {
    fn from_js(
        js_message: webgpu_sys::GpuCompilationMessage,
        compilation_info: &WebShaderCompilationInfo,
    ) -> Self {
        let message_type = match js_message.type_() {
            webgpu_sys::GpuCompilationMessageType::Error => crate::CompilationMessageType::Error,
            webgpu_sys::GpuCompilationMessageType::Warning => {
                crate::CompilationMessageType::Warning
            }
            webgpu_sys::GpuCompilationMessageType::Info => crate::CompilationMessageType::Info,
            _ => crate::CompilationMessageType::Error,
        };
        let utf16_offset = js_message.offset() as u32;
        let utf16_length = js_message.length() as u32;
        let span = match compilation_info {
            WebShaderCompilationInfo::Wgsl { .. } if utf16_offset == 0 && utf16_length == 0 => None,
            WebShaderCompilationInfo::Wgsl { source } => {
                let offset = map_utf16_to_utf8_offset(utf16_offset, source);
                let length = map_utf16_to_utf8_offset(utf16_length, &source[offset as usize..]);
                let line_number = js_message.line_num() as u32; // That's legal, because we're counting lines the same way

                let prefix = &source[..offset as usize];
                let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0) as u32;
                let line_position = offset - line_start + 1; // Counting UTF-8 byte indices

                Some(crate::SourceLocation {
                    offset,
                    length,
                    line_number,
                    line_position,
                })
            }
            WebShaderCompilationInfo::Transformed { .. } => None,
        };

        crate::CompilationMessage {
            message: js_message.message(),
            message_type,
            location: span,
        }
    }
}

// We need to assert that any future we return is Send to match the native API.
//
// This is safe on wasm32 *for now*, but similarly to the unsafe Send impls for the handle type
// wrappers, the full story for threading on wasm32 is still unfolding.

pub(crate) struct MakeSendFuture<F, M> {
    future: F,
    map: M,
}

impl<F: Future, M: Fn(F::Output) -> T, T> Future for MakeSendFuture<F, M> {
    type Output = T;

    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
        // This is safe because we have no Drop implementation to violate the Pin requirements and
        // do not provide any means of moving the inner future.
        unsafe {
            let this = self.get_unchecked_mut();
            match Pin::new_unchecked(&mut this.future).poll(cx) {
                task::Poll::Ready(value) => task::Poll::Ready((this.map)(value)),
                task::Poll::Pending => task::Poll::Pending,
            }
        }
    }
}

impl<F, M> MakeSendFuture<F, M> {
    fn new(future: F, map: M) -> Self {
        Self { future, map }
    }
}

#[cfg(send_sync)]
unsafe impl<F, M> Send for MakeSendFuture<F, M> {}

fn map_texture_format(texture_format: wgt::TextureFormat) -> webgpu_sys::GpuTextureFormat {
    use webgpu_sys::GpuTextureFormat as tf;
    use wgt::TextureFormat;
    match texture_format {
        // 8-bit formats
        TextureFormat::R8Unorm => tf::R8unorm,
        TextureFormat::R8Snorm => tf::R8snorm,
        TextureFormat::R8Uint => tf::R8uint,
        TextureFormat::R8Sint => tf::R8sint,
        // 16-bit formats
        TextureFormat::R16Uint => tf::R16uint,
        TextureFormat::R16Sint => tf::R16sint,
        TextureFormat::R16Float => tf::R16float,
        TextureFormat::Rg8Unorm => tf::Rg8unorm,
        TextureFormat::Rg8Snorm => tf::Rg8snorm,
        TextureFormat::Rg8Uint => tf::Rg8uint,
        TextureFormat::Rg8Sint => tf::Rg8sint,
        // 32-bit formats
        TextureFormat::R32Uint => tf::R32uint,
        TextureFormat::R32Sint => tf::R32sint,
        TextureFormat::R32Float => tf::R32float,
        TextureFormat::Rg16Uint => tf::Rg16uint,
        TextureFormat::Rg16Sint => tf::Rg16sint,
        TextureFormat::Rg16Float => tf::Rg16float,
        TextureFormat::Rgba8Unorm => tf::Rgba8unorm,
        TextureFormat::Rgba8UnormSrgb => tf::Rgba8unormSrgb,
        TextureFormat::Rgba8Snorm => tf::Rgba8snorm,
        TextureFormat::Rgba8Uint => tf::Rgba8uint,
        TextureFormat::Rgba8Sint => tf::Rgba8sint,
        TextureFormat::Bgra8Unorm => tf::Bgra8unorm,
        TextureFormat::Bgra8UnormSrgb => tf::Bgra8unormSrgb,
        // Packed 32-bit formats
        TextureFormat::Rgb9e5Ufloat => tf::Rgb9e5ufloat,
        TextureFormat::Rgb10a2Uint => {
            unimplemented!("Current version of web_sys is missing {texture_format:?}")
        }
        TextureFormat::Rgb10a2Unorm => tf::Rgb10a2unorm,
        TextureFormat::Rg11b10Ufloat => tf::Rg11b10ufloat,
        // 64-bit formats
        TextureFormat::Rg32Uint => tf::Rg32uint,
        TextureFormat::Rg32Sint => tf::Rg32sint,
        TextureFormat::Rg32Float => tf::Rg32float,
        TextureFormat::Rgba16Uint => tf::Rgba16uint,
        TextureFormat::Rgba16Sint => tf::Rgba16sint,
        TextureFormat::Rgba16Float => tf::Rgba16float,
        // 128-bit formats
        TextureFormat::Rgba32Uint => tf::Rgba32uint,
        TextureFormat::Rgba32Sint => tf::Rgba32sint,
        TextureFormat::Rgba32Float => tf::Rgba32float,
        // Depth/stencil formats
        TextureFormat::Stencil8 => tf::Stencil8,
        TextureFormat::Depth16Unorm => tf::Depth16unorm,
        TextureFormat::Depth24Plus => tf::Depth24plus,
        TextureFormat::Depth24PlusStencil8 => tf::Depth24plusStencil8,
        TextureFormat::Depth32Float => tf::Depth32float,
        // "depth32float-stencil8" feature
        TextureFormat::Depth32FloatStencil8 => tf::Depth32floatStencil8,

        TextureFormat::Bc1RgbaUnorm => tf::Bc1RgbaUnorm,
        TextureFormat::Bc1RgbaUnormSrgb => tf::Bc1RgbaUnormSrgb,
        TextureFormat::Bc2RgbaUnorm => tf::Bc2RgbaUnorm,
        TextureFormat::Bc2RgbaUnormSrgb => tf::Bc2RgbaUnormSrgb,
        TextureFormat::Bc3RgbaUnorm => tf::Bc3RgbaUnorm,
        TextureFormat::Bc3RgbaUnormSrgb => tf::Bc3RgbaUnormSrgb,
        TextureFormat::Bc4RUnorm => tf::Bc4RUnorm,
        TextureFormat::Bc4RSnorm => tf::Bc4RSnorm,
        TextureFormat::Bc5RgUnorm => tf::Bc5RgUnorm,
        TextureFormat::Bc5RgSnorm => tf::Bc5RgSnorm,
        TextureFormat::Bc6hRgbUfloat => tf::Bc6hRgbUfloat,
        TextureFormat::Bc6hRgbFloat => tf::Bc6hRgbFloat,
        TextureFormat::Bc7RgbaUnorm => tf::Bc7RgbaUnorm,
        TextureFormat::Bc7RgbaUnormSrgb => tf::Bc7RgbaUnormSrgb,
        TextureFormat::Etc2Rgb8Unorm => tf::Etc2Rgb8unorm,
        TextureFormat::Etc2Rgb8UnormSrgb => tf::Etc2Rgb8unormSrgb,
        TextureFormat::Etc2Rgb8A1Unorm => tf::Etc2Rgb8a1unorm,
        TextureFormat::Etc2Rgb8A1UnormSrgb => tf::Etc2Rgb8a1unormSrgb,
        TextureFormat::Etc2Rgba8Unorm => tf::Etc2Rgba8unorm,
        TextureFormat::Etc2Rgba8UnormSrgb => tf::Etc2Rgba8unormSrgb,
        TextureFormat::EacR11Unorm => tf::EacR11unorm,
        TextureFormat::EacR11Snorm => tf::EacR11snorm,
        TextureFormat::EacRg11Unorm => tf::EacRg11unorm,
        TextureFormat::EacRg11Snorm => tf::EacRg11snorm,
        TextureFormat::Astc { block, channel } => match channel {
            wgt::AstcChannel::Unorm => match block {
                wgt::AstcBlock::B4x4 => tf::Astc4x4Unorm,
                wgt::AstcBlock::B5x4 => tf::Astc5x4Unorm,
                wgt::AstcBlock::B5x5 => tf::Astc5x5Unorm,
                wgt::AstcBlock::B6x5 => tf::Astc6x5Unorm,
                wgt::AstcBlock::B6x6 => tf::Astc6x6Unorm,
                wgt::AstcBlock::B8x5 => tf::Astc8x5Unorm,
                wgt::AstcBlock::B8x6 => tf::Astc8x6Unorm,
                wgt::AstcBlock::B8x8 => tf::Astc8x8Unorm,
                wgt::AstcBlock::B10x5 => tf::Astc10x5Unorm,
                wgt::AstcBlock::B10x6 => tf::Astc10x6Unorm,
                wgt::AstcBlock::B10x8 => tf::Astc10x8Unorm,
                wgt::AstcBlock::B10x10 => tf::Astc10x10Unorm,
                wgt::AstcBlock::B12x10 => tf::Astc12x10Unorm,
                wgt::AstcBlock::B12x12 => tf::Astc12x12Unorm,
            },
            wgt::AstcChannel::UnormSrgb => match block {
                wgt::AstcBlock::B4x4 => tf::Astc4x4UnormSrgb,
                wgt::AstcBlock::B5x4 => tf::Astc5x4UnormSrgb,
                wgt::AstcBlock::B5x5 => tf::Astc5x5UnormSrgb,
                wgt::AstcBlock::B6x5 => tf::Astc6x5UnormSrgb,
                wgt::AstcBlock::B6x6 => tf::Astc6x6UnormSrgb,
                wgt::AstcBlock::B8x5 => tf::Astc8x5UnormSrgb,
                wgt::AstcBlock::B8x6 => tf::Astc8x6UnormSrgb,
                wgt::AstcBlock::B8x8 => tf::Astc8x8UnormSrgb,
                wgt::AstcBlock::B10x5 => tf::Astc10x5UnormSrgb,
                wgt::AstcBlock::B10x6 => tf::Astc10x6UnormSrgb,
                wgt::AstcBlock::B10x8 => tf::Astc10x8UnormSrgb,
                wgt::AstcBlock::B10x10 => tf::Astc10x10UnormSrgb,
                wgt::AstcBlock::B12x10 => tf::Astc12x10UnormSrgb,
                wgt::AstcBlock::B12x12 => tf::Astc12x12UnormSrgb,
            },
            wgt::AstcChannel::Hdr => {
                unimplemented!("Format {texture_format:?} has no WebGPU equivalent")
            }
        },
        _ => unimplemented!("Format {texture_format:?} has no WebGPU equivalent"),
    }
}

fn map_texture_component_type(
    sample_type: wgt::TextureSampleType,
) -> webgpu_sys::GpuTextureSampleType {
    use webgpu_sys::GpuTextureSampleType as ts;
    use wgt::TextureSampleType;
    match sample_type {
        TextureSampleType::Float { filterable: true } => ts::Float,
        TextureSampleType::Float { filterable: false } => ts::UnfilterableFloat,
        TextureSampleType::Sint => ts::Sint,
        TextureSampleType::Uint => ts::Uint,
        TextureSampleType::Depth => ts::Depth,
    }
}

fn map_cull_mode(cull_mode: Option<wgt::Face>) -> webgpu_sys::GpuCullMode {
    use webgpu_sys::GpuCullMode as cm;
    use wgt::Face;
    match cull_mode {
        None => cm::None,
        Some(Face::Front) => cm::Front,
        Some(Face::Back) => cm::Back,
    }
}

fn map_front_face(front_face: wgt::FrontFace) -> webgpu_sys::GpuFrontFace {
    use webgpu_sys::GpuFrontFace as ff;
    use wgt::FrontFace;
    match front_face {
        FrontFace::Ccw => ff::Ccw,
        FrontFace::Cw => ff::Cw,
    }
}

fn map_primitive_state(primitive: &wgt::PrimitiveState) -> webgpu_sys::GpuPrimitiveState {
    use webgpu_sys::GpuPrimitiveTopology as pt;
    use wgt::PrimitiveTopology;

    let mapped = webgpu_sys::GpuPrimitiveState::new();
    mapped.set_cull_mode(map_cull_mode(primitive.cull_mode));
    mapped.set_front_face(map_front_face(primitive.front_face));

    if let Some(format) = primitive.strip_index_format {
        mapped.set_strip_index_format(map_index_format(format));
    }

    mapped.set_topology(match primitive.topology {
        PrimitiveTopology::PointList => pt::PointList,
        PrimitiveTopology::LineList => pt::LineList,
        PrimitiveTopology::LineStrip => pt::LineStrip,
        PrimitiveTopology::TriangleList => pt::TriangleList,
        PrimitiveTopology::TriangleStrip => pt::TriangleStrip,
    });

    mapped.set_unclipped_depth(primitive.unclipped_depth);

    match primitive.polygon_mode {
        wgt::PolygonMode::Fill => {}
        wgt::PolygonMode::Line => panic!(
            "{:?} is not enabled for this backend",
            wgt::Features::POLYGON_MODE_LINE
        ),
        wgt::PolygonMode::Point => panic!(
            "{:?} is not enabled for this backend",
            wgt::Features::POLYGON_MODE_POINT
        ),
    }

    mapped
}

fn map_compare_function(compare_fn: wgt::CompareFunction) -> webgpu_sys::GpuCompareFunction {
    use webgpu_sys::GpuCompareFunction as cf;
    use wgt::CompareFunction;
    match compare_fn {
        CompareFunction::Never => cf::Never,
        CompareFunction::Less => cf::Less,
        CompareFunction::Equal => cf::Equal,
        CompareFunction::LessEqual => cf::LessEqual,
        CompareFunction::Greater => cf::Greater,
        CompareFunction::NotEqual => cf::NotEqual,
        CompareFunction::GreaterEqual => cf::GreaterEqual,
        CompareFunction::Always => cf::Always,
    }
}

fn map_stencil_operation(op: wgt::StencilOperation) -> webgpu_sys::GpuStencilOperation {
    use webgpu_sys::GpuStencilOperation as so;
    use wgt::StencilOperation;
    match op {
        StencilOperation::Keep => so::Keep,
        StencilOperation::Zero => so::Zero,
        StencilOperation::Replace => so::Replace,
        StencilOperation::Invert => so::Invert,
        StencilOperation::IncrementClamp => so::IncrementClamp,
        StencilOperation::DecrementClamp => so::DecrementClamp,
        StencilOperation::IncrementWrap => so::IncrementWrap,
        StencilOperation::DecrementWrap => so::DecrementWrap,
    }
}

fn map_stencil_state_face(desc: &wgt::StencilFaceState) -> webgpu_sys::GpuStencilFaceState {
    let mapped = webgpu_sys::GpuStencilFaceState::new();
    mapped.set_compare(map_compare_function(desc.compare));
    mapped.set_depth_fail_op(map_stencil_operation(desc.depth_fail_op));
    mapped.set_fail_op(map_stencil_operation(desc.fail_op));
    mapped.set_pass_op(map_stencil_operation(desc.pass_op));
    mapped
}

fn map_depth_stencil_state(desc: &wgt::DepthStencilState) -> webgpu_sys::GpuDepthStencilState {
    let mapped = webgpu_sys::GpuDepthStencilState::new(map_texture_format(desc.format));
    mapped.set_depth_compare(map_compare_function(desc.depth_compare));
    mapped.set_depth_write_enabled(desc.depth_write_enabled);
    mapped.set_depth_bias(desc.bias.constant);
    mapped.set_depth_bias_clamp(desc.bias.clamp);
    mapped.set_depth_bias_slope_scale(desc.bias.slope_scale);
    mapped.set_stencil_back(&map_stencil_state_face(&desc.stencil.back));
    mapped.set_stencil_front(&map_stencil_state_face(&desc.stencil.front));
    mapped.set_stencil_read_mask(desc.stencil.read_mask);
    mapped.set_stencil_write_mask(desc.stencil.write_mask);
    mapped
}

fn map_blend_component(desc: &wgt::BlendComponent) -> webgpu_sys::GpuBlendComponent {
    let mapped = webgpu_sys::GpuBlendComponent::new();
    mapped.set_dst_factor(map_blend_factor(desc.dst_factor));
    mapped.set_operation(map_blend_operation(desc.operation));
    mapped.set_src_factor(map_blend_factor(desc.src_factor));
    mapped
}

fn map_blend_factor(factor: wgt::BlendFactor) -> webgpu_sys::GpuBlendFactor {
    use webgpu_sys::GpuBlendFactor as bf;
    use wgt::BlendFactor;
    match factor {
        BlendFactor::Zero => bf::Zero,
        BlendFactor::One => bf::One,
        BlendFactor::Src => bf::Src,
        BlendFactor::OneMinusSrc => bf::OneMinusSrc,
        BlendFactor::SrcAlpha => bf::SrcAlpha,
        BlendFactor::OneMinusSrcAlpha => bf::OneMinusSrcAlpha,
        BlendFactor::Dst => bf::Dst,
        BlendFactor::OneMinusDst => bf::OneMinusDst,
        BlendFactor::DstAlpha => bf::DstAlpha,
        BlendFactor::OneMinusDstAlpha => bf::OneMinusDstAlpha,
        BlendFactor::SrcAlphaSaturated => bf::SrcAlphaSaturated,
        BlendFactor::Constant => bf::Constant,
        BlendFactor::OneMinusConstant => bf::OneMinusConstant,
        BlendFactor::Src1 => bf::Src1,
        BlendFactor::OneMinusSrc1 => bf::OneMinusSrc1,
        BlendFactor::Src1Alpha => bf::Src1Alpha,
        BlendFactor::OneMinusSrc1Alpha => bf::OneMinusSrc1Alpha,
    }
}

fn map_blend_operation(op: wgt::BlendOperation) -> webgpu_sys::GpuBlendOperation {
    use webgpu_sys::GpuBlendOperation as bo;
    use wgt::BlendOperation;
    match op {
        BlendOperation::Add => bo::Add,
        BlendOperation::Subtract => bo::Subtract,
        BlendOperation::ReverseSubtract => bo::ReverseSubtract,
        BlendOperation::Min => bo::Min,
        BlendOperation::Max => bo::Max,
    }
}

fn map_index_format(format: wgt::IndexFormat) -> webgpu_sys::GpuIndexFormat {
    use webgpu_sys::GpuIndexFormat as f;
    use wgt::IndexFormat;
    match format {
        IndexFormat::Uint16 => f::Uint16,
        IndexFormat::Uint32 => f::Uint32,
    }
}

fn map_vertex_format(format: wgt::VertexFormat) -> webgpu_sys::GpuVertexFormat {
    use webgpu_sys::GpuVertexFormat as vf;
    use wgt::VertexFormat;
    match format {
        VertexFormat::Uint8 => vf::Uint8,
        VertexFormat::Uint8x2 => vf::Uint8x2,
        VertexFormat::Uint8x4 => vf::Uint8x4,
        VertexFormat::Sint8 => vf::Sint8,
        VertexFormat::Sint8x2 => vf::Sint8x2,
        VertexFormat::Sint8x4 => vf::Sint8x4,
        VertexFormat::Unorm8 => vf::Unorm8,
        VertexFormat::Unorm8x2 => vf::Unorm8x2,
        VertexFormat::Unorm8x4 => vf::Unorm8x4,
        VertexFormat::Snorm8 => vf::Snorm8,
        VertexFormat::Snorm8x2 => vf::Snorm8x2,
        VertexFormat::Snorm8x4 => vf::Snorm8x4,
        VertexFormat::Uint16 => vf::Uint16,
        VertexFormat::Uint16x2 => vf::Uint16x2,
        VertexFormat::Uint16x4 => vf::Uint16x4,
        VertexFormat::Sint16 => vf::Sint16,
        VertexFormat::Sint16x2 => vf::Sint16x2,
        VertexFormat::Sint16x4 => vf::Sint16x4,
        VertexFormat::Unorm16 => vf::Unorm16,
        VertexFormat::Unorm16x2 => vf::Unorm16x2,
        VertexFormat::Unorm16x4 => vf::Unorm16x4,
        VertexFormat::Snorm16 => vf::Snorm16,
        VertexFormat::Snorm16x2 => vf::Snorm16x2,
        VertexFormat::Snorm16x4 => vf::Snorm16x4,
        VertexFormat::Float16 => vf::Float16,
        VertexFormat::Float16x2 => vf::Float16x2,
        VertexFormat::Float16x4 => vf::Float16x4,
        VertexFormat::Float32 => vf::Float32,
        VertexFormat::Float32x2 => vf::Float32x2,
        VertexFormat::Float32x3 => vf::Float32x3,
        VertexFormat::Float32x4 => vf::Float32x4,
        VertexFormat::Uint32 => vf::Uint32,
        VertexFormat::Uint32x2 => vf::Uint32x2,
        VertexFormat::Uint32x3 => vf::Uint32x3,
        VertexFormat::Uint32x4 => vf::Uint32x4,
        VertexFormat::Sint32 => vf::Sint32,
        VertexFormat::Sint32x2 => vf::Sint32x2,
        VertexFormat::Sint32x3 => vf::Sint32x3,
        VertexFormat::Sint32x4 => vf::Sint32x4,
        VertexFormat::Unorm10_10_10_2 => vf::Unorm1010102,
        VertexFormat::Unorm8x4Bgra => vf::Unorm8x4Bgra,
        VertexFormat::Float64
        | VertexFormat::Float64x2
        | VertexFormat::Float64x3
        | VertexFormat::Float64x4 => {
            panic!("VERTEX_ATTRIBUTE_64BIT feature must be enabled to use Double formats")
        }
    }
}

fn map_vertex_step_mode(mode: wgt::VertexStepMode) -> webgpu_sys::GpuVertexStepMode {
    use webgpu_sys::GpuVertexStepMode as sm;
    use wgt::VertexStepMode;
    match mode {
        VertexStepMode::Vertex => sm::Vertex,
        VertexStepMode::Instance => sm::Instance,
    }
}

fn map_extent_3d(extent: wgt::Extent3d) -> webgpu_sys::GpuExtent3dDict {
    let mapped = webgpu_sys::GpuExtent3dDict::new(extent.width);
    mapped.set_height(extent.height);
    mapped.set_depth_or_array_layers(extent.depth_or_array_layers);
    mapped
}

fn map_origin_2d(extent: wgt::Origin2d) -> webgpu_sys::GpuOrigin2dDict {
    let mapped = webgpu_sys::GpuOrigin2dDict::new();
    mapped.set_x(extent.x);
    mapped.set_y(extent.y);
    mapped
}

fn map_origin_3d(origin: wgt::Origin3d) -> webgpu_sys::GpuOrigin3dDict {
    let mapped = webgpu_sys::GpuOrigin3dDict::new();
    mapped.set_x(origin.x);
    mapped.set_y(origin.y);
    mapped.set_z(origin.z);
    mapped
}

fn map_texture_dimension(
    texture_dimension: wgt::TextureDimension,
) -> webgpu_sys::GpuTextureDimension {
    match texture_dimension {
        wgt::TextureDimension::D1 => webgpu_sys::GpuTextureDimension::N1d,
        wgt::TextureDimension::D2 => webgpu_sys::GpuTextureDimension::N2d,
        wgt::TextureDimension::D3 => webgpu_sys::GpuTextureDimension::N3d,
    }
}

fn map_texture_view_dimension(
    texture_view_dimension: wgt::TextureViewDimension,
) -> webgpu_sys::GpuTextureViewDimension {
    use webgpu_sys::GpuTextureViewDimension as tvd;
    match texture_view_dimension {
        wgt::TextureViewDimension::D1 => tvd::N1d,
        wgt::TextureViewDimension::D2 => tvd::N2d,
        wgt::TextureViewDimension::D2Array => tvd::N2dArray,
        wgt::TextureViewDimension::Cube => tvd::Cube,
        wgt::TextureViewDimension::CubeArray => tvd::CubeArray,
        wgt::TextureViewDimension::D3 => tvd::N3d,
    }
}

fn map_buffer_copy_view(
    view: crate::TexelCopyBufferInfo<'_>,
) -> webgpu_sys::GpuTexelCopyBufferInfo {
    let buffer = view.buffer.inner.as_webgpu();
    let mapped = webgpu_sys::GpuTexelCopyBufferInfo::new(&buffer.inner);
    if let Some(bytes_per_row) = view.layout.bytes_per_row {
        mapped.set_bytes_per_row(bytes_per_row);
    }
    if let Some(rows_per_image) = view.layout.rows_per_image {
        mapped.set_rows_per_image(rows_per_image);
    }
    mapped.set_offset(view.layout.offset as f64);
    mapped
}

fn map_texture_copy_view(
    view: crate::TexelCopyTextureInfo<'_>,
) -> webgpu_sys::GpuTexelCopyTextureInfo {
    let texture = view.texture.inner.as_webgpu();
    let mapped = webgpu_sys::GpuTexelCopyTextureInfo::new(&texture.inner);
    mapped.set_mip_level(view.mip_level);
    mapped.set_origin(&map_origin_3d(view.origin));
    mapped
}

fn map_tagged_texture_copy_view(
    view: crate::CopyExternalImageDestInfo<&crate::api::Texture>,
) -> webgpu_sys::GpuCopyExternalImageDestInfo {
    let texture = view.texture.inner.as_webgpu();
    let mapped = webgpu_sys::GpuCopyExternalImageDestInfo::new(&texture.inner);
    mapped.set_mip_level(view.mip_level);
    mapped.set_origin(&map_origin_3d(view.origin));
    mapped.set_aspect(map_texture_aspect(view.aspect));
    // mapped.set_color_space(map_color_space(view.color_space));
    mapped.set_premultiplied_alpha(view.premultiplied_alpha);
    mapped
}

fn map_external_texture_copy_view(
    view: &crate::CopyExternalImageSourceInfo,
) -> webgpu_sys::GpuCopyExternalImageSourceInfo {
    let mapped = webgpu_sys::GpuCopyExternalImageSourceInfo::new(&view.source);
    mapped.set_origin(&map_origin_2d(view.origin));
    mapped.set_flip_y(view.flip_y);
    mapped
}

fn map_texture_aspect(aspect: wgt::TextureAspect) -> webgpu_sys::GpuTextureAspect {
    match aspect {
        wgt::TextureAspect::All => webgpu_sys::GpuTextureAspect::All,
        wgt::TextureAspect::StencilOnly => webgpu_sys::GpuTextureAspect::StencilOnly,
        wgt::TextureAspect::DepthOnly => webgpu_sys::GpuTextureAspect::DepthOnly,
        wgt::TextureAspect::Plane0 | wgt::TextureAspect::Plane1 | wgt::TextureAspect::Plane2 => {
            panic!("multi-plane textures are not supported")
        }
    }
}

fn map_filter_mode(mode: wgt::FilterMode) -> webgpu_sys::GpuFilterMode {
    match mode {
        wgt::FilterMode::Nearest => webgpu_sys::GpuFilterMode::Nearest,
        wgt::FilterMode::Linear => webgpu_sys::GpuFilterMode::Linear,
    }
}

fn map_mipmap_filter_mode(mode: wgt::FilterMode) -> webgpu_sys::GpuMipmapFilterMode {
    match mode {
        wgt::FilterMode::Nearest => webgpu_sys::GpuMipmapFilterMode::Nearest,
        wgt::FilterMode::Linear => webgpu_sys::GpuMipmapFilterMode::Linear,
    }
}

fn map_address_mode(mode: wgt::AddressMode) -> webgpu_sys::GpuAddressMode {
    match mode {
        wgt::AddressMode::ClampToEdge => webgpu_sys::GpuAddressMode::ClampToEdge,
        wgt::AddressMode::Repeat => webgpu_sys::GpuAddressMode::Repeat,
        wgt::AddressMode::MirrorRepeat => webgpu_sys::GpuAddressMode::MirrorRepeat,
        wgt::AddressMode::ClampToBorder => panic!("Clamp to border is not supported"),
    }
}

fn map_color(color: wgt::Color) -> webgpu_sys::GpuColorDict {
    webgpu_sys::GpuColorDict::new(color.a, color.b, color.g, color.r)
}

fn map_store_op(store: crate::StoreOp) -> webgpu_sys::GpuStoreOp {
    match store {
        crate::StoreOp::Store => webgpu_sys::GpuStoreOp::Store,
        crate::StoreOp::Discard => webgpu_sys::GpuStoreOp::Discard,
    }
}

fn map_map_mode(mode: crate::MapMode) -> u32 {
    match mode {
        crate::MapMode::Read => webgpu_sys::gpu_map_mode::READ,
        crate::MapMode::Write => webgpu_sys::gpu_map_mode::WRITE,
    }
}

const FEATURES_MAPPING: [(wgt::Features, webgpu_sys::GpuFeatureName); 15] = [
    (
        wgt::Features::DEPTH_CLIP_CONTROL,
        webgpu_sys::GpuFeatureName::DepthClipControl,
    ),
    (
        wgt::Features::DEPTH32FLOAT_STENCIL8,
        webgpu_sys::GpuFeatureName::Depth32floatStencil8,
    ),
    (
        wgt::Features::TEXTURE_COMPRESSION_BC,
        webgpu_sys::GpuFeatureName::TextureCompressionBc,
    ),
    (
        wgt::Features::TEXTURE_COMPRESSION_BC_SLICED_3D,
        webgpu_sys::GpuFeatureName::TextureCompressionBcSliced3d,
    ),
    (
        wgt::Features::TEXTURE_COMPRESSION_ETC2,
        webgpu_sys::GpuFeatureName::TextureCompressionEtc2,
    ),
    (
        wgt::Features::TEXTURE_COMPRESSION_ASTC,
        webgpu_sys::GpuFeatureName::TextureCompressionAstc,
    ),
    (
        wgt::Features::TEXTURE_COMPRESSION_ASTC_SLICED_3D,
        webgpu_sys::GpuFeatureName::TextureCompressionAstcSliced3d,
    ),
    (
        wgt::Features::TIMESTAMP_QUERY,
        webgpu_sys::GpuFeatureName::TimestampQuery,
    ),
    (
        wgt::Features::INDIRECT_FIRST_INSTANCE,
        webgpu_sys::GpuFeatureName::IndirectFirstInstance,
    ),
    (
        wgt::Features::SHADER_F16,
        webgpu_sys::GpuFeatureName::ShaderF16,
    ),
    (
        wgt::Features::RG11B10UFLOAT_RENDERABLE,
        webgpu_sys::GpuFeatureName::Rg11b10ufloatRenderable,
    ),
    (
        wgt::Features::BGRA8UNORM_STORAGE,
        webgpu_sys::GpuFeatureName::Bgra8unormStorage,
    ),
    (
        wgt::Features::FLOAT32_FILTERABLE,
        webgpu_sys::GpuFeatureName::Float32Filterable,
    ),
    (
        wgt::Features::DUAL_SOURCE_BLENDING,
        webgpu_sys::GpuFeatureName::DualSourceBlending,
    ),
    (
        wgt::Features::CLIP_DISTANCES,
        webgpu_sys::GpuFeatureName::ClipDistances,
    ),
];

fn map_wgt_features(supported_features: webgpu_sys::GpuSupportedFeatures) -> wgt::Features {
    // We emulate MDI.
    let mut features = wgt::Features::MULTI_DRAW_INDIRECT;
    for (wgpu_feat, web_feat) in FEATURES_MAPPING {
        match wasm_bindgen::JsValue::from(web_feat).as_string() {
            Some(value) if supported_features.has(&value) => features |= wgpu_feat,
            _ => {}
        }
    }
    features
}

fn map_wgt_limits(limits: webgpu_sys::GpuSupportedLimits) -> wgt::Limits {
    wgt::Limits {
        max_texture_dimension_1d: limits.max_texture_dimension_1d(),
        max_texture_dimension_2d: limits.max_texture_dimension_2d(),
        max_texture_dimension_3d: limits.max_texture_dimension_3d(),
        max_texture_array_layers: limits.max_texture_array_layers(),
        max_bind_groups: limits.max_bind_groups(),
        max_bindings_per_bind_group: limits.max_bindings_per_bind_group(),
        max_dynamic_uniform_buffers_per_pipeline_layout: limits
            .max_dynamic_uniform_buffers_per_pipeline_layout(),
        max_dynamic_storage_buffers_per_pipeline_layout: limits
            .max_dynamic_storage_buffers_per_pipeline_layout(),
        max_sampled_textures_per_shader_stage: limits.max_sampled_textures_per_shader_stage(),
        max_samplers_per_shader_stage: limits.max_samplers_per_shader_stage(),
        max_storage_buffers_per_shader_stage: limits.max_storage_buffers_per_shader_stage(),
        max_storage_textures_per_shader_stage: limits.max_storage_textures_per_shader_stage(),
        max_uniform_buffers_per_shader_stage: limits.max_uniform_buffers_per_shader_stage(),
        max_binding_array_elements_per_shader_stage: 0,
        max_binding_array_sampler_elements_per_shader_stage: 0,
        max_uniform_buffer_binding_size: limits.max_uniform_buffer_binding_size() as u32,
        max_storage_buffer_binding_size: limits.max_storage_buffer_binding_size() as u32,
        max_vertex_buffers: limits.max_vertex_buffers(),
        max_buffer_size: limits.max_buffer_size() as u64,
        max_vertex_attributes: limits.max_vertex_attributes(),
        max_vertex_buffer_array_stride: limits.max_vertex_buffer_array_stride(),
        min_uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment(),
        min_storage_buffer_offset_alignment: limits.min_storage_buffer_offset_alignment(),
        max_color_attachments: limits.max_color_attachments(),
        max_color_attachment_bytes_per_sample: limits.max_color_attachment_bytes_per_sample(),
        max_compute_workgroup_storage_size: limits.max_compute_workgroup_storage_size(),
        max_compute_invocations_per_workgroup: limits.max_compute_invocations_per_workgroup(),
        max_compute_workgroup_size_x: limits.max_compute_workgroup_size_x(),
        max_compute_workgroup_size_y: limits.max_compute_workgroup_size_y(),
        max_compute_workgroup_size_z: limits.max_compute_workgroup_size_z(),
        max_compute_workgroups_per_dimension: limits.max_compute_workgroups_per_dimension(),
        // The following are not part of WebGPU
        min_subgroup_size: wgt::Limits::default().min_subgroup_size,
        max_subgroup_size: wgt::Limits::default().max_subgroup_size,
        max_push_constant_size: wgt::Limits::default().max_push_constant_size,
        max_non_sampler_bindings: wgt::Limits::default().max_non_sampler_bindings,
        max_inter_stage_shader_components: wgt::Limits::default().max_inter_stage_shader_components,

        max_task_workgroup_total_count: wgt::Limits::default().max_task_workgroup_total_count,
        max_task_workgroups_per_dimension: wgt::Limits::default().max_task_workgroups_per_dimension,
        max_mesh_output_layers: wgt::Limits::default().max_mesh_output_layers,
        max_mesh_multiview_count: wgt::Limits::default().max_mesh_multiview_count,

        max_blas_primitive_count: wgt::Limits::default().max_blas_primitive_count,
        max_blas_geometry_count: wgt::Limits::default().max_blas_geometry_count,
        max_tlas_instance_count: wgt::Limits::default().max_tlas_instance_count,
        max_acceleration_structures_per_shader_stage: wgt::Limits::default()
            .max_acceleration_structures_per_shader_stage,
    }
}

fn map_js_sys_limits(limits: &wgt::Limits) -> js_sys::Object {
    let object = js_sys::Object::new();

    macro_rules! set_properties {
        (($from:expr) => ($on:expr) : $(($js_ident:ident, $rs_ident:ident)),* $(,)?) => {
            $(
                ::js_sys::Reflect::set(
                    &$on,
                    &::wasm_bindgen::JsValue::from(stringify!($js_ident)),
                    // Numbers may be u64, however using `from` on a u64 yields
                    // errors on the wasm side, since it uses an unsupported api.
                    // Wasm sends us things that need to fit into u64s by sending
                    // us f64s instead. So we just send them f64s back.
                    &::wasm_bindgen::JsValue::from($from.$rs_ident as f64)
                )
                    .expect("Setting Object properties should never fail.");
            )*
        }
    }

    set_properties![
        (limits) => (object):
        (maxTextureDimension1D, max_texture_dimension_1d),
        (maxTextureDimension2D, max_texture_dimension_2d),
        (maxTextureDimension3D, max_texture_dimension_3d),
        (maxTextureArrayLayers, max_texture_array_layers),
        (maxBindGroups, max_bind_groups),
        (maxBindingsPerBindGroup, max_bindings_per_bind_group),
        (maxDynamicUniformBuffersPerPipelineLayout, max_dynamic_uniform_buffers_per_pipeline_layout),
        (maxDynamicStorageBuffersPerPipelineLayout, max_dynamic_storage_buffers_per_pipeline_layout),
        (maxSampledTexturesPerShaderStage, max_sampled_textures_per_shader_stage),
        (maxSamplersPerShaderStage, max_samplers_per_shader_stage),
        (maxStorageBuffersPerShaderStage, max_storage_buffers_per_shader_stage),
        (maxStorageTexturesPerShaderStage, max_storage_textures_per_shader_stage),
        (maxUniformBuffersPerShaderStage, max_uniform_buffers_per_shader_stage),
        (maxUniformBufferBindingSize, max_uniform_buffer_binding_size),
        (maxStorageBufferBindingSize, max_storage_buffer_binding_size),
        (minUniformBufferOffsetAlignment, min_uniform_buffer_offset_alignment),
        (minStorageBufferOffsetAlignment, min_storage_buffer_offset_alignment),
        (maxVertexBuffers, max_vertex_buffers),
        (maxBufferSize, max_buffer_size),
        (maxVertexAttributes, max_vertex_attributes),
        (maxVertexBufferArrayStride, max_vertex_buffer_array_stride),
        (maxComputeWorkgroupStorageSize, max_compute_workgroup_storage_size),
        (maxComputeInvocationsPerWorkgroup, max_compute_invocations_per_workgroup),
        (maxComputeWorkgroupSizeX, max_compute_workgroup_size_x),
        (maxComputeWorkgroupSizeY, max_compute_workgroup_size_y),
        (maxComputeWorkgroupSizeZ, max_compute_workgroup_size_z),
        (maxComputeWorkgroupsPerDimension, max_compute_workgroups_per_dimension),
    ];

    object
}

type JsFutureResult = Result<wasm_bindgen::JsValue, wasm_bindgen::JsValue>;

fn future_request_adapter(
    result: JsFutureResult,
    requested_backends: Backends,
) -> Result<dispatch::DispatchAdapter, wgt::RequestAdapterError> {
    let web_adapter: Option<webgpu_sys::GpuAdapter> =
        result.and_then(wasm_bindgen::JsCast::dyn_into).ok();
    web_adapter
        .map(|adapter| {
            WebAdapter {
                inner: adapter,
                ident: crate::cmp::Identifier::create(),
            }
            .into()
        })
        .ok_or_else(|| request_adapter_null_error(requested_backends))
}

// Translate WebGPU’s null return into our error.
fn request_adapter_null_error(requested_backends: Backends) -> wgt::RequestAdapterError {
    wgt::RequestAdapterError::NotFound {
        active_backends: Backends::BROWSER_WEBGPU,
        requested_backends,
        // TODO: supported_backends should also include wgpu-core-based backends,
        // if they were compiled in.
        supported_backends: Backends::BROWSER_WEBGPU,
        no_fallback_backends: Backends::empty(),
        no_adapter_backends: Backends::BROWSER_WEBGPU,
        incompatible_surface_backends: Backends::empty(),
    }
}

fn future_request_device(
    result: JsFutureResult,
) -> Result<(dispatch::DispatchDevice, dispatch::DispatchQueue), crate::RequestDeviceError> {
    result
        .map(|js_value| {
            let device = webgpu_sys::GpuDevice::from(js_value);
            let queue = device.queue();

            (
                WebDevice {
                    inner: device,
                    ident: crate::cmp::Identifier::create(),
                }
                .into(),
                WebQueue {
                    inner: queue,
                    ident: crate::cmp::Identifier::create(),
                }
                .into(),
            )
        })
        .map_err(|error_value| crate::RequestDeviceError {
            // wasm-bindgen provides a reasonable error stringification via `Debug` impl
            inner: crate::RequestDeviceErrorKind::WebGpu(format!("{error_value:?}")),
        })
}

fn future_pop_error_scope(result: JsFutureResult) -> Option<crate::Error> {
    match result {
        Ok(js_value) if js_value.is_object() => {
            let js_error = wasm_bindgen::JsCast::dyn_into(js_value).unwrap();
            Some(crate::Error::from_js(js_error))
        }
        _ => None,
    }
}

fn future_compilation_info(
    result: JsFutureResult,
    base_compilation_info: &WebShaderCompilationInfo,
) -> crate::CompilationInfo {
    let base_messages = match base_compilation_info {
        WebShaderCompilationInfo::Transformed { compilation_info } => {
            compilation_info.messages.iter().cloned()
        }
        _ => [].iter().cloned(),
    };

    let messages = match result {
        Ok(js_value) => {
            let info = webgpu_sys::GpuCompilationInfo::from(js_value);
            base_messages
                .chain(info.messages().into_iter().map(|message| {
                    crate::CompilationMessage::from_js(
                        webgpu_sys::GpuCompilationMessage::from(message),
                        base_compilation_info,
                    )
                }))
                .collect()
        }
        Err(_v) => base_messages
            .chain(core::iter::once(crate::CompilationMessage {
                message: "Getting compilation info failed".to_string(),
                message_type: crate::CompilationMessageType::Error,
                location: None,
            }))
            .collect(),
    };

    crate::CompilationInfo { messages }
}

/// Calls `callback(success_value)` when the promise completes successfully, calls `callback(failure_value)`
/// when the promise completes unsuccessfully.
fn register_then_closures<F, T>(promise: &Promise, callback: F, success_value: T, failure_value: T)
where
    F: FnOnce(T) + 'static,
    T: 'static,
{
    // Both the 'success' and 'rejected' closures need access to callback, but only one
    // of them will ever run. We have them both hold a reference to a `Rc<RefCell<Option<impl FnOnce...>>>`,
    // and then take ownership of callback when invoked.
    //
    // We also only need Rc's because these will only ever be called on our thread.
    //
    // We also store the actual closure types inside this Rc, as the closures need to be kept alive
    // until they are actually called by the callback. It is valid to drop a closure inside of a callback.
    // This allows us to keep the closures alive without leaking them.
    let rc_callback: Rc<RefCell<Option<(_, _, F)>>> = Rc::new(RefCell::new(None));

    let rc_callback_clone1 = rc_callback.clone();
    let rc_callback_clone2 = rc_callback.clone();
    let closure_success = wasm_bindgen::closure::Closure::once(move |_| {
        let (success_closure, rejection_closure, callback) =
            rc_callback_clone1.borrow_mut().take().unwrap();
        callback(success_value);
        // drop the closures, including ourselves, which will free any captured memory.
        drop((success_closure, rejection_closure));
    });
    let closure_rejected = wasm_bindgen::closure::Closure::once(move |_| {
        let (success_closure, rejection_closure, callback) =
            rc_callback_clone2.borrow_mut().take().unwrap();
        callback(failure_value);
        // drop the closures, including ourselves, which will free any captured memory.
        drop((success_closure, rejection_closure));
    });

    // Calling then before setting the value in the Rc seems like a race, but it isn't
    // because the promise callback will run on this thread, so there is no race.
    let _ = promise.then2(&closure_success, &closure_rejected);

    *rc_callback.borrow_mut() = Some((closure_success, closure_rejected, callback));
}

impl ContextWebGpu {
    /// Common portion of the internal branches of the public `instance_create_surface` function.
    ///
    /// Note: Analogous code also exists in the WebGL2 backend at
    /// `wgpu_hal::gles::web::Instance`.
    fn create_surface_from_context(
        &self,
        canvas: Canvas,
        context_result: Result<Option<js_sys::Object>, wasm_bindgen::JsValue>,
    ) -> Result<dispatch::DispatchSurface, crate::CreateSurfaceError> {
        let context: js_sys::Object = match context_result {
            Ok(Some(context)) => context,
            Ok(None) => {
                // <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev>
                // A getContext() call “returns null if contextId is not supported, or if the
                // canvas has already been initialized with another context type”. Additionally,
                // “not supported” could include “insufficient GPU resources” or “the GPU process
                // previously crashed”. So, we must return it as an `Err` since it could occur
                // for circumstances outside the application author's control.
                return Err(crate::CreateSurfaceError {
                    inner: crate::CreateSurfaceErrorKind::Web(
                        String::from(
                            "canvas.getContext() returned null; webgpu not available or canvas already in use"
                        )
                    )
                });
            }
            Err(js_error) => {
                // <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext>
                // A thrown exception indicates misuse of the canvas state.
                return Err(crate::CreateSurfaceError {
                    inner: crate::CreateSurfaceErrorKind::Web(format!(
                        "canvas.getContext() threw exception {js_error:?}",
                    )),
                });
            }
        };

        // Not returning this error because it is a type error that shouldn't happen unless
        // the browser, JS builtin objects, or wasm bindings are misbehaving somehow.
        let context: webgpu_sys::GpuCanvasContext = context
            .dyn_into()
            .expect("canvas context is not a GPUCanvasContext");

        Ok(WebSurface {
            gpu: self.gpu.clone(),
            context,
            canvas,
            ident: crate::cmp::Identifier::create(),
        }
        .into())
    }
}

// Represents the global object in the JavaScript context.
// 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.
// When called from the UI thread only `window` is defined whereas `worker` is only defined within a web worker context.
// See: https://github.com/rustwasm/gloo/blob/2c9e776701ecb90c53e62dec1abd19c2b70e47c7/crates/timers/src/callback.rs#L8-L40
#[wasm_bindgen]
extern "C" {
    type Global;

    #[wasm_bindgen(method, getter, js_name = Window)]
    fn window(this: &Global) -> JsValue;

    #[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
    fn worker(this: &Global) -> JsValue;
}

#[derive(Debug)]
pub enum Canvas {
    Canvas(web_sys::HtmlCanvasElement),
    Offscreen(web_sys::OffscreenCanvas),
}

#[derive(Debug, Clone, Copy)]
pub struct BrowserGpuPropertyInaccessible;

/// Returns the browser's gpu object or `Err(BrowserGpuPropertyInaccessible)` if
/// the current context is neither the main thread nor a dedicated worker.
///
/// If WebGPU is not supported, the Gpu property may (!) be `undefined`,
/// and so this function will return `Ok(None)`.
/// Note that this check is insufficient to determine whether WebGPU is
/// supported, as the browser may define the Gpu property, but be unable to
/// create any WebGPU adapters.
/// To detect whether WebGPU is supported, use the [`crate::utils::is_browser_webgpu_supported`] function.
///
/// See:
/// * <https://developer.mozilla.org/en-US/docs/Web/API/Navigator/gpu>
/// * <https://developer.mozilla.org/en-US/docs/Web/API/WorkerNavigator/gpu>
pub fn get_browser_gpu_property(
) -> Result<Option<DefinedNonNullJsValue<webgpu_sys::Gpu>>, BrowserGpuPropertyInaccessible> {
    let global: Global = js_sys::global().unchecked_into();

    let maybe_undefined_gpu: webgpu_sys::Gpu = if !global.window().is_undefined() {
        let navigator = global.unchecked_into::<web_sys::Window>().navigator();
        ext_bindings::NavigatorGpu::gpu(&navigator)
    } else if !global.worker().is_undefined() {
        let navigator = global
            .unchecked_into::<web_sys::WorkerGlobalScope>()
            .navigator();
        ext_bindings::NavigatorGpu::gpu(&navigator)
    } else {
        return Err(BrowserGpuPropertyInaccessible);
    };
    Ok(DefinedNonNullJsValue::new(maybe_undefined_gpu))
}

#[derive(Debug)]
pub struct WebAdapter {
    pub(crate) inner: webgpu_sys::GpuAdapter,
    /// Unique identifier for this Adapter.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebDevice {
    pub(crate) inner: webgpu_sys::GpuDevice,
    /// Unique identifier for this Device.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebQueue {
    pub(crate) inner: webgpu_sys::GpuQueue,
    /// Unique identifier for this Queue.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebBindGroupLayout {
    pub(crate) inner: webgpu_sys::GpuBindGroupLayout,
    /// Unique identifier for this BindGroupLayout.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebBindGroup {
    pub(crate) inner: webgpu_sys::GpuBindGroup,
    /// Unique identifier for this BindGroup.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebTextureView {
    pub(crate) inner: webgpu_sys::GpuTextureView,
    /// Unique identifier for this TextureView.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebSampler {
    pub(crate) inner: webgpu_sys::GpuSampler,
    /// Unique identifier for this Sampler.
    ident: crate::cmp::Identifier,
}

/// Remembers which portion of a buffer has been mapped, along with a reference
/// to the mapped portion.
#[derive(Debug)]
struct WebBufferMapState {
    /// The mapped memory of the buffer.
    pub mapped_buffer: Option<js_sys::ArrayBuffer>,
    /// The total range which has been mapped in the buffer overall.
    pub range: Range<wgt::BufferAddress>,
}

/// Stores the state of a GPU buffer and a reference to its mapped `ArrayBuffer` (if any).
/// The WebGPU specification forbids calling `getMappedRange` on a `webgpu_sys::GpuBuffer` more than
/// once, so this struct stores the initial mapped range and re-uses it, allowing for multiple `get_mapped_range`
/// calls on the Rust-side.
#[derive(Debug)]
pub struct WebBuffer {
    /// The associated GPU buffer.
    inner: webgpu_sys::GpuBuffer,
    /// The mapped array buffer and mapped range.
    mapping: RefCell<WebBufferMapState>,
    /// Unique identifier for this Buffer.
    ident: crate::cmp::Identifier,
}

impl WebBuffer {
    /// Creates a new web buffer for the given Javascript object and description.
    fn new(inner: webgpu_sys::GpuBuffer, desc: &crate::BufferDescriptor<'_>) -> Self {
        Self {
            inner,
            mapping: RefCell::new(WebBufferMapState {
                mapped_buffer: None,
                range: 0..desc.size,
            }),
            ident: crate::cmp::Identifier::create(),
        }
    }

    /// Obtains a reference to the re-usable buffer mapping as a Javascript array view.
    fn get_mapped_range(&self, sub_range: Range<wgt::BufferAddress>) -> js_sys::Uint8Array {
        let mut mapping = self.mapping.borrow_mut();
        let range = mapping.range.clone();
        let array_buffer = mapping.mapped_buffer.get_or_insert_with(|| {
            self.inner
                .get_mapped_range_with_f64_and_f64(
                    range.start as f64,
                    (range.end - range.start) as f64,
                )
                .unwrap()
        });
        js_sys::Uint8Array::new_with_byte_offset_and_length(
            array_buffer,
            (sub_range.start - range.start) as u32,
            (sub_range.end - sub_range.start) as u32,
        )
    }

    /// Sets the range of the buffer which is presently mapped.
    fn set_mapped_range(&self, range: Range<wgt::BufferAddress>) {
        self.mapping.borrow_mut().range = range;
    }
}

#[derive(Debug)]
pub struct WebTexture {
    pub(crate) inner: webgpu_sys::GpuTexture,
    /// Unique identifier for this Texture.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebExternalTexture {
    /// Unique identifier for this ExternalTexture.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebBlas {
    /// Unique identifier for this Blas.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebTlas {
    /// Unique identifier for this Blas.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebQuerySet {
    pub(crate) inner: webgpu_sys::GpuQuerySet,
    /// Unique identifier for this QuerySet.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebPipelineLayout {
    pub(crate) inner: webgpu_sys::GpuPipelineLayout,
    /// Unique identifier for this PipelineLayout.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebRenderPipeline {
    pub(crate) inner: webgpu_sys::GpuRenderPipeline,
    /// Unique identifier for this RenderPipeline.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebComputePipeline {
    pub(crate) inner: webgpu_sys::GpuComputePipeline,
    /// Unique identifier for this ComputePipeline.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebPipelineCache {
    /// Unique identifier for this PipelineCache.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebCommandEncoder {
    pub(crate) inner: webgpu_sys::GpuCommandEncoder,
    /// Unique identifier for this CommandEncoder.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebComputePassEncoder {
    pub(crate) inner: webgpu_sys::GpuComputePassEncoder,
    /// Unique identifier for this ComputePassEncoder.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebRenderPassEncoder {
    pub(crate) inner: webgpu_sys::GpuRenderPassEncoder,
    /// Unique identifier for this RenderPassEncoder.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebCommandBuffer {
    pub(crate) inner: webgpu_sys::GpuCommandBuffer,
    /// Unique identifier for this CommandBuffer.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebRenderBundleEncoder {
    pub(crate) inner: webgpu_sys::GpuRenderBundleEncoder,
    /// Unique identifier for this RenderBundleEncoder.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebRenderBundle {
    pub(crate) inner: webgpu_sys::GpuRenderBundle,
    /// Unique identifier for this RenderBundle.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebSurface {
    gpu: Option<DefinedNonNullJsValue<webgpu_sys::Gpu>>,
    canvas: Canvas,
    context: webgpu_sys::GpuCanvasContext,
    /// Unique identifier for this Surface.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebSurfaceOutputDetail {
    /// Unique identifier for this SurfaceOutputDetail.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebQueueWriteBuffer {
    inner: Box<[u8]>,
    /// Unique identifier for this QueueWriteBuffer.
    ident: crate::cmp::Identifier,
}

#[derive(Debug)]
pub struct WebBufferMappedRange {
    actual_mapping: js_sys::Uint8Array,
    /// Copy of actual_mapping that lives in the Rust/Wasm heap instead of JS. This
    /// is done only when accessed for the first time to avoid unnecessary allocations.
    temporary_mapping: OnceCell<Vec<u8>>,
    /// Whether `temporary_mapping` has possibly been written to and needs to be written back to JS.
    temporary_mapping_modified: bool,
    /// Unique identifier for this BufferMappedRange.
    ident: crate::cmp::Identifier,
}

impl_send_sync!(ContextWebGpu);
impl_send_sync!(WebAdapter);
impl_send_sync!(WebDevice);
impl_send_sync!(WebQueue);
impl_send_sync!(WebShaderModule);
impl_send_sync!(WebBindGroupLayout);
impl_send_sync!(WebBindGroup);
impl_send_sync!(WebTextureView);
impl_send_sync!(WebSampler);
impl_send_sync!(WebBuffer);
impl_send_sync!(WebTexture);
impl_send_sync!(WebExternalTexture);
impl_send_sync!(WebBlas);
impl_send_sync!(WebTlas);
impl_send_sync!(WebQuerySet);
impl_send_sync!(WebPipelineLayout);
impl_send_sync!(WebRenderPipeline);
impl_send_sync!(WebComputePipeline);
impl_send_sync!(WebPipelineCache);
impl_send_sync!(WebCommandEncoder);
impl_send_sync!(WebComputePassEncoder);
impl_send_sync!(WebRenderPassEncoder);
impl_send_sync!(WebCommandBuffer);
impl_send_sync!(WebRenderBundleEncoder);
impl_send_sync!(WebRenderBundle);
impl_send_sync!(WebSurface);
impl_send_sync!(WebSurfaceOutputDetail);
impl_send_sync!(WebQueueWriteBuffer);
impl_send_sync!(WebBufferMappedRange);

crate::cmp::impl_eq_ord_hash_proxy!(ContextWebGpu => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebAdapter => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebDevice => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebQueue => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebShaderModule => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebBindGroupLayout => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebBindGroup => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebTextureView => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebSampler => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebBuffer => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebTexture => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebExternalTexture => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebBlas => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebTlas => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebQuerySet => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebPipelineLayout => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebRenderPipeline => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebComputePipeline => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebPipelineCache => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebCommandEncoder => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebComputePassEncoder => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebRenderPassEncoder => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebCommandBuffer => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebRenderBundleEncoder => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebRenderBundle => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebSurface => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebSurfaceOutputDetail => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebQueueWriteBuffer => .ident);
crate::cmp::impl_eq_ord_hash_proxy!(WebBufferMappedRange => .ident);

impl dispatch::InstanceInterface for ContextWebGpu {
    fn new(desc: &crate::InstanceDescriptor) -> Self
    where
        Self: Sized,
    {
        let Ok(gpu) = get_browser_gpu_property() else {
            panic!(
                "Accessing the GPU is only supported on the main thread or from a dedicated worker"
            );
        };

        ContextWebGpu {
            gpu,
            requested_backends: desc.backends,
            ident: crate::cmp::Identifier::create(),
        }
    }

    unsafe fn create_surface(
        &self,
        target: crate::SurfaceTargetUnsafe,
    ) -> Result<dispatch::DispatchSurface, crate::CreateSurfaceError> {
        match target {
            SurfaceTargetUnsafe::RawHandle {
                raw_display_handle: _,
                raw_window_handle,
            } => {
                let canvas_element: web_sys::HtmlCanvasElement = match raw_window_handle {
                    raw_window_handle::RawWindowHandle::Web(handle) => {
                        let canvas_node: wasm_bindgen::JsValue = web_sys::window()
                            .and_then(|win| win.document())
                            .and_then(|doc| {
                                doc.query_selector_all(&format!(
                                    "[data-raw-handle=\"{}\"]",
                                    handle.id
                                ))
                                .ok()
                            })
                            .and_then(|nodes| nodes.get(0))
                            .expect("expected to find single canvas")
                            .into();
                        canvas_node.into()
                    }
                    raw_window_handle::RawWindowHandle::WebCanvas(handle) => {
                        let value: &JsValue = unsafe { handle.obj.cast().as_ref() };
                        value.clone().unchecked_into()
                    }
                    raw_window_handle::RawWindowHandle::WebOffscreenCanvas(handle) => {
                        let value: &JsValue = unsafe { handle.obj.cast().as_ref() };
                        let canvas: web_sys::OffscreenCanvas = value.clone().unchecked_into();
                        let context_result = canvas.get_context("webgpu");

                        return self.create_surface_from_context(
                            Canvas::Offscreen(canvas),
                            context_result,
                        );
                    }
                    _ => panic!("expected valid handle for canvas"),
                };

                let context_result = canvas_element.get_context("webgpu");
                self.create_surface_from_context(Canvas::Canvas(canvas_element), context_result)
            }
        }
    }

    fn request_adapter(
        &self,
        options: &crate::RequestAdapterOptions<'_, '_>,
    ) -> Pin<Box<dyn dispatch::RequestAdapterFuture>> {
        let requested_backends = self.requested_backends;

        //TODO: support this check, return `None` if the flag is not set.
        // It's not trivial, since we need the Future logic to have this check,
        // and currently the Future here has no room for extra parameter `backends`.
        if !(requested_backends.contains(wgt::Backends::BROWSER_WEBGPU)) {
            return Box::pin(core::future::ready(Err(
                wgt::RequestAdapterError::NotFound {
                    active_backends: Backends::BROWSER_WEBGPU,
                    requested_backends,
                    // TODO: supported_backends should also include wgpu-core-based backends,
                    // if they were compiled in.
                    supported_backends: Backends::BROWSER_WEBGPU,
                    no_fallback_backends: Backends::default(),
                    no_adapter_backends: Backends::default(),
                    incompatible_surface_backends: Backends::default(),
                },
            )));
        }
        let mapped_options = webgpu_sys::GpuRequestAdapterOptions::new();
        let mapped_power_preference = match options.power_preference {
            wgt::PowerPreference::None => None,
            wgt::PowerPreference::LowPower => Some(webgpu_sys::GpuPowerPreference::LowPower),
            wgt::PowerPreference::HighPerformance => {
                Some(webgpu_sys::GpuPowerPreference::HighPerformance)
            }
        };
        if let Some(mapped_pref) = mapped_power_preference {
            mapped_options.set_power_preference(mapped_pref);
        }

        if let Some(gpu) = &self.gpu {
            let adapter_promise = gpu.request_adapter_with_options(&mapped_options);
            Box::pin(MakeSendFuture::new(
                wasm_bindgen_futures::JsFuture::from(adapter_promise),
                move |result| future_request_adapter(result, requested_backends),
            ))
        } else {
            // Gpu is undefined; WebGPU is not supported in this browser.
            // Treat this exactly like requestAdapter() returned null.
            Box::pin(core::future::ready(Err(request_adapter_null_error(
                requested_backends,
            ))))
        }
    }

    fn poll_all_devices(&self, _force_wait: bool) -> bool {
        // Devices are automatically polled.
        true
    }

    #[cfg(feature = "wgsl")]
    fn wgsl_language_features(&self) -> crate::WgslLanguageFeatures {
        let mut wgsl_language_features = crate::WgslLanguageFeatures::empty();
        if let Some(gpu) = &self.gpu {
            gpu.wgsl_language_features()
                .keys()
                .into_iter()
                .map(|wlf| wlf.expect("`WgslLanguageFeatures` elements should be valid"))
                .map(|wlf| {
                    wlf.as_string()
                        .expect("`WgslLanguageFeatures` should be string set")
                })
                .filter_map(|wlf| match wlf.as_str() {
                    "readonly_and_readwrite_storage_textures" => {
                        Some(crate::WgslLanguageFeatures::ReadOnlyAndReadWriteStorageTextures)
                    }
                    "packed_4x8_integer_dot_product" => {
                        Some(crate::WgslLanguageFeatures::Packed4x8IntegerDotProduct)
                    }
                    "unrestricted_pointer_parameters" => {
                        Some(crate::WgslLanguageFeatures::UnrestrictedPointerParameters)
                    }
                    "pointer_composite_access" => {
                        Some(crate::WgslLanguageFeatures::PointerCompositeAccess)
                    }
                    _ => None,
                })
                .for_each(|wlf| {
                    wgsl_language_features |= wlf;
                })
        }
        wgsl_language_features
    }
}

impl Drop for ContextWebGpu {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::AdapterInterface for WebAdapter {
    fn request_device(
        &self,
        desc: &crate::DeviceDescriptor<'_>,
    ) -> Pin<Box<dyn dispatch::RequestDeviceFuture>> {
        if !matches!(desc.trace, wgt::Trace::Off) {
            log::warn!("The `trace` parameter is not supported on the WebGPU backend.");
        }

        let mapped_desc = webgpu_sys::GpuDeviceDescriptor::new();

        // TODO: Migrate to a web_sys api.
        // See https://github.com/rustwasm/wasm-bindgen/issues/3587
        let limits_object = map_js_sys_limits(&desc.required_limits);

        js_sys::Reflect::set(
            &mapped_desc,
            &JsValue::from("requiredLimits"),
            &limits_object,
        )
        .expect("Setting Object properties should never fail.");

        let required_features = FEATURES_MAPPING
            .iter()
            .copied()
            .flat_map(|(flag, value)| {
                if desc.required_features.contains(flag) {
                    Some(JsValue::from(value))
                } else {
                    None
                }
            })
            .collect::<js_sys::Array>();
        mapped_desc.set_required_features(&required_features);

        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }

        let device_promise = self.inner.request_device_with_descriptor(&mapped_desc);

        Box::pin(MakeSendFuture::new(
            wasm_bindgen_futures::JsFuture::from(device_promise),
            future_request_device,
        ))
    }

    fn is_surface_supported(&self, _surface: &dispatch::DispatchSurface) -> bool {
        // All surfaces are inherently supported.
        true
    }

    fn features(&self) -> crate::Features {
        map_wgt_features(self.inner.features())
    }

    fn limits(&self) -> crate::Limits {
        map_wgt_limits(self.inner.limits())
    }

    fn downlevel_capabilities(&self) -> crate::DownlevelCapabilities {
        // WebGPU is assumed to be fully compliant
        crate::DownlevelCapabilities::default()
    }

    fn get_info(&self) -> crate::AdapterInfo {
        // TODO: web-sys has no way of getting information on adapters
        wgt::AdapterInfo {
            name: String::new(),
            vendor: 0,
            device: 0,
            device_type: wgt::DeviceType::Other,
            driver: String::new(),
            driver_info: String::new(),
            backend: wgt::Backend::BrowserWebGpu,
        }
    }

    fn get_texture_format_features(
        &self,
        format: crate::TextureFormat,
    ) -> crate::TextureFormatFeatures {
        format.guaranteed_format_features(dispatch::AdapterInterface::features(self))
    }

    fn get_presentation_timestamp(&self) -> crate::PresentationTimestamp {
        crate::PresentationTimestamp::INVALID_TIMESTAMP
    }
}
impl Drop for WebAdapter {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::DeviceInterface for WebDevice {
    fn features(&self) -> crate::Features {
        map_wgt_features(self.inner.features())
    }

    fn limits(&self) -> crate::Limits {
        map_wgt_limits(self.inner.limits())
    }

    fn create_shader_module(
        &self,
        desc: crate::ShaderModuleDescriptor<'_>,
        _shader_runtime_checks: crate::ShaderRuntimeChecks,
    ) -> dispatch::DispatchShaderModule {
        let shader_module_result = match desc.source {
            #[cfg(feature = "spirv")]
            crate::ShaderSource::SpirV(ref spv) => {
                use naga::front;

                let options = naga::front::spv::Options {
                    adjust_coordinate_space: false,
                    strict_capabilities: true,
                    block_ctx_dump_prefix: None,
                };
                let spv_parser = front::spv::Frontend::new(spv.iter().cloned(), &options);
                spv_parser
                    .parse()
                    .map_err(|inner| {
                        crate::CompilationInfo::from(naga::error::ShaderError {
                            source: String::new(),
                            label: desc.label.map(|s| s.to_string()),
                            inner: Box::new(inner),
                        })
                    })
                    .and_then(|spv_module| {
                        validate_transformed_shader_module(&spv_module, "", &desc).map(|v| {
                            (
                                v,
                                WebShaderCompilationInfo::Transformed {
                                    compilation_info: crate::CompilationInfo { messages: vec![] },
                                },
                            )
                        })
                    })
            }
            #[cfg(feature = "glsl")]
            crate::ShaderSource::Glsl {
                ref shader,
                stage,
                defines,
            } => {
                use naga::front;

                // Parse the given shader code and store its representation.
                let options = front::glsl::Options {
                    stage,
                    defines: defines
                        .iter()
                        .map(|&(key, value)| (String::from(key), String::from(value)))
                        .collect(),
                };
                let mut parser = front::glsl::Frontend::default();
                parser
                    .parse(&options, shader)
                    .map_err(|inner| {
                        crate::CompilationInfo::from(naga::error::ShaderError {
                            source: shader.to_string(),
                            label: desc.label.map(|s| s.to_string()),
                            inner: Box::new(inner),
                        })
                    })
                    .and_then(|glsl_module| {
                        validate_transformed_shader_module(&glsl_module, shader, &desc).map(|v| {
                            (
                                v,
                                WebShaderCompilationInfo::Transformed {
                                    compilation_info: crate::CompilationInfo { messages: vec![] },
                                },
                            )
                        })
                    })
            }
            #[cfg(feature = "wgsl")]
            crate::ShaderSource::Wgsl(ref code) => {
                let shader_module = webgpu_sys::GpuShaderModuleDescriptor::new(code);
                Ok((
                    shader_module,
                    WebShaderCompilationInfo::Wgsl {
                        source: code.to_string(),
                    },
                ))
            }
            #[cfg(feature = "naga-ir")]
            crate::ShaderSource::Naga(ref module) => {
                validate_transformed_shader_module(module, "", &desc).map(|v| {
                    (
                        v,
                        WebShaderCompilationInfo::Transformed {
                            compilation_info: crate::CompilationInfo { messages: vec![] },
                        },
                    )
                })
            }
            crate::ShaderSource::Dummy(_) => {
                panic!("found `ShaderSource::Dummy`")
            }
        };

        #[cfg(naga)]
        fn validate_transformed_shader_module(
            module: &naga::Module,
            source: &str,
            desc: &crate::ShaderModuleDescriptor<'_>,
        ) -> Result<webgpu_sys::GpuShaderModuleDescriptor, crate::CompilationInfo> {
            use naga::{back, valid};
            let mut validator =
                valid::Validator::new(valid::ValidationFlags::all(), valid::Capabilities::all());
            let module_info = validator.validate(module).map_err(|err| {
                crate::CompilationInfo::from(naga::error::ShaderError {
                    source: source.to_string(),
                    label: desc.label.map(|s| s.to_string()),
                    inner: Box::new(err),
                })
            })?;

            let writer_flags = naga::back::wgsl::WriterFlags::empty();
            let wgsl_text = back::wgsl::write_string(module, &module_info, writer_flags).unwrap();
            Ok(webgpu_sys::GpuShaderModuleDescriptor::new(
                wgsl_text.as_str(),
            ))
        }
        let (descriptor, compilation_info) = match shader_module_result {
            Ok(v) => v,
            Err(compilation_info) => (
                webgpu_sys::GpuShaderModuleDescriptor::new(""),
                WebShaderCompilationInfo::Transformed { compilation_info },
            ),
        };
        if let Some(label) = desc.label {
            descriptor.set_label(label);
        }
        WebShaderModule {
            module: self.inner.create_shader_module(&descriptor),
            compilation_info,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    unsafe fn create_shader_module_passthrough(
        &self,
        desc: &crate::ShaderModuleDescriptorPassthrough<'_>,
    ) -> dispatch::DispatchShaderModule {
        let shader_module_result = if let Some(ref code) = desc.wgsl {
            let shader_module = webgpu_sys::GpuShaderModuleDescriptor::new(code);
            Ok((
                shader_module,
                WebShaderCompilationInfo::Wgsl {
                    source: code.to_string(),
                },
            ))
        } else {
            Err(crate::CompilationInfo {
                messages: vec![crate::CompilationMessage {
                    message:
                        "Passthrough shader not compiled for WGSL on WebGPU backend (WGPU error)"
                            .to_string(),
                    location: None,
                    message_type: crate::CompilationMessageType::Error,
                }],
            })
        };
        let (descriptor, compilation_info) = match shader_module_result {
            Ok(v) => v,
            Err(compilation_info) => (
                webgpu_sys::GpuShaderModuleDescriptor::new(""),
                WebShaderCompilationInfo::Transformed { compilation_info },
            ),
        };
        if let Some(label) = desc.label {
            descriptor.set_label(label);
        }
        WebShaderModule {
            module: self.inner.create_shader_module(&descriptor),
            compilation_info,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_bind_group_layout(
        &self,
        desc: &crate::BindGroupLayoutDescriptor<'_>,
    ) -> dispatch::DispatchBindGroupLayout {
        let mapped_bindings = desc
            .entries
            .iter()
            .map(|bind| {
                let mapped_entry =
                    webgpu_sys::GpuBindGroupLayoutEntry::new(bind.binding, bind.visibility.bits());

                match bind.ty {
                    wgt::BindingType::Buffer {
                        ty,
                        has_dynamic_offset,
                        min_binding_size,
                    } => {
                        let buffer = webgpu_sys::GpuBufferBindingLayout::new();
                        buffer.set_has_dynamic_offset(has_dynamic_offset);
                        if let Some(size) = min_binding_size {
                            buffer.set_min_binding_size(size.get() as f64);
                        }
                        buffer.set_type(match ty {
                            wgt::BufferBindingType::Uniform => {
                                webgpu_sys::GpuBufferBindingType::Uniform
                            }
                            wgt::BufferBindingType::Storage { read_only: false } => {
                                webgpu_sys::GpuBufferBindingType::Storage
                            }
                            wgt::BufferBindingType::Storage { read_only: true } => {
                                webgpu_sys::GpuBufferBindingType::ReadOnlyStorage
                            }
                        });
                        mapped_entry.set_buffer(&buffer);
                    }
                    wgt::BindingType::Sampler(ty) => {
                        let sampler = webgpu_sys::GpuSamplerBindingLayout::new();
                        sampler.set_type(match ty {
                            wgt::SamplerBindingType::NonFiltering => {
                                webgpu_sys::GpuSamplerBindingType::NonFiltering
                            }
                            wgt::SamplerBindingType::Filtering => {
                                webgpu_sys::GpuSamplerBindingType::Filtering
                            }
                            wgt::SamplerBindingType::Comparison => {
                                webgpu_sys::GpuSamplerBindingType::Comparison
                            }
                        });
                        mapped_entry.set_sampler(&sampler);
                    }
                    wgt::BindingType::Texture {
                        multisampled,
                        sample_type,
                        view_dimension,
                    } => {
                        let texture = webgpu_sys::GpuTextureBindingLayout::new();
                        texture.set_multisampled(multisampled);
                        texture.set_sample_type(map_texture_component_type(sample_type));
                        texture.set_view_dimension(map_texture_view_dimension(view_dimension));
                        mapped_entry.set_texture(&texture);
                    }
                    wgt::BindingType::StorageTexture {
                        access,
                        format,
                        view_dimension,
                    } => {
                        let mapped_access = match access {
                            wgt::StorageTextureAccess::WriteOnly => {
                                webgpu_sys::GpuStorageTextureAccess::WriteOnly
                            }
                            wgt::StorageTextureAccess::ReadOnly => {
                                webgpu_sys::GpuStorageTextureAccess::ReadOnly
                            }
                            wgt::StorageTextureAccess::ReadWrite => {
                                webgpu_sys::GpuStorageTextureAccess::ReadWrite
                            }
                            wgt::StorageTextureAccess::Atomic => {
                                // Validated out by `BindGroupLayoutEntryError::StorageTextureAtomic`
                                unreachable!()
                            }
                        };
                        let storage_texture = webgpu_sys::GpuStorageTextureBindingLayout::new(
                            map_texture_format(format),
                        );
                        storage_texture.set_access(mapped_access);
                        storage_texture
                            .set_view_dimension(map_texture_view_dimension(view_dimension));
                        mapped_entry.set_storage_texture(&storage_texture);
                    }
                    wgt::BindingType::AccelerationStructure { .. } => todo!(),
                    wgt::BindingType::ExternalTexture => {
                        mapped_entry.set_external_texture(
                            &webgpu_sys::GpuExternalTextureBindingLayout::new(),
                        );
                    }
                }

                mapped_entry
            })
            .collect::<js_sys::Array>();

        let mapped_desc = webgpu_sys::GpuBindGroupLayoutDescriptor::new(&mapped_bindings);
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }
        let bind_group_layout = self.inner.create_bind_group_layout(&mapped_desc).unwrap();

        WebBindGroupLayout {
            inner: bind_group_layout,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_bind_group(
        &self,
        desc: &crate::BindGroupDescriptor<'_>,
    ) -> dispatch::DispatchBindGroup {
        let mapped_entries = desc
            .entries
            .iter()
            .map(|binding| {
                let mapped_resource = match binding.resource {
                    crate::BindingResource::Buffer(crate::BufferBinding {
                        buffer,
                        offset,
                        size,
                    }) => {
                        let buffer = buffer.inner.as_webgpu();
                        let mapped_buffer_binding =
                            webgpu_sys::GpuBufferBinding::new(&buffer.inner);
                        mapped_buffer_binding.set_offset(offset as f64);
                        if let Some(s) = size {
                            mapped_buffer_binding.set_size(s.get() as f64);
                        }
                        JsValue::from(mapped_buffer_binding)
                    }
                    crate::BindingResource::BufferArray(..) => {
                        panic!("Web backend does not support arrays of buffers")
                    }
                    crate::BindingResource::Sampler(sampler) => {
                        let sampler = &sampler.inner.as_webgpu().inner;
                        JsValue::from(sampler)
                    }
                    crate::BindingResource::SamplerArray(..) => {
                        panic!("Web backend does not support arrays of samplers")
                    }
                    crate::BindingResource::TextureView(texture_view) => {
                        let texture_view = &texture_view.inner.as_webgpu().inner;
                        JsValue::from(texture_view)
                    }
                    crate::BindingResource::TextureViewArray(..) => {
                        panic!("Web backend does not support BINDING_INDEXING extension")
                    }
                    crate::BindingResource::AccelerationStructure(_) => {
                        unimplemented!("Raytracing not implemented for web")
                    }
                    crate::BindingResource::ExternalTexture(_) => {
                        unimplemented!("ExternalTexture not implemented for web")
                    }
                };

                webgpu_sys::GpuBindGroupEntry::new(binding.binding, &mapped_resource)
            })
            .collect::<js_sys::Array>();

        let bgl = &desc.layout.inner.as_webgpu().inner;
        let mapped_desc = webgpu_sys::GpuBindGroupDescriptor::new(&mapped_entries, bgl);
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }
        let bind_group = self.inner.create_bind_group(&mapped_desc);

        WebBindGroup {
            inner: bind_group,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_pipeline_layout(
        &self,
        desc: &crate::PipelineLayoutDescriptor<'_>,
    ) -> dispatch::DispatchPipelineLayout {
        let temp_layouts = desc
            .bind_group_layouts
            .iter()
            .map(|bgl| &bgl.inner.as_webgpu().inner)
            .collect::<js_sys::Array>();
        let mapped_desc = webgpu_sys::GpuPipelineLayoutDescriptor::new(&temp_layouts);
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }

        let pipeline_layout = self.inner.create_pipeline_layout(&mapped_desc);

        WebPipelineLayout {
            inner: pipeline_layout,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_render_pipeline(
        &self,
        desc: &crate::RenderPipelineDescriptor<'_>,
    ) -> dispatch::DispatchRenderPipeline {
        let module = desc.vertex.module.inner.as_webgpu();
        let mapped_vertex_state = webgpu_sys::GpuVertexState::new(&module.module);
        insert_constants_map(
            &mapped_vertex_state,
            desc.vertex.compilation_options.constants,
        );
        if let Some(ep) = desc.vertex.entry_point {
            mapped_vertex_state.set_entry_point(ep);
        }

        let buffers = desc
            .vertex
            .buffers
            .iter()
            .map(|vbuf| {
                let mapped_attributes = vbuf
                    .attributes
                    .iter()
                    .map(|attr| {
                        webgpu_sys::GpuVertexAttribute::new(
                            map_vertex_format(attr.format),
                            attr.offset as f64,
                            attr.shader_location,
                        )
                    })
                    .collect::<js_sys::Array>();

                let mapped_vbuf = webgpu_sys::GpuVertexBufferLayout::new(
                    vbuf.array_stride as f64,
                    &mapped_attributes,
                );
                mapped_vbuf.set_step_mode(map_vertex_step_mode(vbuf.step_mode));
                mapped_vbuf
            })
            .collect::<js_sys::Array>();

        mapped_vertex_state.set_buffers(&buffers);

        let auto_layout = wasm_bindgen::JsValue::from(webgpu_sys::GpuAutoLayoutMode::Auto);
        let mapped_desc = webgpu_sys::GpuRenderPipelineDescriptor::new(
            &match desc.layout {
                Some(layout) => {
                    let layout = &layout.inner.as_webgpu().inner;
                    JsValue::from(layout)
                }
                None => auto_layout,
            },
            &mapped_vertex_state,
        );

        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }

        if let Some(ref depth_stencil) = desc.depth_stencil {
            mapped_desc.set_depth_stencil(&map_depth_stencil_state(depth_stencil));
        }

        if let Some(ref frag) = desc.fragment {
            let targets = frag
                .targets
                .iter()
                .map(|target| match target {
                    Some(target) => {
                        let mapped_format = map_texture_format(target.format);
                        let mapped_color_state =
                            webgpu_sys::GpuColorTargetState::new(mapped_format);
                        if let Some(ref bs) = target.blend {
                            let alpha = map_blend_component(&bs.alpha);
                            let color = map_blend_component(&bs.color);
                            let mapped_blend_state = webgpu_sys::GpuBlendState::new(&alpha, &color);
                            mapped_color_state.set_blend(&mapped_blend_state);
                        }
                        mapped_color_state.set_write_mask(target.write_mask.bits());
                        wasm_bindgen::JsValue::from(mapped_color_state)
                    }
                    None => wasm_bindgen::JsValue::null(),
                })
                .collect::<js_sys::Array>();
            let module = frag.module.inner.as_webgpu();
            let mapped_fragment_desc = webgpu_sys::GpuFragmentState::new(&module.module, &targets);
            insert_constants_map(&mapped_fragment_desc, frag.compilation_options.constants);
            if let Some(ep) = frag.entry_point {
                mapped_fragment_desc.set_entry_point(ep);
            }
            mapped_desc.set_fragment(&mapped_fragment_desc);
        }

        let mapped_multisample = webgpu_sys::GpuMultisampleState::new();
        mapped_multisample.set_count(desc.multisample.count);
        mapped_multisample.set_mask(desc.multisample.mask as u32);
        mapped_multisample
            .set_alpha_to_coverage_enabled(desc.multisample.alpha_to_coverage_enabled);
        mapped_desc.set_multisample(&mapped_multisample);

        let mapped_primitive = map_primitive_state(&desc.primitive);
        mapped_desc.set_primitive(&mapped_primitive);

        let render_pipeline = self.inner.create_render_pipeline(&mapped_desc).unwrap();

        WebRenderPipeline {
            inner: render_pipeline,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_mesh_pipeline(
        &self,
        _desc: &crate::MeshPipelineDescriptor<'_>,
    ) -> dispatch::DispatchRenderPipeline {
        panic!("MESH_SHADER feature must be enabled to call create_mesh_pipeline")
    }

    fn create_compute_pipeline(
        &self,
        desc: &crate::ComputePipelineDescriptor<'_>,
    ) -> dispatch::DispatchComputePipeline {
        let shader_module = desc.module.inner.as_webgpu();
        let mapped_compute_stage = webgpu_sys::GpuProgrammableStage::new(&shader_module.module);
        insert_constants_map(&mapped_compute_stage, desc.compilation_options.constants);
        if let Some(ep) = desc.entry_point {
            mapped_compute_stage.set_entry_point(ep);
        }
        let auto_layout = wasm_bindgen::JsValue::from(webgpu_sys::GpuAutoLayoutMode::Auto);
        let mapped_desc = webgpu_sys::GpuComputePipelineDescriptor::new(
            &match desc.layout {
                Some(layout) => {
                    let layout = &layout.inner.as_webgpu().inner;
                    JsValue::from(layout)
                }
                None => auto_layout,
            },
            &mapped_compute_stage,
        );
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }

        let compute_pipeline = self.inner.create_compute_pipeline(&mapped_desc);

        WebComputePipeline {
            inner: compute_pipeline,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    unsafe fn create_pipeline_cache(
        &self,
        _desc: &crate::PipelineCacheDescriptor<'_>,
    ) -> dispatch::DispatchPipelineCache {
        WebPipelineCache {
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_buffer(&self, desc: &crate::BufferDescriptor<'_>) -> dispatch::DispatchBuffer {
        let mapped_desc = webgpu_sys::GpuBufferDescriptor::new(desc.size as f64, desc.usage.bits());
        mapped_desc.set_mapped_at_creation(desc.mapped_at_creation);
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }
        WebBuffer::new(self.inner.create_buffer(&mapped_desc).unwrap(), desc).into()
    }

    fn create_texture(&self, desc: &crate::TextureDescriptor<'_>) -> dispatch::DispatchTexture {
        let mapped_desc = webgpu_sys::GpuTextureDescriptor::new(
            map_texture_format(desc.format),
            &map_extent_3d(desc.size),
            desc.usage.bits(),
        );
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }
        mapped_desc.set_dimension(map_texture_dimension(desc.dimension));
        mapped_desc.set_mip_level_count(desc.mip_level_count);
        mapped_desc.set_sample_count(desc.sample_count);
        let mapped_view_formats = desc
            .view_formats
            .iter()
            .map(|format| JsValue::from(map_texture_format(*format)))
            .collect::<js_sys::Array>();
        mapped_desc.set_view_formats(&mapped_view_formats);

        let texture = self.inner.create_texture(&mapped_desc).unwrap();
        WebTexture {
            inner: texture,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_external_texture(
        &self,
        _desc: &crate::ExternalTextureDescriptor<'_>,
        _planes: &[&crate::TextureView],
    ) -> dispatch::DispatchExternalTexture {
        unimplemented!("ExternalTexture not implemented for web");
    }

    fn create_blas(
        &self,
        _desc: &crate::CreateBlasDescriptor<'_>,
        _sizes: crate::BlasGeometrySizeDescriptors,
    ) -> (Option<u64>, dispatch::DispatchBlas) {
        unimplemented!("Raytracing not implemented for web");
    }

    fn create_tlas(&self, _desc: &crate::CreateTlasDescriptor<'_>) -> dispatch::DispatchTlas {
        unimplemented!("Raytracing not implemented for web");
    }

    fn create_sampler(&self, desc: &crate::SamplerDescriptor<'_>) -> dispatch::DispatchSampler {
        let mapped_desc = webgpu_sys::GpuSamplerDescriptor::new();
        mapped_desc.set_address_mode_u(map_address_mode(desc.address_mode_u));
        mapped_desc.set_address_mode_v(map_address_mode(desc.address_mode_v));
        mapped_desc.set_address_mode_w(map_address_mode(desc.address_mode_w));
        if let Some(compare) = desc.compare {
            mapped_desc.set_compare(map_compare_function(compare));
        }
        mapped_desc.set_lod_max_clamp(desc.lod_max_clamp);
        mapped_desc.set_lod_min_clamp(desc.lod_min_clamp);
        mapped_desc.set_mag_filter(map_filter_mode(desc.mag_filter));
        mapped_desc.set_min_filter(map_filter_mode(desc.min_filter));
        mapped_desc.set_mipmap_filter(map_mipmap_filter_mode(desc.mipmap_filter));
        mapped_desc.set_max_anisotropy(desc.anisotropy_clamp);
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }

        let sampler = self.inner.create_sampler_with_descriptor(&mapped_desc);

        WebSampler {
            inner: sampler,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_query_set(&self, desc: &crate::QuerySetDescriptor<'_>) -> dispatch::DispatchQuerySet {
        let ty = match desc.ty {
            wgt::QueryType::Occlusion => webgpu_sys::GpuQueryType::Occlusion,
            wgt::QueryType::Timestamp => webgpu_sys::GpuQueryType::Timestamp,
            wgt::QueryType::PipelineStatistics(_) => unreachable!(),
        };
        let mapped_desc = webgpu_sys::GpuQuerySetDescriptor::new(desc.count, ty);
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }

        let query_set = self.inner.create_query_set(&mapped_desc).unwrap();

        WebQuerySet {
            inner: query_set,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_command_encoder(
        &self,
        desc: &crate::CommandEncoderDescriptor<'_>,
    ) -> dispatch::DispatchCommandEncoder {
        let mapped_desc = webgpu_sys::GpuCommandEncoderDescriptor::new();
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }

        let command_encoder = self
            .inner
            .create_command_encoder_with_descriptor(&mapped_desc);

        WebCommandEncoder {
            inner: command_encoder,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn create_render_bundle_encoder(
        &self,
        desc: &crate::RenderBundleEncoderDescriptor<'_>,
    ) -> dispatch::DispatchRenderBundleEncoder {
        let mapped_color_formats = desc
            .color_formats
            .iter()
            .map(|cf| match cf {
                Some(cf) => wasm_bindgen::JsValue::from(map_texture_format(*cf)),
                None => wasm_bindgen::JsValue::null(),
            })
            .collect::<js_sys::Array>();
        let mapped_desc = webgpu_sys::GpuRenderBundleEncoderDescriptor::new(&mapped_color_formats);
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }
        if let Some(ds) = desc.depth_stencil {
            mapped_desc.set_depth_stencil_format(map_texture_format(ds.format));
            mapped_desc.set_depth_read_only(ds.depth_read_only);
            mapped_desc.set_stencil_read_only(ds.stencil_read_only);
        }
        mapped_desc.set_sample_count(desc.sample_count);

        let render_bundle_encoder = self
            .inner
            .create_render_bundle_encoder(&mapped_desc)
            .unwrap();

        WebRenderBundleEncoder {
            inner: render_bundle_encoder,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn set_device_lost_callback(&self, device_lost_callback: dispatch::BoxDeviceLostCallback) {
        let closure = Closure::once(move |info: JsValue| {
            let info = info.dyn_into::<webgpu_sys::GpuDeviceLostInfo>().unwrap();
            device_lost_callback(
                match info.reason() {
                    webgpu_sys::GpuDeviceLostReason::Destroyed => {
                        crate::DeviceLostReason::Destroyed
                    }
                    webgpu_sys::GpuDeviceLostReason::Unknown => crate::DeviceLostReason::Unknown,
                    _ => crate::DeviceLostReason::Unknown,
                },
                info.message(),
            );
        });
        let _ = self.inner.lost().then(&closure);
        // Release memory management of this closure from Rust to the JS GC.
        // TODO: This will leak if weak references is not supported.
        closure.forget();
    }

    fn on_uncaptured_error(&self, handler: Arc<dyn crate::UncapturedErrorHandler>) {
        let f = Closure::wrap(Box::new(move |event: webgpu_sys::GpuUncapturedErrorEvent| {
            let error = crate::Error::from_js(event.error().value_of());
            handler(error);
        }) as Box<dyn FnMut(_)>);
        self.inner
            .set_onuncapturederror(Some(f.as_ref().unchecked_ref()));
        // Release memory management of this closure from Rust to the JS GC.
        // TODO: This will leak if weak references is not supported.
        f.forget();
    }

    fn push_error_scope(&self, filter: crate::ErrorFilter) {
        self.inner.push_error_scope(match filter {
            crate::ErrorFilter::OutOfMemory => webgpu_sys::GpuErrorFilter::OutOfMemory,
            crate::ErrorFilter::Validation => webgpu_sys::GpuErrorFilter::Validation,
            crate::ErrorFilter::Internal => webgpu_sys::GpuErrorFilter::Internal,
        });
    }

    fn pop_error_scope(&self) -> Pin<Box<dyn dispatch::PopErrorScopeFuture>> {
        let error_promise = self.inner.pop_error_scope();
        Box::pin(MakeSendFuture::new(
            wasm_bindgen_futures::JsFuture::from(error_promise),
            future_pop_error_scope,
        ))
    }

    unsafe fn start_graphics_debugger_capture(&self) {
        // No capturing api in webgpu
    }

    unsafe fn stop_graphics_debugger_capture(&self) {
        // No capturing api in webgpu
    }

    fn poll(&self, _poll_type: wgt::PollType<u64>) -> Result<crate::PollStatus, crate::PollError> {
        // Device is polled automatically
        Ok(crate::PollStatus::QueueEmpty)
    }

    fn get_internal_counters(&self) -> crate::InternalCounters {
        crate::InternalCounters::default()
    }

    fn generate_allocator_report(&self) -> Option<wgt::AllocatorReport> {
        None
    }

    fn destroy(&self) {
        self.inner.destroy();
    }
}
impl Drop for WebDevice {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::QueueInterface for WebQueue {
    fn write_buffer(
        &self,
        buffer: &dispatch::DispatchBuffer,
        offset: crate::BufferAddress,
        data: &[u8],
    ) {
        let buffer = buffer.as_webgpu();
        /* Skip the copy once gecko allows BufferSource instead of ArrayBuffer
        self.inner.write_buffer_with_f64_and_u8_array_and_f64_and_f64(
            &buffer.buffer,
            offset as f64,
            data,
            0f64,
            data.len() as f64,
        );
        */
        self.inner
            .write_buffer_with_f64_and_buffer_source_and_f64_and_f64(
                &buffer.inner,
                offset as f64,
                &js_sys::Uint8Array::from(data).buffer(),
                0f64,
                data.len() as f64,
            )
            .unwrap();
    }

    fn create_staging_buffer(
        &self,
        size: crate::BufferSize,
    ) -> Option<dispatch::DispatchQueueWriteBuffer> {
        Some(
            WebQueueWriteBuffer {
                inner: vec![0; size.get() as usize].into_boxed_slice(),
                ident: crate::cmp::Identifier::create(),
            }
            .into(),
        )
    }

    fn validate_write_buffer(
        &self,
        buffer: &dispatch::DispatchBuffer,
        offset: wgt::BufferAddress,
        size: wgt::BufferSize,
    ) -> Option<()> {
        let buffer = buffer.as_webgpu();

        let usage = wgt::BufferUsages::from_bits_truncate(buffer.inner.usage());
        // TODO: actually send this down the error scope
        if !usage.contains(wgt::BufferUsages::COPY_DST) {
            log::error!("Destination buffer is missing the `COPY_DST` usage flag");
            return None;
        }
        let write_size = u64::from(size);
        if write_size % wgt::COPY_BUFFER_ALIGNMENT != 0 {
            log::error!("Copy size {size} does not respect `COPY_BUFFER_ALIGNMENT`");
            return None;
        }
        if offset % wgt::COPY_BUFFER_ALIGNMENT != 0 {
            log::error!(
                "Buffer offset {offset} is not aligned to block size or `COPY_BUFFER_ALIGNMENT`"
            );
            return None;
        }
        if write_size + offset > buffer.inner.size() as u64 {
            log::error!("copy of {}..{} would end up overrunning the bounds of the destination buffer of size {}", offset, offset + write_size, buffer.inner.size());
            return None;
        }
        Some(())
    }

    fn write_staging_buffer(
        &self,
        buffer: &dispatch::DispatchBuffer,
        offset: crate::BufferAddress,
        staging_buffer: &dispatch::DispatchQueueWriteBuffer,
    ) {
        let staging_buffer = staging_buffer.as_webgpu();

        dispatch::QueueInterface::write_buffer(self, buffer, offset, &staging_buffer.inner)
    }

    fn write_texture(
        &self,
        texture: crate::TexelCopyTextureInfo<'_>,
        data: &[u8],
        data_layout: crate::TexelCopyBufferLayout,
        size: crate::Extent3d,
    ) {
        let mapped_data_layout = webgpu_sys::GpuTexelCopyBufferLayout::new();
        if let Some(bytes_per_row) = data_layout.bytes_per_row {
            mapped_data_layout.set_bytes_per_row(bytes_per_row);
        }
        if let Some(rows_per_image) = data_layout.rows_per_image {
            mapped_data_layout.set_rows_per_image(rows_per_image);
        }
        mapped_data_layout.set_offset(data_layout.offset as f64);

        /* Skip the copy once gecko allows BufferSource instead of ArrayBuffer
        self.inner.write_texture_with_u8_array_and_gpu_extent_3d_dict(
            &map_texture_copy_view(texture),
            data,
            &mapped_data_layout,
            &map_extent_3d(size),
        );
        */
        self.inner
            .write_texture_with_buffer_source_and_gpu_extent_3d_dict(
                &map_texture_copy_view(texture),
                &js_sys::Uint8Array::from(data).buffer(),
                &mapped_data_layout,
                &map_extent_3d(size),
            )
            .unwrap();
    }

    fn copy_external_image_to_texture(
        &self,
        source: &crate::CopyExternalImageSourceInfo,
        dest: crate::CopyExternalImageDestInfo<&crate::api::Texture>,
        size: crate::Extent3d,
    ) {
        self.inner
            .copy_external_image_to_texture_with_gpu_extent_3d_dict(
                &map_external_texture_copy_view(source),
                &map_tagged_texture_copy_view(dest),
                &map_extent_3d(size),
            )
            .unwrap();
    }

    fn submit(
        &self,
        command_buffers: &mut dyn Iterator<Item = dispatch::DispatchCommandBuffer>,
    ) -> u64 {
        let temp_command_buffers = command_buffers.collect::<Vec<_>>();

        let array = temp_command_buffers
            .iter()
            .map(|buffer| &buffer.as_webgpu().inner)
            .collect::<js_sys::Array>();

        self.inner.submit(&array);

        0
    }

    fn get_timestamp_period(&self) -> f32 {
        // Timestamp values are always in nanoseconds, see https://gpuweb.github.io/gpuweb/#timestamp
        1.0
    }

    fn on_submitted_work_done(&self, callback: dispatch::BoxSubmittedWorkDoneCallback) {
        let promise = self.inner.on_submitted_work_done();
        wasm_bindgen_futures::spawn_local(async move {
            match wasm_bindgen_futures::JsFuture::from(promise).await {
                Ok(_) => callback(),
                Err(error) => {
                    log::error!("on_submitted_work_done promise failed: {error:?}");
                    callback();
                }
            }
        });
    }

    fn compact_blas(
        &self,
        _blas: &dispatch::DispatchBlas,
    ) -> (Option<u64>, dispatch::DispatchBlas) {
        unimplemented!("Raytracing not implemented for web")
    }
}
impl Drop for WebQueue {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::ShaderModuleInterface for WebShaderModule {
    fn get_compilation_info(&self) -> Pin<Box<dyn dispatch::ShaderCompilationInfoFuture>> {
        let compilation_info_promise = self.module.get_compilation_info();
        let map_future = Box::new({
            let compilation_info = self.compilation_info.clone();
            move |result| future_compilation_info(result, &compilation_info)
        });
        Box::pin(MakeSendFuture::new(
            wasm_bindgen_futures::JsFuture::from(compilation_info_promise),
            map_future,
        ))
    }
}
impl Drop for WebShaderModule {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::BindGroupLayoutInterface for WebBindGroupLayout {}
impl Drop for WebBindGroupLayout {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::BindGroupInterface for WebBindGroup {}
impl Drop for WebBindGroup {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::TextureViewInterface for WebTextureView {}
impl Drop for WebTextureView {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::SamplerInterface for WebSampler {}
impl Drop for WebSampler {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::BufferInterface for WebBuffer {
    fn map_async(
        &self,
        mode: crate::MapMode,
        range: Range<crate::BufferAddress>,
        callback: dispatch::BufferMapCallback,
    ) {
        let map_promise = self.inner.map_async_with_f64_and_f64(
            map_map_mode(mode),
            range.start as f64,
            (range.end - range.start) as f64,
        );

        self.set_mapped_range(range);

        register_then_closures(&map_promise, callback, Ok(()), Err(crate::BufferAsyncError));
    }

    fn get_mapped_range(
        &self,
        sub_range: Range<crate::BufferAddress>,
    ) -> dispatch::DispatchBufferMappedRange {
        let actual_mapping = self.get_mapped_range(sub_range);
        WebBufferMappedRange {
            actual_mapping,
            temporary_mapping: OnceCell::new(),
            temporary_mapping_modified: false,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn unmap(&self) {
        self.inner.unmap();
        self.mapping.borrow_mut().mapped_buffer = None;
    }

    fn destroy(&self) {
        self.inner.destroy();
    }
}
impl Drop for WebBuffer {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::TextureInterface for WebTexture {
    fn create_view(
        &self,
        desc: &crate::TextureViewDescriptor<'_>,
    ) -> dispatch::DispatchTextureView {
        let mapped = webgpu_sys::GpuTextureViewDescriptor::new();
        if let Some(dim) = desc.dimension {
            mapped.set_dimension(map_texture_view_dimension(dim));
        }
        if let Some(format) = desc.format {
            mapped.set_format(map_texture_format(format));
        }
        mapped.set_aspect(map_texture_aspect(desc.aspect));
        mapped.set_base_array_layer(desc.base_array_layer);
        if let Some(count) = desc.array_layer_count {
            mapped.set_array_layer_count(count);
        }
        mapped.set_base_mip_level(desc.base_mip_level);
        if let Some(count) = desc.mip_level_count {
            mapped.set_mip_level_count(count);
        }
        if let Some(label) = desc.label {
            mapped.set_label(label);
        }
        mapped.set_usage(desc.usage.unwrap_or(wgt::TextureUsages::empty()).bits());

        let view = self.inner.create_view_with_descriptor(&mapped).unwrap();

        WebTextureView {
            inner: view,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn destroy(&self) {
        self.inner.destroy();
    }
}
impl Drop for WebTexture {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::ExternalTextureInterface for WebExternalTexture {
    fn destroy(&self) {
        unimplemented!("ExternalTexture not implemented for web");
    }
}
impl Drop for WebExternalTexture {
    fn drop(&mut self) {
        unimplemented!("ExternalTexture not implemented for web");
    }
}

impl dispatch::BlasInterface for WebBlas {
    fn prepare_compact_async(&self, _callback: BlasCompactCallback) {
        unimplemented!("Raytracing not implemented for web")
    }
    fn ready_for_compaction(&self) -> bool {
        unimplemented!("Raytracing not implemented for web")
    }
}
impl Drop for WebBlas {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::TlasInterface for WebTlas {}
impl Drop for WebTlas {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::QuerySetInterface for WebQuerySet {}
impl Drop for WebQuerySet {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::PipelineLayoutInterface for WebPipelineLayout {}
impl Drop for WebPipelineLayout {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::RenderPipelineInterface for WebRenderPipeline {
    fn get_bind_group_layout(&self, index: u32) -> dispatch::DispatchBindGroupLayout {
        let bind_group_layout = self.inner.get_bind_group_layout(index);

        WebBindGroupLayout {
            inner: bind_group_layout,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }
}
impl Drop for WebRenderPipeline {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::ComputePipelineInterface for WebComputePipeline {
    fn get_bind_group_layout(&self, index: u32) -> dispatch::DispatchBindGroupLayout {
        let bind_group_layout = self.inner.get_bind_group_layout(index);

        WebBindGroupLayout {
            inner: bind_group_layout,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }
}
impl Drop for WebComputePipeline {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::CommandEncoderInterface for WebCommandEncoder {
    fn copy_buffer_to_buffer(
        &self,
        source: &dispatch::DispatchBuffer,
        source_offset: crate::BufferAddress,
        destination: &dispatch::DispatchBuffer,
        destination_offset: crate::BufferAddress,
        copy_size: Option<crate::BufferAddress>,
    ) {
        let source = source.as_webgpu();
        let destination = destination.as_webgpu();

        if let Some(size) = copy_size {
            self.inner
                .copy_buffer_to_buffer_with_f64_and_f64_and_f64(
                    &source.inner,
                    source_offset as f64,
                    &destination.inner,
                    destination_offset as f64,
                    size as f64,
                )
                .unwrap();
        } else {
            self.inner
                .copy_buffer_to_buffer_with_f64_and_f64(
                    &source.inner,
                    source_offset as f64,
                    &destination.inner,
                    destination_offset as f64,
                )
                .unwrap();
        }
    }

    fn copy_buffer_to_texture(
        &self,
        source: crate::TexelCopyBufferInfo<'_>,
        destination: crate::TexelCopyTextureInfo<'_>,
        copy_size: crate::Extent3d,
    ) {
        self.inner
            .copy_buffer_to_texture_with_gpu_extent_3d_dict(
                &map_buffer_copy_view(source),
                &map_texture_copy_view(destination),
                &map_extent_3d(copy_size),
            )
            .unwrap();
    }

    fn copy_texture_to_buffer(
        &self,
        source: crate::TexelCopyTextureInfo<'_>,
        destination: crate::TexelCopyBufferInfo<'_>,
        copy_size: crate::Extent3d,
    ) {
        self.inner
            .copy_texture_to_buffer_with_gpu_extent_3d_dict(
                &map_texture_copy_view(source),
                &map_buffer_copy_view(destination),
                &map_extent_3d(copy_size),
            )
            .unwrap();
    }

    fn copy_texture_to_texture(
        &self,
        source: crate::TexelCopyTextureInfo<'_>,
        destination: crate::TexelCopyTextureInfo<'_>,
        copy_size: crate::Extent3d,
    ) {
        self.inner
            .copy_texture_to_texture_with_gpu_extent_3d_dict(
                &map_texture_copy_view(source),
                &map_texture_copy_view(destination),
                &map_extent_3d(copy_size),
            )
            .unwrap();
    }

    fn begin_compute_pass(
        &self,
        desc: &crate::ComputePassDescriptor<'_>,
    ) -> dispatch::DispatchComputePass {
        let mapped_desc = webgpu_sys::GpuComputePassDescriptor::new();
        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }

        if let Some(ref timestamp_writes) = desc.timestamp_writes {
            let query_set = timestamp_writes.query_set.inner.as_webgpu();
            let writes = webgpu_sys::GpuComputePassTimestampWrites::new(&query_set.inner);
            if let Some(index) = timestamp_writes.beginning_of_pass_write_index {
                writes.set_beginning_of_pass_write_index(index);
            }
            if let Some(index) = timestamp_writes.end_of_pass_write_index {
                writes.set_end_of_pass_write_index(index);
            }
            mapped_desc.set_timestamp_writes(&writes);
        }

        let compute_pass = self.inner.begin_compute_pass_with_descriptor(&mapped_desc);

        WebComputePassEncoder {
            inner: compute_pass,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn begin_render_pass(
        &self,
        desc: &crate::RenderPassDescriptor<'_>,
    ) -> dispatch::DispatchRenderPass {
        let mapped_color_attachments = desc
            .color_attachments
            .iter()
            .map(|attachment| match attachment {
                Some(ca) => {
                    let mut clear_value: Option<wasm_bindgen::JsValue> = None;
                    let load_value = match ca.ops.load {
                        crate::LoadOp::Clear(color) => {
                            clear_value = Some(wasm_bindgen::JsValue::from(map_color(color)));
                            webgpu_sys::GpuLoadOp::Clear
                        }
                        crate::LoadOp::Load => webgpu_sys::GpuLoadOp::Load,
                    };

                    let view = &ca.view.inner.as_webgpu().inner;

                    let mapped_color_attachment = webgpu_sys::GpuRenderPassColorAttachment::new(
                        load_value,
                        map_store_op(ca.ops.store),
                        view,
                    );
                    if let Some(cv) = clear_value {
                        mapped_color_attachment.set_clear_value(&cv);
                    }
                    if let Some(rt) = ca.resolve_target {
                        let resolve_target_view = &rt.inner.as_webgpu().inner;
                        mapped_color_attachment.set_resolve_target(resolve_target_view);
                    }
                    mapped_color_attachment.set_store_op(map_store_op(ca.ops.store));

                    wasm_bindgen::JsValue::from(mapped_color_attachment)
                }
                None => wasm_bindgen::JsValue::null(),
            })
            .collect::<js_sys::Array>();

        let mapped_desc = webgpu_sys::GpuRenderPassDescriptor::new(&mapped_color_attachments);

        if let Some(label) = desc.label {
            mapped_desc.set_label(label);
        }

        if let Some(dsa) = &desc.depth_stencil_attachment {
            let depth_stencil_attachment = &dsa.view.inner.as_webgpu().inner;
            let mapped_depth_stencil_attachment =
                webgpu_sys::GpuRenderPassDepthStencilAttachment::new(depth_stencil_attachment);
            if let Some(ref ops) = dsa.depth_ops {
                let load_op = match ops.load {
                    crate::LoadOp::Clear(v) => {
                        mapped_depth_stencil_attachment.set_depth_clear_value(v);
                        webgpu_sys::GpuLoadOp::Clear
                    }
                    crate::LoadOp::Load => webgpu_sys::GpuLoadOp::Load,
                };
                mapped_depth_stencil_attachment.set_depth_load_op(load_op);
                mapped_depth_stencil_attachment.set_depth_store_op(map_store_op(ops.store));
            }
            mapped_depth_stencil_attachment.set_depth_read_only(dsa.depth_ops.is_none());
            if let Some(ref ops) = dsa.stencil_ops {
                let load_op = match ops.load {
                    crate::LoadOp::Clear(v) => {
                        mapped_depth_stencil_attachment.set_stencil_clear_value(v);
                        webgpu_sys::GpuLoadOp::Clear
                    }
                    crate::LoadOp::Load => webgpu_sys::GpuLoadOp::Load,
                };
                mapped_depth_stencil_attachment.set_stencil_load_op(load_op);
                mapped_depth_stencil_attachment.set_stencil_store_op(map_store_op(ops.store));
            }
            mapped_depth_stencil_attachment.set_stencil_read_only(dsa.stencil_ops.is_none());
            mapped_desc.set_depth_stencil_attachment(&mapped_depth_stencil_attachment);
        }

        if let Some(ref timestamp_writes) = desc.timestamp_writes {
            let query_set = &timestamp_writes.query_set.inner.as_webgpu().inner;
            let writes = webgpu_sys::GpuRenderPassTimestampWrites::new(query_set);
            if let Some(index) = timestamp_writes.beginning_of_pass_write_index {
                writes.set_beginning_of_pass_write_index(index);
            }
            if let Some(index) = timestamp_writes.end_of_pass_write_index {
                writes.set_end_of_pass_write_index(index);
            }
            mapped_desc.set_timestamp_writes(&writes);
        }

        let render_pass = self.inner.begin_render_pass(&mapped_desc).unwrap();

        WebRenderPassEncoder {
            inner: render_pass,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn finish(&mut self) -> dispatch::DispatchCommandBuffer {
        let label = self.inner.label();
        let buffer = if label.is_empty() {
            self.inner.finish()
        } else {
            let mapped_desc = webgpu_sys::GpuCommandBufferDescriptor::new();
            mapped_desc.set_label(&label);

            self.inner.finish_with_descriptor(&mapped_desc)
        };

        WebCommandBuffer {
            inner: buffer,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }

    fn clear_texture(
        &self,
        _texture: &dispatch::DispatchTexture,
        _subresource_range: &crate::ImageSubresourceRange,
    ) {
        unimplemented!("clear_texture is not yet implemented");
    }

    fn clear_buffer(
        &self,
        buffer: &dispatch::DispatchBuffer,
        offset: crate::BufferAddress,
        size: Option<crate::BufferAddress>,
    ) {
        let buffer = buffer.as_webgpu();

        match size {
            Some(size) => {
                self.inner
                    .clear_buffer_with_f64_and_f64(&buffer.inner, offset as f64, size as f64)
            }
            None => self
                .inner
                .clear_buffer_with_f64(&buffer.inner, offset as f64),
        }
    }

    fn insert_debug_marker(&self, _label: &str) {
        // Not available in gecko yet
        // self.insert_debug_marker(label);
    }

    fn push_debug_group(&self, _label: &str) {
        // Not available in gecko yet
        // self.push_debug_group(label);
    }

    fn pop_debug_group(&self) {
        // Not available in gecko yet
        // self.pop_debug_group();
    }

    fn write_timestamp(&self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32) {
        // Not available on WebGPU.
        // This was part of the spec originally but got removed, see https://github.com/gpuweb/gpuweb/pull/4370
        panic!("TIMESTAMP_QUERY_INSIDE_ENCODERS feature must be enabled to call write_timestamp on a command encoder.")
    }

    fn resolve_query_set(
        &self,
        query_set: &dispatch::DispatchQuerySet,
        first_query: u32,
        query_count: u32,
        destination: &dispatch::DispatchBuffer,
        destination_offset: crate::BufferAddress,
    ) {
        let query_set = &query_set.as_webgpu().inner;
        let destination = &destination.as_webgpu().inner;

        self.inner.resolve_query_set_with_u32(
            query_set,
            first_query,
            query_count,
            destination,
            destination_offset as u32,
        );
    }

    fn mark_acceleration_structures_built<'a>(
        &self,
        _blas: &mut dyn Iterator<Item = &'a Blas>,
        _tlas: &mut dyn Iterator<Item = &'a Tlas>,
    ) {
        unimplemented!("Raytracing not implemented for web");
    }

    fn build_acceleration_structures<'a>(
        &self,
        _blas: &mut dyn Iterator<Item = &'a crate::BlasBuildEntry<'a>>,
        _tlas: &mut dyn Iterator<Item = &'a crate::Tlas>,
    ) {
        unimplemented!("Raytracing not implemented for web");
    }

    fn transition_resources<'a>(
        &mut self,
        _buffer_transitions: &mut dyn Iterator<
            Item = wgt::BufferTransition<&'a dispatch::DispatchBuffer>,
        >,
        _texture_transitions: &mut dyn Iterator<
            Item = wgt::TextureTransition<&'a dispatch::DispatchTexture>,
        >,
    ) {
        // no-op
    }
}
impl Drop for WebCommandEncoder {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::PipelineCacheInterface for WebPipelineCache {
    fn get_data(&self) -> Option<Vec<u8>> {
        todo!()
    }
}
impl Drop for WebPipelineCache {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::ComputePassInterface for WebComputePassEncoder {
    fn set_pipeline(&mut self, pipeline: &dispatch::DispatchComputePipeline) {
        let pipeline = &pipeline.as_webgpu().inner;
        self.inner.set_pipeline(pipeline);
    }

    fn set_bind_group(
        &mut self,
        index: u32,
        bind_group: Option<&dispatch::DispatchBindGroup>,
        offsets: &[crate::DynamicOffset],
    ) {
        let Some(bind_group) = bind_group else {
            return;
        };
        let bind_group = &bind_group.as_webgpu().inner;

        if offsets.is_empty() {
            self.inner.set_bind_group(index, Some(bind_group));
        } else {
            self.inner
                .set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length(
                    index,
                    Some(bind_group),
                    offsets,
                    0f64,
                    offsets.len() as u32,
                )
                .unwrap();
        }
    }

    fn set_push_constants(&mut self, _offset: u32, _data: &[u8]) {
        panic!("PUSH_CONSTANTS feature must be enabled to call multi_draw_indexed_indirect")
    }

    fn insert_debug_marker(&mut self, _label: &str) {
        // Not available in gecko yet
        // self.inner.insert_debug_marker(label);
    }

    fn push_debug_group(&mut self, _group_label: &str) {
        // Not available in gecko yet
        // self.inner.push_debug_group(group_label);
    }

    fn pop_debug_group(&mut self) {
        // Not available in gecko yet
        // self.inner.pop_debug_group();
    }

    fn write_timestamp(&mut self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32) {
        panic!("TIMESTAMP_QUERY_INSIDE_PASSES feature must be enabled to call write_timestamp in a compute pass.")
    }

    fn begin_pipeline_statistics_query(
        &mut self,
        _query_set: &dispatch::DispatchQuerySet,
        _query_index: u32,
    ) {
        // Not available in gecko yet
    }

    fn end_pipeline_statistics_query(&mut self) {
        // Not available in gecko yet
    }

    fn dispatch_workgroups(&mut self, x: u32, y: u32, z: u32) {
        self.inner
            .dispatch_workgroups_with_workgroup_count_y_and_workgroup_count_z(x, y, z);
    }

    fn dispatch_workgroups_indirect(
        &mut self,
        indirect_buffer: &dispatch::DispatchBuffer,
        indirect_offset: crate::BufferAddress,
    ) {
        let indirect_buffer = indirect_buffer.as_webgpu();

        self.inner
            .dispatch_workgroups_indirect_with_f64(&indirect_buffer.inner, indirect_offset as f64);
    }

    fn end(&mut self) {
        self.inner.end();
    }
}
impl Drop for WebComputePassEncoder {
    fn drop(&mut self) {
        dispatch::ComputePassInterface::end(self);
    }
}

impl dispatch::RenderPassInterface for WebRenderPassEncoder {
    fn set_pipeline(&mut self, pipeline: &dispatch::DispatchRenderPipeline) {
        let pipeline = &pipeline.as_webgpu().inner;

        self.inner.set_pipeline(pipeline);
    }

    fn set_bind_group(
        &mut self,
        index: u32,
        bind_group: Option<&dispatch::DispatchBindGroup>,
        offsets: &[crate::DynamicOffset],
    ) {
        let Some(bind_group) = bind_group else {
            return;
        };
        let bind_group = &bind_group.as_webgpu().inner;

        if offsets.is_empty() {
            self.inner.set_bind_group(index, Some(bind_group));
        } else {
            self.inner
                .set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length(
                    index,
                    Some(bind_group),
                    offsets,
                    0f64,
                    offsets.len() as u32,
                )
                .unwrap();
        }
    }

    fn set_index_buffer(
        &mut self,
        buffer: &dispatch::DispatchBuffer,
        index_format: crate::IndexFormat,
        offset: crate::BufferAddress,
        size: Option<crate::BufferSize>,
    ) {
        let buffer = buffer.as_webgpu();
        let index_format = map_index_format(index_format);

        if let Some(size) = size {
            self.inner.set_index_buffer_with_f64_and_f64(
                &buffer.inner,
                index_format,
                offset as f64,
                size.get() as f64,
            );
        } else {
            self.inner
                .set_index_buffer_with_f64(&buffer.inner, index_format, offset as f64);
        }
    }

    fn set_vertex_buffer(
        &mut self,
        slot: u32,
        buffer: &dispatch::DispatchBuffer,
        offset: crate::BufferAddress,
        size: Option<crate::BufferSize>,
    ) {
        let buffer = buffer.as_webgpu();

        if let Some(size) = size {
            self.inner.set_vertex_buffer_with_f64_and_f64(
                slot,
                Some(&buffer.inner),
                offset as f64,
                size.get() as f64,
            );
        } else {
            self.inner
                .set_vertex_buffer_with_f64(slot, Some(&buffer.inner), offset as f64);
        }
    }

    fn set_push_constants(&mut self, _stages: crate::ShaderStages, _offset: u32, _data: &[u8]) {
        panic!("PUSH_CONSTANTS feature must be enabled to call multi_draw_indexed_indirect")
    }

    fn set_blend_constant(&mut self, color: crate::Color) {
        self.inner
            .set_blend_constant_with_gpu_color_dict(&map_color(color))
            .unwrap();
    }

    fn set_scissor_rect(&mut self, x: u32, y: u32, width: u32, height: u32) {
        self.inner.set_scissor_rect(x, y, width, height);
    }

    fn set_viewport(
        &mut self,
        x: f32,
        y: f32,
        width: f32,
        height: f32,
        min_depth: f32,
        max_depth: f32,
    ) {
        self.inner
            .set_viewport(x, y, width, height, min_depth, max_depth);
    }

    fn set_stencil_reference(&mut self, reference: u32) {
        self.inner.set_stencil_reference(reference);
    }

    fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
        self.inner
            .draw_with_instance_count_and_first_vertex_and_first_instance(
                vertices.end - vertices.start,
                instances.end - instances.start,
                vertices.start,
                instances.start,
            );
    }

    fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
        self.inner
            .draw_indexed_with_instance_count_and_first_index_and_base_vertex_and_first_instance(
                indices.end - indices.start,
                instances.end - instances.start,
                indices.start,
                base_vertex,
                instances.start,
            )
    }

    fn draw_mesh_tasks(&mut self, _group_count_x: u32, _group_count_y: u32, _group_count_z: u32) {
        panic!("MESH_SHADER feature must be enabled to call draw_mesh_tasks")
    }

    fn draw_indirect(
        &mut self,
        indirect_buffer: &dispatch::DispatchBuffer,
        indirect_offset: crate::BufferAddress,
    ) {
        let buffer = indirect_buffer.as_webgpu();
        self.inner
            .draw_indirect_with_f64(&buffer.inner, indirect_offset as f64);
    }

    fn draw_indexed_indirect(
        &mut self,
        indirect_buffer: &dispatch::DispatchBuffer,
        indirect_offset: crate::BufferAddress,
    ) {
        let buffer = indirect_buffer.as_webgpu();
        self.inner
            .draw_indexed_indirect_with_f64(&buffer.inner, indirect_offset as f64);
    }

    fn draw_mesh_tasks_indirect(
        &mut self,
        _indirect_buffer: &dispatch::DispatchBuffer,
        _indirect_offset: crate::BufferAddress,
    ) {
        panic!("MESH_SHADER feature must be enabled to call draw_mesh_tasks_indirect")
    }

    fn multi_draw_indirect(
        &mut self,
        indirect_buffer: &dispatch::DispatchBuffer,
        indirect_offset: crate::BufferAddress,
        count: u32,
    ) {
        let buffer = indirect_buffer.as_webgpu();

        for i in 0..count {
            let offset = indirect_offset + i as crate::BufferAddress * 16;
            self.inner
                .draw_indirect_with_f64(&buffer.inner, offset as f64);
        }
    }

    fn multi_draw_indexed_indirect(
        &mut self,
        indirect_buffer: &dispatch::DispatchBuffer,
        indirect_offset: crate::BufferAddress,
        count: u32,
    ) {
        let buffer = indirect_buffer.as_webgpu();

        for i in 0..count {
            let offset = indirect_offset + i as crate::BufferAddress * 20;
            self.inner
                .draw_indexed_indirect_with_f64(&buffer.inner, offset as f64);
        }
    }

    fn multi_draw_mesh_tasks_indirect(
        &mut self,
        _indirect_buffer: &dispatch::DispatchBuffer,
        _indirect_offset: crate::BufferAddress,
        _count: u32,
    ) {
        panic!("MESH_SHADER feature must be enabled to call multi_draw_mesh_tasks_indirect")
    }

    fn multi_draw_indirect_count(
        &mut self,
        _indirect_buffer: &dispatch::DispatchBuffer,
        _indirect_offset: crate::BufferAddress,
        _count_buffer: &dispatch::DispatchBuffer,
        _count_buffer_offset: crate::BufferAddress,
        _max_count: u32,
    ) {
        panic!(
            "MULTI_DRAW_INDIRECT_COUNT feature must be enabled to call multi_draw_indirect_count"
        )
    }

    fn multi_draw_indexed_indirect_count(
        &mut self,
        _indirect_buffer: &dispatch::DispatchBuffer,
        _indirect_offset: crate::BufferAddress,
        _count_buffer: &dispatch::DispatchBuffer,
        _count_buffer_offset: crate::BufferAddress,
        _max_count: u32,
    ) {
        panic!("MULTI_DRAW_INDIRECT_COUNT feature must be enabled to call multi_draw_indexed_indirect_count")
    }

    fn multi_draw_mesh_tasks_indirect_count(
        &mut self,
        _indirect_buffer: &dispatch::DispatchBuffer,
        _indirect_offset: crate::BufferAddress,
        _count_buffer: &dispatch::DispatchBuffer,
        _count_buffer_offset: crate::BufferAddress,
        _max_count: u32,
    ) {
        panic!("MESH_SHADER feature must be enabled to call multi_draw_mesh_tasks_indirect_count")
    }

    fn insert_debug_marker(&mut self, _label: &str) {
        // Not available in gecko yet
        // self.inner.insert_debug_marker(label);
    }

    fn push_debug_group(&mut self, _group_label: &str) {
        // Not available in gecko yet
        // self.inner.push_debug_group(group_label);
    }

    fn pop_debug_group(&mut self) {
        // Not available in gecko yet
        // self.inner.pop_debug_group();
    }

    fn write_timestamp(&mut self, _query_set: &dispatch::DispatchQuerySet, _query_index: u32) {
        panic!("TIMESTAMP_QUERY_INSIDE_PASSES feature must be enabled to call write_timestamp in a render pass.")
    }

    fn begin_occlusion_query(&mut self, _query_index: u32) {
        // Not available in gecko yet
        // self.inner.begin_occlusion_query(query_index);
    }

    fn end_occlusion_query(&mut self) {
        // Not available in gecko yet
        // self.inner.end_occlusion_query();
    }

    fn begin_pipeline_statistics_query(
        &mut self,
        _query_set: &dispatch::DispatchQuerySet,
        _query_index: u32,
    ) {
        // Not available in gecko yet
        // let query_set = query_set.as_webgpu();
        // self.inner.begin_pipeline_statistics_query(query_set, query_index);
    }

    fn end_pipeline_statistics_query(&mut self) {
        // Not available in gecko yet
        // self.inner.end_pipeline_statistics_query();
    }

    fn execute_bundles(
        &mut self,
        render_bundles: &mut dyn Iterator<Item = &dispatch::DispatchRenderBundle>,
    ) {
        let mapped = render_bundles
            .map(|bundle| &bundle.as_webgpu().inner)
            .collect::<js_sys::Array>();
        self.inner.execute_bundles(&mapped);
    }

    fn end(&mut self) {
        self.inner.end();
    }
}
impl Drop for WebRenderPassEncoder {
    fn drop(&mut self) {
        dispatch::RenderPassInterface::end(self);
    }
}

impl dispatch::CommandBufferInterface for WebCommandBuffer {}
impl Drop for WebCommandBuffer {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::RenderBundleEncoderInterface for WebRenderBundleEncoder {
    fn set_pipeline(&mut self, pipeline: &dispatch::DispatchRenderPipeline) {
        let pipeline = &pipeline.as_webgpu().inner;
        self.inner.set_pipeline(pipeline);
    }

    fn set_bind_group(
        &mut self,
        index: u32,
        bind_group: Option<&dispatch::DispatchBindGroup>,
        offsets: &[crate::DynamicOffset],
    ) {
        let Some(bind_group) = bind_group else {
            return;
        };
        let bind_group = &bind_group.as_webgpu().inner;

        if offsets.is_empty() {
            self.inner.set_bind_group(index, Some(bind_group));
        } else {
            self.inner
                .set_bind_group_with_u32_slice_and_f64_and_dynamic_offsets_data_length(
                    index,
                    Some(bind_group),
                    offsets,
                    0f64,
                    offsets.len() as u32,
                )
                .unwrap();
        }
    }

    fn set_index_buffer(
        &mut self,
        buffer: &dispatch::DispatchBuffer,
        index_format: crate::IndexFormat,
        offset: crate::BufferAddress,
        size: Option<crate::BufferSize>,
    ) {
        let buffer = buffer.as_webgpu();
        let index_format = map_index_format(index_format);

        if let Some(size) = size {
            self.inner.set_index_buffer_with_f64_and_f64(
                &buffer.inner,
                index_format,
                offset as f64,
                size.get() as f64,
            );
        } else {
            self.inner
                .set_index_buffer_with_f64(&buffer.inner, index_format, offset as f64);
        }
    }

    fn set_vertex_buffer(
        &mut self,
        slot: u32,
        buffer: &dispatch::DispatchBuffer,
        offset: crate::BufferAddress,
        size: Option<crate::BufferSize>,
    ) {
        let buffer = buffer.as_webgpu();

        if let Some(size) = size {
            self.inner.set_vertex_buffer_with_f64_and_f64(
                slot,
                Some(&buffer.inner),
                offset as f64,
                size.get() as f64,
            );
        } else {
            self.inner
                .set_vertex_buffer_with_f64(slot, Some(&buffer.inner), offset as f64);
        }
    }

    fn set_push_constants(&mut self, _stages: crate::ShaderStages, _offset: u32, _data: &[u8]) {
        panic!("PUSH_CONSTANTS feature must be enabled to call multi_draw_indexed_indirect")
    }

    fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
        self.inner
            .draw_with_instance_count_and_first_vertex_and_first_instance(
                vertices.end - vertices.start,
                instances.end - instances.start,
                vertices.start,
                instances.start,
            );
    }

    fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
        self.inner
            .draw_indexed_with_instance_count_and_first_index_and_base_vertex_and_first_instance(
                indices.end - indices.start,
                instances.end - instances.start,
                indices.start,
                base_vertex,
                instances.start,
            )
    }

    fn draw_indirect(
        &mut self,
        indirect_buffer: &dispatch::DispatchBuffer,
        indirect_offset: crate::BufferAddress,
    ) {
        let buffer = indirect_buffer.as_webgpu();
        self.inner
            .draw_indirect_with_f64(&buffer.inner, indirect_offset as f64);
    }

    fn draw_indexed_indirect(
        &mut self,
        indirect_buffer: &dispatch::DispatchBuffer,
        indirect_offset: crate::BufferAddress,
    ) {
        let buffer = indirect_buffer.as_webgpu();
        self.inner
            .draw_indexed_indirect_with_f64(&buffer.inner, indirect_offset as f64);
    }

    fn finish(self, desc: &crate::RenderBundleDescriptor<'_>) -> dispatch::DispatchRenderBundle
    where
        Self: Sized,
    {
        let bundle = match desc.label {
            Some(label) => {
                let mapped_desc = webgpu_sys::GpuRenderBundleDescriptor::new();
                mapped_desc.set_label(label);
                self.inner.finish_with_descriptor(&mapped_desc)
            }
            None => self.inner.finish(),
        };

        WebRenderBundle {
            inner: bundle,
            ident: crate::cmp::Identifier::create(),
        }
        .into()
    }
}
impl Drop for WebRenderBundleEncoder {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::RenderBundleInterface for WebRenderBundle {}
impl Drop for WebRenderBundle {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::SurfaceInterface for WebSurface {
    fn get_capabilities(&self, _adapter: &dispatch::DispatchAdapter) -> wgt::SurfaceCapabilities {
        let mut formats = vec![
            wgt::TextureFormat::Rgba8Unorm,
            wgt::TextureFormat::Bgra8Unorm,
            wgt::TextureFormat::Rgba16Float,
        ];
        let mut mapped_formats = formats.iter().map(|format| map_texture_format(*format));
        // Preferred canvas format will only be either "rgba8unorm" or "bgra8unorm".
        // https://www.w3.org/TR/webgpu/#dom-gpu-getpreferredcanvasformat
        let preferred_format = self
            .gpu
            .as_ref()
            .expect("Caller could not have created an adapter if gpu is undefined.")
            .get_preferred_canvas_format();
        if let Some(index) = mapped_formats.position(|format| format == preferred_format) {
            formats.swap(0, index);
        }

        wgt::SurfaceCapabilities {
            // https://gpuweb.github.io/gpuweb/#supported-context-formats
            formats,
            // Doesn't really have meaning on the web.
            present_modes: vec![wgt::PresentMode::Fifo],
            alpha_modes: vec![wgt::CompositeAlphaMode::Opaque],
            // Statically set to RENDER_ATTACHMENT for now. See https://gpuweb.github.io/gpuweb/#dom-gpucanvasconfiguration-usage
            usages: wgt::TextureUsages::RENDER_ATTACHMENT,
        }
    }

    fn configure(&self, device: &dispatch::DispatchDevice, config: &crate::SurfaceConfiguration) {
        let device = device.as_webgpu();

        match self.canvas {
            Canvas::Canvas(ref canvas) => {
                canvas.set_width(config.width);
                canvas.set_height(config.height);
            }
            Canvas::Offscreen(ref canvas) => {
                canvas.set_width(config.width);
                canvas.set_height(config.height);
            }
        }

        if let wgt::PresentMode::Mailbox | wgt::PresentMode::Immediate = config.present_mode {
            panic!("Only FIFO/Auto* is supported on web");
        }
        if let wgt::CompositeAlphaMode::PostMultiplied | wgt::CompositeAlphaMode::Inherit =
            config.alpha_mode
        {
            panic!("Only Opaque/Auto or PreMultiplied alpha mode are supported on web");
        }
        let alpha_mode = match config.alpha_mode {
            wgt::CompositeAlphaMode::PreMultiplied => webgpu_sys::GpuCanvasAlphaMode::Premultiplied,
            _ => webgpu_sys::GpuCanvasAlphaMode::Opaque,
        };
        let mapped = webgpu_sys::GpuCanvasConfiguration::new(
            &device.inner,
            map_texture_format(config.format),
        );
        mapped.set_usage(config.usage.bits());
        mapped.set_alpha_mode(alpha_mode);
        let mapped_view_formats = config
            .view_formats
            .iter()
            .map(|format| JsValue::from(map_texture_format(*format)))
            .collect::<js_sys::Array>();
        mapped.set_view_formats(&mapped_view_formats);
        self.context.configure(&mapped).unwrap();
    }

    fn get_current_texture(
        &self,
    ) -> (
        Option<dispatch::DispatchTexture>,
        crate::SurfaceStatus,
        dispatch::DispatchSurfaceOutputDetail,
    ) {
        let surface_texture = self.context.get_current_texture().unwrap();

        let web_surface_texture = WebTexture {
            inner: surface_texture,
            ident: crate::cmp::Identifier::create(),
        };

        (
            Some(web_surface_texture.into()),
            crate::SurfaceStatus::Good,
            WebSurfaceOutputDetail {
                ident: crate::cmp::Identifier::create(),
            }
            .into(),
        )
    }
}
impl Drop for WebSurface {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::SurfaceOutputDetailInterface for WebSurfaceOutputDetail {
    fn present(&self) {
        // Swapchain is presented automatically on the web.
    }

    fn texture_discard(&self) {
        // Can't really discard the texture on the web.
    }
}
impl Drop for WebSurfaceOutputDetail {
    fn drop(&mut self) {
        // no-op
    }
}

impl dispatch::BufferMappedRangeInterface for WebBufferMappedRange {
    #[inline]
    fn slice(&self) -> &[u8] {
        self.temporary_mapping
            .get_or_init(|| self.actual_mapping.to_vec())
            .as_slice()
    }

    #[inline]
    fn slice_mut(&mut self) -> &mut [u8] {
        self.temporary_mapping_modified = true;
        self.temporary_mapping
            .get_or_init(|| self.actual_mapping.to_vec());
        self.temporary_mapping.get_mut().unwrap()
    }

    #[inline]
    fn as_uint8array(&self) -> &js_sys::Uint8Array {
        &self.actual_mapping
    }
}
impl Drop for WebBufferMappedRange {
    fn drop(&mut self) {
        if !self.temporary_mapping_modified {
            // For efficiency, skip the copy if it is not needed.
            // This is also how we skip copying back on *read-only* mappings.
            return;
        }

        // Copy from the temporary mapping back into the array buffer that was
        // originally provided by the browser
        let temporary_mapping_slice = self.temporary_mapping.get().unwrap().as_slice();
        unsafe {
            // Note: no allocations can happen between `view` and `set`, or this
            // will break
            self.actual_mapping
                .set(&js_sys::Uint8Array::view(temporary_mapping_slice), 0);
        }
    }
}

impl dispatch::QueueWriteBufferInterface for WebQueueWriteBuffer {
    fn slice(&self) -> &[u8] {
        &self.inner
    }

    #[inline]
    fn slice_mut(&mut self) -> &mut [u8] {
        &mut self.inner
    }
}
impl Drop for WebQueueWriteBuffer {
    fn drop(&mut self) {
        // The api struct calls write_staging_buffer

        // no-op
    }
}

/// Adds the constants map to the given pipeline descriptor if the map is nonempty.
/// Panics if the map cannot be set.
///
/// This function is necessary because the constants array is not currently
/// exposed by `wasm-bindgen`. See the following issues for details:
/// - [gfx-rs/wgpu#5688](https://github.com/gfx-rs/wgpu/pull/5688)
/// - [rustwasm/wasm-bindgen#3587](https://github.com/rustwasm/wasm-bindgen/issues/3587)
fn insert_constants_map(target: &JsValue, map: &[(&str, f64)]) {
    if !map.is_empty() {
        js_sys::Reflect::set(
            target,
            &JsValue::from_str("constants"),
            &hashmap_to_jsvalue(map),
        )
        .expect("Setting the values in a Javascript pipeline descriptor should never fail");
    }
}

/// Converts a hashmap to a Javascript object.
fn hashmap_to_jsvalue(map: &[(&str, f64)]) -> JsValue {
    let obj = js_sys::Object::new();

    for &(key, v) in map.iter() {
        js_sys::Reflect::set(&obj, &JsValue::from_str(key), &JsValue::from_f64(v))
            .expect("Setting the values in a Javascript map should never fail");
    }

    JsValue::from(obj)
}
