Skip to content

Commit 8c4aebc

Browse files
authored
(Naga) Implement OpImageGather and OpImageDrefGather when ingesting SPIR-V (#8280)
1 parent ea80c7d commit 8c4aebc

File tree

11 files changed

+284
-6
lines changed

11 files changed

+284
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162).
201201
- The [`source()`](https://doc.rust-lang.org/std/error/trait.Error.html#method.source) method of `ShaderError` no longer reports the error as its own source. By @andyleiserson in [#8258](https://github.com/gfx-rs/wgpu/pull/8258).
202202
- naga correctly ingests SPIR-V that use descriptor runtime indexing, which in turn is correctly converted into WGSLs binding array. By @hasenbanck in [8256](https://github.com/gfx-rs/wgpu/pull/8256).
203203
- naga correctly ingests SPIR-V that loads from multi-sampled textures, which in turn is correctly converted into WGSLs texture_multisampled_2d and load operations. By @hasenbanck in [8270](https://github.com/gfx-rs/wgpu/pull/8270).
204-
204+
- naga implement OpImageGather and OpImageDrefGather operations when ingesting SPIR-V. By @hasenbanck in [8280](https://github.com/gfx-rs/wgpu/pull/8280).
205+
205206
#### DX12
206207

207208
- Allow disabling waiting for latency waitable object. By @marcpabst in [#7400](https://github.com/gfx-rs/wgpu/pull/7400)

naga/src/front/spv/image.rs

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub struct SamplingOptions {
4848
pub project: bool,
4949
/// Depth comparison sampling with a reference value.
5050
pub compare: bool,
51+
/// Gather sampling: Operates on four samples of one channel.
52+
pub gather: bool,
5153
}
5254

5355
enum ExtraCoordinate {
@@ -500,10 +502,10 @@ impl<I: Iterator<Item = u32>> super::Frontend<I> {
500502
let result_id = self.next()?;
501503
let sampled_image_id = self.next()?;
502504
let coordinate_id = self.next()?;
503-
let dref_id = if options.compare {
504-
Some(self.next()?)
505-
} else {
506-
None
505+
let (component_id, dref_id) = match (options.gather, options.compare) {
506+
(true, false) => (Some(self.next()?), None),
507+
(_, true) => (None, Some(self.next()?)),
508+
(_, _) => (None, None),
507509
};
508510
let span = self.span_from_with_op(start);
509511

@@ -629,6 +631,58 @@ impl<I: Iterator<Item = u32>> super::Frontend<I> {
629631
self.get_expr_handle(coordinate_id, coord_lexp, ctx, emitter, block, body_idx);
630632
let coord_type_handle = self.lookup_type.lookup(coord_lexp.type_id)?.handle;
631633

634+
let gather = match (options.gather, component_id) {
635+
(true, Some(component_id)) => {
636+
let component_lexp = self.lookup_expression.lookup(component_id)?;
637+
638+
let component_value = match ctx.expressions[component_lexp.handle] {
639+
// VUID-StandaloneSpirv-OpImageGather-04664:
640+
// The “Component” operand of OpImageGather, and OpImageSparseGather must be the
641+
// <id> of a constant instruction.
642+
crate::Expression::Constant(const_handle) => {
643+
let constant = &ctx.module.constants[const_handle];
644+
match ctx.module.global_expressions[constant.init] {
645+
// SPIR-V specification: "It must be a 32-bit integer type scalar."
646+
crate::Expression::Literal(crate::Literal::U32(value)) => value,
647+
crate::Expression::Literal(crate::Literal::I32(value)) => value as u32,
648+
_ => {
649+
log::error!(
650+
"Image gather component constant must be a 32-bit integer literal"
651+
);
652+
return Err(Error::InvalidOperand);
653+
}
654+
}
655+
}
656+
_ => {
657+
log::error!("Image gather component must be a constant");
658+
return Err(Error::InvalidOperand);
659+
}
660+
};
661+
662+
debug_assert_eq!(level, crate::SampleLevel::Auto);
663+
level = crate::SampleLevel::Zero;
664+
665+
// SPIR-V specification: "Behavior is undefined if its value is not 0, 1, 2 or 3."
666+
match component_value {
667+
0 => Some(crate::SwizzleComponent::X),
668+
1 => Some(crate::SwizzleComponent::Y),
669+
2 => Some(crate::SwizzleComponent::Z),
670+
3 => Some(crate::SwizzleComponent::W),
671+
other => {
672+
log::error!("Invalid gather component operand: {other}");
673+
return Err(Error::InvalidOperand);
674+
}
675+
}
676+
}
677+
(true, None) => {
678+
debug_assert_eq!(level, crate::SampleLevel::Auto);
679+
level = crate::SampleLevel::Zero;
680+
681+
Some(crate::SwizzleComponent::X)
682+
}
683+
(_, _) => None,
684+
};
685+
632686
let sampling_bit = if options.compare {
633687
SamplingFlags::COMPARISON
634688
} else {
@@ -745,7 +799,7 @@ impl<I: Iterator<Item = u32>> super::Frontend<I> {
745799
let expr = crate::Expression::ImageSample {
746800
image: si_lexp.image,
747801
sampler: si_lexp.sampler,
748-
gather: None, //TODO
802+
gather,
749803
coordinate,
750804
array_index,
751805
offset,

naga/src/front/spv/mod.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2802,6 +2802,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
28022802
let options = image::SamplingOptions {
28032803
compare: false,
28042804
project: false,
2805+
gather: false,
28052806
};
28062807
self.parse_image_sample(
28072808
extra,
@@ -2818,6 +2819,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
28182819
let options = image::SamplingOptions {
28192820
compare: false,
28202821
project: true,
2822+
gather: false,
28212823
};
28222824
self.parse_image_sample(
28232825
extra,
@@ -2834,6 +2836,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
28342836
let options = image::SamplingOptions {
28352837
compare: true,
28362838
project: false,
2839+
gather: false,
28372840
};
28382841
self.parse_image_sample(
28392842
extra,
@@ -2850,6 +2853,41 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
28502853
let options = image::SamplingOptions {
28512854
compare: true,
28522855
project: true,
2856+
gather: false,
2857+
};
2858+
self.parse_image_sample(
2859+
extra,
2860+
options,
2861+
ctx,
2862+
&mut emitter,
2863+
&mut block,
2864+
block_id,
2865+
body_idx,
2866+
)?;
2867+
}
2868+
Op::ImageGather => {
2869+
let extra = inst.expect_at_least(6)?;
2870+
let options = image::SamplingOptions {
2871+
compare: false,
2872+
project: false,
2873+
gather: true,
2874+
};
2875+
self.parse_image_sample(
2876+
extra,
2877+
options,
2878+
ctx,
2879+
&mut emitter,
2880+
&mut block,
2881+
block_id,
2882+
body_idx,
2883+
)?;
2884+
}
2885+
Op::ImageDrefGather => {
2886+
let extra = inst.expect_at_least(6)?;
2887+
let options = image::SamplingOptions {
2888+
compare: true,
2889+
project: false,
2890+
gather: true,
28532891
};
28542892
self.parse_image_sample(
28552893
extra,

naga/tests/in/spv/gather-cmp.slang

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Compiled with:
2+
// slangc -target spirv -profile spirv_1_5 -o naga/tests/in/spv/gather-cmp.spv naga/tests/in/spv/gather-cmp.slang
3+
// Disassembled with:
4+
// spirv-dis naga/tests/in/spv/gather-cmp.spv -o naga/tests/in/spv/gather-cmp.spvasm
5+
6+
#language slang 2026
7+
8+
[[vk::binding(0, 0)]] var texture: Texture2D;
9+
[[vk::binding(1, 0)]] var depth_sampler: SamplerComparisonState;
10+
11+
struct VertexOutput {
12+
var texture_coordinates: float2;
13+
};
14+
15+
[[shader("pixel")]]
16+
func main(input: VertexOutput) -> float4 {
17+
return texture.GatherCmp(depth_sampler, input.texture_coordinates, 0.5);
18+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
; SPIR-V
2+
; Version: 1.5
3+
; Generator: Khronos Slang Compiler; 0
4+
; Bound: 27
5+
; Schema: 0
6+
OpCapability Shader
7+
OpMemoryModel Logical GLSL450
8+
OpEntryPoint Fragment %main "main" %texture %depth_sampler %entryPointParam_main %input_texture_coordinates
9+
OpExecutionMode %main OriginUpperLeft
10+
OpSource Slang 1
11+
OpName %input_texture_coordinates "input.texture_coordinates"
12+
OpName %texture "texture"
13+
OpName %depth_sampler "depth_sampler"
14+
OpName %sampledImage "sampledImage"
15+
OpName %entryPointParam_main "entryPointParam_main"
16+
OpName %main "main"
17+
OpDecorate %input_texture_coordinates Location 0
18+
OpDecorate %texture Binding 0
19+
OpDecorate %texture DescriptorSet 0
20+
OpDecorate %depth_sampler Binding 1
21+
OpDecorate %depth_sampler DescriptorSet 0
22+
OpDecorate %entryPointParam_main Location 0
23+
%void = OpTypeVoid
24+
%3 = OpTypeFunction %void
25+
%float = OpTypeFloat 32
26+
%v2float = OpTypeVector %float 2
27+
%_ptr_Input_v2float = OpTypePointer Input %v2float
28+
%10 = OpTypeImage %float 2D 2 0 0 1 Unknown
29+
%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
30+
%14 = OpTypeSampler
31+
%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
32+
%18 = OpTypeSampledImage %10
33+
%v4float = OpTypeVector %float 4
34+
%float_0_5 = OpConstant %float 0.5
35+
%_ptr_Output_v4float = OpTypePointer Output %v4float
36+
%input_texture_coordinates = OpVariable %_ptr_Input_v2float Input
37+
%texture = OpVariable %_ptr_UniformConstant_10 UniformConstant
38+
%depth_sampler = OpVariable %_ptr_UniformConstant_14 UniformConstant
39+
%entryPointParam_main = OpVariable %_ptr_Output_v4float Output
40+
%main = OpFunction %void None %3
41+
%4 = OpLabel
42+
%7 = OpLoad %v2float %input_texture_coordinates
43+
%11 = OpLoad %10 %texture
44+
%15 = OpLoad %14 %depth_sampler
45+
%sampledImage = OpSampledImage %18 %11 %15
46+
%21 = OpImageDrefGather %v4float %sampledImage %7 %float_0_5
47+
OpStore %entryPointParam_main %21
48+
OpReturn
49+
OpFunctionEnd

naga/tests/in/spv/gather-cmp.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
god_mode = true
2+
3+
[spv-in]
4+
adjust_coordinate_space = true

naga/tests/in/spv/gather.slang

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Compiled with:
2+
// slangc -target spirv -profile spirv_1_5 -o naga/tests/in/spv/gather.spv naga/tests/in/spv/gather.slang
3+
// Disassembled with:
4+
// spirv-dis naga/tests/in/spv/gather.spv -o naga/tests/in/spv/gather.spvasm
5+
6+
#language slang 2026
7+
8+
[[vk::binding(0, 0)]] var texture: Texture2D;
9+
[[vk::binding(1, 0)]] var linear_sampler: SamplerState;
10+
11+
struct VertexOutput {
12+
var texture_coordinates: float2;
13+
};
14+
15+
[[shader("pixel")]]
16+
func main(input: VertexOutput) -> float4 {
17+
return texture.GatherGreen(linear_sampler, input.texture_coordinates);
18+
}

naga/tests/in/spv/gather.spvasm

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
; SPIR-V
2+
; Version: 1.5
3+
; Generator: Khronos Slang Compiler; 0
4+
; Bound: 28
5+
; Schema: 0
6+
OpCapability Shader
7+
OpMemoryModel Logical GLSL450
8+
OpEntryPoint Fragment %main "main" %texture %linear_sampler %entryPointParam_main %input_texture_coordinates
9+
OpExecutionMode %main OriginUpperLeft
10+
OpSource Slang 1
11+
OpName %input_texture_coordinates "input.texture_coordinates"
12+
OpName %texture "texture"
13+
OpName %linear_sampler "linear_sampler"
14+
OpName %sampledImage "sampledImage"
15+
OpName %entryPointParam_main "entryPointParam_main"
16+
OpName %main "main"
17+
OpDecorate %input_texture_coordinates Location 0
18+
OpDecorate %texture Binding 0
19+
OpDecorate %texture DescriptorSet 0
20+
OpDecorate %linear_sampler Binding 1
21+
OpDecorate %linear_sampler DescriptorSet 0
22+
OpDecorate %entryPointParam_main Location 0
23+
%void = OpTypeVoid
24+
%3 = OpTypeFunction %void
25+
%float = OpTypeFloat 32
26+
%v2float = OpTypeVector %float 2
27+
%_ptr_Input_v2float = OpTypePointer Input %v2float
28+
%10 = OpTypeImage %float 2D 2 0 0 1 Unknown
29+
%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
30+
%14 = OpTypeSampler
31+
%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
32+
%18 = OpTypeSampledImage %10
33+
%v4float = OpTypeVector %float 4
34+
%int = OpTypeInt 32 1
35+
%int_1 = OpConstant %int 1
36+
%_ptr_Output_v4float = OpTypePointer Output %v4float
37+
%input_texture_coordinates = OpVariable %_ptr_Input_v2float Input
38+
%texture = OpVariable %_ptr_UniformConstant_10 UniformConstant
39+
%linear_sampler = OpVariable %_ptr_UniformConstant_14 UniformConstant
40+
%entryPointParam_main = OpVariable %_ptr_Output_v4float Output
41+
%main = OpFunction %void None %3
42+
%4 = OpLabel
43+
%7 = OpLoad %v2float %input_texture_coordinates
44+
%11 = OpLoad %10 %texture
45+
%15 = OpLoad %14 %linear_sampler
46+
%sampledImage = OpSampledImage %18 %11 %15
47+
%21 = OpImageGather %v4float %sampledImage %7 %int_1
48+
OpStore %entryPointParam_main %21
49+
OpReturn
50+
OpFunctionEnd

naga/tests/in/spv/gather.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
god_mode = true
2+
3+
[spv-in]
4+
adjust_coordinate_space = true
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
var<private> inputtexture_coordinates_1: vec2<f32>;
2+
@group(0) @binding(0)
3+
var texture: texture_depth_2d;
4+
@group(0) @binding(1)
5+
var depth_sampler: sampler_comparison;
6+
var<private> entryPointParam_main: vec4<f32>;
7+
8+
fn main_1() {
9+
let _e5 = inputtexture_coordinates_1;
10+
let _e6 = textureGatherCompare(texture, depth_sampler, _e5, 0.5f);
11+
entryPointParam_main = _e6;
12+
return;
13+
}
14+
15+
@fragment
16+
fn main(@location(0) inputtexture_coordinates: vec2<f32>) -> @location(0) vec4<f32> {
17+
inputtexture_coordinates_1 = inputtexture_coordinates;
18+
main_1();
19+
let _e3 = entryPointParam_main;
20+
return _e3;
21+
}

0 commit comments

Comments
 (0)