diff --git a/cts_runner/test.lst b/cts_runner/test.lst index aacb7776ed..62afa3008c 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -135,6 +135,7 @@ webgpu:api,validation,render_pass,render_pass_descriptor:attachments,* webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,* webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,* webgpu:api,validation,render_pass,resolve:resolve_attachment:* +webgpu:api,validation,render_pipeline,depth_stencil_state:stencil_write:* webgpu:api,validation,resource_usages,buffer,in_pass_encoder:* // FAIL: 2 other cases in resource_usages,texture,in_pass_encoder. https://github.com/gfx-rs/wgpu/issues/3126 webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,* diff --git a/deno_webgpu/01_webgpu.js b/deno_webgpu/01_webgpu.js index 8c5ce5c593..5f09cf3afd 100644 --- a/deno_webgpu/01_webgpu.js +++ b/deno_webgpu/01_webgpu.js @@ -841,6 +841,14 @@ ObjectDefineProperty(GPUQuerySetPrototype, privateCustomInspect, { // Converters +webidl.converters["GPUPipelineErrorReason"] = webidl.createEnumConverter( + "GPUPipelineErrorReason", + [ + "validation", + "internal", + ], +); + webidl.converters["GPUPipelineErrorInit"] = webidl.createDictionaryConverter( "GPUPipelineErrorInit", [ @@ -852,14 +860,6 @@ webidl.converters["GPUPipelineErrorInit"] = webidl.createDictionaryConverter( ], ); -webidl.converters["GPUPipelineErrorReason"] = webidl.createEnumConverter( - "GPUPipelineErrorReason", - [ - "validation", - "internal", - ], -); - webidl.converters["GPUError"] = webidl.converters.any /* put union here! */; const dictMembersGPUUncapturedErrorEventInit = [ @@ -892,6 +892,7 @@ function initGPU() { webidl.brand, setEventTargetData, GPUUncapturedErrorEvent, + GPUPipelineError, ); } } diff --git a/deno_webgpu/bind_group_layout.rs b/deno_webgpu/bind_group_layout.rs index 79b7bbe0bd..fe3fe8a0f6 100644 --- a/deno_webgpu/bind_group_layout.rs +++ b/deno_webgpu/bind_group_layout.rs @@ -6,6 +6,7 @@ use deno_core::WebIDL; use crate::error::GPUGenericError; use crate::texture::GPUTextureViewDimension; +use crate::webidl::GPUShaderStageFlags; use crate::Instance; pub struct GPUBindGroupLayout { @@ -63,8 +64,7 @@ pub(crate) struct GPUBindGroupLayoutDescriptor { pub(crate) struct GPUBindGroupLayoutEntry { #[options(enforce_range = true)] pub binding: u32, - #[options(enforce_range = true)] - pub visibility: u32, + pub visibility: GPUShaderStageFlags, pub buffer: Option, pub sampler: Option, pub texture: Option, diff --git a/deno_webgpu/device.rs b/deno_webgpu/device.rs index 243a9e05d6..e84f971e2e 100644 --- a/deno_webgpu/device.rs +++ b/deno_webgpu/device.rs @@ -29,7 +29,8 @@ use crate::adapter::GPUAdapterInfo; use crate::adapter::GPUSupportedFeatures; use crate::adapter::GPUSupportedLimits; use crate::command_encoder::GPUCommandEncoder; -use crate::error::{GPUError, GPUGenericError}; +use crate::error::make_pipeline_error; +use crate::error::{GPUError, GPUGenericError, GPUPipelineErrorReason}; use crate::query_set::GPUQuerySet; use crate::render_bundle::GPURenderBundleEncoder; use crate::render_pipeline::GPURenderPipeline; @@ -221,8 +222,7 @@ impl GPUDevice { sample_count: descriptor.sample_count, dimension: descriptor.dimension.clone().into(), format: descriptor.format.clone().into(), - usage: wgpu_types::TextureUsages::from_bits(descriptor.usage) - .ok_or_else(|| JsErrorBox::type_error("usage is not valid"))?, + usage: descriptor.usage.into(), view_formats: descriptor .view_formats .into_iter() @@ -340,8 +340,7 @@ impl GPUDevice { entries.push(wgpu_types::BindGroupLayoutEntry { binding: entry.binding, - visibility: wgpu_types::ShaderStages::from_bits(entry.visibility) - .ok_or_else(|| JsErrorBox::type_error("usage is not valid"))?, + visibility: entry.visibility.into(), ty, count: None, // native-only }); @@ -500,7 +499,9 @@ impl GPUDevice { &self, #[webidl] descriptor: super::compute_pipeline::GPUComputePipelineDescriptor, ) -> GPUComputePipeline { - self.new_compute_pipeline(descriptor) + let (pipeline, err) = self.new_compute_pipeline(descriptor); + self.error_handler.push_error(err); + pipeline } #[required(1)] @@ -508,28 +509,64 @@ impl GPUDevice { fn create_render_pipeline( &self, #[webidl] descriptor: super::render_pipeline::GPURenderPipelineDescriptor, - ) -> Result { - self.new_render_pipeline(descriptor) + ) -> GPURenderPipeline { + let (pipeline, err) = self.new_render_pipeline(descriptor); + self.error_handler.push_error(err); + pipeline } - #[async_method] + #[async_method(fake)] #[required(1)] #[cppgc] - async fn create_compute_pipeline_async( + #[global] + fn create_compute_pipeline_async( &self, + scope: &mut v8::HandleScope, #[webidl] descriptor: super::compute_pipeline::GPUComputePipelineDescriptor, - ) -> GPUComputePipeline { - self.new_compute_pipeline(descriptor) + ) -> v8::Global { + let resolver = v8::PromiseResolver::new(scope).unwrap(); + let promise = resolver.get_promise(scope); + + let (pipeline, err) = self.new_compute_pipeline(descriptor); + if let Some(err) = err { + let err = make_pipeline_error( + scope, + GPUPipelineErrorReason::Validation, + &err.to_string(), + ); + resolver.reject(scope, err.into()); + } else { + let val = make_cppgc_object(scope, pipeline).into(); + resolver.resolve(scope, val); + } + v8::Global::new(scope, promise) } - #[async_method] + #[async_method(fake)] #[required(1)] #[cppgc] - async fn create_render_pipeline_async( + #[global] + fn create_render_pipeline_async( &self, + scope: &mut v8::HandleScope, #[webidl] descriptor: super::render_pipeline::GPURenderPipelineDescriptor, - ) -> Result { - self.new_render_pipeline(descriptor) + ) -> v8::Global { + let resolver = v8::PromiseResolver::new(scope).unwrap(); + let promise = resolver.get_promise(scope); + + let (pipeline, err) = self.new_render_pipeline(descriptor); + if let Some(err) = err { + let err = make_pipeline_error( + scope, + GPUPipelineErrorReason::Validation, + &err.to_string(), + ); + resolver.reject(scope, err.into()); + } else { + let val = make_cppgc_object(scope, pipeline).into(); + resolver.resolve(scope, val); + } + v8::Global::new(scope, promise) } fn create_command_encoder<'a>( @@ -741,7 +778,10 @@ impl GPUDevice { fn new_compute_pipeline( &self, descriptor: super::compute_pipeline::GPUComputePipelineDescriptor, - ) -> GPUComputePipeline { + ) -> ( + GPUComputePipeline, + Option, + ) { let wgpu_descriptor = wgpu_core::pipeline::ComputePipelineDescriptor { label: crate::transform_label(descriptor.label.clone()), layout: descriptor.layout.into(), @@ -760,20 +800,24 @@ impl GPUDevice { None, ); - self.error_handler.push_error(err); - - GPUComputePipeline { - instance: self.instance.clone(), - error_handler: self.error_handler.clone(), - id, - label: descriptor.label.clone(), - } + ( + GPUComputePipeline { + instance: self.instance.clone(), + error_handler: self.error_handler.clone(), + id, + label: descriptor.label.clone(), + }, + err, + ) } fn new_render_pipeline( &self, descriptor: super::render_pipeline::GPURenderPipelineDescriptor, - ) -> Result { + ) -> ( + GPURenderPipeline, + Option, + ) { let vertex = wgpu_core::pipeline::VertexState { stage: ProgrammableStageDescriptor { module: descriptor.vertex.module.id, @@ -868,10 +912,10 @@ impl GPUDevice { .alpha_to_coverage_enabled, }; - let fragment = descriptor - .fragment - .map(|fragment| { - Ok::<_, JsErrorBox>(wgpu_core::pipeline::FragmentState { + let fragment = + descriptor + .fragment + .map(|fragment| wgpu_core::pipeline::FragmentState { stage: ProgrammableStageDescriptor { module: fragment.module.id, entry_point: fragment.entry_point.map(Into::into), @@ -883,38 +927,28 @@ impl GPUDevice { .targets .into_iter() .map(|target| { - target - .into_option() - .map(|target| { - Ok(wgpu_types::ColorTargetState { - format: target.format.into(), - blend: target.blend.map(|blend| wgpu_types::BlendState { - color: wgpu_types::BlendComponent { - src_factor: blend.color.src_factor.into(), - dst_factor: blend.color.dst_factor.into(), - operation: blend.color.operation.into(), - }, - alpha: wgpu_types::BlendComponent { - src_factor: blend.alpha.src_factor.into(), - dst_factor: blend.alpha.dst_factor.into(), - operation: blend.alpha.operation.into(), - }, - }), - write_mask: wgpu_types::ColorWrites::from_bits( - target.write_mask, - ) - .ok_or_else(|| { - JsErrorBox::type_error("usage is not valid") - })?, - }) - }) - .transpose() + target.into_option().map(|target| { + wgpu_types::ColorTargetState { + format: target.format.into(), + blend: target.blend.map(|blend| wgpu_types::BlendState { + color: wgpu_types::BlendComponent { + src_factor: blend.color.src_factor.into(), + dst_factor: blend.color.dst_factor.into(), + operation: blend.color.operation.into(), + }, + alpha: wgpu_types::BlendComponent { + src_factor: blend.alpha.src_factor.into(), + dst_factor: blend.alpha.dst_factor.into(), + operation: blend.alpha.operation.into(), + }, + }), + write_mask: target.write_mask.into(), + } + }) }) - .collect::>()?, + .collect(), ), - }) - }) - .transpose()?; + }); let wgpu_descriptor = wgpu_core::pipeline::RenderPipelineDescriptor { label: crate::transform_label(descriptor.label.clone()), @@ -934,14 +968,15 @@ impl GPUDevice { None, ); - self.error_handler.push_error(err); - - Ok(GPURenderPipeline { - instance: self.instance.clone(), - error_handler: self.error_handler.clone(), - id, - label: descriptor.label, - }) + ( + GPURenderPipeline { + instance: self.instance.clone(), + error_handler: self.error_handler.clone(), + id, + label: descriptor.label, + }, + err, + ) } } diff --git a/deno_webgpu/error.rs b/deno_webgpu/error.rs index 57bfd14451..71f5252370 100644 --- a/deno_webgpu/error.rs +++ b/deno_webgpu/error.rs @@ -379,3 +379,43 @@ pub enum GPUGenericError { #[error("Illegal constructor")] InvalidConstructor, } + +pub enum GPUPipelineErrorReason { + Validation, + #[expect(dead_code)] + Internal, +} + +impl Display for GPUPipelineErrorReason { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Validation => f.write_str("validation"), + Self::Internal => f.write_str("internal"), + } + } +} + +pub(crate) fn make_pipeline_error<'a>( + scope: &mut v8::HandleScope<'a>, + reason: GPUPipelineErrorReason, + message: &str, +) -> v8::Local<'a, v8::Object> { + let state = JsRuntime::op_state_from(scope); + let class = state + .borrow() + .borrow::() + .0 + .clone(); + let constructor = + v8::Local::::try_from(v8::Local::new(scope, class)).unwrap(); + let message_str = v8::String::new(scope, message).unwrap(); + let reason_str = v8::String::new(scope, &reason.to_string()).unwrap(); + + let options = v8::Object::new(scope); + let key = v8::String::new(scope, "reason").unwrap(); + options.set(scope, key.into(), reason_str.into()); + + constructor + .new_instance(scope, &[message_str.into(), options.into()]) + .unwrap() +} diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs index 8c0f79cafd..a9aeb5d16e 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -114,13 +114,21 @@ pub fn op_create_gpu( scope: &mut v8::HandleScope, webidl_brand: v8::Local, set_event_target_data: v8::Local, - error_event_class: v8::Local, + uncaptured_error_event_class: v8::Local, + pipeline_error_class: v8::Local, ) -> GPU { state.put(EventTargetSetup { brand: v8::Global::new(scope, webidl_brand), set_event_target_data: v8::Global::new(scope, set_event_target_data), }); - state.put(ErrorEventClass(v8::Global::new(scope, error_event_class))); + state.put(ErrorEventClass(v8::Global::new( + scope, + uncaptured_error_event_class, + ))); + state.put(PipelineErrorClass(v8::Global::new( + scope, + pipeline_error_class, + ))); GPU } @@ -129,6 +137,7 @@ struct EventTargetSetup { set_event_target_data: v8::Global, } struct ErrorEventClass(v8::Global); +struct PipelineErrorClass(v8::Global); pub struct GPU; diff --git a/deno_webgpu/render_pipeline.rs b/deno_webgpu/render_pipeline.rs index 1a0fc2210b..613f795b03 100644 --- a/deno_webgpu/render_pipeline.rs +++ b/deno_webgpu/render_pipeline.rs @@ -13,6 +13,7 @@ use crate::error::GPUGenericError; use crate::sampler::GPUCompareFunction; use crate::shader::GPUShaderModule; use crate::texture::GPUTextureFormat; +use crate::webidl::GPUColorWriteFlags; use crate::webidl::GPUPipelineLayoutOrGPUAutoLayoutMode; use crate::Instance; @@ -193,9 +194,8 @@ pub(crate) struct GPUFragmentState { pub(crate) struct GPUColorTargetState { pub format: GPUTextureFormat, pub blend: Option, - #[webidl(default = 0xF)] - #[options(enforce_range = true)] - pub write_mask: u32, + #[webidl(default = GPUColorWriteFlags(wgpu_types::ColorWrites::ALL))] + pub write_mask: GPUColorWriteFlags, } #[derive(WebIDL)] diff --git a/deno_webgpu/surface.rs b/deno_webgpu/surface.rs index 60df27ce0e..24b4b11880 100644 --- a/deno_webgpu/surface.rs +++ b/deno_webgpu/surface.rs @@ -15,6 +15,7 @@ use crate::device::GPUDevice; use crate::error::GPUGenericError; use crate::texture::GPUTexture; use crate::texture::GPUTextureFormat; +use crate::webidl::GPUTextureUsageFlags; #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum SurfaceError { @@ -31,7 +32,7 @@ pub enum SurfaceError { pub struct Configuration { pub device: Ptr, - pub usage: u32, + pub usage: GPUTextureUsageFlags, pub format: GPUTextureFormat, pub surface_config: wgpu_types::SurfaceConfiguration>, @@ -73,11 +74,9 @@ impl GPUCanvasContext { &self, #[webidl] configuration: GPUCanvasConfiguration, ) -> Result<(), JsErrorBox> { - let usage = wgpu_types::TextureUsages::from_bits(configuration.usage) - .ok_or_else(|| JsErrorBox::type_error("usage is not valid"))?; let format = configuration.format.clone().into(); let conf = wgpu_types::SurfaceConfiguration { - usage, + usage: configuration.usage.into(), format, width: *self.width.borrow(), height: *self.height.borrow(), @@ -214,9 +213,8 @@ impl GPUCanvasContext { struct GPUCanvasConfiguration { device: Ptr, format: GPUTextureFormat, - #[webidl(default = wgpu_types::TextureUsages::RENDER_ATTACHMENT.bits())] - #[options(enforce_range = true)] - usage: u32, + #[webidl(default = GPUTextureUsageFlags(wgpu_types::TextureUsages::RENDER_ATTACHMENT))] + usage: GPUTextureUsageFlags, #[webidl(default = GPUCanvasAlphaMode::Opaque)] alpha_mode: GPUCanvasAlphaMode, diff --git a/deno_webgpu/texture.rs b/deno_webgpu/texture.rs index 8d92d7f308..6f421ee543 100644 --- a/deno_webgpu/texture.rs +++ b/deno_webgpu/texture.rs @@ -16,6 +16,7 @@ use wgpu_types::TextureFormat; use wgpu_types::TextureViewDimension; use crate::error::GPUGenericError; +use crate::webidl::GPUTextureUsageFlags; use crate::Instance; #[derive(WebIDL)] @@ -34,8 +35,7 @@ pub(crate) struct GPUTextureDescriptor { #[webidl(default = GPUTextureDimension::D2)] pub dimension: GPUTextureDimension, pub format: GPUTextureFormat, - #[options(enforce_range = true)] - pub usage: u32, + pub usage: GPUTextureUsageFlags, #[webidl(default = vec![])] pub view_formats: Vec, } @@ -54,7 +54,7 @@ pub struct GPUTexture { pub sample_count: u32, pub dimension: GPUTextureDimension, pub format: GPUTextureFormat, - pub usage: u32, + pub usage: GPUTextureUsageFlags, } impl GPUTexture { @@ -151,7 +151,7 @@ impl GPUTexture { } #[getter] fn usage(&self) -> u32 { - self.usage + self.usage.bits() } #[fast] #[undefined] @@ -168,10 +168,7 @@ impl GPUTexture { label: crate::transform_label(descriptor.label.clone()), format: descriptor.format.map(Into::into), dimension: descriptor.dimension.map(Into::into), - usage: Some( - wgpu_types::TextureUsages::from_bits(descriptor.usage) - .ok_or_else(|| JsErrorBox::type_error("usage is not valid"))?, - ), + usage: Some(descriptor.usage.into()), range: wgpu_types::ImageSubresourceRange { aspect: descriptor.aspect.into(), base_mip_level: descriptor.base_mip_level, @@ -204,9 +201,8 @@ struct GPUTextureViewDescriptor { format: Option, dimension: Option, - #[webidl(default = 0)] - #[options(enforce_range = true)] - usage: u32, + #[webidl(default = GPUTextureUsageFlags(wgpu_types::TextureUsages::empty()))] + usage: GPUTextureUsageFlags, #[webidl(default = GPUTextureAspect::All)] aspect: GPUTextureAspect, #[webidl(default = 0)] diff --git a/deno_webgpu/webidl.rs b/deno_webgpu/webidl.rs index 1aa581b47c..81da9b91e9 100644 --- a/deno_webgpu/webidl.rs +++ b/deno_webgpu/webidl.rs @@ -680,3 +680,138 @@ pub fn features_to_feature_names( return_features } + +#[derive(Clone, Copy)] +pub(crate) struct GPUTextureUsageFlags(pub(crate) wgpu_types::TextureUsages); + +impl<'a> WebIdlConverter<'a> for GPUTextureUsageFlags { + type Options = (); + + fn convert<'b>( + scope: &mut v8::HandleScope<'a>, + value: v8::Local<'a, v8::Value>, + prefix: Cow<'static, str>, + context: ContextFn<'b>, + _options: &Self::Options, + ) -> Result { + let flags_value = u32::convert( + scope, + value, + prefix.clone(), + context.borrowed(), + &IntOptions { + clamp: false, + enforce_range: true, + }, + )?; + + let flags = + wgpu_types::TextureUsages::from_bits(flags_value).ok_or_else(|| { + WebIdlError::other( + prefix, + context, + JsErrorBox::type_error("usage is not valid"), + ) + })?; + + Ok(GPUTextureUsageFlags(flags)) + } +} + +impl From for wgpu_types::TextureUsages { + fn from(value: GPUTextureUsageFlags) -> Self { + value.0 + } +} + +impl GPUTextureUsageFlags { + pub fn bits(&self) -> u32 { + self.0.bits() + } +} + +#[derive(Clone, Copy)] +pub(crate) struct GPUShaderStageFlags(pub(crate) wgpu_types::ShaderStages); + +impl<'a> WebIdlConverter<'a> for GPUShaderStageFlags { + type Options = (); + + fn convert<'b>( + scope: &mut v8::HandleScope<'a>, + value: v8::Local<'a, v8::Value>, + prefix: Cow<'static, str>, + context: ContextFn<'b>, + _options: &Self::Options, + ) -> Result { + let flags_value = u32::convert( + scope, + value, + prefix.clone(), + context.borrowed(), + &IntOptions { + clamp: false, + enforce_range: true, + }, + )?; + + let flags = + wgpu_types::ShaderStages::from_bits(flags_value).ok_or_else(|| { + WebIdlError::other( + prefix, + context, + JsErrorBox::type_error("shader stage is not valid"), + ) + })?; + + Ok(GPUShaderStageFlags(flags)) + } +} + +impl From for wgpu_types::ShaderStages { + fn from(value: GPUShaderStageFlags) -> Self { + value.0 + } +} + +#[derive(Clone, Copy)] +pub(crate) struct GPUColorWriteFlags(pub(crate) wgpu_types::ColorWrites); + +impl<'a> WebIdlConverter<'a> for GPUColorWriteFlags { + type Options = (); + + fn convert<'b>( + scope: &mut v8::HandleScope<'a>, + value: v8::Local<'a, v8::Value>, + prefix: Cow<'static, str>, + context: ContextFn<'b>, + _options: &Self::Options, + ) -> Result { + let flags_value = u32::convert( + scope, + value, + prefix.clone(), + context.borrowed(), + &IntOptions { + clamp: false, + enforce_range: true, + }, + )?; + + let flags = + wgpu_types::ColorWrites::from_bits(flags_value).ok_or_else(|| { + WebIdlError::other( + prefix, + context, + JsErrorBox::type_error("color write flags are not valid"), + ) + })?; + + Ok(GPUColorWriteFlags(flags)) + } +} + +impl From for wgpu_types::ColorWrites { + fn from(value: GPUColorWriteFlags) -> Self { + value.0 + } +}