diff --git a/naga/src/back/glsl/features.rs b/naga/src/back/glsl/features.rs index 2d8870b797..b43208f468 100644 --- a/naga/src/back/glsl/features.rs +++ b/naga/src/back/glsl/features.rs @@ -628,6 +628,9 @@ impl Writer<'_, W> { if interpolation == Some(Interpolation::Linear) { self.features.request(Features::NOPERSPECTIVE_QUALIFIER); } + if interpolation == Some(Interpolation::PerVertex) { + self.features.request(Features::SHADER_BARYCENTRICS); + } if sampling == Some(Sampling::Sample) { self.features.request(Features::SAMPLE_QUALIFIER); } diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index 062734b049..0d106273ca 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -5299,6 +5299,7 @@ const fn glsl_interpolation(interpolation: crate::Interpolation) -> &'static str I::Perspective => "smooth", I::Linear => "noperspective", I::Flat => "flat", + I::PerVertex => "pervertexEXT", } } diff --git a/naga/src/back/hlsl/conv.rs b/naga/src/back/hlsl/conv.rs index 6cd3679e81..a64e8ded6b 100644 --- a/naga/src/back/hlsl/conv.rs +++ b/naga/src/back/hlsl/conv.rs @@ -205,6 +205,7 @@ impl crate::Interpolation { Self::Perspective => None, Self::Linear => Some("noperspective"), Self::Flat => Some("nointerpolation"), + Self::PerVertex => Some("nointerpolation"), } } } diff --git a/naga/src/back/msl/mod.rs b/naga/src/back/msl/mod.rs index 2456cdbae8..5c5b24f9ef 100644 --- a/naga/src/back/msl/mod.rs +++ b/naga/src/back/msl/mod.rs @@ -774,6 +774,7 @@ impl ResolvedInterpolation { (I::Linear, S::Centroid) => Self::CentroidNoPerspective, (I::Linear, S::Sample) => Self::SampleNoPerspective, (I::Flat, _) => Self::Flat, + (I::PerVertex, _) => Self::Flat, _ => unreachable!(), } } diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 71b761c736..900964fd11 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -2081,6 +2081,14 @@ impl Writer { Some(crate::Interpolation::Linear) => { self.decorate(id, Decoration::NoPerspective, &[]); } + Some(crate::Interpolation::PerVertex) => { + self.decorate(id, Decoration::PerVertexKHR, &[]); + self.require_any( + "`per_vertex` interpolation", + &[spirv::Capability::FragmentBarycentricKHR], + )?; + self.use_extension("SPV_KHR_fragment_shader_barycentric"); + } } match sampling { // Center sampling is the default in SPIR-V. diff --git a/naga/src/common/wgsl/to_wgsl.rs b/naga/src/common/wgsl/to_wgsl.rs index 5e6178c049..30d907d56c 100644 --- a/naga/src/common/wgsl/to_wgsl.rs +++ b/naga/src/common/wgsl/to_wgsl.rs @@ -209,6 +209,7 @@ impl ToWgsl for crate::Interpolation { crate::Interpolation::Perspective => "perspective", crate::Interpolation::Linear => "linear", crate::Interpolation::Flat => "flat", + crate::Interpolation::PerVertex => "per_vertex", } } } diff --git a/naga/src/front/glsl/lex.rs b/naga/src/front/glsl/lex.rs index 9337491b5d..2a419bbbf5 100644 --- a/naga/src/front/glsl/lex.rs +++ b/naga/src/front/glsl/lex.rs @@ -78,6 +78,7 @@ impl Iterator for Lexer<'_> { "invariant" => TokenValue::Invariant, "flat" => TokenValue::Interpolation(crate::Interpolation::Flat), "noperspective" => TokenValue::Interpolation(crate::Interpolation::Linear), + "pervertexEXT" => TokenValue::Interpolation(crate::Interpolation::PerVertex), "smooth" => TokenValue::Interpolation(crate::Interpolation::Perspective), "centroid" => TokenValue::Sampling(crate::Sampling::Centroid), "sample" => TokenValue::Sampling(crate::Sampling::Sample), diff --git a/naga/src/front/spv/mod.rs b/naga/src/front/spv/mod.rs index d9a3b986e0..a79ff67bc2 100644 --- a/naga/src/front/spv/mod.rs +++ b/naga/src/front/spv/mod.rs @@ -777,6 +777,9 @@ impl> Frontend { spirv::Decoration::Flat => { dec.interpolation = Some(crate::Interpolation::Flat); } + spirv::Decoration::PerVertexKHR => { + dec.interpolation = Some(crate::Interpolation::PerVertex); + } spirv::Decoration::Centroid => { dec.sampling = Some(crate::Sampling::Centroid); } diff --git a/naga/src/front/wgsl/parse/conv.rs b/naga/src/front/wgsl/parse/conv.rs index 5431f0d252..d1d092924b 100644 --- a/naga/src/front/wgsl/parse/conv.rs +++ b/naga/src/front/wgsl/parse/conv.rs @@ -111,6 +111,7 @@ pub fn map_interpolation(word: &str, span: Span) -> Result<'_, crate::Interpolat "linear" => Ok(crate::Interpolation::Linear), "flat" => Ok(crate::Interpolation::Flat), "perspective" => Ok(crate::Interpolation::Perspective), + "per_vertex" => Ok(crate::Interpolation::PerVertex), _ => Err(Box::new(Error::UnknownAttribute(span))), } } diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 5aa411b72e..1d4a74ce19 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -565,6 +565,9 @@ pub enum Interpolation { Linear, /// Indicates that no interpolation will be performed. Flat, + /// Indicates the fragment input binding holds an array of per-vertex values. + /// This is typically used with barycentrics. + PerVertex, } /// The sampling qualifiers of a binding or struct field. diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index d0d6f125ab..4626ad78ae 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -98,6 +98,10 @@ pub enum VaryingError { InvalidPerPrimitive, #[error("Non-builtin members of a mesh primitive output struct must be decorated with `@per_primitive`")] MissingPerPrimitive, + #[error("The `PER_VERTEX` capability must be enabled to use per-vertex fragment inputs.")] + PerVertexNotAllowed, + #[error("Per vertex fragment inputs must be an array of length 3.")] + PerVertexNotArrayOfThree, } #[derive(Clone, Debug, thiserror::Error)] @@ -441,8 +445,16 @@ impl VaryingContext<'_> { Capabilities::MESH_SHADER, )); } + if interpolation == Some(crate::Interpolation::PerVertex) { + if !self.capabilities.contains(Capabilities::SHADER_PER_VERTEX) + || self.stage != crate::ShaderStage::Fragment + { + return Err(VaryingError::PerVertexNotAllowed); + } + } // Only IO-shareable types may be stored in locations. - if !self.type_info[ty.index()] + // Per Vertex case is checked later. + else if !self.type_info[ty.index()] .flags .contains(super::TypeFlags::IO_SHAREABLE) { @@ -548,19 +560,32 @@ impl VaryingContext<'_> { return Err(VaryingError::UnsupportedCapability(required)); } - match ty_inner.scalar_kind() { - Some(crate::ScalarKind::Float) => { - if needs_interpolation && interpolation.is_none() { - return Err(VaryingError::MissingInterpolation); + if interpolation == Some(crate::Interpolation::PerVertex) { + let three = crate::ArraySize::Constant(core::num::NonZeroU32::new(3).unwrap()); + match ty_inner { + &Ti::Array { base, size, .. } if size == three => { + if self.types[base].inner.scalar_kind().is_none() { + return Err(VaryingError::InvalidType(base)); + } } + _ => return Err(VaryingError::PerVertexNotArrayOfThree), } - Some(_) => { - if needs_interpolation && interpolation != Some(crate::Interpolation::Flat) - { - return Err(VaryingError::InvalidInterpolation); + } else { + match ty_inner.scalar_kind() { + Some(crate::ScalarKind::Float) => { + if needs_interpolation && interpolation.is_none() { + return Err(VaryingError::MissingInterpolation); + } + } + Some(_) => { + if needs_interpolation + && interpolation != Some(crate::Interpolation::Flat) + { + return Err(VaryingError::InvalidInterpolation); + } } + None => return Err(VaryingError::InvalidType(ty)), } - None => return Err(VaryingError::InvalidType(ty)), } } } diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index e76b61561e..ed1624f199 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -83,7 +83,7 @@ bitflags::bitflags! { #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct Capabilities: u32 { + pub struct Capabilities: u64 { /// Support for [`AddressSpace::PushConstant`][1]. /// /// [1]: crate::AddressSpace::PushConstant @@ -192,6 +192,8 @@ bitflags::bitflags! { const MESH_SHADER = 1 << 30; /// Support for mesh shaders which output points. const MESH_SHADER_POINT_TOPOLOGY = 1 << 31; + /// Support for per-vertex fragment input. + const SHADER_PER_VERTEX = 1 << 32; } } diff --git a/naga/tests/in/wgsl/per-vertex.toml b/naga/tests/in/wgsl/per-vertex.toml new file mode 100644 index 0000000000..29f10c25af --- /dev/null +++ b/naga/tests/in/wgsl/per-vertex.toml @@ -0,0 +1,10 @@ +god_mode = true + +[msl] +lang_version = [4, 0] + +[hlsl] +shader_model = "V6_1" + +[glsl] +version.Desktop = 450 diff --git a/naga/tests/in/wgsl/per-vertex.wgsl b/naga/tests/in/wgsl/per-vertex.wgsl new file mode 100644 index 0000000000..6a7c09ba88 --- /dev/null +++ b/naga/tests/in/wgsl/per-vertex.wgsl @@ -0,0 +1,4 @@ +@fragment +fn fs_main(@location(0) @interpolate(per_vertex) v: array) -> @location(0) vec4 { + return vec4(v[0], v[1], v[2], 1.0); +} diff --git a/naga/tests/naga/validation.rs b/naga/tests/naga/validation.rs index 78d5ff19f7..d7a52f2c7d 100644 --- a/naga/tests/naga/validation.rs +++ b/naga/tests/naga/validation.rs @@ -409,6 +409,7 @@ fn incompatible_interpolation_and_sampling_types() { naga::Interpolation::Flat, naga::Interpolation::Linear, naga::Interpolation::Perspective, + naga::Interpolation::PerVertex, ] .into_iter() .cartesian_product( @@ -498,6 +499,7 @@ mod dummy_interpolation_shader { naga::Interpolation::Flat => "flat", naga::Interpolation::Linear => "linear", naga::Interpolation::Perspective => "perspective", + naga::Interpolation::PerVertex => "per_vertex", }; let sampling_str = match sampling { None => String::new(), @@ -515,6 +517,7 @@ mod dummy_interpolation_shader { let member_type = match interpolation { naga::Interpolation::Perspective | naga::Interpolation::Linear => "f32", naga::Interpolation::Flat => "u32", + naga::Interpolation::PerVertex => "array", }; let interpolate_attr = format!("@interpolate({interpolation_str}{sampling_str})"); diff --git a/naga/tests/out/glsl/wgsl-per-vertex.fs_main.Fragment.glsl b/naga/tests/out/glsl/wgsl-per-vertex.fs_main.Fragment.glsl new file mode 100644 index 0000000000..06ca96ab67 --- /dev/null +++ b/naga/tests/out/glsl/wgsl-per-vertex.fs_main.Fragment.glsl @@ -0,0 +1,11 @@ +#version 450 core +#extension GL_EXT_fragment_shader_barycentric : require +layout(location = 0) pervertexEXT in float _vs2fs_location0; +layout(location = 0) out vec4 _fs2p_location0; + +void main() { + float v = _vs2fs_location0; + _fs2p_location0 = vec4(v[0], v[1], v[2], 1.0); + return; +} + diff --git a/naga/tests/out/hlsl/wgsl-per-vertex.hlsl b/naga/tests/out/hlsl/wgsl-per-vertex.hlsl new file mode 100644 index 0000000000..2162414f01 --- /dev/null +++ b/naga/tests/out/hlsl/wgsl-per-vertex.hlsl @@ -0,0 +1,9 @@ +struct FragmentInput_fs_main { + nointerpolation float v_1 : LOC0; +}; + +float4 fs_main(FragmentInput_fs_main fragmentinput_fs_main) : SV_Target0 +{ + float v[3] = fragmentinput_fs_main.v_1; + return float4(v[0], v[1], v[2], 1.0); +} diff --git a/naga/tests/out/hlsl/wgsl-per-vertex.ron b/naga/tests/out/hlsl/wgsl-per-vertex.ron new file mode 100644 index 0000000000..9ed6bdee58 --- /dev/null +++ b/naga/tests/out/hlsl/wgsl-per-vertex.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ( + entry_point:"fs_main", + target_profile:"ps_6_1", + ), + ], + compute:[ + ], +) diff --git a/naga/tests/out/msl/wgsl-per-vertex.msl b/naga/tests/out/msl/wgsl-per-vertex.msl new file mode 100644 index 0000000000..1c1ced5098 --- /dev/null +++ b/naga/tests/out/msl/wgsl-per-vertex.msl @@ -0,0 +1,22 @@ +// language: metal4.0 +#include +#include + +using metal::uint; + +struct type_1 { + float inner[3]; +}; + +struct fs_mainInput { + type_1 v [[user(loc0), flat]]; +}; +struct fs_mainOutput { + metal::float4 member [[color(0)]]; +}; +fragment fs_mainOutput fs_main( + fs_mainInput varyings [[stage_in]] +) { + const auto v = varyings.v; + return fs_mainOutput { metal::float4(v.inner[0], v.inner[1], v.inner[2], 1.0) }; +} diff --git a/naga/tests/out/spv/wgsl-per-vertex.spvasm b/naga/tests/out/spv/wgsl-per-vertex.spvasm new file mode 100644 index 0000000000..81abac09ce --- /dev/null +++ b/naga/tests/out/spv/wgsl-per-vertex.spvasm @@ -0,0 +1,39 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 22 +OpCapability Shader +OpCapability FragmentBarycentricKHR +OpExtension "SPV_KHR_fragment_shader_barycentric" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %14 "fs_main" %9 %12 +OpExecutionMode %14 OriginUpperLeft +OpDecorate %4 ArrayStride 4 +OpDecorate %9 Location 0 +OpDecorate %9 PerVertexKHR +OpDecorate %12 Location 0 +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%6 = OpTypeInt 32 0 +%5 = OpConstant %6 3 +%4 = OpTypeArray %3 %5 +%7 = OpTypeVector %3 4 +%10 = OpTypePointer Input %4 +%9 = OpVariable %10 Input +%13 = OpTypePointer Output %7 +%12 = OpVariable %13 Output +%15 = OpTypeFunction %2 +%16 = OpConstant %3 1 +%14 = OpFunction %2 None %15 +%8 = OpLabel +%11 = OpLoad %4 %9 +OpBranch %17 +%17 = OpLabel +%18 = OpCompositeExtract %3 %11 0 +%19 = OpCompositeExtract %3 %11 1 +%20 = OpCompositeExtract %3 %11 2 +%21 = OpCompositeConstruct %7 %18 %19 %20 %16 +OpStore %12 %21 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/wgsl/wgsl-per-vertex.wgsl b/naga/tests/out/wgsl/wgsl-per-vertex.wgsl new file mode 100644 index 0000000000..dd45184d7c --- /dev/null +++ b/naga/tests/out/wgsl/wgsl-per-vertex.wgsl @@ -0,0 +1,4 @@ +@fragment +fn fs_main(@location(0) @interpolate(per_vertex) v: array) -> @location(0) vec4 { + return vec4(v[0], v[1], v[2], 1f); +} diff --git a/tests/tests/wgpu-gpu/main.rs b/tests/tests/wgpu-gpu/main.rs index 77669982a0..3a1a4f83b7 100644 --- a/tests/tests/wgpu-gpu/main.rs +++ b/tests/tests/wgpu-gpu/main.rs @@ -40,6 +40,7 @@ mod multiview; mod occlusion_query; mod oob_indexing; mod oom; +mod per_vertex; mod pipeline; mod pipeline_cache; mod planar_texture; @@ -104,6 +105,7 @@ fn all_tests() -> Vec { occlusion_query::all_tests(&mut tests); oob_indexing::all_tests(&mut tests); oom::all_tests(&mut tests); + per_vertex::all_tests(&mut tests); pipeline_cache::all_tests(&mut tests); pipeline::all_tests(&mut tests); planar_texture::all_tests(&mut tests); diff --git a/tests/tests/wgpu-gpu/per_vertex/mod.rs b/tests/tests/wgpu-gpu/per_vertex/mod.rs new file mode 100644 index 0000000000..f8d36f2649 --- /dev/null +++ b/tests/tests/wgpu-gpu/per_vertex/mod.rs @@ -0,0 +1,169 @@ +use wgpu::util::DeviceExt; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +pub fn all_tests(vec: &mut Vec) { + vec.push(PER_VERTEX); +} + +// +// These tests render two triangles to a 2x2 render target. The first triangle +// in the vertex buffer covers the bottom-left pixel, the second triangle +// covers the top-right pixel. +// XY layout of the render target, with two triangles: +// +// (-1,1) (0,1) (1,1) +// +-------+-------+ +// | |o-----o| +// | | \ / | +// | | \ / | +// | | o | +// (-1,0) +-------+-------+ (1,0) +// | o | | +// | / \ | | +// | / \ | | +// |o-----o| | +// +-------+-------+ +// (-1,-1) (0,-1) (1,-1) +// +// The fragment shader outputs color based on per-vertex position: +// +// return vec4(z[0], z[1], z[2], 1.0); +// + +#[gpu_test] +static PER_VERTEX: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::SHADER_BARYCENTRICS), + ) + .run_async(barycentric); + +async fn barycentric(ctx: TestingContext) { + let shader = ctx + .device + .create_shader_module(wgpu::include_wgsl!("per_vertex.wgsl")); + + let two_triangles_xyz: [f32; 18] = [ + -1.0, -1.0, 0.0, 0.0, -1.0, 1.0, -0.5, 0.0, + 1.0, // left triangle, negative x, negative y. cyan + 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.5, 1.0, + 0.0, // right triangle, positive x, positive y. red + ]; + let vertex_buffer = ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&two_triangles_xyz), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + }); + + let indices = [3u32, 4, 5, 0, 1, 2]; + let index_buffer = ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&indices), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + }); + + let pipeline = ctx + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + buffers: &[wgpu::VertexBufferLayout { + array_stride: 12, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x3, + offset: 0, + shader_location: 0, + }], + }], + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview_mask: None, + cache: None, + }); + + let width = 2; + let height = 2; + let texture_size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + let color_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + let color_view = color_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let readback_buffer = wgpu_test::image::ReadbackBuffers::new(&ctx.device, &color_texture); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), + store: wgpu::StoreOp::Store, + }, + resolve_target: None, + view: &color_view, + depth_slice: None, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: None, + }); + + rpass.set_pipeline(&pipeline); + rpass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32); + rpass.set_vertex_buffer(0, vertex_buffer.slice(..)); + rpass.draw(0..6, 0..1); + } + readback_buffer.copy_from(&ctx.device, &mut encoder, &color_texture); + ctx.queue.submit(Some(encoder.finish())); + + // + // +-----+-----+ + // |white| red | + // +-----+-----+ + // | cyan|white| + // +-----+-----+ + // + let expected = [ + 255, 255, 255, 255, 255, 0, 0, 255, 0, 255, 255, 255, 255, 255, 255, 255, + ]; + readback_buffer + .assert_buffer_contents(&ctx, &expected) + .await; +} diff --git a/tests/tests/wgpu-gpu/per_vertex/per_vertex.wgsl b/tests/tests/wgpu-gpu/per_vertex/per_vertex.wgsl new file mode 100644 index 0000000000..c0b4f24480 --- /dev/null +++ b/tests/tests/wgpu-gpu/per_vertex/per_vertex.wgsl @@ -0,0 +1,14 @@ +struct VertexOutput { + @builtin(position) clip: vec4, + @interpolate(flat) @location(0) z: f32, +} + +@vertex +fn vs_main(@location(0) xyz: vec3) -> VertexOutput { + return VertexOutput(vec4(xyz.xy, 0.0, 1.0), xyz.z); +} + +@fragment +fn fs_main(@interpolate(per_vertex) @location(0) z: array) -> @location(0) vec4 { + return vec4(z[0], z[1], z[2], 1.0); +} diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 8683b37dd0..26c6e015cb 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -522,6 +522,10 @@ pub fn create_validator( Caps::MESH_SHADER_POINT_TOPOLOGY, features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER_POINTS), ); + caps.set( + Caps::SHADER_PER_VERTEX, + features.intersects(wgt::Features::SHADER_BARYCENTRICS), + ); naga::valid::Validator::new(flags, caps) } diff --git a/wgpu-types/src/features.rs b/wgpu-types/src/features.rs index ce58eb9ad4..37fd1372a1 100644 --- a/wgpu-types/src/features.rs +++ b/wgpu-types/src/features.rs @@ -1263,6 +1263,16 @@ bitflags_array! { /// /// This is a native only feature. const EXPERIMENTAL_MESH_SHADER_POINTS = 1 << 55; + + /// Enables shader barycentric coordinates. + /// + /// Supported platforms: + /// - Vulkan (with VK_KHR_fragment_shader_barycentric) + /// - DX12 (with SM 6.1+) + /// - Metal (with MSL 4+) + /// + /// This is a native only feature. + const SHADER_PER_VERTEX = 1 << 56; } /// Features that are not guaranteed to be supported.