diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a45a5ff..496b033f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Bottom level categories: - Add support for astc-sliced-3d feature. By @mehmetoguzderin in [#7577](https://github.com/gfx-rs/wgpu/issues/7577) - Add support for rendering to slices of 3D texture views and single layered 2D-Array texture views (this requires `VK_KHR_maintenance1` which should be widely available on newer drivers). By @teoxoy in [#7596](https://github.com/gfx-rs/wgpu/pull/7596) - Add extra acceleration structure vertex formats. By @Vecvec in [#7580](https://github.com/gfx-rs/wgpu/pull/7580). +- Add support for clip-distances feature for Vulkan and GL backends. By @dzamkov in [#7730](https://github.com/gfx-rs/wgpu/pull/7730) #### Naga diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index ef1621cfb..d803cee50 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -377,6 +377,8 @@ pub struct ReflectionInfo { pub varying: crate::FastHashMap, /// List of push constant items in the shader. pub push_constant_items: Vec, + /// Number of user-defined clip planes. Only applicable to vertex shaders. + pub clip_distance_count: u32, } /// Mapping between a texture and its sampler, if it exists. @@ -475,7 +477,7 @@ impl VaryingOptions { /// Helper wrapper used to get a name for a varying /// /// Varying have different naming schemes depending on their binding: -/// - Varyings with builtin bindings get the from [`glsl_built_in`]. +/// - Varyings with builtin bindings get their name from [`glsl_built_in`]. /// - Varyings with location bindings are named `_S_location_X` where `S` is a /// prefix identifying which pipeline stage the varying connects, and `X` is /// the location. @@ -621,6 +623,8 @@ pub struct Writer<'a, W> { multiview: Option, /// Mapping of varying variables to their location. Needed for reflections. varying: crate::FastHashMap, + /// Number of user-defined clip planes. Only non-zero for vertex shaders. + clip_distance_count: u32, } impl<'a, W: Write> Writer<'a, W> { @@ -688,6 +692,7 @@ impl<'a, W: Write> Writer<'a, W> { need_bake_expressions: Default::default(), continue_ctx: back::continue_forward::ContinueCtx::default(), varying: Default::default(), + clip_distance_count: 0, }; // Find all features required to print this module @@ -1610,31 +1615,47 @@ impl<'a, W: Write> Writer<'a, W> { blend_src, } => (location, interpolation, sampling, blend_src), crate::Binding::BuiltIn(built_in) => { - if let crate::BuiltIn::Position { invariant: true } = built_in { - match (self.options.version, self.entry_point.stage) { - ( - Version::Embedded { - version: 300, - is_webgl: true, - }, - ShaderStage::Fragment, - ) => { - // `invariant gl_FragCoord` is not allowed in WebGL2 and possibly - // OpenGL ES in general (waiting on confirmation). - // - // See https://github.com/KhronosGroup/WebGL/issues/3518 - } - _ => { - writeln!( - self.out, - "invariant {};", - glsl_built_in( - built_in, - VaryingOptions::from_writer_options(self.options, output) - ) - )?; + match built_in { + crate::BuiltIn::Position { invariant: true } => { + match (self.options.version, self.entry_point.stage) { + ( + Version::Embedded { + version: 300, + is_webgl: true, + }, + ShaderStage::Fragment, + ) => { + // `invariant gl_FragCoord` is not allowed in WebGL2 and possibly + // OpenGL ES in general (waiting on confirmation). + // + // See https://github.com/KhronosGroup/WebGL/issues/3518 + } + _ => { + writeln!( + self.out, + "invariant {};", + glsl_built_in( + built_in, + VaryingOptions::from_writer_options(self.options, output) + ) + )?; + } } } + crate::BuiltIn::ClipDistance => { + // Re-declare `gl_ClipDistance` with number of clip planes. + let TypeInner::Array { size, .. } = self.module.types[ty].inner else { + unreachable!(); + }; + let proc::IndexableLength::Known(size) = + size.resolve(self.module.to_ctx())? + else { + unreachable!(); + }; + self.clip_distance_count = size; + writeln!(self.out, "out float gl_ClipDistance[{size}];")?; + } + _ => {} } return Ok(()); } @@ -5049,6 +5070,7 @@ impl<'a, W: Write> Writer<'a, W> { uniforms, varying: mem::take(&mut self.varying), push_constant_items, + clip_distance_count: self.clip_distance_count, }) } diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index e89fa3e42..fdb17134c 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -5,7 +5,6 @@ use alloc::{ vec::Vec, }; use core::fmt::Write; -use hashbrown::HashSet; use super::Error; use super::ToWgslIfImplemented as _; @@ -127,9 +126,6 @@ impl Writer { self.reset(module); - // Write all needed directives. - self.write_enable_dual_source_blending_if_needed(module)?; - // Write all `enable` declarations self.write_enable_declarations(module)?; @@ -227,31 +223,52 @@ impl Writer { /// Helper method which writes all the `enable` declarations /// needed for a module. fn write_enable_declarations(&mut self, module: &Module) -> BackendResult { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - enum WrittenDeclarations { - F16, - } + let mut needs_f16 = false; + let mut needs_dual_source_blending = false; + let mut needs_clip_distances = false; - let mut written_declarations = HashSet::new(); - - // Write all the `enable` declarations + // Determine which `enable` declarations are needed for (_, ty) in module.types.iter() { match ty.inner { TypeInner::Scalar(scalar) | TypeInner::Vector { scalar, .. } | TypeInner::Matrix { scalar, .. } => { - if scalar == crate::Scalar::F16 - && !written_declarations.contains(&WrittenDeclarations::F16) - { - writeln!(self.out, "enable f16;")?; - written_declarations.insert(WrittenDeclarations::F16); + needs_f16 |= scalar == crate::Scalar::F16; + } + TypeInner::Struct { ref members, .. } => { + for binding in members.iter().filter_map(|m| m.binding.as_ref()) { + match *binding { + crate::Binding::Location { + blend_src: Some(_), .. + } => { + needs_dual_source_blending = true; + } + crate::Binding::BuiltIn(crate::BuiltIn::ClipDistance) => { + needs_clip_distances = true; + } + _ => {} + } } } _ => {} } } - if !written_declarations.is_empty() { + // Write required declarations + let mut any_written = false; + if needs_f16 { + writeln!(self.out, "enable f16;")?; + any_written = true; + } + if needs_dual_source_blending { + writeln!(self.out, "enable dual_source_blending;")?; + any_written = true; + } + if needs_clip_distances { + writeln!(self.out, "enable clip_distances;")?; + any_written = true; + } + if any_written { // Empty line for readability writeln!(self.out)?; } @@ -404,32 +421,6 @@ impl Writer { Ok(()) } - /// Writes all the necessary directives out - fn write_enable_dual_source_blending_if_needed(&mut self, module: &Module) -> BackendResult { - // Check for dual source blending. - if module.types.iter().any(|(_handle, ty)| { - if let TypeInner::Struct { ref members, .. } = ty.inner { - members.iter().any(|member| { - member.binding.as_ref().is_some_and(|binding| { - matches!( - binding, - &crate::Binding::Location { - blend_src: Some(_), - .. - } - ) - }) - }) - } else { - false - } - }) { - writeln!(self.out, "enable dual_source_blending;")?; - } - - Ok(()) - } - /// Helper method used to write structs /// Write the full declaration of a struct type. /// diff --git a/naga/src/common/wgsl/to_wgsl.rs b/naga/src/common/wgsl/to_wgsl.rs index fcc7307f4..035c4eafb 100644 --- a/naga/src/common/wgsl/to_wgsl.rs +++ b/naga/src/common/wgsl/to_wgsl.rs @@ -165,6 +165,7 @@ impl TryToWgsl for crate::BuiltIn { Bi::ViewIndex => "view_index", Bi::InstanceIndex => "instance_index", Bi::VertexIndex => "vertex_index", + Bi::ClipDistance => "clip_distances", Bi::FragDepth => "frag_depth", Bi::FrontFacing => "front_facing", Bi::PrimitiveIndex => "primitive_index", @@ -183,7 +184,6 @@ impl TryToWgsl for crate::BuiltIn { // Non-standard built-ins. Bi::BaseInstance | Bi::BaseVertex - | Bi::ClipDistance | Bi::CullDistance | Bi::PointSize | Bi::DrawID diff --git a/naga/src/front/wgsl/parse/conv.rs b/naga/src/front/wgsl/parse/conv.rs index d3b6e77e8..cbc485fb2 100644 --- a/naga/src/front/wgsl/parse/conv.rs +++ b/naga/src/front/wgsl/parse/conv.rs @@ -20,13 +20,18 @@ pub fn map_address_space(word: &str, span: Span) -> Result<'_, crate::AddressSpa } } -pub fn map_built_in(word: &str, span: Span) -> Result<'_, crate::BuiltIn> { - Ok(match word { +pub fn map_built_in( + enable_extensions: &EnableExtensions, + word: &str, + span: Span, +) -> Result<'static, crate::BuiltIn> { + let built_in = match word { "position" => crate::BuiltIn::Position { invariant: false }, // vertex "vertex_index" => crate::BuiltIn::VertexIndex, "instance_index" => crate::BuiltIn::InstanceIndex, "view_index" => crate::BuiltIn::ViewIndex, + "clip_distances" => crate::BuiltIn::ClipDistance, // fragment "front_facing" => crate::BuiltIn::FrontFacing, "frag_depth" => crate::BuiltIn::FragDepth, @@ -45,7 +50,19 @@ pub fn map_built_in(word: &str, span: Span) -> Result<'_, crate::BuiltIn> { "subgroup_size" => crate::BuiltIn::SubgroupSize, "subgroup_invocation_id" => crate::BuiltIn::SubgroupInvocationId, _ => return Err(Box::new(Error::UnknownBuiltin(span))), - }) + }; + match built_in { + crate::BuiltIn::ClipDistance => { + if !enable_extensions.contains(ImplementedEnableExtension::ClipDistances) { + return Err(Box::new(Error::EnableExtensionNotEnabled { + span, + kind: ImplementedEnableExtension::ClipDistances.into(), + })); + } + } + _ => {} + } + Ok(built_in) } pub fn map_interpolation(word: &str, span: Span) -> Result<'_, crate::Interpolation> { diff --git a/naga/src/front/wgsl/parse/directive/enable_extension.rs b/naga/src/front/wgsl/parse/directive/enable_extension.rs index 45b359a55..7064e40fb 100644 --- a/naga/src/front/wgsl/parse/directive/enable_extension.rs +++ b/naga/src/front/wgsl/parse/directive/enable_extension.rs @@ -13,6 +13,7 @@ pub struct EnableExtensions { dual_source_blending: bool, /// Whether `enable f16;` was written earlier in the shader module. f16: bool, + clip_distances: bool, } impl EnableExtensions { @@ -20,6 +21,7 @@ impl EnableExtensions { Self { f16: false, dual_source_blending: false, + clip_distances: false, } } @@ -28,6 +30,7 @@ impl EnableExtensions { let field = match ext { ImplementedEnableExtension::DualSourceBlending => &mut self.dual_source_blending, ImplementedEnableExtension::F16 => &mut self.f16, + ImplementedEnableExtension::ClipDistances => &mut self.clip_distances, }; *field = true; } @@ -37,6 +40,7 @@ impl EnableExtensions { match ext { ImplementedEnableExtension::DualSourceBlending => self.dual_source_blending, ImplementedEnableExtension::F16 => self.f16, + ImplementedEnableExtension::ClipDistances => self.clip_distances, } } } @@ -72,9 +76,7 @@ impl EnableExtension { pub(crate) fn from_ident(word: &str, span: Span) -> Result { Ok(match word { Self::F16 => Self::Implemented(ImplementedEnableExtension::F16), - Self::CLIP_DISTANCES => { - Self::Unimplemented(UnimplementedEnableExtension::ClipDistances) - } + Self::CLIP_DISTANCES => Self::Implemented(ImplementedEnableExtension::ClipDistances), Self::DUAL_SOURCE_BLENDING => { Self::Implemented(ImplementedEnableExtension::DualSourceBlending) } @@ -89,9 +91,9 @@ impl EnableExtension { Self::Implemented(kind) => match kind { ImplementedEnableExtension::DualSourceBlending => Self::DUAL_SOURCE_BLENDING, ImplementedEnableExtension::F16 => Self::F16, + ImplementedEnableExtension::ClipDistances => Self::CLIP_DISTANCES, }, Self::Unimplemented(kind) => match kind { - UnimplementedEnableExtension::ClipDistances => Self::CLIP_DISTANCES, UnimplementedEnableExtension::Subgroups => Self::SUBGROUPS, }, } @@ -113,17 +115,17 @@ pub enum ImplementedEnableExtension { /// /// [`enable dual_source_blending;`]: https://www.w3.org/TR/WGSL/#extension-dual_source_blending DualSourceBlending, -} - -/// A variant of [`EnableExtension::Unimplemented`]. -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] -pub enum UnimplementedEnableExtension { /// Enables the `clip_distances` variable in WGSL. /// /// In the WGSL standard, this corresponds to [`enable clip_distances;`]. /// /// [`enable clip_distances;`]: https://www.w3.org/TR/WGSL/#extension-clip_distances ClipDistances, +} + +/// A variant of [`EnableExtension::Unimplemented`]. +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub enum UnimplementedEnableExtension { /// Enables subgroup built-ins in all languages. /// /// In the WGSL standard, this corresponds to [`enable subgroups;`]. @@ -135,7 +137,6 @@ pub enum UnimplementedEnableExtension { impl UnimplementedEnableExtension { pub(crate) const fn tracking_issue_num(self) -> u16 { match self { - Self::ClipDistances => 6236, Self::Subgroups => 5555, } } diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index 567e44a0a..892930b43 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -199,8 +199,10 @@ impl<'a> BindingParser<'a> { "builtin" => { lexer.expect(Token::Paren('('))?; let (raw, span) = lexer.next_ident_with_span()?; - self.built_in - .set(conv::map_built_in(raw, span)?, name_span)?; + self.built_in.set( + conv::map_built_in(&lexer.enable_extensions, raw, span)?, + name_span, + )?; lexer.expect(Token::Paren(')'))?; } "interpolate" => { diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 3792c71ab..14cfa42e8 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -212,8 +212,12 @@ impl VaryingContext<'_> { Bi::ClipDistance | Bi::CullDistance => ( self.stage == St::Vertex && self.output, match *ty_inner { - Ti::Array { base, .. } => { + Ti::Array { base, size, .. } => { self.types[base].inner == Ti::Scalar(crate::Scalar::F32) + && match size { + crate::ArraySize::Constant(non_zero) => non_zero.get() <= 8, + _ => false, + } } _ => false, }, diff --git a/naga/tests/in/wgsl/clip-distances.toml b/naga/tests/in/wgsl/clip-distances.toml new file mode 100644 index 000000000..8c4df3aee --- /dev/null +++ b/naga/tests/in/wgsl/clip-distances.toml @@ -0,0 +1,5 @@ +god_mode = true +targets = "SPIRV | GLSL | WGSL" + +[glsl] +version.Desktop = 330 diff --git a/naga/tests/in/wgsl/clip-distances.wgsl b/naga/tests/in/wgsl/clip-distances.wgsl new file mode 100644 index 000000000..7727f4285 --- /dev/null +++ b/naga/tests/in/wgsl/clip-distances.wgsl @@ -0,0 +1,12 @@ +enable clip_distances; +struct VertexOutput { + @builtin(position) position: vec4, + @builtin(clip_distances) clip_distances: array, +} + +@vertex +fn main() -> VertexOutput { + var out: VertexOutput; + out.clip_distances[0] = 0.5; + return out; +} \ No newline at end of file diff --git a/naga/tests/naga/wgsl_errors.rs b/naga/tests/naga/wgsl_errors.rs index 2a330b88b..962c8f46a 100644 --- a/naga/tests/naga/wgsl_errors.rs +++ b/naga/tests/naga/wgsl_errors.rs @@ -3667,3 +3667,78 @@ fn subgroup_invalid_broadcast() { naga::valid::Capabilities::SUBGROUP } } + +#[test] +fn invalid_clip_distances() { + // Missing capability. + check_validation! { + r#" + enable clip_distances; + struct VertexOutput { + @builtin(position) pos: vec4f, + @builtin(clip_distances) clip_distances: array, + } + + @vertex + fn vs_main() -> VertexOutput { + var out: VertexOutput; + return out; + } + "#: + Err( + naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Vertex, + source: naga::valid::EntryPointError::Result( + naga::valid::VaryingError::UnsupportedCapability(Capabilities::CLIP_DISTANCE), + ), + .. + }, + ) + } + + // Missing enable directive. + // Note that this is a parsing error, not a validation error. + check( + r#" + @vertex + fn vs_main() -> @builtin(clip_distances) array { + var out: array; + return out; + } + "#, + r###"error: the `clip_distances` enable extension is not enabled + ┌─ wgsl:3:38 + │ +3 │ fn vs_main() -> @builtin(clip_distances) array { + │ ^^^^^^^^^^^^^^ the `clip_distances` "Enable Extension" is needed for this functionality, but it is not currently enabled. + │ + = note: You can enable this extension by adding `enable clip_distances;` at the top of the shader, before any other items. + +"###, + ); + + // Maximum clip distances exceeded + check_validation! { + r#" + enable clip_distances; + struct VertexOutput { + @builtin(position) pos: vec4f, + @builtin(clip_distances) clip_distances: array, + } + + @vertex + fn vs_main() -> VertexOutput { + var out: VertexOutput; + return out; + } + "#: + Err(naga::valid::ValidationError::EntryPoint { + stage: naga::ShaderStage::Vertex, + source: naga::valid::EntryPointError::Result( + naga::valid::VaryingError::InvalidBuiltInType(naga::ir::BuiltIn::ClipDistance) + ), + .. + }), + naga::valid::Capabilities::CLIP_DISTANCE + } +} diff --git a/naga/tests/out/glsl/wgsl-clip-distances.main.Vertex.glsl b/naga/tests/out/glsl/wgsl-clip-distances.main.Vertex.glsl new file mode 100644 index 000000000..e0f2a4e66 --- /dev/null +++ b/naga/tests/out/glsl/wgsl-clip-distances.main.Vertex.glsl @@ -0,0 +1,17 @@ +#version 330 core +struct VertexOutput { + vec4 position; + float clip_distances[1]; +}; +out float gl_ClipDistance[1]; + +void main() { + VertexOutput out_ = VertexOutput(vec4(0.0), float[1](0.0)); + out_.clip_distances[0] = 0.5; + VertexOutput _e4 = out_; + gl_Position = _e4.position; + gl_ClipDistance = _e4.clip_distances; + gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w); + return; +} + diff --git a/naga/tests/out/spv/wgsl-clip-distances.spvasm b/naga/tests/out/spv/wgsl-clip-distances.spvasm new file mode 100644 index 000000000..24deba3c7 --- /dev/null +++ b/naga/tests/out/spv/wgsl-clip-distances.spvasm @@ -0,0 +1,46 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 28 +OpCapability Shader +OpCapability ClipDistance +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %14 "main" %10 %12 +OpDecorate %5 ArrayStride 4 +OpMemberDecorate %8 0 Offset 0 +OpMemberDecorate %8 1 Offset 16 +OpDecorate %10 BuiltIn Position +OpDecorate %12 BuiltIn ClipDistance +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%4 = OpTypeVector %3 4 +%7 = OpTypeInt 32 0 +%6 = OpConstant %7 1 +%5 = OpTypeArray %3 %6 +%8 = OpTypeStruct %4 %5 +%11 = OpTypePointer Output %4 +%10 = OpVariable %11 Output +%13 = OpTypePointer Output %5 +%12 = OpVariable %13 Output +%15 = OpTypeFunction %2 +%16 = OpConstant %3 0.5 +%18 = OpTypePointer Function %8 +%19 = OpConstantNull %8 +%21 = OpTypePointer Function %5 +%22 = OpTypePointer Function %3 +%23 = OpConstant %7 0 +%14 = OpFunction %2 None %15 +%9 = OpLabel +%17 = OpVariable %18 Function %19 +OpBranch %20 +%20 = OpLabel +%24 = OpAccessChain %22 %17 %6 %23 +OpStore %24 %16 +%25 = OpLoad %8 %17 +%26 = OpCompositeExtract %4 %25 0 +OpStore %10 %26 +%27 = OpCompositeExtract %5 %25 1 +OpStore %12 %27 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/wgsl/glsl-dual-source-blending.frag.wgsl b/naga/tests/out/wgsl/glsl-dual-source-blending.frag.wgsl index 90ed4bf9d..fe4dd58bd 100644 --- a/naga/tests/out/wgsl/glsl-dual-source-blending.frag.wgsl +++ b/naga/tests/out/wgsl/glsl-dual-source-blending.frag.wgsl @@ -1,4 +1,5 @@ enable dual_source_blending; + struct FragmentOutput { @location(0) @blend_src(0) output0_: vec4, @location(0) @blend_src(1) output1_: vec4, diff --git a/naga/tests/out/wgsl/wgsl-clip-distances.wgsl b/naga/tests/out/wgsl/wgsl-clip-distances.wgsl new file mode 100644 index 000000000..5610b9728 --- /dev/null +++ b/naga/tests/out/wgsl/wgsl-clip-distances.wgsl @@ -0,0 +1,15 @@ +enable clip_distances; + +struct VertexOutput { + @builtin(position) position: vec4, + @builtin(clip_distances) clip_distances: array, +} + +@vertex +fn main() -> VertexOutput { + var out: VertexOutput; + + out.clip_distances[0] = 0.5f; + let _e4 = out; + return _e4; +} diff --git a/naga/tests/out/wgsl/wgsl-dualsource.wgsl b/naga/tests/out/wgsl/wgsl-dualsource.wgsl index 5856394be..c3add5937 100644 --- a/naga/tests/out/wgsl/wgsl-dualsource.wgsl +++ b/naga/tests/out/wgsl/wgsl-dualsource.wgsl @@ -1,4 +1,5 @@ enable dual_source_blending; + struct FragmentOutput { @location(0) @blend_src(0) output0_: vec4, @location(0) @blend_src(1) output1_: vec4, diff --git a/tests/tests/wgpu-gpu/clip_distances.rs b/tests/tests/wgpu-gpu/clip_distances.rs new file mode 100644 index 000000000..26961d7e0 --- /dev/null +++ b/tests/tests/wgpu-gpu/clip_distances.rs @@ -0,0 +1,152 @@ +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +#[gpu_test] +static CLIP_DISTANCES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::CLIP_DISTANCES)) + .run_async(clip_distances); + +async fn clip_distances(ctx: TestingContext) { + // Create pipeline + let shader = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(SHADER_SRC.into()), + }); + let pipeline = ctx + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + buffers: &[], + module: &shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + }, + 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::R8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + cache: None, + }); + + // Create render target + let render_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: Some("Render Texture"), + size: wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::R8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + // Perform render + 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 { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }), + store: wgpu::StoreOp::Store, + }, + resolve_target: None, + view: &render_texture.create_view(&wgpu::TextureViewDescriptor::default()), + depth_slice: None, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_pipeline(&pipeline); + rpass.draw(0..3, 0..1); + } + + // Read texture data + let readback_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256 * 256, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + encoder.copy_texture_to_buffer( + wgpu::TexelCopyTextureInfo { + texture: &render_texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::TexelCopyBufferInfo { + buffer: &readback_buffer, + layout: wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(256), + rows_per_image: None, + }, + }, + wgpu::Extent3d { + width: 256, + height: 256, + depth_or_array_layers: 1, + }, + ); + ctx.queue.submit([encoder.finish()]); + let slice = readback_buffer.slice(..); + slice.map_async(wgpu::MapMode::Read, |_| ()); + ctx.async_poll(wgpu::PollType::wait()).await.unwrap(); + let data: &[u8] = &slice.get_mapped_range(); + + // We should have filled the upper sector of the texture. Verify that this is the case. + assert_eq!(data[128 + 64 * 256], 0xFF); + assert_eq!(data[64 + 128 * 256], 0x00); + assert_eq!(data[192 + 128 * 256], 0x00); + assert_eq!(data[128 + 192 * 256], 0x00); +} + +const SHADER_SRC: &str = " +enable clip_distances; +struct VertexOutput { + @builtin(position) pos: vec4f, + @builtin(clip_distances) clip_distances: array, +} + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { + var out: VertexOutput; + let x = f32(i32(vertex_index) / 2) * 4.0 - 1.0; + let y = f32(i32(vertex_index) & 1) * 4.0 - 1.0; + out.pos = vec4f(x, y, 0.5, 1.0); + out.clip_distances[0] = x + y; + out.clip_distances[1] = y - x; + return out; +} + +@fragment +fn fs_main() -> @location(0) vec4f { + return vec4f(1.0); +} +"; diff --git a/tests/tests/wgpu-gpu/main.rs b/tests/tests/wgpu-gpu/main.rs index aaa56ffb8..8fd24c434 100644 --- a/tests/tests/wgpu-gpu/main.rs +++ b/tests/tests/wgpu-gpu/main.rs @@ -19,6 +19,7 @@ mod buffer; mod buffer_copy; mod buffer_usages; mod clear_texture; +mod clip_distances; mod cloneable_types; mod compute_pass_ownership; mod create_surface_error; diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 8e6f28d55..42ebffdaa 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -461,6 +461,10 @@ pub fn create_validator( Caps::DUAL_SOURCE_BLENDING, features.contains(wgt::Features::DUAL_SOURCE_BLENDING), ); + caps.set( + Caps::CLIP_DISTANCE, + features.contains(wgt::Features::CLIP_DISTANCES), + ); caps.set( Caps::CUBE_ARRAY_TEXTURES, downlevel.contains(wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES), diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 7332ecd2b..ced977ebf 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -465,6 +465,10 @@ impl super::Adapter { extensions.contains("GL_EXT_blend_func_extended") || extensions.contains("GL_ARB_blend_func_extended"), ); + features.set( + wgt::Features::CLIP_DISTANCES, + full_ver.is_some() || extensions.contains("GL_EXT_clip_cull_distance"), + ); features.set( wgt::Features::SHADER_PRIMITIVE_INDEX, supported((3, 2), (3, 2)) diff --git a/wgpu-hal/src/gles/command.rs b/wgpu-hal/src/gles/command.rs index eb5c3ade8..6136b4fd0 100644 --- a/wgpu-hal/src/gles/command.rs +++ b/wgpu-hal/src/gles/command.rs @@ -37,6 +37,7 @@ pub(super) struct State { // The current state of the push constant data block. current_push_constant_data: [u32; super::MAX_PUSH_CONSTANTS], end_of_pass_timestamp: Option, + clip_distance_count: u32, } impl Default for State { @@ -65,6 +66,7 @@ impl Default for State { push_constant_descs: Default::default(), current_push_constant_data: [0; super::MAX_PUSH_CONSTANTS], end_of_pass_timestamp: Default::default(), + clip_distance_count: Default::default(), } } } @@ -981,6 +983,15 @@ impl crate::CommandEncoder for super::CommandEncoder { for ct in pipeline.color_targets.iter() { self.state.color_targets.push(ct.clone()); } + + // set clip plane count + if pipeline.inner.clip_distance_count != self.state.clip_distance_count { + self.cmd_buffer.commands.push(C::SetClipDistances { + old_count: self.state.clip_distance_count, + new_count: pipeline.inner.clip_distance_count, + }); + self.state.clip_distance_count = pipeline.inner.clip_distance_count; + } } unsafe fn set_index_buffer<'a>( diff --git a/wgpu-hal/src/gles/device.rs b/wgpu-hal/src/gles/device.rs index 4e02c8775..42ca63821 100644 --- a/wgpu-hal/src/gles/device.rs +++ b/wgpu-hal/src/gles/device.rs @@ -23,6 +23,7 @@ struct CompilationContext<'a> { name_binding_map: &'a mut NameBindingMap, push_constant_items: &'a mut Vec, multiview: Option, + clip_distance_count: &'a mut u32, } impl CompilationContext<'_> { @@ -103,6 +104,10 @@ impl CompilationContext<'_> { } *self.push_constant_items = reflection_info.push_constant_items; + + if naga_stage == naga::ShaderStage::Vertex { + *self.clip_distance_count = reflection_info.clip_distance_count; + } } } @@ -372,6 +377,7 @@ impl super::Device { let mut sampler_map = [None; super::MAX_TEXTURE_SLOTS]; let mut has_stages = wgt::ShaderStages::empty(); let mut shaders_to_delete = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); + let mut clip_distance_count = 0; for &(naga_stage, stage) in &shaders { has_stages |= map_naga_stage(naga_stage); @@ -385,6 +391,7 @@ impl super::Device { name_binding_map: &mut name_binding_map, push_constant_items: pc_item, multiview, + clip_distance_count: &mut clip_distance_count, }; let shader = Self::create_shader(gl, naga_stage, stage, context, program)?; @@ -496,6 +503,7 @@ impl super::Device { sampler_map, first_instance_location, push_constant_descs: uniforms, + clip_distance_count, })) } } diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index 4df93e9a9..a6073b4ec 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -669,6 +669,7 @@ struct PipelineInner { sampler_map: SamplerBindMap, first_instance_location: Option, push_constant_descs: ArrayVec, + clip_distance_count: u32, } #[derive(Clone, Debug)] @@ -1006,6 +1007,10 @@ enum Command { /// Offset from the start of the `data_bytes` offset: u32, }, + SetClipDistances { + old_count: u32, + new_count: u32, + }, } #[derive(Default)] diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index d653e7d2d..a287a856c 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -1824,6 +1824,20 @@ impl super::Queue { _ => panic!("Unsupported uniform datatype: {:?}!", uniform.ty), } } + C::SetClipDistances { + old_count, + new_count, + } => { + // Disable clip planes that are no longer active + for i in new_count..old_count { + unsafe { gl.disable(glow::CLIP_DISTANCE0 + i) }; + } + + // Enable clip planes that are now active + for i in old_count..new_count { + unsafe { gl.enable(glow::CLIP_DISTANCE0 + i) }; + } + } } } } diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 83ec9653b..acd32ff6e 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -308,7 +308,7 @@ impl PhysicalDeviceFeatures { | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY, )) //.shader_storage_image_array_dynamic_indexing( - //.shader_clip_distance(requested_features.contains(wgt::Features::SHADER_CLIP_DISTANCE)) + .shader_clip_distance(requested_features.contains(wgt::Features::CLIP_DISTANCES)) //.shader_cull_distance(requested_features.contains(wgt::Features::SHADER_CULL_DISTANCE)) .shader_float64(requested_features.contains(wgt::Features::SHADER_F64)) .shader_int64(requested_features.contains(wgt::Features::SHADER_INT64)) @@ -709,6 +709,7 @@ impl PhysicalDeviceFeatures { features.set(F::DEPTH_CLIP_CONTROL, self.core.depth_clamp != 0); features.set(F::DUAL_SOURCE_BLENDING, self.core.dual_src_blend != 0); + features.set(F::CLIP_DISTANCES, self.core.shader_clip_distance != 0); if let Some(ref multiview) = self.multiview { features.set(F::MULTIVIEW, multiview.multiview != 0); @@ -2023,6 +2024,10 @@ impl super::Adapter { capabilities.push(spv::Capability::AtomicFloat32AddEXT); } + if features.contains(wgt::Features::CLIP_DISTANCES) { + capabilities.push(spv::Capability::ClipDistance); + } + let mut flags = spv::WriterFlags::empty(); flags.set( spv::WriterFlags::DEBUG, diff --git a/wgpu-types/src/features.rs b/wgpu-types/src/features.rs index 1ac4116c6..d2142598a 100644 --- a/wgpu-types/src/features.rs +++ b/wgpu-types/src/features.rs @@ -59,6 +59,9 @@ mod webgpu_impl { #[doc(hidden)] pub const WEBGPU_FEATURE_DUAL_SOURCE_BLENDING: u64 = 1 << 13; + + #[doc(hidden)] + pub const WEBGPU_FEATURE_CLIP_DISTANCES: u64 = 1 << 14; } macro_rules! bitflags_array_impl { @@ -1463,7 +1466,18 @@ bitflags_array! { /// - Metal (with MSL 1.2+) /// - Vulkan (with dualSrcBlend) /// - DX12 + /// + /// This is a web and native feature. const DUAL_SOURCE_BLENDING = WEBGPU_FEATURE_DUAL_SOURCE_BLENDING; + + /// Allows the use of `@builtin(clip_distances)` in WGSL. + /// + /// Supported platforms: + /// - Vulkan (mainly on Desktop GPUs) + /// - GL (Desktop or `GL_EXT_clip_cull_distance`) + /// + /// This is a web and native feature. + const CLIP_DISTANCES = WEBGPU_FEATURE_CLIP_DISTANCES; } } diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index a61ef7bb7..1755cd335 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -712,7 +712,7 @@ fn map_map_mode(mode: crate::MapMode) -> u32 { } } -const FEATURES_MAPPING: [(wgt::Features, webgpu_sys::GpuFeatureName); 14] = [ +const FEATURES_MAPPING: [(wgt::Features, webgpu_sys::GpuFeatureName); 15] = [ ( wgt::Features::DEPTH_CLIP_CONTROL, webgpu_sys::GpuFeatureName::DepthClipControl, @@ -769,6 +769,10 @@ const FEATURES_MAPPING: [(wgt::Features, webgpu_sys::GpuFeatureName); 14] = [ wgt::Features::DUAL_SOURCE_BLENDING, webgpu_sys::GpuFeatureName::DualSourceBlending, ), + ( + wgt::Features::CLIP_DISTANCES, + webgpu_sys::GpuFeatureName::ClipDistances, + ), ]; fn map_wgt_features(supported_features: webgpu_sys::GpuSupportedFeatures) -> wgt::Features {