deno_webgpu 0.101.0

WebGPU implementation for Deno
Documentation
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
use serde::Deserialize;
use std::borrow::Cow;
use std::rc::Rc;

use super::error::WebGpuResult;

pub(crate) struct WebGpuBindGroupLayout(
  pub(crate) crate::Instance,
  pub(crate) wgpu_core::id::BindGroupLayoutId,
);
impl Resource for WebGpuBindGroupLayout {
  fn name(&self) -> Cow<str> {
    "webGPUBindGroupLayout".into()
  }

  fn close(self: Rc<Self>) {
    let instance = &self.0;
    gfx_select!(self.1 => instance.bind_group_layout_drop(self.1));
  }
}

pub(crate) struct WebGpuBindGroup(
  pub(crate) crate::Instance,
  pub(crate) wgpu_core::id::BindGroupId,
);
impl Resource for WebGpuBindGroup {
  fn name(&self) -> Cow<str> {
    "webGPUBindGroup".into()
  }

  fn close(self: Rc<Self>) {
    let instance = &self.0;
    gfx_select!(self.1 => instance.bind_group_drop(self.1));
  }
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuBufferBindingLayout {
  r#type: GpuBufferBindingType,
  has_dynamic_offset: bool,
  min_binding_size: u64,
}

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
enum GpuBufferBindingType {
  Uniform,
  Storage,
  ReadOnlyStorage,
}

impl From<GpuBufferBindingType> for wgpu_types::BufferBindingType {
  fn from(binding_type: GpuBufferBindingType) -> Self {
    match binding_type {
      GpuBufferBindingType::Uniform => wgpu_types::BufferBindingType::Uniform,
      GpuBufferBindingType::Storage => {
        wgpu_types::BufferBindingType::Storage { read_only: false }
      }
      GpuBufferBindingType::ReadOnlyStorage => {
        wgpu_types::BufferBindingType::Storage { read_only: true }
      }
    }
  }
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuSamplerBindingLayout {
  r#type: wgpu_types::SamplerBindingType,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuTextureBindingLayout {
  sample_type: GpuTextureSampleType,
  view_dimension: wgpu_types::TextureViewDimension,
  multisampled: bool,
}

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
enum GpuTextureSampleType {
  Float,
  UnfilterableFloat,
  Depth,
  Sint,
  Uint,
}

impl From<GpuTextureSampleType> for wgpu_types::TextureSampleType {
  fn from(sample_type: GpuTextureSampleType) -> Self {
    match sample_type {
      GpuTextureSampleType::Float => {
        wgpu_types::TextureSampleType::Float { filterable: true }
      }
      GpuTextureSampleType::UnfilterableFloat => {
        wgpu_types::TextureSampleType::Float { filterable: false }
      }
      GpuTextureSampleType::Depth => wgpu_types::TextureSampleType::Depth,
      GpuTextureSampleType::Sint => wgpu_types::TextureSampleType::Sint,
      GpuTextureSampleType::Uint => wgpu_types::TextureSampleType::Uint,
    }
  }
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct GpuStorageTextureBindingLayout {
  access: GpuStorageTextureAccess,
  format: wgpu_types::TextureFormat,
  view_dimension: wgpu_types::TextureViewDimension,
}

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
enum GpuStorageTextureAccess {
  WriteOnly,
}

impl From<GpuStorageTextureAccess> for wgpu_types::StorageTextureAccess {
  fn from(access: GpuStorageTextureAccess) -> Self {
    match access {
      GpuStorageTextureAccess::WriteOnly => {
        wgpu_types::StorageTextureAccess::WriteOnly
      }
    }
  }
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuBindGroupLayoutEntry {
  binding: u32,
  visibility: u32,
  #[serde(flatten)]
  binding_type: GpuBindingType,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
enum GpuBindingType {
  Buffer(GpuBufferBindingLayout),
  Sampler(GpuSamplerBindingLayout),
  Texture(GpuTextureBindingLayout),
  StorageTexture(GpuStorageTextureBindingLayout),
}

impl From<GpuBindingType> for wgpu_types::BindingType {
  fn from(binding_type: GpuBindingType) -> wgpu_types::BindingType {
    match binding_type {
      GpuBindingType::Buffer(buffer) => wgpu_types::BindingType::Buffer {
        ty: buffer.r#type.into(),
        has_dynamic_offset: buffer.has_dynamic_offset,
        min_binding_size: std::num::NonZeroU64::new(buffer.min_binding_size),
      },
      GpuBindingType::Sampler(sampler) => {
        wgpu_types::BindingType::Sampler(sampler.r#type)
      }
      GpuBindingType::Texture(texture) => wgpu_types::BindingType::Texture {
        sample_type: texture.sample_type.into(),
        view_dimension: texture.view_dimension,
        multisampled: texture.multisampled,
      },
      GpuBindingType::StorageTexture(storage_texture) => {
        wgpu_types::BindingType::StorageTexture {
          access: storage_texture.access.into(),
          format: storage_texture.format,
          view_dimension: storage_texture.view_dimension,
        }
      }
    }
  }
}

#[op2]
#[serde]
pub fn op_webgpu_create_bind_group_layout(
  state: &mut OpState,
  #[smi] device_rid: ResourceId,
  #[string] label: Cow<str>,
  #[serde] entries: Vec<GpuBindGroupLayoutEntry>,
) -> Result<WebGpuResult, AnyError> {
  let instance = state.borrow::<super::Instance>();
  let device_resource = state
    .resource_table
    .get::<super::WebGpuDevice>(device_rid)?;
  let device = device_resource.1;

  let entries = entries
    .into_iter()
    .map(|entry| {
      wgpu_types::BindGroupLayoutEntry {
        binding: entry.binding,
        visibility: wgpu_types::ShaderStages::from_bits(entry.visibility)
          .unwrap(),
        ty: entry.binding_type.into(),
        count: None, // native-only
      }
    })
    .collect::<Vec<_>>();

  let descriptor = wgpu_core::binding_model::BindGroupLayoutDescriptor {
    label: Some(label),
    entries: Cow::from(entries),
  };

  gfx_put!(device => instance.device_create_bind_group_layout(
    device,
    &descriptor,
    ()
  ) => state, WebGpuBindGroupLayout)
}

#[op2]
#[serde]
pub fn op_webgpu_create_pipeline_layout(
  state: &mut OpState,
  #[smi] device_rid: ResourceId,
  #[string] label: Cow<str>,
  #[serde] bind_group_layouts: Vec<u32>,
) -> Result<WebGpuResult, AnyError> {
  let instance = state.borrow::<super::Instance>();
  let device_resource = state
    .resource_table
    .get::<super::WebGpuDevice>(device_rid)?;
  let device = device_resource.1;

  let bind_group_layouts = bind_group_layouts
    .into_iter()
    .map(|rid| {
      let bind_group_layout =
        state.resource_table.get::<WebGpuBindGroupLayout>(rid)?;
      Ok(bind_group_layout.1)
    })
    .collect::<Result<Vec<_>, AnyError>>()?;

  let descriptor = wgpu_core::binding_model::PipelineLayoutDescriptor {
    label: Some(label),
    bind_group_layouts: Cow::from(bind_group_layouts),
    push_constant_ranges: Default::default(),
  };

  gfx_put!(device => instance.device_create_pipeline_layout(
    device,
    &descriptor,
    ()
  ) => state, super::pipeline::WebGpuPipelineLayout)
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuBindGroupEntry {
  binding: u32,
  kind: String,
  resource: ResourceId,
  offset: Option<u64>,
  size: Option<u64>,
}

#[op2]
#[serde]
pub fn op_webgpu_create_bind_group(
  state: &mut OpState,
  #[smi] device_rid: ResourceId,
  #[string] label: Cow<str>,
  #[smi] layout: ResourceId,
  #[serde] entries: Vec<GpuBindGroupEntry>,
) -> Result<WebGpuResult, AnyError> {
  let instance = state.borrow::<super::Instance>();
  let device_resource = state
    .resource_table
    .get::<super::WebGpuDevice>(device_rid)?;
  let device = device_resource.1;

  let entries = entries
    .into_iter()
    .map(|entry| {
      Ok(wgpu_core::binding_model::BindGroupEntry {
        binding: entry.binding,
        resource: match entry.kind.as_str() {
          "GPUSampler" => {
            let sampler_resource =
              state
                .resource_table
                .get::<super::sampler::WebGpuSampler>(entry.resource)?;
            wgpu_core::binding_model::BindingResource::Sampler(
              sampler_resource.1,
            )
          }
          "GPUTextureView" => {
            let texture_view_resource =
              state
                .resource_table
                .get::<super::texture::WebGpuTextureView>(entry.resource)?;
            wgpu_core::binding_model::BindingResource::TextureView(
              texture_view_resource.1,
            )
          }
          "GPUBufferBinding" => {
            let buffer_resource =
              state
                .resource_table
                .get::<super::buffer::WebGpuBuffer>(entry.resource)?;
            wgpu_core::binding_model::BindingResource::Buffer(
              wgpu_core::binding_model::BufferBinding {
                buffer_id: buffer_resource.1,
                offset: entry.offset.unwrap_or(0),
                size: std::num::NonZeroU64::new(entry.size.unwrap_or(0)),
              },
            )
          }
          _ => unreachable!(),
        },
      })
    })
    .collect::<Result<Vec<_>, AnyError>>()?;

  let bind_group_layout =
    state.resource_table.get::<WebGpuBindGroupLayout>(layout)?;

  let descriptor = wgpu_core::binding_model::BindGroupDescriptor {
    label: Some(label),
    layout: bind_group_layout.1,
    entries: Cow::from(entries),
  };

  gfx_put!(device => instance.device_create_bind_group(
    device,
    &descriptor,
    ()
  ) => state, WebGpuBindGroup)
}