mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-12-08 21:26:17 +00:00
Implement clip-distances extension for GL and Vulkan backends (#7730)
* Basic implementation of `clip_distances` for Vulkan and GL backends * Added GPU test for `clip-distances` * Update feature array size * Add changelog entry * Validate `clip_distances` array size * Check for `clip_distances` enable directive * Consolidate code for generating `enable` directives in WGSL backend and add `clip_distances`.
This commit is contained in:
parent
486a77d682
commit
bbb7cc79ef
@ -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
|
||||
|
||||
|
||||
@ -377,6 +377,8 @@ pub struct ReflectionInfo {
|
||||
pub varying: crate::FastHashMap<String, VaryingLocation>,
|
||||
/// List of push constant items in the shader.
|
||||
pub push_constant_items: Vec<PushConstantItem>,
|
||||
/// 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<core::num::NonZeroU32>,
|
||||
/// Mapping of varying variables to their location. Needed for reflections.
|
||||
varying: crate::FastHashMap<String, VaryingLocation>,
|
||||
/// 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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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<W: Write> Writer<W> {
|
||||
|
||||
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<W: Write> Writer<W> {
|
||||
/// 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<W: Write> Writer<W> {
|
||||
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.
|
||||
///
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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<Self> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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" => {
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
5
naga/tests/in/wgsl/clip-distances.toml
Normal file
5
naga/tests/in/wgsl/clip-distances.toml
Normal file
@ -0,0 +1,5 @@
|
||||
god_mode = true
|
||||
targets = "SPIRV | GLSL | WGSL"
|
||||
|
||||
[glsl]
|
||||
version.Desktop = 330
|
||||
12
naga/tests/in/wgsl/clip-distances.wgsl
Normal file
12
naga/tests/in/wgsl/clip-distances.wgsl
Normal file
@ -0,0 +1,12 @@
|
||||
enable clip_distances;
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@builtin(clip_distances) clip_distances: array<f32, 1>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn main() -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_distances[0] = 0.5;
|
||||
return out;
|
||||
}
|
||||
@ -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<f32, 8>,
|
||||
}
|
||||
|
||||
@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<f32, 8> {
|
||||
var out: array<f32, 8>;
|
||||
return out;
|
||||
}
|
||||
"#,
|
||||
r###"error: the `clip_distances` enable extension is not enabled
|
||||
┌─ wgsl:3:38
|
||||
│
|
||||
3 │ fn vs_main() -> @builtin(clip_distances) array<f32, 8> {
|
||||
│ ^^^^^^^^^^^^^^ 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<f32, 9>,
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
}
|
||||
|
||||
17
naga/tests/out/glsl/wgsl-clip-distances.main.Vertex.glsl
Normal file
17
naga/tests/out/glsl/wgsl-clip-distances.main.Vertex.glsl
Normal file
@ -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;
|
||||
}
|
||||
|
||||
46
naga/tests/out/spv/wgsl-clip-distances.spvasm
Normal file
46
naga/tests/out/spv/wgsl-clip-distances.spvasm
Normal file
@ -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
|
||||
@ -1,4 +1,5 @@
|
||||
enable dual_source_blending;
|
||||
|
||||
struct FragmentOutput {
|
||||
@location(0) @blend_src(0) output0_: vec4<f32>,
|
||||
@location(0) @blend_src(1) output1_: vec4<f32>,
|
||||
|
||||
15
naga/tests/out/wgsl/wgsl-clip-distances.wgsl
Normal file
15
naga/tests/out/wgsl/wgsl-clip-distances.wgsl
Normal file
@ -0,0 +1,15 @@
|
||||
enable clip_distances;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@builtin(clip_distances) clip_distances: array<f32, 1>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn main() -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
out.clip_distances[0] = 0.5f;
|
||||
let _e4 = out;
|
||||
return _e4;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
enable dual_source_blending;
|
||||
|
||||
struct FragmentOutput {
|
||||
@location(0) @blend_src(0) output0_: vec4<f32>,
|
||||
@location(0) @blend_src(1) output1_: vec4<f32>,
|
||||
|
||||
152
tests/tests/wgpu-gpu/clip_distances.rs
Normal file
152
tests/tests/wgpu-gpu/clip_distances.rs
Normal file
@ -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<f32, 2>,
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
";
|
||||
@ -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;
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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<glow::Query>,
|
||||
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>(
|
||||
|
||||
@ -23,6 +23,7 @@ struct CompilationContext<'a> {
|
||||
name_binding_map: &'a mut NameBindingMap,
|
||||
push_constant_items: &'a mut Vec<naga::back::glsl::PushConstantItem>,
|
||||
multiview: Option<NonZeroU32>,
|
||||
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,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -669,6 +669,7 @@ struct PipelineInner {
|
||||
sampler_map: SamplerBindMap,
|
||||
first_instance_location: Option<glow::UniformLocation>,
|
||||
push_constant_descs: ArrayVec<PushConstantDesc, MAX_PUSH_CONSTANT_COMMANDS>,
|
||||
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)]
|
||||
|
||||
@ -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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user