diff options
author | Aaron O'Mullan <aaron.omullan@gmail.com> | 2022-01-24 23:47:05 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-24 23:47:05 +0100 |
commit | bd5d445da98435d03e2f6a6f6d5478ff623bd714 (patch) | |
tree | a3b43fe0a33048b6eaa4d8adf736545f740bb423 /ext/webgpu/src | |
parent | bc8de78da3c37bb5ce70547a7d3a3576d1a7734f (diff) |
chore: re-enable wgpu_sync (#13453)
Diffstat (limited to 'ext/webgpu/src')
-rw-r--r-- | ext/webgpu/src/binding.rs | 341 | ||||
-rw-r--r-- | ext/webgpu/src/buffer.rs | 229 | ||||
-rw-r--r-- | ext/webgpu/src/bundle.rs | 459 | ||||
-rw-r--r-- | ext/webgpu/src/command_encoder.rs | 646 | ||||
-rw-r--r-- | ext/webgpu/src/compute_pass.rs | 354 | ||||
-rw-r--r-- | ext/webgpu/src/error.rs | 292 | ||||
-rw-r--r-- | ext/webgpu/src/lib.rs | 908 | ||||
-rw-r--r-- | ext/webgpu/src/queue.rs | 142 | ||||
-rw-r--r-- | ext/webgpu/src/render_pass.rs | 649 | ||||
-rw-r--r-- | ext/webgpu/src/shader.rs | 52 |
10 files changed, 4072 insertions, 0 deletions
diff --git a/ext/webgpu/src/binding.rs b/ext/webgpu/src/binding.rs new file mode 100644 index 000000000..9a8fa455f --- /dev/null +++ b/ext/webgpu/src/binding.rs @@ -0,0 +1,341 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::convert::{TryFrom, TryInto}; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuBindGroupLayout( + pub(crate) wgpu_core::id::BindGroupLayoutId, +); +impl Resource for WebGpuBindGroupLayout { + fn name(&self) -> Cow<str> { + "webGPUBindGroupLayout".into() + } +} + +pub(crate) struct WebGpuBindGroup(pub(crate) wgpu_core::id::BindGroupId); +impl Resource for WebGpuBindGroup { + fn name(&self) -> Cow<str> { + "webGPUBindGroup".into() + } +} + +#[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")] +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 TryFrom<GpuBindingType> for wgpu_types::BindingType { + type Error = AnyError; + + fn try_from( + binding_type: GpuBindingType, + ) -> Result<wgpu_types::BindingType, Self::Error> { + let binding_type = 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, + } + } + }; + Ok(binding_type) + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateBindGroupLayoutArgs { + device_rid: ResourceId, + label: Option<String>, + entries: Vec<GpuBindGroupLayoutEntry>, +} + +pub fn op_webgpu_create_bind_group_layout( + state: &mut OpState, + args: CreateBindGroupLayoutArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid)?; + let device = device_resource.0; + + let mut entries = vec![]; + + for entry in args.entries { + entries.push(wgpu_types::BindGroupLayoutEntry { + binding: entry.binding, + visibility: wgpu_types::ShaderStages::from_bits(entry.visibility) + .unwrap(), + ty: entry.binding_type.try_into()?, + count: None, // native-only + }); + } + + let descriptor = wgpu_core::binding_model::BindGroupLayoutDescriptor { + label: args.label.map(Cow::from), + entries: Cow::from(entries), + }; + + gfx_put!(device => instance.device_create_bind_group_layout( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuBindGroupLayout) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreatePipelineLayoutArgs { + device_rid: ResourceId, + label: Option<String>, + bind_group_layouts: Vec<u32>, +} + +pub fn op_webgpu_create_pipeline_layout( + state: &mut OpState, + args: CreatePipelineLayoutArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid)?; + let device = device_resource.0; + + let mut bind_group_layouts = vec![]; + + for rid in &args.bind_group_layouts { + let bind_group_layout = + state.resource_table.get::<WebGpuBindGroupLayout>(*rid)?; + bind_group_layouts.push(bind_group_layout.0); + } + + let descriptor = wgpu_core::binding_model::PipelineLayoutDescriptor { + label: args.label.map(Cow::from), + bind_group_layouts: Cow::from(bind_group_layouts), + push_constant_ranges: Default::default(), + }; + + gfx_put!(device => instance.device_create_pipeline_layout( + device, + &descriptor, + std::marker::PhantomData + ) => state, super::pipeline::WebGpuPipelineLayout) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuBindGroupEntry { + binding: u32, + kind: String, + resource: ResourceId, + offset: Option<u64>, + size: Option<u64>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateBindGroupArgs { + device_rid: ResourceId, + label: Option<String>, + layout: ResourceId, + entries: Vec<GpuBindGroupEntry>, +} + +pub fn op_webgpu_create_bind_group( + state: &mut OpState, + args: CreateBindGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid)?; + let device = device_resource.0; + + let mut entries = vec![]; + + for entry in &args.entries { + let e = 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.0) + } + "GPUTextureView" => { + let texture_view_resource = + state + .resource_table + .get::<super::texture::WebGpuTextureView>(entry.resource)?; + wgpu_core::binding_model::BindingResource::TextureView( + texture_view_resource.0, + ) + } + "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.0, + offset: entry.offset.unwrap_or(0), + size: std::num::NonZeroU64::new(entry.size.unwrap_or(0)), + }, + ) + } + _ => unreachable!(), + }, + }; + entries.push(e); + } + + let bind_group_layout = state + .resource_table + .get::<WebGpuBindGroupLayout>(args.layout)?; + + let descriptor = wgpu_core::binding_model::BindGroupDescriptor { + label: args.label.map(Cow::from), + layout: bind_group_layout.0, + entries: Cow::from(entries), + }; + + gfx_put!(device => instance.device_create_bind_group( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuBindGroup) +} diff --git a/ext/webgpu/src/buffer.rs b/ext/webgpu/src/buffer.rs new file mode 100644 index 000000000..3f2c07883 --- /dev/null +++ b/ext/webgpu/src/buffer.rs @@ -0,0 +1,229 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::futures::channel::oneshot; +use deno_core::OpState; +use deno_core::Resource; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; +use std::time::Duration; + +use super::error::DomExceptionOperationError; +use super::error::WebGpuResult; + +pub(crate) struct WebGpuBuffer(pub(crate) wgpu_core::id::BufferId); +impl Resource for WebGpuBuffer { + fn name(&self) -> Cow<str> { + "webGPUBuffer".into() + } +} + +struct WebGpuBufferMapped(*mut u8, usize); +impl Resource for WebGpuBufferMapped { + fn name(&self) -> Cow<str> { + "webGPUBufferMapped".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateBufferArgs { + device_rid: ResourceId, + label: Option<String>, + size: u64, + usage: u32, + mapped_at_creation: bool, +} + +pub fn op_webgpu_create_buffer( + state: &mut OpState, + args: CreateBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid)?; + let device = device_resource.0; + + let descriptor = wgpu_core::resource::BufferDescriptor { + label: args.label.map(Cow::from), + size: args.size, + usage: wgpu_types::BufferUsages::from_bits(args.usage) + .ok_or_else(|| type_error("usage is not valid"))?, + mapped_at_creation: args.mapped_at_creation, + }; + + gfx_put!(device => instance.device_create_buffer( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuBuffer) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferGetMapAsyncArgs { + buffer_rid: ResourceId, + device_rid: ResourceId, + mode: u32, + offset: u64, + size: u64, +} + +pub async fn op_webgpu_buffer_get_map_async( + state: Rc<RefCell<OpState>>, + args: BufferGetMapAsyncArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let (sender, receiver) = oneshot::channel::<Result<(), AnyError>>(); + + let device; + { + let state_ = state.borrow(); + let instance = state_.borrow::<super::Instance>(); + let buffer_resource = + state_.resource_table.get::<WebGpuBuffer>(args.buffer_rid)?; + let buffer = buffer_resource.0; + let device_resource = state_ + .resource_table + .get::<super::WebGpuDevice>(args.device_rid)?; + device = device_resource.0; + + let boxed_sender = Box::new(sender); + let sender_ptr = Box::into_raw(boxed_sender) as *mut u8; + + extern "C" fn buffer_map_future_wrapper( + status: wgpu_core::resource::BufferMapAsyncStatus, + user_data: *mut u8, + ) { + let sender_ptr = user_data as *mut oneshot::Sender<Result<(), AnyError>>; + let boxed_sender = unsafe { Box::from_raw(sender_ptr) }; + boxed_sender + .send(match status { + wgpu_core::resource::BufferMapAsyncStatus::Success => Ok(()), + _ => unreachable!(), // TODO + }) + .unwrap(); + } + + // TODO(lucacasonato): error handling + let maybe_err = gfx_select!(buffer => instance.buffer_map_async( + buffer, + args.offset..(args.offset + args.size), + wgpu_core::resource::BufferMapOperation { + host: match args.mode { + 1 => wgpu_core::device::HostMap::Read, + 2 => wgpu_core::device::HostMap::Write, + _ => unreachable!(), + }, + callback: buffer_map_future_wrapper, + user_data: sender_ptr, + } + )) + .err(); + + if maybe_err.is_some() { + return Ok(WebGpuResult::maybe_err(maybe_err)); + } + } + + let done = Rc::new(RefCell::new(false)); + let done_ = done.clone(); + let device_poll_fut = async move { + while !*done.borrow() { + { + let state = state.borrow(); + let instance = state.borrow::<super::Instance>(); + gfx_select!(device => instance.device_poll(device, false)).unwrap() + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + Ok::<(), AnyError>(()) + }; + + let receiver_fut = async move { + receiver.await??; + let mut done = done_.borrow_mut(); + *done = true; + Ok::<(), AnyError>(()) + }; + + tokio::try_join!(device_poll_fut, receiver_fut)?; + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferGetMappedRangeArgs { + buffer_rid: ResourceId, + offset: u64, + size: Option<u64>, +} + +pub fn op_webgpu_buffer_get_mapped_range( + state: &mut OpState, + args: BufferGetMappedRangeArgs, + mut zero_copy: ZeroCopyBuf, +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let buffer_resource = + state.resource_table.get::<WebGpuBuffer>(args.buffer_rid)?; + let buffer = buffer_resource.0; + + let (slice_pointer, range_size) = + gfx_select!(buffer => instance.buffer_get_mapped_range( + buffer, + args.offset, + args.size + )) + .map_err(|e| DomExceptionOperationError::new(&e.to_string()))?; + + let slice = unsafe { + std::slice::from_raw_parts_mut(slice_pointer, range_size as usize) + }; + zero_copy.copy_from_slice(slice); + + let rid = state + .resource_table + .add(WebGpuBufferMapped(slice_pointer, range_size as usize)); + + Ok(WebGpuResult::rid(rid)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BufferUnmapArgs { + buffer_rid: ResourceId, + mapped_rid: ResourceId, +} + +pub fn op_webgpu_buffer_unmap( + state: &mut OpState, + args: BufferUnmapArgs, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<WebGpuResult, AnyError> { + let mapped_resource = state + .resource_table + .take::<WebGpuBufferMapped>(args.mapped_rid)?; + let instance = state.borrow::<super::Instance>(); + let buffer_resource = + state.resource_table.get::<WebGpuBuffer>(args.buffer_rid)?; + let buffer = buffer_resource.0; + + let slice_pointer = mapped_resource.0; + let size = mapped_resource.1; + + if let Some(buffer) = zero_copy { + let slice = unsafe { std::slice::from_raw_parts_mut(slice_pointer, size) }; + slice.copy_from_slice(&buffer); + } + + gfx_ok!(buffer => instance.buffer_unmap(buffer)) +} diff --git a/ext/webgpu/src/bundle.rs b/ext/webgpu/src/bundle.rs new file mode 100644 index 000000000..ea327651a --- /dev/null +++ b/ext/webgpu/src/bundle.rs @@ -0,0 +1,459 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; + +use super::error::WebGpuResult; + +struct WebGpuRenderBundleEncoder( + RefCell<wgpu_core::command::RenderBundleEncoder>, +); +impl Resource for WebGpuRenderBundleEncoder { + fn name(&self) -> Cow<str> { + "webGPURenderBundleEncoder".into() + } +} + +pub(crate) struct WebGpuRenderBundle(pub(crate) wgpu_core::id::RenderBundleId); +impl Resource for WebGpuRenderBundle { + fn name(&self) -> Cow<str> { + "webGPURenderBundle".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateRenderBundleEncoderArgs { + device_rid: ResourceId, + label: Option<String>, + color_formats: Vec<wgpu_types::TextureFormat>, + depth_stencil_format: Option<wgpu_types::TextureFormat>, + sample_count: u32, + depth_read_only: bool, + stencil_read_only: bool, +} + +pub fn op_webgpu_create_render_bundle_encoder( + state: &mut OpState, + args: CreateRenderBundleEncoderArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid)?; + let device = device_resource.0; + + let mut color_formats = vec![]; + + for format in args.color_formats { + color_formats.push(format); + } + + let depth_stencil = if let Some(format) = args.depth_stencil_format { + Some(wgpu_types::RenderBundleDepthStencil { + format, + depth_read_only: args.depth_read_only, + stencil_read_only: args.stencil_read_only, + }) + } else { + None + }; + + let descriptor = wgpu_core::command::RenderBundleEncoderDescriptor { + label: args.label.map(Cow::from), + color_formats: Cow::from(color_formats), + sample_count: args.sample_count, + depth_stencil, + multiview: None, + }; + + let res = + wgpu_core::command::RenderBundleEncoder::new(&descriptor, device, None); + let (render_bundle_encoder, maybe_err) = match res { + Ok(encoder) => (encoder, None), + Err(e) => ( + wgpu_core::command::RenderBundleEncoder::dummy(device), + Some(e), + ), + }; + + let rid = state + .resource_table + .add(WebGpuRenderBundleEncoder(RefCell::new( + render_bundle_encoder, + ))); + + Ok(WebGpuResult::rid_err(rid, maybe_err)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderFinishArgs { + render_bundle_encoder_rid: ResourceId, + label: Option<String>, +} + +pub fn op_webgpu_render_bundle_encoder_finish( + state: &mut OpState, + args: RenderBundleEncoderFinishArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = + state + .resource_table + .take::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + let render_bundle_encoder = Rc::try_unwrap(render_bundle_encoder_resource) + .ok() + .expect("unwrapping render_bundle_encoder_resource should succeed") + .0 + .into_inner(); + let instance = state.borrow::<super::Instance>(); + + gfx_put!(render_bundle_encoder.parent() => instance.render_bundle_encoder_finish( + render_bundle_encoder, + &wgpu_core::command::RenderBundleDescriptor { + label: args.label.map(Cow::from), + }, + std::marker::PhantomData + ) => state, WebGpuRenderBundle) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetBindGroupArgs { + render_bundle_encoder_rid: ResourceId, + index: u32, + bind_group: ResourceId, + dynamic_offsets_data: ZeroCopyBuf, + dynamic_offsets_data_start: usize, + dynamic_offsets_data_length: usize, +} + +pub fn op_webgpu_render_bundle_encoder_set_bind_group( + state: &mut OpState, + args: RenderBundleEncoderSetBindGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let bind_group_resource = + state + .resource_table + .get::<super::binding::WebGpuBindGroup>(args.bind_group)?; + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + // Align the data + assert!(args.dynamic_offsets_data.len() % std::mem::size_of::<u32>() == 0); + // SAFETY: A u8 to u32 cast is safe because we asserted that the length is a + // multiple of 4. + let (prefix, dynamic_offsets_data, suffix) = + unsafe { args.dynamic_offsets_data.align_to::<u32>() }; + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + + let start = args.dynamic_offsets_data_start; + let len = args.dynamic_offsets_data_length; + + // Assert that length and start are both in bounds + assert!(start <= dynamic_offsets_data.len()); + assert!(len <= dynamic_offsets_data.len() - start); + + let dynamic_offsets_data: &[u32] = &dynamic_offsets_data[start..start + len]; + + // SAFETY: the raw pointer and length are of the same slice, and that slice + // lives longer than the below function invocation. + unsafe { + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_bind_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + dynamic_offsets_data.as_ptr(), + dynamic_offsets_data.len(), + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderPushDebugGroupArgs { + render_bundle_encoder_rid: ResourceId, + group_label: String, +} + +pub fn op_webgpu_render_bundle_encoder_push_debug_group( + state: &mut OpState, + args: RenderBundleEncoderPushDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + let label = std::ffi::CString::new(args.group_label).unwrap(); + // SAFETY: the string the raw pointer points to lives longer than the below + // function invocation. + unsafe { + wgpu_core::command::bundle_ffi::wgpu_render_bundle_push_debug_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + label.as_ptr(), + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderPopDebugGroupArgs { + render_bundle_encoder_rid: ResourceId, +} + +pub fn op_webgpu_render_bundle_encoder_pop_debug_group( + state: &mut OpState, + args: RenderBundleEncoderPopDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_pop_debug_group( + &mut render_bundle_encoder_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderInsertDebugMarkerArgs { + render_bundle_encoder_rid: ResourceId, + marker_label: String, +} + +pub fn op_webgpu_render_bundle_encoder_insert_debug_marker( + state: &mut OpState, + args: RenderBundleEncoderInsertDebugMarkerArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + let label = std::ffi::CString::new(args.marker_label).unwrap(); + // SAFETY: the string the raw pointer points to lives longer than the below + // function invocation. + unsafe { + wgpu_core::command::bundle_ffi::wgpu_render_bundle_insert_debug_marker( + &mut render_bundle_encoder_resource.0.borrow_mut(), + label.as_ptr(), + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetPipelineArgs { + render_bundle_encoder_rid: ResourceId, + pipeline: ResourceId, +} + +pub fn op_webgpu_render_bundle_encoder_set_pipeline( + state: &mut OpState, + args: RenderBundleEncoderSetPipelineArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pipeline_resource = + state + .resource_table + .get::<super::pipeline::WebGpuRenderPipeline>(args.pipeline)?; + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_pipeline( + &mut render_bundle_encoder_resource.0.borrow_mut(), + render_pipeline_resource.0, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetIndexBufferArgs { + render_bundle_encoder_rid: ResourceId, + buffer: ResourceId, + index_format: wgpu_types::IndexFormat, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_bundle_encoder_set_index_buffer( + state: &mut OpState, + args: RenderBundleEncoderSetIndexBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer)?; + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + render_bundle_encoder_resource + .0 + .borrow_mut() + .set_index_buffer( + buffer_resource.0, + args.index_format, + args.offset, + std::num::NonZeroU64::new(args.size), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderSetVertexBufferArgs { + render_bundle_encoder_rid: ResourceId, + slot: u32, + buffer: ResourceId, + offset: u64, + size: u64, +} + +pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( + state: &mut OpState, + args: RenderBundleEncoderSetVertexBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer)?; + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_vertex_buffer( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.slot, + buffer_resource.0, + args.offset, + std::num::NonZeroU64::new(args.size), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawArgs { + render_bundle_encoder_rid: ResourceId, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, +} + +pub fn op_webgpu_render_bundle_encoder_draw( + state: &mut OpState, + args: RenderBundleEncoderDrawArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.vertex_count, + args.instance_count, + args.first_vertex, + args.first_instance, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawIndexedArgs { + render_bundle_encoder_rid: ResourceId, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, +} + +pub fn op_webgpu_render_bundle_encoder_draw_indexed( + state: &mut OpState, + args: RenderBundleEncoderDrawIndexedArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indexed( + &mut render_bundle_encoder_resource.0.borrow_mut(), + args.index_count, + args.instance_count, + args.first_index, + args.base_vertex, + args.first_instance, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderBundleEncoderDrawIndirectArgs { + render_bundle_encoder_rid: ResourceId, + indirect_buffer: ResourceId, + indirect_offset: u64, +} + +pub fn op_webgpu_render_bundle_encoder_draw_indirect( + state: &mut OpState, + args: RenderBundleEncoderDrawIndirectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.indirect_buffer)?; + let render_bundle_encoder_resource = + state + .resource_table + .get::<WebGpuRenderBundleEncoder>(args.render_bundle_encoder_rid)?; + + wgpu_core::command::bundle_ffi::wgpu_render_bundle_draw_indirect( + &mut render_bundle_encoder_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(WebGpuResult::empty()) +} diff --git a/ext/webgpu/src/command_encoder.rs b/ext/webgpu/src/command_encoder.rs new file mode 100644 index 000000000..894b08f27 --- /dev/null +++ b/ext/webgpu/src/command_encoder.rs @@ -0,0 +1,646 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::num::NonZeroU32; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuCommandEncoder( + pub(crate) wgpu_core::id::CommandEncoderId, +); +impl Resource for WebGpuCommandEncoder { + fn name(&self) -> Cow<str> { + "webGPUCommandEncoder".into() + } +} + +pub(crate) struct WebGpuCommandBuffer( + pub(crate) wgpu_core::id::CommandBufferId, +); +impl Resource for WebGpuCommandBuffer { + fn name(&self) -> Cow<str> { + "webGPUCommandBuffer".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateCommandEncoderArgs { + device_rid: ResourceId, + label: Option<String>, + _measure_execution_time: Option<bool>, // not yet implemented +} + +pub fn op_webgpu_create_command_encoder( + state: &mut OpState, + args: CreateCommandEncoderArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid)?; + let device = device_resource.0; + + let descriptor = wgpu_types::CommandEncoderDescriptor { + label: args.label.map(Cow::from), + }; + + gfx_put!(device => instance.device_create_command_encoder( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuCommandEncoder) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuRenderPassColorAttachment { + view: ResourceId, + resolve_target: Option<ResourceId>, + load_op: GpuLoadOp<wgpu_types::Color>, + store_op: wgpu_core::command::StoreOp, +} + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +enum GpuLoadOp<T> { + Load, + Clear(T), +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuRenderPassDepthStencilAttachment { + view: ResourceId, + depth_load_op: GpuLoadOp<f32>, + depth_store_op: wgpu_core::command::StoreOp, + depth_read_only: bool, + stencil_load_op: GpuLoadOp<u32>, + stencil_store_op: wgpu_core::command::StoreOp, + stencil_read_only: bool, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderBeginRenderPassArgs { + command_encoder_rid: ResourceId, + label: Option<String>, + color_attachments: Vec<GpuRenderPassColorAttachment>, + depth_stencil_attachment: Option<GpuRenderPassDepthStencilAttachment>, + _occlusion_query_set: Option<u32>, // not yet implemented +} + +pub fn op_webgpu_command_encoder_begin_render_pass( + state: &mut OpState, + args: CommandEncoderBeginRenderPassArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + + let mut color_attachments = vec![]; + + for color_attachment in args.color_attachments { + let texture_view_resource = + state + .resource_table + .get::<super::texture::WebGpuTextureView>(color_attachment.view)?; + + let resolve_target = color_attachment + .resolve_target + .map(|rid| { + state + .resource_table + .get::<super::texture::WebGpuTextureView>(rid) + }) + .transpose()? + .map(|texture| texture.0); + + let attachment = wgpu_core::command::RenderPassColorAttachment { + view: texture_view_resource.0, + resolve_target, + channel: match color_attachment.load_op { + GpuLoadOp::Load => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Load, + store_op: color_attachment.store_op, + clear_value: Default::default(), + read_only: false, + }, + GpuLoadOp::Clear(color) => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Clear, + store_op: color_attachment.store_op, + clear_value: color, + read_only: false, + }, + }, + }; + + color_attachments.push(attachment) + } + + let mut depth_stencil_attachment = None; + + if let Some(attachment) = args.depth_stencil_attachment { + let texture_view_resource = + state + .resource_table + .get::<super::texture::WebGpuTextureView>(attachment.view)?; + + depth_stencil_attachment = + Some(wgpu_core::command::RenderPassDepthStencilAttachment { + view: texture_view_resource.0, + depth: match attachment.depth_load_op { + GpuLoadOp::Load => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Load, + store_op: attachment.depth_store_op, + clear_value: 0.0, + read_only: attachment.depth_read_only, + }, + GpuLoadOp::Clear(value) => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Clear, + store_op: attachment.depth_store_op, + clear_value: value, + read_only: attachment.depth_read_only, + }, + }, + stencil: match attachment.stencil_load_op { + GpuLoadOp::Load => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Load, + store_op: attachment.stencil_store_op, + clear_value: 0, + read_only: attachment.stencil_read_only, + }, + GpuLoadOp::Clear(value) => wgpu_core::command::PassChannel { + load_op: wgpu_core::command::LoadOp::Clear, + store_op: attachment.stencil_store_op, + clear_value: value, + read_only: attachment.stencil_read_only, + }, + }, + }); + } + + let descriptor = wgpu_core::command::RenderPassDescriptor { + label: args.label.map(Cow::from), + color_attachments: Cow::from(color_attachments), + depth_stencil_attachment: depth_stencil_attachment.as_ref(), + }; + + let render_pass = wgpu_core::command::RenderPass::new( + command_encoder_resource.0, + &descriptor, + ); + + let rid = state + .resource_table + .add(super::render_pass::WebGpuRenderPass(RefCell::new( + render_pass, + ))); + + Ok(WebGpuResult::rid(rid)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderBeginComputePassArgs { + command_encoder_rid: ResourceId, + label: Option<String>, +} + +pub fn op_webgpu_command_encoder_begin_compute_pass( + state: &mut OpState, + args: CommandEncoderBeginComputePassArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + + let descriptor = wgpu_core::command::ComputePassDescriptor { + label: args.label.map(Cow::from), + }; + + let compute_pass = wgpu_core::command::ComputePass::new( + command_encoder_resource.0, + &descriptor, + ); + + let rid = state + .resource_table + .add(super::compute_pass::WebGpuComputePass(RefCell::new( + compute_pass, + ))); + + Ok(WebGpuResult::rid(rid)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyBufferToBufferArgs { + command_encoder_rid: ResourceId, + source: ResourceId, + source_offset: u64, + destination: ResourceId, + destination_offset: u64, + size: u64, +} + +pub fn op_webgpu_command_encoder_copy_buffer_to_buffer( + state: &mut OpState, + args: CommandEncoderCopyBufferToBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + let source_buffer_resource = + state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.source)?; + let source_buffer = source_buffer_resource.0; + let destination_buffer_resource = + state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.destination)?; + let destination_buffer = destination_buffer_resource.0; + + gfx_ok!(command_encoder => instance.command_encoder_copy_buffer_to_buffer( + command_encoder, + source_buffer, + args.source_offset, + destination_buffer, + args.destination_offset, + args.size + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuImageCopyBuffer { + buffer: ResourceId, + offset: u64, + bytes_per_row: Option<u32>, + rows_per_image: Option<u32>, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuImageCopyTexture { + pub texture: ResourceId, + pub mip_level: u32, + pub origin: wgpu_types::Origin3d, + pub aspect: wgpu_types::TextureAspect, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyBufferToTextureArgs { + command_encoder_rid: ResourceId, + source: GpuImageCopyBuffer, + destination: GpuImageCopyTexture, + copy_size: wgpu_types::Extent3d, +} + +pub fn op_webgpu_command_encoder_copy_buffer_to_texture( + state: &mut OpState, + args: CommandEncoderCopyBufferToTextureArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + let source_buffer_resource = + state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.source.buffer)?; + let destination_texture_resource = + state + .resource_table + .get::<super::texture::WebGpuTexture>(args.destination.texture)?; + + let source = wgpu_core::command::ImageCopyBuffer { + buffer: source_buffer_resource.0, + layout: wgpu_types::ImageDataLayout { + offset: args.source.offset, + bytes_per_row: NonZeroU32::new(args.source.bytes_per_row.unwrap_or(0)), + rows_per_image: NonZeroU32::new(args.source.rows_per_image.unwrap_or(0)), + }, + }; + let destination = wgpu_core::command::ImageCopyTexture { + texture: destination_texture_resource.0, + mip_level: args.destination.mip_level, + origin: args.destination.origin, + aspect: args.destination.aspect, + }; + gfx_ok!(command_encoder => instance.command_encoder_copy_buffer_to_texture( + command_encoder, + &source, + &destination, + &args.copy_size + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyTextureToBufferArgs { + command_encoder_rid: ResourceId, + source: GpuImageCopyTexture, + destination: GpuImageCopyBuffer, + copy_size: wgpu_types::Extent3d, +} + +pub fn op_webgpu_command_encoder_copy_texture_to_buffer( + state: &mut OpState, + args: CommandEncoderCopyTextureToBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + let source_texture_resource = + state + .resource_table + .get::<super::texture::WebGpuTexture>(args.source.texture)?; + let destination_buffer_resource = + state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.destination.buffer)?; + + let source = wgpu_core::command::ImageCopyTexture { + texture: source_texture_resource.0, + mip_level: args.source.mip_level, + origin: args.source.origin, + aspect: args.source.aspect, + }; + let destination = wgpu_core::command::ImageCopyBuffer { + buffer: destination_buffer_resource.0, + layout: wgpu_types::ImageDataLayout { + offset: args.destination.offset, + bytes_per_row: NonZeroU32::new( + args.destination.bytes_per_row.unwrap_or(0), + ), + rows_per_image: NonZeroU32::new( + args.destination.rows_per_image.unwrap_or(0), + ), + }, + }; + gfx_ok!(command_encoder => instance.command_encoder_copy_texture_to_buffer( + command_encoder, + &source, + &destination, + &args.copy_size + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderCopyTextureToTextureArgs { + command_encoder_rid: ResourceId, + source: GpuImageCopyTexture, + destination: GpuImageCopyTexture, + copy_size: wgpu_types::Extent3d, +} + +pub fn op_webgpu_command_encoder_copy_texture_to_texture( + state: &mut OpState, + args: CommandEncoderCopyTextureToTextureArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + let source_texture_resource = + state + .resource_table + .get::<super::texture::WebGpuTexture>(args.source.texture)?; + let destination_texture_resource = + state + .resource_table + .get::<super::texture::WebGpuTexture>(args.destination.texture)?; + + let source = wgpu_core::command::ImageCopyTexture { + texture: source_texture_resource.0, + mip_level: args.source.mip_level, + origin: args.source.origin, + aspect: args.source.aspect, + }; + let destination = wgpu_core::command::ImageCopyTexture { + texture: destination_texture_resource.0, + mip_level: args.destination.mip_level, + origin: args.destination.origin, + aspect: args.destination.aspect, + }; + gfx_ok!(command_encoder => instance.command_encoder_copy_texture_to_texture( + command_encoder, + &source, + &destination, + &args.copy_size + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderClearBufferArgs { + command_encoder_rid: u32, + destination_rid: u32, + destination_offset: u64, + size: u64, +} + +pub fn op_webgpu_command_encoder_clear_buffer( + state: &mut OpState, + args: CommandEncoderClearBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + let destination_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.destination_rid)?; + + gfx_ok!(command_encoder => instance.command_encoder_clear_buffer( + command_encoder, + destination_resource.0, + args.destination_offset, + std::num::NonZeroU64::new(args.size) + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderPushDebugGroupArgs { + command_encoder_rid: ResourceId, + group_label: String, +} + +pub fn op_webgpu_command_encoder_push_debug_group( + state: &mut OpState, + args: CommandEncoderPushDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + + gfx_ok!(command_encoder => instance + .command_encoder_push_debug_group(command_encoder, &args.group_label)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderPopDebugGroupArgs { + command_encoder_rid: ResourceId, +} + +pub fn op_webgpu_command_encoder_pop_debug_group( + state: &mut OpState, + args: CommandEncoderPopDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + + gfx_ok!(command_encoder => instance.command_encoder_pop_debug_group(command_encoder)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderInsertDebugMarkerArgs { + command_encoder_rid: ResourceId, + marker_label: String, +} + +pub fn op_webgpu_command_encoder_insert_debug_marker( + state: &mut OpState, + args: CommandEncoderInsertDebugMarkerArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + + gfx_ok!(command_encoder => instance.command_encoder_insert_debug_marker( + command_encoder, + &args.marker_label + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderWriteTimestampArgs { + command_encoder_rid: ResourceId, + query_set: ResourceId, + query_index: u32, +} + +pub fn op_webgpu_command_encoder_write_timestamp( + state: &mut OpState, + args: CommandEncoderWriteTimestampArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set)?; + + gfx_ok!(command_encoder => instance.command_encoder_write_timestamp( + command_encoder, + query_set_resource.0, + args.query_index + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderResolveQuerySetArgs { + command_encoder_rid: ResourceId, + query_set: ResourceId, + first_query: u32, + query_count: u32, + destination: ResourceId, + destination_offset: u64, +} + +pub fn op_webgpu_command_encoder_resolve_query_set( + state: &mut OpState, + args: CommandEncoderResolveQuerySetArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let command_encoder_resource = state + .resource_table + .get::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set)?; + let destination_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.destination)?; + + gfx_ok!(command_encoder => instance.command_encoder_resolve_query_set( + command_encoder, + query_set_resource.0, + args.first_query, + args.query_count, + destination_resource.0, + args.destination_offset + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandEncoderFinishArgs { + command_encoder_rid: ResourceId, + label: Option<String>, +} + +pub fn op_webgpu_command_encoder_finish( + state: &mut OpState, + args: CommandEncoderFinishArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .take::<WebGpuCommandEncoder>(args.command_encoder_rid)?; + let command_encoder = command_encoder_resource.0; + let instance = state.borrow::<super::Instance>(); + + let descriptor = wgpu_types::CommandBufferDescriptor { + label: args.label.map(Cow::from), + }; + + gfx_put!(command_encoder => instance.command_encoder_finish( + command_encoder, + &descriptor + ) => state, WebGpuCommandBuffer) +} diff --git a/ext/webgpu/src/compute_pass.rs b/ext/webgpu/src/compute_pass.rs new file mode 100644 index 000000000..03d9163cb --- /dev/null +++ b/ext/webgpu/src/compute_pass.rs @@ -0,0 +1,354 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuComputePass( + pub(crate) RefCell<wgpu_core::command::ComputePass>, +); +impl Resource for WebGpuComputePass { + fn name(&self) -> Cow<str> { + "webGPUComputePass".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassSetPipelineArgs { + compute_pass_rid: ResourceId, + pipeline: ResourceId, +} + +pub fn op_webgpu_compute_pass_set_pipeline( + state: &mut OpState, + args: ComputePassSetPipelineArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pipeline_resource = + state + .resource_table + .get::<super::pipeline::WebGpuComputePipeline>(args.pipeline)?; + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_set_pipeline( + &mut compute_pass_resource.0.borrow_mut(), + compute_pipeline_resource.0, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassDispatchArgs { + compute_pass_rid: ResourceId, + x: u32, + y: u32, + z: u32, +} + +pub fn op_webgpu_compute_pass_dispatch( + state: &mut OpState, + args: ComputePassDispatchArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_dispatch( + &mut compute_pass_resource.0.borrow_mut(), + args.x, + args.y, + args.z, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassDispatchIndirectArgs { + compute_pass_rid: ResourceId, + indirect_buffer: ResourceId, + indirect_offset: u64, +} + +pub fn op_webgpu_compute_pass_dispatch_indirect( + state: &mut OpState, + args: ComputePassDispatchIndirectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.indirect_buffer)?; + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_dispatch_indirect( + &mut compute_pass_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassBeginPipelineStatisticsQueryArgs { + compute_pass_rid: ResourceId, + query_set: ResourceId, + query_index: u32, +} + +pub fn op_webgpu_compute_pass_begin_pipeline_statistics_query( + state: &mut OpState, + args: ComputePassBeginPipelineStatisticsQueryArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_begin_pipeline_statistics_query( + &mut compute_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassEndPipelineStatisticsQueryArgs { + compute_pass_rid: ResourceId, +} + +pub fn op_webgpu_compute_pass_end_pipeline_statistics_query( + state: &mut OpState, + args: ComputePassEndPipelineStatisticsQueryArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_end_pipeline_statistics_query( + &mut compute_pass_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassWriteTimestampArgs { + compute_pass_rid: ResourceId, + query_set: ResourceId, + query_index: u32, +} + +pub fn op_webgpu_compute_pass_write_timestamp( + state: &mut OpState, + args: ComputePassWriteTimestampArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_write_timestamp( + &mut compute_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassEndPassArgs { + command_encoder_rid: ResourceId, + compute_pass_rid: ResourceId, +} + +pub fn op_webgpu_compute_pass_end_pass( + state: &mut OpState, + args: ComputePassEndPassArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .get::<super::command_encoder::WebGpuCommandEncoder>( + args.command_encoder_rid, + )?; + let command_encoder = command_encoder_resource.0; + let compute_pass_resource = state + .resource_table + .take::<WebGpuComputePass>(args.compute_pass_rid)?; + let compute_pass = &compute_pass_resource.0.borrow(); + let instance = state.borrow::<super::Instance>(); + + gfx_ok!(command_encoder => instance.command_encoder_run_compute_pass( + command_encoder, + compute_pass + )) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassSetBindGroupArgs { + compute_pass_rid: ResourceId, + index: u32, + bind_group: ResourceId, + dynamic_offsets_data: ZeroCopyBuf, + dynamic_offsets_data_start: usize, + dynamic_offsets_data_length: usize, +} + +pub fn op_webgpu_compute_pass_set_bind_group( + state: &mut OpState, + args: ComputePassSetBindGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let bind_group_resource = + state + .resource_table + .get::<super::binding::WebGpuBindGroup>(args.bind_group)?; + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + + // Align the data + assert!(args.dynamic_offsets_data_start % std::mem::size_of::<u32>() == 0); + // SAFETY: A u8 to u32 cast is safe because we asserted that the length is a + // multiple of 4. + let (prefix, dynamic_offsets_data, suffix) = + unsafe { args.dynamic_offsets_data.align_to::<u32>() }; + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + + let start = args.dynamic_offsets_data_start; + let len = args.dynamic_offsets_data_length; + + // Assert that length and start are both in bounds + assert!(start <= dynamic_offsets_data.len()); + assert!(len <= dynamic_offsets_data.len() - start); + + let dynamic_offsets_data: &[u32] = &dynamic_offsets_data[start..start + len]; + + // SAFETY: the raw pointer and length are of the same slice, and that slice + // lives longer than the below function invocation. + unsafe { + wgpu_core::command::compute_ffi::wgpu_compute_pass_set_bind_group( + &mut compute_pass_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + dynamic_offsets_data.as_ptr(), + dynamic_offsets_data.len(), + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassPushDebugGroupArgs { + compute_pass_rid: ResourceId, + group_label: String, +} + +pub fn op_webgpu_compute_pass_push_debug_group( + state: &mut OpState, + args: ComputePassPushDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + + let label = std::ffi::CString::new(args.group_label).unwrap(); + // SAFETY: the string the raw pointer points to lives longer than the below + // function invocation. + unsafe { + wgpu_core::command::compute_ffi::wgpu_compute_pass_push_debug_group( + &mut compute_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassPopDebugGroupArgs { + compute_pass_rid: ResourceId, +} + +pub fn op_webgpu_compute_pass_pop_debug_group( + state: &mut OpState, + args: ComputePassPopDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + + wgpu_core::command::compute_ffi::wgpu_compute_pass_pop_debug_group( + &mut compute_pass_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputePassInsertDebugMarkerArgs { + compute_pass_rid: ResourceId, + marker_label: String, +} + +pub fn op_webgpu_compute_pass_insert_debug_marker( + state: &mut OpState, + args: ComputePassInsertDebugMarkerArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let compute_pass_resource = state + .resource_table + .get::<WebGpuComputePass>(args.compute_pass_rid)?; + + let label = std::ffi::CString::new(args.marker_label).unwrap(); + // SAFETY: the string the raw pointer points to lives longer than the below + // function invocation. + unsafe { + wgpu_core::command::compute_ffi::wgpu_compute_pass_insert_debug_marker( + &mut compute_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(WebGpuResult::empty()) +} diff --git a/ext/webgpu/src/error.rs b/ext/webgpu/src/error.rs new file mode 100644 index 000000000..ae6e48054 --- /dev/null +++ b/ext/webgpu/src/error.rs @@ -0,0 +1,292 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use deno_core::error::AnyError; +use deno_core::ResourceId; +use serde::Serialize; +use std::convert::From; +use std::fmt; +use wgpu_core::binding_model::CreateBindGroupError; +use wgpu_core::binding_model::CreateBindGroupLayoutError; +use wgpu_core::binding_model::CreatePipelineLayoutError; +use wgpu_core::binding_model::GetBindGroupLayoutError; +use wgpu_core::command::ClearError; +use wgpu_core::command::CommandEncoderError; +use wgpu_core::command::ComputePassError; +use wgpu_core::command::CopyError; +use wgpu_core::command::CreateRenderBundleError; +use wgpu_core::command::QueryError; +use wgpu_core::command::RenderBundleError; +use wgpu_core::command::RenderPassError; +use wgpu_core::device::queue::QueueSubmitError; +use wgpu_core::device::queue::QueueWriteError; +use wgpu_core::device::DeviceError; +use wgpu_core::pipeline::CreateComputePipelineError; +use wgpu_core::pipeline::CreateRenderPipelineError; +use wgpu_core::pipeline::CreateShaderModuleError; +use wgpu_core::resource::BufferAccessError; +use wgpu_core::resource::CreateBufferError; +use wgpu_core::resource::CreateQuerySetError; +use wgpu_core::resource::CreateSamplerError; +use wgpu_core::resource::CreateTextureError; +use wgpu_core::resource::CreateTextureViewError; + +#[derive(Serialize)] +pub struct WebGpuResult { + pub rid: Option<ResourceId>, + pub err: Option<WebGpuError>, +} + +impl WebGpuResult { + pub fn rid(rid: ResourceId) -> Self { + Self { + rid: Some(rid), + err: None, + } + } + + pub fn rid_err<T: Into<WebGpuError>>( + rid: ResourceId, + err: Option<T>, + ) -> Self { + Self { + rid: Some(rid), + err: err.map(|e| e.into()), + } + } + + pub fn maybe_err<T: Into<WebGpuError>>(err: Option<T>) -> Self { + Self { + rid: None, + err: err.map(|e| e.into()), + } + } + + pub fn empty() -> Self { + Self { + rid: None, + err: None, + } + } +} + +#[derive(Serialize)] +#[serde(tag = "type", content = "value")] +#[serde(rename_all = "kebab-case")] +pub enum WebGpuError { + Lost, + OutOfMemory, + Validation(String), +} + +impl From<CreateBufferError> for WebGpuError { + fn from(err: CreateBufferError) -> Self { + match err { + CreateBufferError::Device(err) => err.into(), + CreateBufferError::AccessError(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<DeviceError> for WebGpuError { + fn from(err: DeviceError) -> Self { + match err { + DeviceError::Lost => WebGpuError::Lost, + DeviceError::OutOfMemory => WebGpuError::OutOfMemory, + DeviceError::Invalid => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<BufferAccessError> for WebGpuError { + fn from(err: BufferAccessError) -> Self { + match err { + BufferAccessError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateBindGroupLayoutError> for WebGpuError { + fn from(err: CreateBindGroupLayoutError) -> Self { + match err { + CreateBindGroupLayoutError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreatePipelineLayoutError> for WebGpuError { + fn from(err: CreatePipelineLayoutError) -> Self { + match err { + CreatePipelineLayoutError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateBindGroupError> for WebGpuError { + fn from(err: CreateBindGroupError) -> Self { + match err { + CreateBindGroupError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<RenderBundleError> for WebGpuError { + fn from(err: RenderBundleError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateRenderBundleError> for WebGpuError { + fn from(err: CreateRenderBundleError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CopyError> for WebGpuError { + fn from(err: CopyError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CommandEncoderError> for WebGpuError { + fn from(err: CommandEncoderError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<QueryError> for WebGpuError { + fn from(err: QueryError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<ComputePassError> for WebGpuError { + fn from(err: ComputePassError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateComputePipelineError> for WebGpuError { + fn from(err: CreateComputePipelineError) -> Self { + match err { + CreateComputePipelineError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<GetBindGroupLayoutError> for WebGpuError { + fn from(err: GetBindGroupLayoutError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateRenderPipelineError> for WebGpuError { + fn from(err: CreateRenderPipelineError) -> Self { + match err { + CreateRenderPipelineError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<RenderPassError> for WebGpuError { + fn from(err: RenderPassError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateSamplerError> for WebGpuError { + fn from(err: CreateSamplerError) -> Self { + match err { + CreateSamplerError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateShaderModuleError> for WebGpuError { + fn from(err: CreateShaderModuleError) -> Self { + match err { + CreateShaderModuleError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateTextureError> for WebGpuError { + fn from(err: CreateTextureError) -> Self { + match err { + CreateTextureError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<CreateTextureViewError> for WebGpuError { + fn from(err: CreateTextureViewError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +impl From<CreateQuerySetError> for WebGpuError { + fn from(err: CreateQuerySetError) -> Self { + match err { + CreateQuerySetError::Device(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<QueueSubmitError> for WebGpuError { + fn from(err: QueueSubmitError) -> Self { + match err { + QueueSubmitError::Queue(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<QueueWriteError> for WebGpuError { + fn from(err: QueueWriteError) -> Self { + match err { + QueueWriteError::Queue(err) => err.into(), + err => WebGpuError::Validation(err.to_string()), + } + } +} + +impl From<ClearError> for WebGpuError { + fn from(err: ClearError) -> Self { + WebGpuError::Validation(err.to_string()) + } +} + +#[derive(Debug)] +pub struct DomExceptionOperationError { + pub msg: String, +} + +impl DomExceptionOperationError { + pub fn new(msg: &str) -> Self { + DomExceptionOperationError { + msg: msg.to_string(), + } + } +} + +impl fmt::Display for DomExceptionOperationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad(&self.msg) + } +} + +impl std::error::Error for DomExceptionOperationError {} + +pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { + e.downcast_ref::<DomExceptionOperationError>() + .map(|_| "DOMExceptionOperationError") +} diff --git a/ext/webgpu/src/lib.rs b/ext/webgpu/src/lib.rs new file mode 100644 index 000000000..9d227a51e --- /dev/null +++ b/ext/webgpu/src/lib.rs @@ -0,0 +1,908 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::include_js_files; +use deno_core::op_async; +use deno_core::op_sync; +use deno_core::Extension; +use deno_core::OpFn; +use deno_core::OpState; +use deno_core::Resource; +use deno_core::ResourceId; +use serde::Deserialize; +use serde::Serialize; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::HashSet; +use std::rc::Rc; +pub use wgpu_core; +pub use wgpu_types; +use wgpu_types::PowerPreference; + +use error::DomExceptionOperationError; +use error::WebGpuResult; + +#[macro_use] +mod macros { + macro_rules! gfx_select { + ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => { + match $id.backend() { + #[cfg(not(target_os = "macos"))] + wgpu_types::Backend::Vulkan => $global.$method::<wgpu_core::api::Vulkan>( $($param),* ), + #[cfg(target_os = "macos")] + wgpu_types::Backend::Metal => $global.$method::<wgpu_core::api::Metal>( $($param),* ), + #[cfg(windows)] + wgpu_types::Backend::Dx12 => $global.$method::<wgpu_core::api::Dx12>( $($param),* ), + #[cfg(all(unix, not(target_os = "macos")))] + wgpu_types::Backend::Gl => $global.$method::<wgpu_core::api::Gles>( $($param),+ ), + other => panic!("Unexpected backend {:?}", other), + } + }; + } + + macro_rules! gfx_put { + ($id:expr => $global:ident.$method:ident( $($param:expr),* ) => $state:expr, $rc:expr) => {{ + let (val, maybe_err) = gfx_select!($id => $global.$method($($param),*)); + let rid = $state.resource_table.add($rc(val)); + Ok(WebGpuResult::rid_err(rid, maybe_err)) + }}; + } + + macro_rules! gfx_ok { + ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => {{ + let maybe_err = gfx_select!($id => $global.$method($($param),*)).err(); + Ok(WebGpuResult::maybe_err(maybe_err)) + }}; + } +} + +pub mod binding; +pub mod buffer; +pub mod bundle; +pub mod command_encoder; +pub mod compute_pass; +pub mod error; +pub mod pipeline; +pub mod queue; +pub mod render_pass; +pub mod sampler; +pub mod shader; +pub mod texture; + +pub struct Unstable(pub bool); + +fn check_unstable(state: &OpState, api_name: &str) { + let unstable = state.borrow::<Unstable>(); + if !unstable.0 { + eprintln!( + "Unstable API '{}'. The --unstable flag must be provided.", + api_name + ); + std::process::exit(70); + } +} + +type Instance = wgpu_core::hub::Global<wgpu_core::hub::IdentityManagerFactory>; + +struct WebGpuAdapter(wgpu_core::id::AdapterId); +impl Resource for WebGpuAdapter { + fn name(&self) -> Cow<str> { + "webGPUAdapter".into() + } +} + +struct WebGpuDevice(wgpu_core::id::DeviceId); +impl Resource for WebGpuDevice { + fn name(&self) -> Cow<str> { + "webGPUDevice".into() + } +} + +struct WebGpuQuerySet(wgpu_core::id::QuerySetId); +impl Resource for WebGpuQuerySet { + fn name(&self) -> Cow<str> { + "webGPUQuerySet".into() + } +} + +pub fn init(unstable: bool) -> Extension { + Extension::builder() + .js(include_js_files!( + prefix "deno:ext/webgpu", + "01_webgpu.js", + "02_idl_types.js", + )) + .ops(declare_webgpu_ops()) + .state(move |state| { + // TODO: check & possibly streamline this + // Unstable might be able to be OpMiddleware + // let unstable_checker = state.borrow::<super::UnstableChecker>(); + // let unstable = unstable_checker.unstable; + state.put(Unstable(unstable)); + Ok(()) + }) + .build() +} + +fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> { + let mut return_features: Vec<&'static str> = vec![]; + + if features.contains(wgpu_types::Features::DEPTH_CLIP_CONTROL) { + return_features.push("depth-clip-control"); + } + if features.contains(wgpu_types::Features::PIPELINE_STATISTICS_QUERY) { + return_features.push("pipeline-statistics-query"); + } + if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_BC) { + return_features.push("texture-compression-bc"); + } + if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ETC2) { + return_features.push("texture-compression-etc2"); + } + if features.contains(wgpu_types::Features::TEXTURE_COMPRESSION_ASTC_LDR) { + return_features.push("texture-compression-astc"); + } + if features.contains(wgpu_types::Features::TIMESTAMP_QUERY) { + return_features.push("timestamp-query"); + } + if features.contains(wgpu_types::Features::INDIRECT_FIRST_INSTANCE) { + return_features.push("indirect-first-instance"); + } + + // extended from spec + if features.contains(wgpu_types::Features::MAPPABLE_PRIMARY_BUFFERS) { + return_features.push("mappable-primary-buffers"); + } + if features.contains(wgpu_types::Features::TEXTURE_BINDING_ARRAY) { + return_features.push("texture-binding-array"); + } + if features.contains(wgpu_types::Features::BUFFER_BINDING_ARRAY) { + return_features.push("buffer-binding-array"); + } + if features.contains(wgpu_types::Features::STORAGE_RESOURCE_BINDING_ARRAY) { + return_features.push("storage-resource-binding-array"); + } + if features.contains( + wgpu_types::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ) { + return_features.push("sampled-texture-and-storage-buffer-array-non-uniform-indexing"); + } + if features.contains( + wgpu_types::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + ) { + return_features.push("uniform-buffer-and-storage-buffer-texture-non-uniform-indexing"); + } + if features.contains(wgpu_types::Features::UNSIZED_BINDING_ARRAY) { + return_features.push("unsized-binding-array"); + } + if features.contains(wgpu_types::Features::MULTI_DRAW_INDIRECT) { + return_features.push("multi-draw-indirect"); + } + if features.contains(wgpu_types::Features::MULTI_DRAW_INDIRECT_COUNT) { + return_features.push("multi-draw-indirect-count"); + } + if features.contains(wgpu_types::Features::PUSH_CONSTANTS) { + return_features.push("push-constants"); + } + if features.contains(wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_BORDER) { + return_features.push("address-mode-clamp-to-border"); + } + if features + .contains(wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES) + { + return_features.push("texture-adapter-specific-format-features"); + } + if features.contains(wgpu_types::Features::SHADER_FLOAT64) { + return_features.push("shader-float64"); + } + if features.contains(wgpu_types::Features::VERTEX_ATTRIBUTE_64BIT) { + return_features.push("vertex-attribute-64bit"); + } + if features.contains(wgpu_types::Features::CONSERVATIVE_RASTERIZATION) { + return_features.push("conservative-rasterization"); + } + if features.contains(wgpu_types::Features::VERTEX_WRITABLE_STORAGE) { + return_features.push("vertex-writable-storage"); + } + if features.contains(wgpu_types::Features::CLEAR_COMMANDS) { + return_features.push("clear-texture"); + } + if features.contains(wgpu_types::Features::SPIRV_SHADER_PASSTHROUGH) { + return_features.push("spirv-shader-passthrough"); + } + if features.contains(wgpu_types::Features::SHADER_PRIMITIVE_INDEX) { + return_features.push("shader-primitive-index"); + } + + return_features +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestAdapterArgs { + power_preference: Option<wgpu_types::PowerPreference>, + force_fallback_adapter: bool, +} + +#[derive(Serialize)] +#[serde(untagged)] +pub enum GpuAdapterDeviceOrErr { + Error { err: String }, + Features(GpuAdapterDevice), +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuAdapterDevice { + rid: ResourceId, + name: Option<String>, + limits: wgpu_types::Limits, + features: Vec<&'static str>, + is_software: bool, +} + +pub async fn op_webgpu_request_adapter( + state: Rc<RefCell<OpState>>, + args: RequestAdapterArgs, + _: (), +) -> Result<GpuAdapterDeviceOrErr, AnyError> { + let mut state = state.borrow_mut(); + check_unstable(&state, "navigator.gpu.requestAdapter"); + let backends = std::env::var("DENO_WEBGPU_BACKEND") + .ok() + .map_or_else(wgpu_types::Backends::all, |s| { + wgpu_core::instance::parse_backends_from_comma_list(&s) + }); + let instance = if let Some(instance) = state.try_borrow::<Instance>() { + instance + } else { + state.put(wgpu_core::hub::Global::new( + "webgpu", + wgpu_core::hub::IdentityManagerFactory, + backends, + )); + state.borrow::<Instance>() + }; + + let descriptor = wgpu_core::instance::RequestAdapterOptions { + power_preference: match args.power_preference { + Some(power_preference) => power_preference, + None => PowerPreference::default(), + }, + force_fallback_adapter: args.force_fallback_adapter, + compatible_surface: None, // windowless + }; + let res = instance.request_adapter( + &descriptor, + wgpu_core::instance::AdapterInputs::Mask(backends, |_| { + std::marker::PhantomData + }), + ); + + let adapter = match res { + Ok(adapter) => adapter, + Err(err) => { + return Ok(GpuAdapterDeviceOrErr::Error { + err: err.to_string(), + }) + } + }; + let name = gfx_select!(adapter => instance.adapter_get_info(adapter))?.name; + let adapter_features = + gfx_select!(adapter => instance.adapter_features(adapter))?; + let features = deserialize_features(&adapter_features); + let adapter_limits = + gfx_select!(adapter => instance.adapter_limits(adapter))?; + + let rid = state.resource_table.add(WebGpuAdapter(adapter)); + + Ok(GpuAdapterDeviceOrErr::Features(GpuAdapterDevice { + rid, + name: Some(name), + features, + limits: adapter_limits, + is_software: false, + })) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestDeviceArgs { + adapter_rid: ResourceId, + label: Option<String>, + required_features: Option<GpuRequiredFeatures>, + required_limits: Option<wgpu_types::Limits>, +} + +#[derive(Deserialize)] +pub struct GpuRequiredFeatures(HashSet<String>); + +impl From<GpuRequiredFeatures> for wgpu_types::Features { + fn from(required_features: GpuRequiredFeatures) -> wgpu_types::Features { + let mut features: wgpu_types::Features = wgpu_types::Features::empty(); + features.set( + wgpu_types::Features::DEPTH_CLIP_CONTROL, + required_features.0.contains("depth-clip-control"), + ); + features.set( + wgpu_types::Features::PIPELINE_STATISTICS_QUERY, + required_features.0.contains("pipeline-statistics-query"), + ); + features.set( + wgpu_types::Features::TEXTURE_COMPRESSION_BC, + required_features.0.contains("texture-compression-bc"), + ); + features.set( + wgpu_types::Features::TEXTURE_COMPRESSION_ETC2, + required_features.0.contains("texture-compression-etc2"), + ); + features.set( + wgpu_types::Features::TEXTURE_COMPRESSION_ASTC_LDR, + required_features.0.contains("texture-compression-astc"), + ); + features.set( + wgpu_types::Features::TIMESTAMP_QUERY, + required_features.0.contains("timestamp-query"), + ); + features.set( + wgpu_types::Features::INDIRECT_FIRST_INSTANCE, + required_features.0.contains("indirect-first-instance"), + ); + + // extended from spec + features.set( + wgpu_types::Features::MAPPABLE_PRIMARY_BUFFERS, + required_features.0.contains("mappable-primary-buffers"), + ); + features.set( + wgpu_types::Features::TEXTURE_BINDING_ARRAY, + required_features.0.contains("texture-binding-array"), + ); + features.set( + wgpu_types::Features::BUFFER_BINDING_ARRAY, + required_features.0.contains("buffer-binding-array"), + ); + features.set( + wgpu_types::Features::STORAGE_RESOURCE_BINDING_ARRAY, + required_features + .0 + .contains("storage-resource-binding-array"), + ); + features.set( + wgpu_types::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + required_features + .0 + .contains("sampled-texture-and-storage-buffer-array-non-uniform-indexing"), + ); + features.set( + wgpu_types::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + required_features + .0 + .contains("uniform-buffer-and-storage-buffer-texture-non-uniform-indexing"), + ); + features.set( + wgpu_types::Features::UNSIZED_BINDING_ARRAY, + required_features.0.contains("unsized-binding-array"), + ); + features.set( + wgpu_types::Features::MULTI_DRAW_INDIRECT, + required_features.0.contains("multi-draw-indirect"), + ); + features.set( + wgpu_types::Features::MULTI_DRAW_INDIRECT_COUNT, + required_features.0.contains("multi-draw-indirect-count"), + ); + features.set( + wgpu_types::Features::PUSH_CONSTANTS, + required_features.0.contains("push-constants"), + ); + features.set( + wgpu_types::Features::ADDRESS_MODE_CLAMP_TO_BORDER, + required_features.0.contains("address-mode-clamp-to-border"), + ); + features.set( + wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + required_features + .0 + .contains("texture-adapter-specific-format-features"), + ); + features.set( + wgpu_types::Features::SHADER_FLOAT64, + required_features.0.contains("shader-float64"), + ); + features.set( + wgpu_types::Features::VERTEX_ATTRIBUTE_64BIT, + required_features.0.contains("vertex-attribute-64bit"), + ); + features.set( + wgpu_types::Features::CONSERVATIVE_RASTERIZATION, + required_features.0.contains("conservative-rasterization"), + ); + features.set( + wgpu_types::Features::VERTEX_WRITABLE_STORAGE, + required_features.0.contains("vertex-writable-storage"), + ); + features.set( + wgpu_types::Features::CLEAR_COMMANDS, + required_features.0.contains("clear-commands"), + ); + features.set( + wgpu_types::Features::SPIRV_SHADER_PASSTHROUGH, + required_features.0.contains("spirv-shader-passthrough"), + ); + features.set( + wgpu_types::Features::SHADER_PRIMITIVE_INDEX, + required_features.0.contains("shader-primitive-index"), + ); + + features + } +} + +pub async fn op_webgpu_request_device( + state: Rc<RefCell<OpState>>, + args: RequestDeviceArgs, + _: (), +) -> Result<GpuAdapterDevice, AnyError> { + let mut state = state.borrow_mut(); + let adapter_resource = state + .resource_table + .get::<WebGpuAdapter>(args.adapter_rid)?; + let adapter = adapter_resource.0; + let instance = state.borrow::<Instance>(); + + let descriptor = wgpu_types::DeviceDescriptor { + label: args.label.map(Cow::from), + features: args.required_features.map(Into::into).unwrap_or_default(), + limits: args.required_limits.map(Into::into).unwrap_or_default(), + }; + + let (device, maybe_err) = gfx_select!(adapter => instance.adapter_request_device( + adapter, + &descriptor, + std::env::var("DENO_WEBGPU_TRACE").ok().as_ref().map(std::path::Path::new), + std::marker::PhantomData + )); + if let Some(err) = maybe_err { + return Err(DomExceptionOperationError::new(&err.to_string()).into()); + } + + let device_features = + gfx_select!(device => instance.device_features(device))?; + let features = deserialize_features(&device_features); + let limits = gfx_select!(device => instance.device_limits(device))?; + + let rid = state.resource_table.add(WebGpuDevice(device)); + + Ok(GpuAdapterDevice { + rid, + name: None, + features, + limits, + // TODO(lucacasonato): report correctly from wgpu + is_software: false, + }) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateQuerySetArgs { + device_rid: ResourceId, + label: Option<String>, + #[serde(flatten)] + r#type: GpuQueryType, + count: u32, +} + +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case", tag = "type")] +enum GpuQueryType { + Occlusion, + #[serde(rename_all = "camelCase")] + PipelineStatistics { + pipeline_statistics: HashSet<String>, + }, + Timestamp, +} + +impl From<GpuQueryType> for wgpu_types::QueryType { + fn from(query_type: GpuQueryType) -> Self { + match query_type { + GpuQueryType::Occlusion => wgpu_types::QueryType::Occlusion, + GpuQueryType::PipelineStatistics { + pipeline_statistics, + } => { + use wgpu_types::PipelineStatisticsTypes; + + let mut types = PipelineStatisticsTypes::empty(); + + if pipeline_statistics.contains("vertex-shader-invocations") { + types.set(PipelineStatisticsTypes::VERTEX_SHADER_INVOCATIONS, true); + } + if pipeline_statistics.contains("clipper-invocations") { + types.set(PipelineStatisticsTypes::CLIPPER_INVOCATIONS, true); + } + if pipeline_statistics.contains("clipper-primitives-out") { + types.set(PipelineStatisticsTypes::CLIPPER_PRIMITIVES_OUT, true); + } + if pipeline_statistics.contains("fragment-shader-invocations") { + types.set(PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS, true); + } + if pipeline_statistics.contains("compute-shader-invocations") { + types.set(PipelineStatisticsTypes::COMPUTE_SHADER_INVOCATIONS, true); + } + + wgpu_types::QueryType::PipelineStatistics(types) + } + GpuQueryType::Timestamp => wgpu_types::QueryType::Timestamp, + } + } +} + +pub fn op_webgpu_create_query_set( + state: &mut OpState, + args: CreateQuerySetArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let device_resource = + state.resource_table.get::<WebGpuDevice>(args.device_rid)?; + let device = device_resource.0; + let instance = &state.borrow::<Instance>(); + + let descriptor = wgpu_types::QuerySetDescriptor { + label: args.label.map(Cow::from), + ty: args.r#type.into(), + count: args.count, + }; + + gfx_put!(device => instance.device_create_query_set( + device, + &descriptor, + std::marker::PhantomData + ) => state, WebGpuQuerySet) +} + +fn declare_webgpu_ops() -> Vec<(&'static str, Box<OpFn>)> { + vec![ + // Request device/adapter + ( + "op_webgpu_request_adapter", + op_async(op_webgpu_request_adapter), + ), + ( + "op_webgpu_request_device", + op_async(op_webgpu_request_device), + ), + // Query Set + ( + "op_webgpu_create_query_set", + op_sync(op_webgpu_create_query_set), + ), + // buffer + ( + "op_webgpu_create_buffer", + op_sync(buffer::op_webgpu_create_buffer), + ), + ( + "op_webgpu_buffer_get_mapped_range", + op_sync(buffer::op_webgpu_buffer_get_mapped_range), + ), + ( + "op_webgpu_buffer_unmap", + op_sync(buffer::op_webgpu_buffer_unmap), + ), + // buffer async + ( + "op_webgpu_buffer_get_map_async", + op_async(buffer::op_webgpu_buffer_get_map_async), + ), + // remaining sync ops + + // texture + ( + "op_webgpu_create_texture", + op_sync(texture::op_webgpu_create_texture), + ), + ( + "op_webgpu_create_texture_view", + op_sync(texture::op_webgpu_create_texture_view), + ), + // sampler + ( + "op_webgpu_create_sampler", + op_sync(sampler::op_webgpu_create_sampler), + ), + // binding + ( + "op_webgpu_create_bind_group_layout", + op_sync(binding::op_webgpu_create_bind_group_layout), + ), + ( + "op_webgpu_create_pipeline_layout", + op_sync(binding::op_webgpu_create_pipeline_layout), + ), + ( + "op_webgpu_create_bind_group", + op_sync(binding::op_webgpu_create_bind_group), + ), + // pipeline + ( + "op_webgpu_create_compute_pipeline", + op_sync(pipeline::op_webgpu_create_compute_pipeline), + ), + ( + "op_webgpu_compute_pipeline_get_bind_group_layout", + op_sync(pipeline::op_webgpu_compute_pipeline_get_bind_group_layout), + ), + ( + "op_webgpu_create_render_pipeline", + op_sync(pipeline::op_webgpu_create_render_pipeline), + ), + ( + "op_webgpu_render_pipeline_get_bind_group_layout", + op_sync(pipeline::op_webgpu_render_pipeline_get_bind_group_layout), + ), + // command_encoder + ( + "op_webgpu_create_command_encoder", + op_sync(command_encoder::op_webgpu_create_command_encoder), + ), + ( + "op_webgpu_command_encoder_begin_render_pass", + op_sync(command_encoder::op_webgpu_command_encoder_begin_render_pass), + ), + ( + "op_webgpu_command_encoder_begin_compute_pass", + op_sync(command_encoder::op_webgpu_command_encoder_begin_compute_pass), + ), + ( + "op_webgpu_command_encoder_copy_buffer_to_buffer", + op_sync(command_encoder::op_webgpu_command_encoder_copy_buffer_to_buffer), + ), + ( + "op_webgpu_command_encoder_copy_buffer_to_texture", + op_sync( + command_encoder::op_webgpu_command_encoder_copy_buffer_to_texture, + ), + ), + ( + "op_webgpu_command_encoder_copy_texture_to_buffer", + op_sync( + command_encoder::op_webgpu_command_encoder_copy_texture_to_buffer, + ), + ), + ( + "op_webgpu_command_encoder_copy_texture_to_texture", + op_sync( + command_encoder::op_webgpu_command_encoder_copy_texture_to_texture, + ), + ), + ( + "op_webgpu_command_encoder_clear_buffer", + op_sync(command_encoder::op_webgpu_command_encoder_clear_buffer), + ), + ( + "op_webgpu_command_encoder_push_debug_group", + op_sync(command_encoder::op_webgpu_command_encoder_push_debug_group), + ), + ( + "op_webgpu_command_encoder_pop_debug_group", + op_sync(command_encoder::op_webgpu_command_encoder_pop_debug_group), + ), + ( + "op_webgpu_command_encoder_insert_debug_marker", + op_sync(command_encoder::op_webgpu_command_encoder_insert_debug_marker), + ), + ( + "op_webgpu_command_encoder_write_timestamp", + op_sync(command_encoder::op_webgpu_command_encoder_write_timestamp), + ), + ( + "op_webgpu_command_encoder_resolve_query_set", + op_sync(command_encoder::op_webgpu_command_encoder_resolve_query_set), + ), + ( + "op_webgpu_command_encoder_finish", + op_sync(command_encoder::op_webgpu_command_encoder_finish), + ), + // render_pass + ( + "op_webgpu_render_pass_set_viewport", + op_sync(render_pass::op_webgpu_render_pass_set_viewport), + ), + ( + "op_webgpu_render_pass_set_scissor_rect", + op_sync(render_pass::op_webgpu_render_pass_set_scissor_rect), + ), + ( + "op_webgpu_render_pass_set_blend_constant", + op_sync(render_pass::op_webgpu_render_pass_set_blend_constant), + ), + ( + "op_webgpu_render_pass_set_stencil_reference", + op_sync(render_pass::op_webgpu_render_pass_set_stencil_reference), + ), + ( + "op_webgpu_render_pass_begin_pipeline_statistics_query", + op_sync( + render_pass::op_webgpu_render_pass_begin_pipeline_statistics_query, + ), + ), + ( + "op_webgpu_render_pass_end_pipeline_statistics_query", + op_sync(render_pass::op_webgpu_render_pass_end_pipeline_statistics_query), + ), + ( + "op_webgpu_render_pass_write_timestamp", + op_sync(render_pass::op_webgpu_render_pass_write_timestamp), + ), + ( + "op_webgpu_render_pass_execute_bundles", + op_sync(render_pass::op_webgpu_render_pass_execute_bundles), + ), + ( + "op_webgpu_render_pass_end_pass", + op_sync(render_pass::op_webgpu_render_pass_end_pass), + ), + ( + "op_webgpu_render_pass_set_bind_group", + op_sync(render_pass::op_webgpu_render_pass_set_bind_group), + ), + ( + "op_webgpu_render_pass_push_debug_group", + op_sync(render_pass::op_webgpu_render_pass_push_debug_group), + ), + ( + "op_webgpu_render_pass_pop_debug_group", + op_sync(render_pass::op_webgpu_render_pass_pop_debug_group), + ), + ( + "op_webgpu_render_pass_insert_debug_marker", + op_sync(render_pass::op_webgpu_render_pass_insert_debug_marker), + ), + ( + "op_webgpu_render_pass_set_pipeline", + op_sync(render_pass::op_webgpu_render_pass_set_pipeline), + ), + ( + "op_webgpu_render_pass_set_index_buffer", + op_sync(render_pass::op_webgpu_render_pass_set_index_buffer), + ), + ( + "op_webgpu_render_pass_set_vertex_buffer", + op_sync(render_pass::op_webgpu_render_pass_set_vertex_buffer), + ), + ( + "op_webgpu_render_pass_draw", + op_sync(render_pass::op_webgpu_render_pass_draw), + ), + ( + "op_webgpu_render_pass_draw_indexed", + op_sync(render_pass::op_webgpu_render_pass_draw_indexed), + ), + ( + "op_webgpu_render_pass_draw_indirect", + op_sync(render_pass::op_webgpu_render_pass_draw_indirect), + ), + ( + "op_webgpu_render_pass_draw_indexed_indirect", + op_sync(render_pass::op_webgpu_render_pass_draw_indexed_indirect), + ), + // compute_pass + ( + "op_webgpu_compute_pass_set_pipeline", + op_sync(compute_pass::op_webgpu_compute_pass_set_pipeline), + ), + ( + "op_webgpu_compute_pass_dispatch", + op_sync(compute_pass::op_webgpu_compute_pass_dispatch), + ), + ( + "op_webgpu_compute_pass_dispatch_indirect", + op_sync(compute_pass::op_webgpu_compute_pass_dispatch_indirect), + ), + ( + "op_webgpu_compute_pass_begin_pipeline_statistics_query", + op_sync( + compute_pass::op_webgpu_compute_pass_begin_pipeline_statistics_query, + ), + ), + ( + "op_webgpu_compute_pass_end_pipeline_statistics_query", + op_sync( + compute_pass::op_webgpu_compute_pass_end_pipeline_statistics_query, + ), + ), + ( + "op_webgpu_compute_pass_write_timestamp", + op_sync(compute_pass::op_webgpu_compute_pass_write_timestamp), + ), + ( + "op_webgpu_compute_pass_end_pass", + op_sync(compute_pass::op_webgpu_compute_pass_end_pass), + ), + ( + "op_webgpu_compute_pass_set_bind_group", + op_sync(compute_pass::op_webgpu_compute_pass_set_bind_group), + ), + ( + "op_webgpu_compute_pass_push_debug_group", + op_sync(compute_pass::op_webgpu_compute_pass_push_debug_group), + ), + ( + "op_webgpu_compute_pass_pop_debug_group", + op_sync(compute_pass::op_webgpu_compute_pass_pop_debug_group), + ), + ( + "op_webgpu_compute_pass_insert_debug_marker", + op_sync(compute_pass::op_webgpu_compute_pass_insert_debug_marker), + ), + // bundle + ( + "op_webgpu_create_render_bundle_encoder", + op_sync(bundle::op_webgpu_create_render_bundle_encoder), + ), + ( + "op_webgpu_render_bundle_encoder_finish", + op_sync(bundle::op_webgpu_render_bundle_encoder_finish), + ), + ( + "op_webgpu_render_bundle_encoder_set_bind_group", + op_sync(bundle::op_webgpu_render_bundle_encoder_set_bind_group), + ), + ( + "op_webgpu_render_bundle_encoder_push_debug_group", + op_sync(bundle::op_webgpu_render_bundle_encoder_push_debug_group), + ), + ( + "op_webgpu_render_bundle_encoder_pop_debug_group", + op_sync(bundle::op_webgpu_render_bundle_encoder_pop_debug_group), + ), + ( + "op_webgpu_render_bundle_encoder_insert_debug_marker", + op_sync(bundle::op_webgpu_render_bundle_encoder_insert_debug_marker), + ), + ( + "op_webgpu_render_bundle_encoder_set_pipeline", + op_sync(bundle::op_webgpu_render_bundle_encoder_set_pipeline), + ), + ( + "op_webgpu_render_bundle_encoder_set_index_buffer", + op_sync(bundle::op_webgpu_render_bundle_encoder_set_index_buffer), + ), + ( + "op_webgpu_render_bundle_encoder_set_vertex_buffer", + op_sync(bundle::op_webgpu_render_bundle_encoder_set_vertex_buffer), + ), + ( + "op_webgpu_render_bundle_encoder_draw", + op_sync(bundle::op_webgpu_render_bundle_encoder_draw), + ), + ( + "op_webgpu_render_bundle_encoder_draw_indexed", + op_sync(bundle::op_webgpu_render_bundle_encoder_draw_indexed), + ), + ( + "op_webgpu_render_bundle_encoder_draw_indirect", + op_sync(bundle::op_webgpu_render_bundle_encoder_draw_indirect), + ), + // queue + ( + "op_webgpu_queue_submit", + op_sync(queue::op_webgpu_queue_submit), + ), + ( + "op_webgpu_write_buffer", + op_sync(queue::op_webgpu_write_buffer), + ), + ( + "op_webgpu_write_texture", + op_sync(queue::op_webgpu_write_texture), + ), + // shader + ( + "op_webgpu_create_shader_module", + op_sync(shader::op_webgpu_create_shader_module), + ), + ] +} diff --git a/ext/webgpu/src/queue.rs b/ext/webgpu/src/queue.rs new file mode 100644 index 000000000..a662c4ead --- /dev/null +++ b/ext/webgpu/src/queue.rs @@ -0,0 +1,142 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::num::NonZeroU32; + +use deno_core::error::AnyError; +use deno_core::OpState; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use serde::Deserialize; + +use super::error::WebGpuResult; + +type WebGpuQueue = super::WebGpuDevice; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueSubmitArgs { + queue_rid: ResourceId, + command_buffers: Vec<ResourceId>, +} + +pub fn op_webgpu_queue_submit( + state: &mut OpState, + args: QueueSubmitArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let queue_resource = + state.resource_table.get::<WebGpuQueue>(args.queue_rid)?; + let queue = queue_resource.0; + + let mut ids = vec![]; + + for rid in args.command_buffers { + let buffer_resource = + state + .resource_table + .get::<super::command_encoder::WebGpuCommandBuffer>(rid)?; + ids.push(buffer_resource.0); + } + + let maybe_err = + gfx_select!(queue => instance.queue_submit(queue, &ids)).err(); + + Ok(WebGpuResult::maybe_err(maybe_err)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct GpuImageDataLayout { + offset: u64, + bytes_per_row: Option<u32>, + rows_per_image: Option<u32>, +} + +impl From<GpuImageDataLayout> for wgpu_types::ImageDataLayout { + fn from(layout: GpuImageDataLayout) -> Self { + wgpu_types::ImageDataLayout { + offset: layout.offset, + bytes_per_row: NonZeroU32::new(layout.bytes_per_row.unwrap_or(0)), + rows_per_image: NonZeroU32::new(layout.rows_per_image.unwrap_or(0)), + } + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueWriteBufferArgs { + queue_rid: ResourceId, + buffer: ResourceId, + buffer_offset: u64, + data_offset: usize, + size: Option<usize>, +} + +pub fn op_webgpu_write_buffer( + state: &mut OpState, + args: QueueWriteBufferArgs, + zero_copy: ZeroCopyBuf, +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer)?; + let buffer = buffer_resource.0; + let queue_resource = + state.resource_table.get::<WebGpuQueue>(args.queue_rid)?; + let queue = queue_resource.0; + + let data = match args.size { + Some(size) => &zero_copy[args.data_offset..(args.data_offset + size)], + None => &zero_copy[args.data_offset..], + }; + let maybe_err = gfx_select!(queue => instance.queue_write_buffer( + queue, + buffer, + args.buffer_offset, + data + )) + .err(); + + Ok(WebGpuResult::maybe_err(maybe_err)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct QueueWriteTextureArgs { + queue_rid: ResourceId, + destination: super::command_encoder::GpuImageCopyTexture, + data_layout: GpuImageDataLayout, + size: wgpu_types::Extent3d, +} + +pub fn op_webgpu_write_texture( + state: &mut OpState, + args: QueueWriteTextureArgs, + zero_copy: ZeroCopyBuf, +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let texture_resource = state + .resource_table + .get::<super::texture::WebGpuTexture>(args.destination.texture)?; + let queue_resource = + state.resource_table.get::<WebGpuQueue>(args.queue_rid)?; + let queue = queue_resource.0; + + let destination = wgpu_core::command::ImageCopyTexture { + texture: texture_resource.0, + mip_level: args.destination.mip_level, + origin: args.destination.origin, + aspect: args.destination.aspect, + }; + let data_layout = args.data_layout.into(); + + gfx_ok!(queue => instance.queue_write_texture( + queue, + &destination, + &*zero_copy, + &data_layout, + &args.size + )) +} diff --git a/ext/webgpu/src/render_pass.rs b/ext/webgpu/src/render_pass.rs new file mode 100644 index 000000000..469bf727e --- /dev/null +++ b/ext/webgpu/src/render_pass.rs @@ -0,0 +1,649 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; +use std::cell::RefCell; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuRenderPass( + pub(crate) RefCell<wgpu_core::command::RenderPass>, +); +impl Resource for WebGpuRenderPass { + fn name(&self) -> Cow<str> { + "webGPURenderPass".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetViewportArgs { + render_pass_rid: ResourceId, + x: f32, + y: f32, + width: f32, + height: f32, + min_depth: f32, + max_depth: f32, +} + +pub fn op_webgpu_render_pass_set_viewport( + state: &mut OpState, + args: RenderPassSetViewportArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_viewport( + &mut render_pass_resource.0.borrow_mut(), + args.x, + args.y, + args.width, + args.height, + args.min_depth, + args.max_depth, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetScissorRectArgs { + render_pass_rid: ResourceId, + x: u32, + y: u32, + width: u32, + height: u32, +} + +pub fn op_webgpu_render_pass_set_scissor_rect( + state: &mut OpState, + args: RenderPassSetScissorRectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_scissor_rect( + &mut render_pass_resource.0.borrow_mut(), + args.x, + args.y, + args.width, + args.height, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetBlendConstantArgs { + render_pass_rid: ResourceId, + color: wgpu_types::Color, +} + +pub fn op_webgpu_render_pass_set_blend_constant( + state: &mut OpState, + args: RenderPassSetBlendConstantArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_blend_constant( + &mut render_pass_resource.0.borrow_mut(), + &args.color, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetStencilReferenceArgs { + render_pass_rid: ResourceId, + reference: u32, +} + +pub fn op_webgpu_render_pass_set_stencil_reference( + state: &mut OpState, + args: RenderPassSetStencilReferenceArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_stencil_reference( + &mut render_pass_resource.0.borrow_mut(), + args.reference, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassBeginPipelineStatisticsQueryArgs { + render_pass_rid: ResourceId, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_render_pass_begin_pipeline_statistics_query( + state: &mut OpState, + args: RenderPassBeginPipelineStatisticsQueryArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_begin_pipeline_statistics_query( + &mut render_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassEndPipelineStatisticsQueryArgs { + render_pass_rid: ResourceId, +} + +pub fn op_webgpu_render_pass_end_pipeline_statistics_query( + state: &mut OpState, + args: RenderPassEndPipelineStatisticsQueryArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_end_pipeline_statistics_query( + &mut render_pass_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassWriteTimestampArgs { + render_pass_rid: ResourceId, + query_set: u32, + query_index: u32, +} + +pub fn op_webgpu_render_pass_write_timestamp( + state: &mut OpState, + args: RenderPassWriteTimestampArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + let query_set_resource = state + .resource_table + .get::<super::WebGpuQuerySet>(args.query_set)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_write_timestamp( + &mut render_pass_resource.0.borrow_mut(), + query_set_resource.0, + args.query_index, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassExecuteBundlesArgs { + render_pass_rid: ResourceId, + bundles: Vec<u32>, +} + +pub fn op_webgpu_render_pass_execute_bundles( + state: &mut OpState, + args: RenderPassExecuteBundlesArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let mut render_bundle_ids = vec![]; + + for rid in &args.bundles { + let render_bundle_resource = + state + .resource_table + .get::<super::bundle::WebGpuRenderBundle>(*rid)?; + render_bundle_ids.push(render_bundle_resource.0); + } + + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + // SAFETY: the raw pointer and length are of the same slice, and that slice + // lives longer than the below function invocation. + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_execute_bundles( + &mut render_pass_resource.0.borrow_mut(), + render_bundle_ids.as_ptr(), + render_bundle_ids.len(), + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassEndPassArgs { + command_encoder_rid: ResourceId, + render_pass_rid: ResourceId, +} + +pub fn op_webgpu_render_pass_end_pass( + state: &mut OpState, + args: RenderPassEndPassArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let command_encoder_resource = state + .resource_table + .get::<super::command_encoder::WebGpuCommandEncoder>( + args.command_encoder_rid, + )?; + let command_encoder = command_encoder_resource.0; + let render_pass_resource = state + .resource_table + .take::<WebGpuRenderPass>(args.render_pass_rid)?; + let render_pass = &render_pass_resource.0.borrow(); + let instance = state.borrow::<super::Instance>(); + + gfx_ok!(command_encoder => instance.command_encoder_run_render_pass(command_encoder, render_pass)) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetBindGroupArgs { + render_pass_rid: ResourceId, + index: u32, + bind_group: u32, + dynamic_offsets_data: ZeroCopyBuf, + dynamic_offsets_data_start: usize, + dynamic_offsets_data_length: usize, +} + +pub fn op_webgpu_render_pass_set_bind_group( + state: &mut OpState, + args: RenderPassSetBindGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let bind_group_resource = + state + .resource_table + .get::<super::binding::WebGpuBindGroup>(args.bind_group)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + // Align the data + assert!(args.dynamic_offsets_data_start % std::mem::size_of::<u32>() == 0); + // SAFETY: A u8 to u32 cast is safe because we asserted that the length is a + // multiple of 4. + let (prefix, dynamic_offsets_data, suffix) = + unsafe { args.dynamic_offsets_data.align_to::<u32>() }; + assert!(prefix.is_empty()); + assert!(suffix.is_empty()); + + let start = args.dynamic_offsets_data_start; + let len = args.dynamic_offsets_data_length; + + // Assert that length and start are both in bounds + assert!(start <= dynamic_offsets_data.len()); + assert!(len <= dynamic_offsets_data.len() - start); + + let dynamic_offsets_data: &[u32] = &dynamic_offsets_data[start..start + len]; + + // SAFETY: the raw pointer and length are of the same slice, and that slice + // lives longer than the below function invocation. + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_set_bind_group( + &mut render_pass_resource.0.borrow_mut(), + args.index, + bind_group_resource.0, + dynamic_offsets_data.as_ptr(), + dynamic_offsets_data.len(), + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassPushDebugGroupArgs { + render_pass_rid: ResourceId, + group_label: String, +} + +pub fn op_webgpu_render_pass_push_debug_group( + state: &mut OpState, + args: RenderPassPushDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + let label = std::ffi::CString::new(args.group_label).unwrap(); + // SAFETY: the string the raw pointer points to lives longer than the below + // function invocation. + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_push_debug_group( + &mut render_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassPopDebugGroupArgs { + render_pass_rid: ResourceId, +} + +pub fn op_webgpu_render_pass_pop_debug_group( + state: &mut OpState, + args: RenderPassPopDebugGroupArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_pop_debug_group( + &mut render_pass_resource.0.borrow_mut(), + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassInsertDebugMarkerArgs { + render_pass_rid: ResourceId, + marker_label: String, +} + +pub fn op_webgpu_render_pass_insert_debug_marker( + state: &mut OpState, + args: RenderPassInsertDebugMarkerArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + let label = std::ffi::CString::new(args.marker_label).unwrap(); + // SAFETY: the string the raw pointer points to lives longer than the below + // function invocation. + unsafe { + wgpu_core::command::render_ffi::wgpu_render_pass_insert_debug_marker( + &mut render_pass_resource.0.borrow_mut(), + label.as_ptr(), + 0, // wgpu#975 + ); + } + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetPipelineArgs { + render_pass_rid: ResourceId, + pipeline: u32, +} + +pub fn op_webgpu_render_pass_set_pipeline( + state: &mut OpState, + args: RenderPassSetPipelineArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pipeline_resource = + state + .resource_table + .get::<super::pipeline::WebGpuRenderPipeline>(args.pipeline)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_pipeline( + &mut render_pass_resource.0.borrow_mut(), + render_pipeline_resource.0, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetIndexBufferArgs { + render_pass_rid: ResourceId, + buffer: u32, + index_format: wgpu_types::IndexFormat, + offset: u64, + size: Option<u64>, +} + +pub fn op_webgpu_render_pass_set_index_buffer( + state: &mut OpState, + args: RenderPassSetIndexBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + let size = if let Some(size) = args.size { + Some( + std::num::NonZeroU64::new(size) + .ok_or_else(|| type_error("size must be larger than 0"))?, + ) + } else { + None + }; + + render_pass_resource.0.borrow_mut().set_index_buffer( + buffer_resource.0, + args.index_format, + args.offset, + size, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassSetVertexBufferArgs { + render_pass_rid: ResourceId, + slot: u32, + buffer: u32, + offset: u64, + size: Option<u64>, +} + +pub fn op_webgpu_render_pass_set_vertex_buffer( + state: &mut OpState, + args: RenderPassSetVertexBufferArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.buffer)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + let size = if let Some(size) = args.size { + Some( + std::num::NonZeroU64::new(size) + .ok_or_else(|| type_error("size must be larger than 0"))?, + ) + } else { + None + }; + + wgpu_core::command::render_ffi::wgpu_render_pass_set_vertex_buffer( + &mut render_pass_resource.0.borrow_mut(), + args.slot, + buffer_resource.0, + args.offset, + size, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawArgs { + render_pass_rid: ResourceId, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, +} + +pub fn op_webgpu_render_pass_draw( + state: &mut OpState, + args: RenderPassDrawArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw( + &mut render_pass_resource.0.borrow_mut(), + args.vertex_count, + args.instance_count, + args.first_vertex, + args.first_instance, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndexedArgs { + render_pass_rid: ResourceId, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, +} + +pub fn op_webgpu_render_pass_draw_indexed( + state: &mut OpState, + args: RenderPassDrawIndexedArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw_indexed( + &mut render_pass_resource.0.borrow_mut(), + args.index_count, + args.instance_count, + args.first_index, + args.base_vertex, + args.first_instance, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndirectArgs { + render_pass_rid: ResourceId, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_pass_draw_indirect( + state: &mut OpState, + args: RenderPassDrawIndirectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.indirect_buffer)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw_indirect( + &mut render_pass_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(WebGpuResult::empty()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenderPassDrawIndexedIndirectArgs { + render_pass_rid: ResourceId, + indirect_buffer: u32, + indirect_offset: u64, +} + +pub fn op_webgpu_render_pass_draw_indexed_indirect( + state: &mut OpState, + args: RenderPassDrawIndexedIndirectArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let buffer_resource = state + .resource_table + .get::<super::buffer::WebGpuBuffer>(args.indirect_buffer)?; + let render_pass_resource = state + .resource_table + .get::<WebGpuRenderPass>(args.render_pass_rid)?; + + wgpu_core::command::render_ffi::wgpu_render_pass_draw_indexed_indirect( + &mut render_pass_resource.0.borrow_mut(), + buffer_resource.0, + args.indirect_offset, + ); + + Ok(WebGpuResult::empty()) +} diff --git a/ext/webgpu/src/shader.rs b/ext/webgpu/src/shader.rs new file mode 100644 index 000000000..60290de8b --- /dev/null +++ b/ext/webgpu/src/shader.rs @@ -0,0 +1,52 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::ResourceId; +use deno_core::{OpState, Resource}; +use serde::Deserialize; +use std::borrow::Cow; + +use super::error::WebGpuResult; + +pub(crate) struct WebGpuShaderModule(pub(crate) wgpu_core::id::ShaderModuleId); +impl Resource for WebGpuShaderModule { + fn name(&self) -> Cow<str> { + "webGPUShaderModule".into() + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateShaderModuleArgs { + device_rid: ResourceId, + label: Option<String>, + code: String, + _source_map: Option<()>, // not yet implemented +} + +pub fn op_webgpu_create_shader_module( + state: &mut OpState, + args: CreateShaderModuleArgs, + _: (), +) -> Result<WebGpuResult, AnyError> { + let instance = state.borrow::<super::Instance>(); + let device_resource = state + .resource_table + .get::<super::WebGpuDevice>(args.device_rid)?; + let device = device_resource.0; + + let source = + wgpu_core::pipeline::ShaderModuleSource::Wgsl(Cow::from(args.code)); + + let descriptor = wgpu_core::pipeline::ShaderModuleDescriptor { + label: args.label.map(Cow::from), + shader_bound_checks: wgpu_types::ShaderBoundChecks::default(), + }; + + gfx_put!(device => instance.device_create_shader_module( + device, + &descriptor, + source, + std::marker::PhantomData + ) => state, WebGpuShaderModule) +} |