Skip to content

Implement clip-distances extension for GL and Vulkan backends #7730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,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 support for clip-distances feature for Vulkan and GL backends. By @dzamkov in [#7730](https://github.com/gfx-rs/wgpu/pull/7730)

#### Naga

Expand Down
70 changes: 46 additions & 24 deletions naga/src/back/glsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1596,31 +1601,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(());
}
Expand Down Expand Up @@ -5006,6 +5027,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,
})
}

Expand Down
2 changes: 1 addition & 1 deletion naga/src/common/wgsl/to_wgsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions naga/src/front/wgsl/parse/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub fn map_built_in(word: &str, span: Span) -> Result<'_, crate::BuiltIn> {
"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,
Expand Down
5 changes: 5 additions & 0 deletions naga/tests/in/wgsl/clip-distances.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
god_mode = true
targets = "SPIRV | GLSL | WGSL"

[glsl]
version.Desktop = 330
11 changes: 11 additions & 0 deletions naga/tests/in/wgsl/clip-distances.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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;
}
17 changes: 17 additions & 0 deletions naga/tests/out/glsl/wgsl-clip-distances.main.Vertex.glsl
Original file line number Diff line number Diff line change
@@ -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 changes: 46 additions & 0 deletions naga/tests/out/spv/wgsl-clip-distances.spvasm
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions naga/tests/out/wgsl/wgsl-clip-distances.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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;
}
151 changes: 151 additions & 0 deletions tests/tests/wgpu-gpu/clip_distances.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
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 = "
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);
}
";
Loading