// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; use deno_core::ResourceId; use serde::Deserialize; use serde::Serialize; use std::borrow::Cow; use std::collections::HashMap; use std::rc::Rc; use super::error::WebGpuError; use super::error::WebGpuResult; const MAX_BIND_GROUPS: usize = 8; pub(crate) struct WebGpuPipelineLayout( pub(crate) crate::Instance, pub(crate) wgpu_core::id::PipelineLayoutId, ); impl Resource for WebGpuPipelineLayout { fn name(&self) -> Cow { "webGPUPipelineLayout".into() } fn close(self: Rc) { gfx_select!(self.1 => self.0.pipeline_layout_drop(self.1)); } } pub(crate) struct WebGpuComputePipeline( pub(crate) crate::Instance, pub(crate) wgpu_core::id::ComputePipelineId, ); impl Resource for WebGpuComputePipeline { fn name(&self) -> Cow { "webGPUComputePipeline".into() } fn close(self: Rc) { gfx_select!(self.1 => self.0.compute_pipeline_drop(self.1)); } } pub(crate) struct WebGpuRenderPipeline( pub(crate) crate::Instance, pub(crate) wgpu_core::id::RenderPipelineId, ); impl Resource for WebGpuRenderPipeline { fn name(&self) -> Cow { "webGPURenderPipeline".into() } fn close(self: Rc) { gfx_select!(self.1 => self.0.render_pipeline_drop(self.1)); } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub enum GPUAutoLayoutMode { Auto, } #[derive(Deserialize)] #[serde(untagged)] pub enum GPUPipelineLayoutOrGPUAutoLayoutMode { Layout(ResourceId), Auto(GPUAutoLayoutMode), } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct GpuProgrammableStage { module: ResourceId, entry_point: Option, constants: Option>, } #[op2] #[serde] pub fn op_webgpu_create_compute_pipeline( state: &mut OpState, #[smi] device_rid: ResourceId, #[string] label: Cow, #[serde] layout: GPUPipelineLayoutOrGPUAutoLayoutMode, #[serde] compute: GpuProgrammableStage, ) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table .get::(device_rid)?; let device = device_resource.1; let pipeline_layout = match layout { GPUPipelineLayoutOrGPUAutoLayoutMode::Layout(rid) => { let id = state.resource_table.get::(rid)?; Some(id.1) } GPUPipelineLayoutOrGPUAutoLayoutMode::Auto(GPUAutoLayoutMode::Auto) => None, }; let compute_shader_module_resource = state .resource_table .get::(compute.module)?; let descriptor = wgpu_core::pipeline::ComputePipelineDescriptor { label: Some(label), layout: pipeline_layout, stage: wgpu_core::pipeline::ProgrammableStageDescriptor { module: compute_shader_module_resource.1, entry_point: compute.entry_point.map(Cow::from), constants: Cow::Owned(compute.constants.unwrap_or_default()), zero_initialize_workgroup_memory: true, }, }; let implicit_pipelines = match layout { GPUPipelineLayoutOrGPUAutoLayoutMode::Layout(_) => None, GPUPipelineLayoutOrGPUAutoLayoutMode::Auto(GPUAutoLayoutMode::Auto) => { Some(wgpu_core::device::ImplicitPipelineIds { root_id: None, group_ids: &[None; MAX_BIND_GROUPS], }) } }; let (compute_pipeline, maybe_err) = gfx_select!(device => instance.device_create_compute_pipeline( device, &descriptor, None, implicit_pipelines )); let rid = state .resource_table .add(WebGpuComputePipeline(instance.clone(), compute_pipeline)); Ok(WebGpuResult::rid_err(rid, maybe_err)) } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct PipelineLayout { rid: ResourceId, label: String, err: Option, } #[op2] #[serde] pub fn op_webgpu_compute_pipeline_get_bind_group_layout( state: &mut OpState, #[smi] compute_pipeline_rid: ResourceId, index: u32, ) -> Result { let instance = state.borrow::(); let compute_pipeline_resource = state .resource_table .get::(compute_pipeline_rid)?; let compute_pipeline = compute_pipeline_resource.1; let (bind_group_layout, maybe_err) = gfx_select!(compute_pipeline => instance.compute_pipeline_get_bind_group_layout(compute_pipeline, index, None)); let label = gfx_select!(bind_group_layout => instance.bind_group_layout_label(bind_group_layout)); let rid = state .resource_table .add(super::binding::WebGpuBindGroupLayout( instance.clone(), bind_group_layout, )); Ok(PipelineLayout { rid, label, err: maybe_err.map(WebGpuError::from), }) } #[derive(Deserialize)] #[serde(rename_all = "kebab-case")] pub enum GpuCullMode { None, Front, Back, } impl From for Option { fn from(value: GpuCullMode) -> Option { match value { GpuCullMode::None => None, GpuCullMode::Front => Some(wgpu_types::Face::Front), GpuCullMode::Back => Some(wgpu_types::Face::Back), } } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct GpuPrimitiveState { topology: wgpu_types::PrimitiveTopology, strip_index_format: Option, front_face: wgpu_types::FrontFace, cull_mode: GpuCullMode, unclipped_depth: bool, } impl From for wgpu_types::PrimitiveState { fn from(value: GpuPrimitiveState) -> wgpu_types::PrimitiveState { wgpu_types::PrimitiveState { topology: value.topology, strip_index_format: value.strip_index_format, front_face: value.front_face, cull_mode: value.cull_mode.into(), unclipped_depth: value.unclipped_depth, polygon_mode: Default::default(), // native-only conservative: false, // native-only } } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct GpuDepthStencilState { format: wgpu_types::TextureFormat, depth_write_enabled: bool, depth_compare: wgpu_types::CompareFunction, stencil_front: wgpu_types::StencilFaceState, stencil_back: wgpu_types::StencilFaceState, stencil_read_mask: u32, stencil_write_mask: u32, depth_bias: i32, depth_bias_slope_scale: f32, depth_bias_clamp: f32, } impl From for wgpu_types::DepthStencilState { fn from(state: GpuDepthStencilState) -> wgpu_types::DepthStencilState { wgpu_types::DepthStencilState { format: state.format, depth_write_enabled: state.depth_write_enabled, depth_compare: state.depth_compare, stencil: wgpu_types::StencilState { front: state.stencil_front, back: state.stencil_back, read_mask: state.stencil_read_mask, write_mask: state.stencil_write_mask, }, bias: wgpu_types::DepthBiasState { constant: state.depth_bias, slope_scale: state.depth_bias_slope_scale, clamp: state.depth_bias_clamp, }, } } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct GpuVertexBufferLayout { array_stride: u64, step_mode: wgpu_types::VertexStepMode, attributes: Vec, } impl<'a> From for wgpu_core::pipeline::VertexBufferLayout<'a> { fn from( layout: GpuVertexBufferLayout, ) -> wgpu_core::pipeline::VertexBufferLayout<'a> { wgpu_core::pipeline::VertexBufferLayout { array_stride: layout.array_stride, step_mode: layout.step_mode, attributes: Cow::Owned(layout.attributes), } } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct GpuVertexState { module: ResourceId, entry_point: Option, constants: Option>, buffers: Vec>, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct GpuMultisampleState { count: u32, mask: u64, alpha_to_coverage_enabled: bool, } impl From for wgpu_types::MultisampleState { fn from(gms: GpuMultisampleState) -> wgpu_types::MultisampleState { wgpu_types::MultisampleState { count: gms.count, mask: gms.mask, alpha_to_coverage_enabled: gms.alpha_to_coverage_enabled, } } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct GpuFragmentState { targets: Vec>, module: u32, entry_point: Option, constants: Option>, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct CreateRenderPipelineArgs { device_rid: ResourceId, label: String, layout: GPUPipelineLayoutOrGPUAutoLayoutMode, vertex: GpuVertexState, primitive: GpuPrimitiveState, depth_stencil: Option, multisample: wgpu_types::MultisampleState, fragment: Option, } #[op2] #[serde] pub fn op_webgpu_create_render_pipeline( state: &mut OpState, #[serde] args: CreateRenderPipelineArgs, ) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table .get::(args.device_rid)?; let device = device_resource.1; let layout = match args.layout { GPUPipelineLayoutOrGPUAutoLayoutMode::Layout(rid) => { let pipeline_layout_resource = state.resource_table.get::(rid)?; Some(pipeline_layout_resource.1) } GPUPipelineLayoutOrGPUAutoLayoutMode::Auto(GPUAutoLayoutMode::Auto) => None, }; let vertex_shader_module_resource = state .resource_table .get::(args.vertex.module)?; let fragment = if let Some(fragment) = args.fragment { let fragment_shader_module_resource = state .resource_table .get::(fragment.module)?; Some(wgpu_core::pipeline::FragmentState { stage: wgpu_core::pipeline::ProgrammableStageDescriptor { module: fragment_shader_module_resource.1, entry_point: fragment.entry_point.map(Cow::from), constants: Cow::Owned(fragment.constants.unwrap_or_default()), // Required to be true for WebGPU zero_initialize_workgroup_memory: true, }, targets: Cow::Owned(fragment.targets), }) } else { None }; let vertex_buffers = args .vertex .buffers .into_iter() .flatten() .map(Into::into) .collect(); let descriptor = wgpu_core::pipeline::RenderPipelineDescriptor { label: Some(Cow::Owned(args.label)), layout, vertex: wgpu_core::pipeline::VertexState { stage: wgpu_core::pipeline::ProgrammableStageDescriptor { module: vertex_shader_module_resource.1, entry_point: args.vertex.entry_point.map(Cow::Owned), constants: Cow::Owned(args.vertex.constants.unwrap_or_default()), // Required to be true for WebGPU zero_initialize_workgroup_memory: true, }, buffers: Cow::Owned(vertex_buffers), }, primitive: args.primitive.into(), depth_stencil: args.depth_stencil.map(Into::into), multisample: args.multisample, fragment, multiview: None, }; let implicit_pipelines = match args.layout { GPUPipelineLayoutOrGPUAutoLayoutMode::Layout(_) => None, GPUPipelineLayoutOrGPUAutoLayoutMode::Auto(GPUAutoLayoutMode::Auto) => { Some(wgpu_core::device::ImplicitPipelineIds { root_id: None, group_ids: &[None; MAX_BIND_GROUPS], }) } }; let (render_pipeline, maybe_err) = gfx_select!(device => instance.device_create_render_pipeline( device, &descriptor, None, implicit_pipelines )); let rid = state .resource_table .add(WebGpuRenderPipeline(instance.clone(), render_pipeline)); Ok(WebGpuResult::rid_err(rid, maybe_err)) } #[op2] #[serde] pub fn op_webgpu_render_pipeline_get_bind_group_layout( state: &mut OpState, #[smi] render_pipeline_rid: ResourceId, index: u32, ) -> Result { let instance = state.borrow::(); let render_pipeline_resource = state .resource_table .get::(render_pipeline_rid)?; let render_pipeline = render_pipeline_resource.1; let (bind_group_layout, maybe_err) = gfx_select!(render_pipeline => instance.render_pipeline_get_bind_group_layout(render_pipeline, index, None)); let label = gfx_select!(bind_group_layout => instance.bind_group_layout_label(bind_group_layout)); let rid = state .resource_table .add(super::binding::WebGpuBindGroupLayout( instance.clone(), bind_group_layout, )); Ok(PipelineLayout { rid, label, err: maybe_err.map(WebGpuError::from), }) }