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:
Dmitry Zamkov 2025-06-16 03:33:31 -05:00 committed by GitHub
parent 486a77d682
commit bbb7cc79ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 518 additions and 86 deletions

View File

@ -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

View File

@ -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,
})
}

View File

@ -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.
///

View File

@ -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

View File

@ -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> {

View File

@ -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,
}
}

View File

@ -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" => {

View File

@ -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,
},

View File

@ -0,0 +1,5 @@
god_mode = true
targets = "SPIRV | GLSL | WGSL"
[glsl]
version.Desktop = 330

View 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;
}

View File

@ -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
}
}

View 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;
}

View 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

View File

@ -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>,

View 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;
}

View File

@ -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>,

View 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);
}
";

View File

@ -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;

View File

@ -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),

View File

@ -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))

View File

@ -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>(

View File

@ -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,
}))
}
}

View File

@ -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)]

View File

@ -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) };
}
}
}
}
}

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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 {