diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e570504e2..6eddbd1ea02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -258,6 +258,10 @@ By @kpreid in [#9042](https://github.com/gfx-rs/wgpu/pull/9042). - Tracing support has been restored. By @andyleiserson in [#8429](https://github.com/gfx-rs/wgpu/pull/8429). - Pipelines using passthrough shaders now correctly require explicit pipeline layout. By @inner-daemons in #8881. - Allow using a shader that defines I/O for dual-source blending in a pipeline that does not make use of it. By @andyleiserson in [#8856](https://github.com/gfx-rs/wgpu/pull/8856). +- Improve validation of dual-source blending, by @andyleiserson in [#9200](https://github.com/gfx-rs/wgpu/pull/9200): + - Validate structs with `@blend_src` members whether or not they are used by an entry point. + - Dual-source blending is not supported when there are multiple color attachments. + - `TypeFlags::IO_SHAREABLE` is not set for structs other than `@blend_src` structs. - Validate `strip_index_format` isn't None and equals index buffer format for indexed drawing with strip topology. By @beicause in [#8850](https://github.com/gfx-rs/wgpu/pull/8850). - Renamed `EXPERIMENTAL_PASSTHROUGH_SHADERS` to `PASSTHROUGH_SHADERS` and made this no longer an experimental feature. by @inner-daemons in [#9054](https://github.com/gfx-rs/wgpu/pull/9054). - BREAKING: End offsets in trace and `player` commands are now represented using `offset` + `size` instead. By @ErichDonGubler in [9073](https://github.com/gfx-rs/wgpu/pull/9073). diff --git a/cts_runner/test.lst b/cts_runner/test.lst index 1c94f7f217b..1217c459880 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -247,7 +247,7 @@ webgpu:api,validation,render_pass,resolve:resolve_attachment:* webgpu:api,validation,render_pipeline,depth_stencil_state:* webgpu:api,validation,render_pipeline,float32_blendable:* webgpu:api,validation,render_pipeline,fragment_state:color_target_exists:* -webgpu:api,validation,render_pipeline,fragment_state:dual_source_blending,use_blend_src:* +webgpu:api,validation,render_pipeline,fragment_state:dual_source_blending,* webgpu:api,validation,render_pipeline,fragment_state:limits,* webgpu:api,validation,render_pipeline,fragment_state:targets_blend:* webgpu:api,validation,render_pipeline,fragment_state:targets_format_filterable:* @@ -415,10 +415,7 @@ webgpu:shader,validation,expression,call,builtin,value_constructor:* webgpu:shader,validation,expression,call,builtin,workgroupUniformLoad:* webgpu:shader,validation,expression,overload_resolution:* webgpu:shader,validation,extension,clip_distances:* -webgpu:shader,validation,extension,dual_source_blending:blend_src_same_type:* -webgpu:shader,validation,extension,dual_source_blending:blend_src_stage_input_output:* -webgpu:shader,validation,extension,dual_source_blending:blend_src_syntax_validation:* -webgpu:shader,validation,extension,dual_source_blending:use_blend_src_requires_extension_enabled:* +webgpu:shader,validation,extension,dual_source_blending:* webgpu:shader,validation,extension,pointer_composite_access:* webgpu:shader,validation,functions,restrictions:param_type_can_be_alias:* webgpu:shader,validation,parse,blankspace:blankspace:* diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index b2160f1ef32..98924cbc5ca 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -60,7 +60,10 @@ pub enum GlobalVariableError { pub enum VaryingError { #[error("The type {0:?} does not match the varying")] InvalidType(Handle), - #[error("The type {0:?} cannot be used for user-defined entry point inputs or outputs")] + #[error( + "The type {0:?} cannot be used for user-defined entry point inputs or outputs. \ + Only numeric scalars and vectors are allowed." + )] NotIOShareableType(Handle), #[error("Interpolation is not valid")] InvalidInterpolation, @@ -93,15 +96,23 @@ pub enum VaryingError { InvalidInputAttributeInStage(&'static str, crate::ShaderStage), #[error("The attribute {0:?} is not valid for stage {1:?}")] InvalidAttributeInStage(&'static str, crate::ShaderStage), - #[error("The `blend_src` attribute can only be used on location 0, only indices 0 and 1 are valid. Location was {location}, index was {blend_src}.")] + #[error("`@blend_src` can only be used at location 0, indices 0 and 1. Found `@location({location}) @blend_src({blend_src})`.")] InvalidBlendSrcIndex { location: u32, blend_src: u32 }, - #[error("If `blend_src` is used, there must be exactly two outputs both with location 0, one with `blend_src(0)` and the other with `blend_src(1)`.")] - IncompleteBlendSrcUsage, - #[error("If `blend_src` is used, both outputs must have the same type. `blend_src(0)` has type {blend_src_0_type:?} and `blend_src(1)` has type {blend_src_1_type:?}.")] + #[error( + "`@blend_src` structure must specify two sources. \ + Found `@blend_src({present_blend_src})` but not `@blend_src({absent_blend_src})`.", + absent_blend_src = if *present_blend_src == 0 { 1 } else { 0 }, + )] + IncompleteBlendSrcUsage { present_blend_src: u32 }, + #[error("Structure using `@blend_src` may not specify `@location` on any other members. Found a binding at `@location({location})`.")] + InvalidBlendSrcWithOtherBindings { location: u32 }, + #[error("Both `@blend_src` structure members must have the same type. `blend_src(0)` has type {blend_src_0_type:?} and `blend_src(1)` has type {blend_src_1_type:?}.")] BlendSrcOutputTypeMismatch { blend_src_0_type: Handle, blend_src_1_type: Handle, }, + #[error("`@blend_src` can only be used on struct members, not directly on entry point I/O")] + BlendSrcNotOnStructMember, #[error("Workgroup size is multi dimensional, `@builtin(subgroup_id)` and `@builtin(subgroup_invocation_id)` are not supported.")] InvalidMultiDimensionalSubgroupBuiltIn, #[error("The `@per_primitive` attribute can only be used in fragment shader inputs or mesh shader primitive outputs")] @@ -209,7 +220,7 @@ struct VaryingContext<'a> { types: &'a UniqueArena, type_info: &'a Vec, location_mask: &'a mut BitSet, - blend_src_mask: &'a mut BitSet, + dual_source_blending: Option<&'a mut bool>, built_ins: &'a mut crate::FastHashSet, capabilities: Capabilities, flags: super::ValidationFlags, @@ -721,38 +732,8 @@ impl VaryingContext<'_> { return Err(VaryingError::InvalidPerPrimitive); } - if let Some(blend_src) = blend_src { - // `blend_src` is only valid if dual source blending was explicitly enabled, - // see https://www.w3.org/TR/WGSL/#extension-dual_source_blending - if !self - .capabilities - .contains(Capabilities::DUAL_SOURCE_BLENDING) - { - return Err(VaryingError::UnsupportedCapability( - Capabilities::DUAL_SOURCE_BLENDING, - )); - } - if self.stage != crate::ShaderStage::Fragment { - return Err(VaryingError::InvalidAttributeInStage( - "blend_src", - self.stage, - )); - } - if !self.output { - return Err(VaryingError::InvalidInputAttributeInStage( - "blend_src", - self.stage, - )); - } - if (blend_src != 0 && blend_src != 1) || location != 0 { - return Err(VaryingError::InvalidBlendSrcIndex { - location, - blend_src, - }); - } - if !self.blend_src_mask.insert(blend_src as usize) { - return Err(VaryingError::BindingCollisionBlendSrc { blend_src }); - } + if blend_src.is_some() { + return Err(VaryingError::BlendSrcNotOnStructMember); } else if !self.location_mask.insert(location as usize) && self.flags.contains(super::ValidationFlags::BINDINGS) { @@ -852,37 +833,51 @@ impl VaryingContext<'_> { } }; - for (index, member) in members.iter().enumerate() { - let span_context = self.types.get_span_context(ty); - match member.binding { - None => { - if self.flags.contains(super::ValidationFlags::BINDINGS) { - return Err(VaryingError::MemberMissingBinding(index as u32) - .with_span_context(span_context)); - } - } - Some(ref binding) => self - .validate_impl(ep, member.ty, binding) - .map_err(|e| e.with_span_context(span_context))?, - } - } - - if !self.blend_src_mask.is_empty() { - let span_context = self.types.get_span_context(ty); - - // If there's any blend_src usage, it must apply to all members of which there must be exactly 2. - if members.len() != 2 || self.blend_src_mask.count() != 2 { + if self.type_info[ty.index()] + .flags + .contains(super::TypeFlags::IO_SHAREABLE) + { + // `@blend_src` is the only case where `IO_SHAREABLE` is set on a struct (as + // opposed to members of a struct). The struct definition is validated during + // type validation. + if self.stage != crate::ShaderStage::Fragment { return Err( - VaryingError::IncompleteBlendSrcUsage.with_span_context(span_context) + VaryingError::InvalidAttributeInStage("blend_src", self.stage) + .with_span(), ); } - // Also, all members must have the same type. - if members[0].ty != members[1].ty { - return Err(VaryingError::BlendSrcOutputTypeMismatch { - blend_src_0_type: members[0].ty, - blend_src_1_type: members[1].ty, + if !self.output { + return Err(VaryingError::InvalidInputAttributeInStage( + "blend_src", + self.stage, + ) + .with_span()); + } + // Dual blend sources must always be at location 0. + if !self.location_mask.insert(0) + && self.flags.contains(super::ValidationFlags::BINDINGS) + { + return Err(VaryingError::BindingCollision { location: 0 }.with_span()); + } + + **self + .dual_source_blending + .as_mut() + .expect("unexpected dual source blending") = true; + } else { + for (index, member) in members.iter().enumerate() { + let span_context = self.types.get_span_context(ty); + match member.binding { + None => { + if self.flags.contains(super::ValidationFlags::BINDINGS) { + return Err(VaryingError::MemberMissingBinding(index as u32) + .with_span_context(span_context)); + } + } + Some(ref binding) => self + .validate_impl(ep, member.ty, binding) + .map_err(|e| e.with_span_context(span_context))?, } - .with_span_context(span_context)); } } Ok(()) @@ -1198,7 +1193,7 @@ impl super::Validator { types: &module.types, type_info: &self.types, location_mask: &mut self.location_mask, - blend_src_mask: &mut self.blend_src_mask, + dual_source_blending: None, built_ins: &mut result_built_ins, capabilities: self.capabilities, flags: self.flags, @@ -1370,7 +1365,7 @@ impl super::Validator { types: &module.types, type_info: &self.types, location_mask: &mut self.location_mask, - blend_src_mask: &mut self.blend_src_mask, + dual_source_blending: Some(&mut info.dual_source_blending), built_ins: &mut argument_built_ins, capabilities: self.capabilities, flags: self.flags, @@ -1390,7 +1385,7 @@ impl super::Validator { types: &module.types, type_info: &self.types, location_mask: &mut self.location_mask, - blend_src_mask: &mut self.blend_src_mask, + dual_source_blending: Some(&mut info.dual_source_blending), built_ins: &mut result_built_ins, capabilities: self.capabilities, flags: self.flags, @@ -1418,9 +1413,6 @@ impl super::Validator { return Err(EntryPointError::WrongTaskShaderEntryResult.with_span()); } } - if !self.blend_src_mask.is_empty() { - info.dual_source_blending = true; - } } else if ep.stage == crate::ShaderStage::Vertex { return Err(EntryPointError::MissingVertexOutputPosition.with_span()); } else if ep.stage == crate::ShaderStage::Task { diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index cc3a4b5739c..d772bf04f9f 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -361,7 +361,6 @@ pub struct Validator { types: Vec, layouter: Layouter, location_mask: BitSet, - blend_src_mask: BitSet, ep_resource_bindings: FastHashSet, switch_values: FastHashSet, valid_expression_list: Vec>, @@ -608,7 +607,6 @@ impl Validator { types: Vec::new(), layouter: Layouter::default(), location_mask: BitSet::new(), - blend_src_mask: BitSet::new(), ep_resource_bindings: FastHashSet::default(), switch_values: FastHashSet::default(), valid_expression_list: Vec::new(), @@ -638,7 +636,6 @@ impl Validator { self.types.clear(); self.layouter.clear(); self.location_mask.make_empty(); - self.blend_src_mask.make_empty(); self.ep_resource_bindings.clear(); self.switch_values.clear(); self.valid_expression_list.clear(); diff --git a/naga/src/valid/type.rs b/naga/src/valid/type.rs index dc5d5a48a2b..b78a18c51bc 100644 --- a/naga/src/valid/type.rs +++ b/naga/src/valid/type.rs @@ -1,7 +1,7 @@ use alloc::string::String; use super::Capabilities; -use crate::{arena::Handle, proc::Alignment}; +use crate::{arena::Handle, ir, proc::Alignment}; bitflags::bitflags! { /// Flags associated with [`Type`]s by [`Validator`]. @@ -44,13 +44,18 @@ bitflags::bitflags! { /// The data can be copied around. const COPY = 0x4; - /// Can be be used for user-defined IO between pipeline stages. + /// Can be be used in pipeline stage I/O. /// - /// This covers anything that can be in [`Location`] binding: - /// non-bool scalars and vectors, matrices, and structs and - /// arrays containing only interface types. + /// Applies to the following: + /// - Types that may be used in a [`Location`] binding (numeric scalars and vectors) + /// - `@blend_src` structs + /// + /// See [location-attr] and [input-output]. /// /// [`Location`]: crate::Binding::Location + /// [location-attr]: https://gpuweb.github.io/gpuweb/wgsl/#location-attr + /// [input-output]: https://gpuweb.github.io/gpuweb/wgsl/#input-output-locations + /// https://gpuweb.github.io/gpuweb/wgsl/#location-attr const IO_SHAREABLE = 0x8; /// Can be used for host-shareable structures. @@ -148,6 +153,8 @@ pub enum TypeError { }, #[error("Structure types must have at least one member")] EmptyStruct, + #[error("Invalid `@blend_src` structure: {0}")] + InvalidBlendSrc(super::VaryingError), #[error(transparent)] WidthError(#[from] WidthError), #[error( @@ -640,12 +647,14 @@ impl super::Validator { return Err(TypeError::EmptyStruct); } + let mut blend_src_types = [None, None]; + let mut non_blend_src_location = None; + let mut ti = TypeInfo::new( TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::COPY | TypeFlags::HOST_SHAREABLE - | TypeFlags::IO_SHAREABLE | TypeFlags::ARGUMENT | TypeFlags::CONSTRUCTIBLE | TypeFlags::CREATION_RESOLVED, @@ -674,6 +683,48 @@ impl super::Validator { } ti.flags &= base_info.flags; + match member.binding { + Some(ir::Binding::Location { + location, + blend_src: Some(blend_src), + .. + }) => { + // `blend_src` is only valid if dual source blending was explicitly enabled, + // see https://www.w3.org/TR/WGSL/#extension-dual_source_blending + if !self + .capabilities + .contains(Capabilities::DUAL_SOURCE_BLENDING) + { + return Err(TypeError::MissingCapability( + Capabilities::DUAL_SOURCE_BLENDING, + )); + } + if !(location == 0 && (blend_src == 0 || blend_src == 1)) { + return Err(TypeError::InvalidBlendSrc( + super::VaryingError::InvalidBlendSrcIndex { + location, + blend_src, + }, + )); + } + if blend_src_types[blend_src as usize] + .replace(member.ty) + .is_some() + { + // @blend_src(i) appeared multiple times + return Err(TypeError::InvalidBlendSrc( + super::VaryingError::BindingCollisionBlendSrc { blend_src }, + )); + } + } + Some(ir::Binding::Location { + location, + blend_src: None, + .. + }) => non_blend_src_location = Some(location), + _ => {} + } + if member.offset < min_offset { // HACK: this could be nicer. We want to allow some structures // to not bother with offsets/alignments if they are never @@ -757,6 +808,57 @@ impl super::Validator { } } + match blend_src_types { + [None, None] => {} + [Some(ty0), Some(ty1)] => { + if let Some(location) = non_blend_src_location { + // If `@blend_src` members are present, then `@location` + // may only be used for those members. + return Err(TypeError::InvalidBlendSrc( + super::VaryingError::InvalidBlendSrcWithOtherBindings { location }, + )); + } + let ty0_inner = &gctx.types[ty0].inner; + let ty1_inner = &gctx.types[ty1].inner; + // The two blend sources must have the same type... + if !ty0_inner.non_struct_equivalent(ty1_inner, gctx.types) { + return Err(TypeError::InvalidBlendSrc( + super::VaryingError::BlendSrcOutputTypeMismatch { + blend_src_0_type: ty0, + blend_src_1_type: ty1, + }, + )); + } + // ... and that type must be I/O-shareable. + if !self.types[ty0.index()] + .flags + .contains(TypeFlags::IO_SHAREABLE) + { + return Err(TypeError::InvalidBlendSrc( + super::VaryingError::NotIOShareableType(ty0), + )); + } + + // `@blend_src` is the only case where we classify a struct as + // I/O-shareable. (In the case of a struct with `@location` bindings, we + // process the members individually in interface validation, and do not + // classify the struct as I/O-shareable.) + ti.flags |= TypeFlags::IO_SHAREABLE; + } + [None, Some(_)] | [Some(_), None] => { + // Only one of the blend sources was specified. + return Err(TypeError::InvalidBlendSrc( + super::VaryingError::IncompleteBlendSrcUsage { + present_blend_src: blend_src_types + .iter() + .position(|src| src.is_some()) + .unwrap() + as u32, + }, + )); + } + } + let alignment = self.layouter[handle].alignment; if !alignment.is_aligned(span) { ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment })); diff --git a/naga/tests/naga/wgsl_errors.rs b/naga/tests/naga/wgsl_errors.rs index ea38d3a7f51..d0ffd36db25 100644 --- a/naga/tests/naga/wgsl_errors.rs +++ b/naga/tests/naga/wgsl_errors.rs @@ -1948,6 +1948,8 @@ fn missing_bindings2() { #[test] fn invalid_blend_src() { + use naga::valid::{TypeError, ValidationError, VaryingError}; + // Missing capability or enable directive check_extension_validation! { Capabilities::DUAL_SOURCE_BLENDING, @@ -1969,11 +1971,8 @@ fn invalid_blend_src() { "###, Err( - naga::valid::ValidationError::EntryPoint { - stage: naga::ShaderStage::Fragment, - source: naga::valid::EntryPointError::Result( - naga::valid::VaryingError::UnsupportedCapability(Capabilities::DUAL_SOURCE_BLENDING), - ), + ValidationError::Type { + source: TypeError::MissingCapability(Capabilities::DUAL_SOURCE_BLENDING), .. }, ) @@ -1987,11 +1986,11 @@ fn invalid_blend_src() { fn main(@location(0) @blend_src(0) input: f32) -> vec4f { return vec4f(0.0); } ": Err( - naga::valid::ValidationError::EntryPoint { + ValidationError::EntryPoint { stage: naga::ShaderStage::Fragment, source: naga::valid::EntryPointError::Argument( 0, - naga::valid::VaryingError::InvalidInputAttributeInStage("blend_src", naga::ShaderStage::Fragment), + VaryingError::BlendSrcNotOnStructMember, ), .. }, @@ -2011,10 +2010,10 @@ fn invalid_blend_src() { fn main() -> VertexOutput { return VertexOutput(vec4(0.0), vec4(1.0)); } ": Err( - naga::valid::ValidationError::EntryPoint { + ValidationError::EntryPoint { stage: naga::ShaderStage::Vertex, source: naga::valid::EntryPointError::Result( - naga::valid::VaryingError::InvalidAttributeInStage("blend_src", naga::ShaderStage::Vertex), + VaryingError::InvalidAttributeInStage("blend_src", naga::ShaderStage::Vertex), ), .. }, @@ -2034,10 +2033,12 @@ fn invalid_blend_src() { fn main() -> FragmentOutput { return FragmentOutput(vec4(0.0), vec4(1.0)); } ": Err( - naga::valid::ValidationError::EntryPoint { - stage: naga::ShaderStage::Fragment, - source: naga::valid::EntryPointError::Result( - naga::valid::VaryingError::InvalidBlendSrcIndex { location: 0, blend_src: 2 }, + ValidationError::Type { + source: TypeError::InvalidBlendSrc( + VaryingError::InvalidBlendSrcIndex { + location: 0, + blend_src: 2, + } ), .. }, @@ -2045,7 +2046,7 @@ fn invalid_blend_src() { Capabilities::DUAL_SOURCE_BLENDING } - // Using a location other than 1 on blend_src + // Using a location other than 0 on blend_src check_validation! { " enable dual_source_blending; @@ -2057,10 +2058,12 @@ fn invalid_blend_src() { fn main() -> FragmentOutput { return FragmentOutput(vec4(0.0), vec4(1.0)); } ": Err( - naga::valid::ValidationError::EntryPoint { - stage: naga::ShaderStage::Fragment, - source: naga::valid::EntryPointError::Result( - naga::valid::VaryingError::InvalidBlendSrcIndex { location: 1, blend_src: 1 }, + ValidationError::Type { + source: TypeError::InvalidBlendSrc( + VaryingError::InvalidBlendSrcIndex { + location: 1, + blend_src: 1, + } ), .. }, @@ -2080,10 +2083,9 @@ fn invalid_blend_src() { fn main() -> FragmentOutput { return FragmentOutput(vec4(0.0), vec4(1.0)); } ": Err( - naga::valid::ValidationError::EntryPoint { - stage: naga::ShaderStage::Fragment, - source: naga::valid::EntryPointError::Result( - naga::valid::VaryingError::BindingCollisionBlendSrc { blend_src: 1 }, + ValidationError::Type { + source: TypeError::InvalidBlendSrc( + VaryingError::BindingCollisionBlendSrc { blend_src: 1 } ), .. }, @@ -2103,10 +2105,11 @@ fn invalid_blend_src() { fn main() -> FragmentOutput { return FragmentOutput(vec4(0.0), vec4(1.0)); } ": Err( - naga::valid::ValidationError::EntryPoint { - stage: naga::ShaderStage::Fragment, - source: naga::valid::EntryPointError::Result( - naga::valid::VaryingError::IncompleteBlendSrcUsage, + ValidationError::Type { + source: TypeError::InvalidBlendSrc( + VaryingError::IncompleteBlendSrcUsage { + present_blend_src: 0, + } ), .. }, @@ -2119,16 +2122,17 @@ fn invalid_blend_src() { " enable dual_source_blending; struct FragmentOutput { - @location(0) @blend_src(0) output0: vec4, + @location(0) @blend_src(1) output0: vec4, } @fragment fn main() -> FragmentOutput { return FragmentOutput(vec4(0.0)); } ": Err( - naga::valid::ValidationError::EntryPoint { - stage: naga::ShaderStage::Fragment, - source: naga::valid::EntryPointError::Result( - naga::valid::VaryingError::IncompleteBlendSrcUsage, + ValidationError::Type { + source: TypeError::InvalidBlendSrc( + VaryingError::IncompleteBlendSrcUsage{ + present_blend_src: 1, + } ), .. }, @@ -2148,10 +2152,70 @@ fn invalid_blend_src() { fn main() -> FragmentOutput { return FragmentOutput(vec4(0.0), 1.0); } ": Err( - naga::valid::ValidationError::EntryPoint { - stage: naga::ShaderStage::Fragment, - source: naga::valid::EntryPointError::Result( - naga::valid::VaryingError::BlendSrcOutputTypeMismatch { blend_src_0_type: _, blend_src_1_type: _ }, + ValidationError::Type { + source: TypeError::InvalidBlendSrc( + VaryingError::BlendSrcOutputTypeMismatch { .. } + ), + .. + }, + ), + Capabilities::DUAL_SOURCE_BLENDING + } + + // Multiple entrypoints (regression test for https://github.com/gfx-rs/wgpu/issues/9111) + check_validation! { + " + enable dual_source_blending; + struct FragmentOutput { + @location(0) @blend_src(0) output0: vec4, + @location(0) @blend_src(1) output1: vec4, + } + @fragment + fn fs1() -> FragmentOutput { return FragmentOutput(vec4(0.0), vec4(1.0)); } + @fragment + fn fs2() -> FragmentOutput { return FragmentOutput(vec4(0.0), vec4(1.0)); } + ": + Ok(_), + Capabilities::DUAL_SOURCE_BLENDING + } + + // @blend_src struct with no entry point should still be validated at the type level. + check_validation! { + " + enable dual_source_blending; + struct FragmentOutput { + @location(0) @blend_src(0) output0: vec4, + } + ": + Err( + ValidationError::Type { + source: TypeError::InvalidBlendSrc( + VaryingError::IncompleteBlendSrcUsage { + present_blend_src: 0, + } + ), + .. + }, + ), + Capabilities::DUAL_SOURCE_BLENDING + } + + // First member has @location(1) without @blend_src, followed by two @blend_src members. + check_validation! { + " + enable dual_source_blending; + struct FragmentOutput { + @location(1) output0: vec4, + @location(0) @blend_src(0) output1: vec4, + @location(0) @blend_src(1) output2: vec4, + } + @fragment + fn main() -> FragmentOutput { return FragmentOutput(vec4(0.0), vec4(1.0), vec4(2.0)); } + ": + Err( + ValidationError::Type { + source: TypeError::InvalidBlendSrc( + VaryingError::InvalidBlendSrcWithOtherBindings { location: 1 } ), .. }, diff --git a/naga/tests/out/analysis/spv-shadow.info.ron b/naga/tests/out/analysis/spv-shadow.info.ron index c2adf7d22dd..015a3d3654e 100644 --- a/naga/tests/out/analysis/spv-shadow.info.ron +++ b/naga/tests/out/analysis/spv-shadow.info.ron @@ -8,7 +8,7 @@ ("CREATION_RESOLVED | ARGUMENT"), ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | COPY | HOST_SHAREABLE | CREATION_RESOLVED"), diff --git a/naga/tests/out/analysis/wgsl-access.info.ron b/naga/tests/out/analysis/wgsl-access.info.ron index 32ba1b0b291..04705b66c87 100644 --- a/naga/tests/out/analysis/wgsl-access.info.ron +++ b/naga/tests/out/analysis/wgsl-access.info.ron @@ -3,8 +3,8 @@ ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), @@ -29,15 +29,15 @@ ("SIZED | COPY | CREATION_RESOLVED | ARGUMENT"), ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("SIZED | COPY | CREATION_RESOLVED | ARGUMENT"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("SIZED | COPY | CREATION_RESOLVED | ARGUMENT"), ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("SIZED | COPY | CREATION_RESOLVED | ARGUMENT"), ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ], functions: [ ( diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 654f2546984..73043f2e458 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -4245,6 +4245,14 @@ impl Device { } } + if dual_source_blending && color_targets.len() > 1 { + return Err( + pipeline::CreateRenderPipelineError::DualSourceBlendingWithMultipleColorTargets { + count: color_targets.len(), + }, + ); + } + validation::validate_color_attachment_bytes_per_sample( color_targets.iter().flatten().map(|cs| cs.format), self.limits.max_color_attachment_bytes_per_sample, diff --git a/wgpu-core/src/pipeline.rs b/wgpu-core/src/pipeline.rs index 775a9f5d7ac..a9d3ad0a8e2 100644 --- a/wgpu-core/src/pipeline.rs +++ b/wgpu-core/src/pipeline.rs @@ -724,6 +724,8 @@ pub enum CreateRenderPipelineError { }, #[error("In the provided shader, the type given for group {group} binding {binding} has a size of {size}. As the device does not support `DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED`, the type must have a size that is a multiple of 16 bytes.")] UnalignedShader { group: u32, binding: u32, size: u64 }, + #[error("Dual-source blending requires exactly one color target, but {count} color targets are present")] + DualSourceBlendingWithMultipleColorTargets { count: usize }, #[error("{}", concat!( "At least one color attachment or depth-stencil attachment was expected, ", "but no render target for the pipeline was specified." @@ -759,6 +761,7 @@ impl WebGpuError for CreateRenderPipelineError { | Self::ConservativeRasterizationNonFillPolygonMode | Self::Stage { .. } | Self::UnalignedShader { .. } + | Self::DualSourceBlendingWithMultipleColorTargets { .. } | Self::NoTargetSpecified | Self::PipelineConstants { .. } | Self::VertexAttributeStrideTooLarge { .. } => ErrorType::Validation,