(Naga) Implement OpImageGather and OpImageDrefGather when ingesting SPIR-V (#8280)

This commit is contained in:
Nils Hasenbanck 2025-09-29 19:15:32 +02:00 committed by GitHub
parent ea80c7dbc3
commit 8c4aebc0c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 284 additions and 6 deletions

View File

@ -201,6 +201,7 @@ By @cwfitzgerald in [#8162](https://github.com/gfx-rs/wgpu/pull/8162).
- 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).
- 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).
- 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).
- naga implement OpImageGather and OpImageDrefGather operations when ingesting SPIR-V. By @hasenbanck in [8280](https://github.com/gfx-rs/wgpu/pull/8280).
#### DX12

View File

@ -48,6 +48,8 @@ pub struct SamplingOptions {
pub project: bool,
/// Depth comparison sampling with a reference value.
pub compare: bool,
/// Gather sampling: Operates on four samples of one channel.
pub gather: bool,
}
enum ExtraCoordinate {
@ -500,10 +502,10 @@ impl<I: Iterator<Item = u32>> super::Frontend<I> {
let result_id = self.next()?;
let sampled_image_id = self.next()?;
let coordinate_id = self.next()?;
let dref_id = if options.compare {
Some(self.next()?)
} else {
None
let (component_id, dref_id) = match (options.gather, options.compare) {
(true, false) => (Some(self.next()?), None),
(_, true) => (None, Some(self.next()?)),
(_, _) => (None, None),
};
let span = self.span_from_with_op(start);
@ -629,6 +631,58 @@ impl<I: Iterator<Item = u32>> super::Frontend<I> {
self.get_expr_handle(coordinate_id, coord_lexp, ctx, emitter, block, body_idx);
let coord_type_handle = self.lookup_type.lookup(coord_lexp.type_id)?.handle;
let gather = match (options.gather, component_id) {
(true, Some(component_id)) => {
let component_lexp = self.lookup_expression.lookup(component_id)?;
let component_value = match ctx.expressions[component_lexp.handle] {
// VUID-StandaloneSpirv-OpImageGather-04664:
// The “Component” operand of OpImageGather, and OpImageSparseGather must be the
// <id> of a constant instruction.
crate::Expression::Constant(const_handle) => {
let constant = &ctx.module.constants[const_handle];
match ctx.module.global_expressions[constant.init] {
// SPIR-V specification: "It must be a 32-bit integer type scalar."
crate::Expression::Literal(crate::Literal::U32(value)) => value,
crate::Expression::Literal(crate::Literal::I32(value)) => value as u32,
_ => {
log::error!(
"Image gather component constant must be a 32-bit integer literal"
);
return Err(Error::InvalidOperand);
}
}
}
_ => {
log::error!("Image gather component must be a constant");
return Err(Error::InvalidOperand);
}
};
debug_assert_eq!(level, crate::SampleLevel::Auto);
level = crate::SampleLevel::Zero;
// SPIR-V specification: "Behavior is undefined if its value is not 0, 1, 2 or 3."
match component_value {
0 => Some(crate::SwizzleComponent::X),
1 => Some(crate::SwizzleComponent::Y),
2 => Some(crate::SwizzleComponent::Z),
3 => Some(crate::SwizzleComponent::W),
other => {
log::error!("Invalid gather component operand: {other}");
return Err(Error::InvalidOperand);
}
}
}
(true, None) => {
debug_assert_eq!(level, crate::SampleLevel::Auto);
level = crate::SampleLevel::Zero;
Some(crate::SwizzleComponent::X)
}
(_, _) => None,
};
let sampling_bit = if options.compare {
SamplingFlags::COMPARISON
} else {
@ -745,7 +799,7 @@ impl<I: Iterator<Item = u32>> super::Frontend<I> {
let expr = crate::Expression::ImageSample {
image: si_lexp.image,
sampler: si_lexp.sampler,
gather: None, //TODO
gather,
coordinate,
array_index,
offset,

View File

@ -2802,6 +2802,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
let options = image::SamplingOptions {
compare: false,
project: false,
gather: false,
};
self.parse_image_sample(
extra,
@ -2818,6 +2819,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
let options = image::SamplingOptions {
compare: false,
project: true,
gather: false,
};
self.parse_image_sample(
extra,
@ -2834,6 +2836,7 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
let options = image::SamplingOptions {
compare: true,
project: false,
gather: false,
};
self.parse_image_sample(
extra,
@ -2850,6 +2853,41 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
let options = image::SamplingOptions {
compare: true,
project: true,
gather: false,
};
self.parse_image_sample(
extra,
options,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageGather => {
let extra = inst.expect_at_least(6)?;
let options = image::SamplingOptions {
compare: false,
project: false,
gather: true,
};
self.parse_image_sample(
extra,
options,
ctx,
&mut emitter,
&mut block,
block_id,
body_idx,
)?;
}
Op::ImageDrefGather => {
let extra = inst.expect_at_least(6)?;
let options = image::SamplingOptions {
compare: true,
project: false,
gather: true,
};
self.parse_image_sample(
extra,

View File

@ -0,0 +1,18 @@
// Compiled with:
// slangc -target spirv -profile spirv_1_5 -o naga/tests/in/spv/gather-cmp.spv naga/tests/in/spv/gather-cmp.slang
// Disassembled with:
// spirv-dis naga/tests/in/spv/gather-cmp.spv -o naga/tests/in/spv/gather-cmp.spvasm
#language slang 2026
[[vk::binding(0, 0)]] var texture: Texture2D;
[[vk::binding(1, 0)]] var depth_sampler: SamplerComparisonState;
struct VertexOutput {
var texture_coordinates: float2;
};
[[shader("pixel")]]
func main(input: VertexOutput) -> float4 {
return texture.GatherCmp(depth_sampler, input.texture_coordinates, 0.5);
}

View File

@ -0,0 +1,49 @@
; SPIR-V
; Version: 1.5
; Generator: Khronos Slang Compiler; 0
; Bound: 27
; Schema: 0
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %texture %depth_sampler %entryPointParam_main %input_texture_coordinates
OpExecutionMode %main OriginUpperLeft
OpSource Slang 1
OpName %input_texture_coordinates "input.texture_coordinates"
OpName %texture "texture"
OpName %depth_sampler "depth_sampler"
OpName %sampledImage "sampledImage"
OpName %entryPointParam_main "entryPointParam_main"
OpName %main "main"
OpDecorate %input_texture_coordinates Location 0
OpDecorate %texture Binding 0
OpDecorate %texture DescriptorSet 0
OpDecorate %depth_sampler Binding 1
OpDecorate %depth_sampler DescriptorSet 0
OpDecorate %entryPointParam_main Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%_ptr_Input_v2float = OpTypePointer Input %v2float
%10 = OpTypeImage %float 2D 2 0 0 1 Unknown
%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
%14 = OpTypeSampler
%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
%18 = OpTypeSampledImage %10
%v4float = OpTypeVector %float 4
%float_0_5 = OpConstant %float 0.5
%_ptr_Output_v4float = OpTypePointer Output %v4float
%input_texture_coordinates = OpVariable %_ptr_Input_v2float Input
%texture = OpVariable %_ptr_UniformConstant_10 UniformConstant
%depth_sampler = OpVariable %_ptr_UniformConstant_14 UniformConstant
%entryPointParam_main = OpVariable %_ptr_Output_v4float Output
%main = OpFunction %void None %3
%4 = OpLabel
%7 = OpLoad %v2float %input_texture_coordinates
%11 = OpLoad %10 %texture
%15 = OpLoad %14 %depth_sampler
%sampledImage = OpSampledImage %18 %11 %15
%21 = OpImageDrefGather %v4float %sampledImage %7 %float_0_5
OpStore %entryPointParam_main %21
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,4 @@
god_mode = true
[spv-in]
adjust_coordinate_space = true

View File

@ -0,0 +1,18 @@
// Compiled with:
// slangc -target spirv -profile spirv_1_5 -o naga/tests/in/spv/gather.spv naga/tests/in/spv/gather.slang
// Disassembled with:
// spirv-dis naga/tests/in/spv/gather.spv -o naga/tests/in/spv/gather.spvasm
#language slang 2026
[[vk::binding(0, 0)]] var texture: Texture2D;
[[vk::binding(1, 0)]] var linear_sampler: SamplerState;
struct VertexOutput {
var texture_coordinates: float2;
};
[[shader("pixel")]]
func main(input: VertexOutput) -> float4 {
return texture.GatherGreen(linear_sampler, input.texture_coordinates);
}

View File

@ -0,0 +1,50 @@
; SPIR-V
; Version: 1.5
; Generator: Khronos Slang Compiler; 0
; Bound: 28
; Schema: 0
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %texture %linear_sampler %entryPointParam_main %input_texture_coordinates
OpExecutionMode %main OriginUpperLeft
OpSource Slang 1
OpName %input_texture_coordinates "input.texture_coordinates"
OpName %texture "texture"
OpName %linear_sampler "linear_sampler"
OpName %sampledImage "sampledImage"
OpName %entryPointParam_main "entryPointParam_main"
OpName %main "main"
OpDecorate %input_texture_coordinates Location 0
OpDecorate %texture Binding 0
OpDecorate %texture DescriptorSet 0
OpDecorate %linear_sampler Binding 1
OpDecorate %linear_sampler DescriptorSet 0
OpDecorate %entryPointParam_main Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%_ptr_Input_v2float = OpTypePointer Input %v2float
%10 = OpTypeImage %float 2D 2 0 0 1 Unknown
%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
%14 = OpTypeSampler
%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
%18 = OpTypeSampledImage %10
%v4float = OpTypeVector %float 4
%int = OpTypeInt 32 1
%int_1 = OpConstant %int 1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%input_texture_coordinates = OpVariable %_ptr_Input_v2float Input
%texture = OpVariable %_ptr_UniformConstant_10 UniformConstant
%linear_sampler = OpVariable %_ptr_UniformConstant_14 UniformConstant
%entryPointParam_main = OpVariable %_ptr_Output_v4float Output
%main = OpFunction %void None %3
%4 = OpLabel
%7 = OpLoad %v2float %input_texture_coordinates
%11 = OpLoad %10 %texture
%15 = OpLoad %14 %linear_sampler
%sampledImage = OpSampledImage %18 %11 %15
%21 = OpImageGather %v4float %sampledImage %7 %int_1
OpStore %entryPointParam_main %21
OpReturn
OpFunctionEnd

View File

@ -0,0 +1,4 @@
god_mode = true
[spv-in]
adjust_coordinate_space = true

View File

@ -0,0 +1,21 @@
var<private> inputtexture_coordinates_1: vec2<f32>;
@group(0) @binding(0)
var texture: texture_depth_2d;
@group(0) @binding(1)
var depth_sampler: sampler_comparison;
var<private> entryPointParam_main: vec4<f32>;
fn main_1() {
let _e5 = inputtexture_coordinates_1;
let _e6 = textureGatherCompare(texture, depth_sampler, _e5, 0.5f);
entryPointParam_main = _e6;
return;
}
@fragment
fn main(@location(0) inputtexture_coordinates: vec2<f32>) -> @location(0) vec4<f32> {
inputtexture_coordinates_1 = inputtexture_coordinates;
main_1();
let _e3 = entryPointParam_main;
return _e3;
}

View File

@ -0,0 +1,21 @@
var<private> inputtexture_coordinates_1: vec2<f32>;
@group(0) @binding(0)
var texture: texture_2d<f32>;
@group(0) @binding(1)
var linear_sampler: sampler;
var<private> entryPointParam_main: vec4<f32>;
fn main_1() {
let _e4 = inputtexture_coordinates_1;
let _e5 = textureGather(1, texture, linear_sampler, _e4);
entryPointParam_main = _e5;
return;
}
@fragment
fn main(@location(0) inputtexture_coordinates: vec2<f32>) -> @location(0) vec4<f32> {
inputtexture_coordinates_1 = inputtexture_coordinates;
main_1();
let _e3 = entryPointParam_main;
return _e3;
}