From 4c34d2736429bbc658c4d5ce511ce4e84a9b2743 Mon Sep 17 00:00:00 2001 From: Lucie Choi Date: Fri, 13 Feb 2026 20:28:27 -0800 Subject: [PATCH 1/2] Gather, CalculateLevelOfDetailUnclamped --- .../clang/include/clang/SPIRV/SpirvBuilder.h | 4 ++ tools/clang/lib/SPIRV/SpirvBuilder.cpp | 11 ++++- ...ampledtexture.calculate-lod-unclamped.hlsl | 21 ++++++++++ .../vk.sampledtexture.gather.hlsl | 42 +++++++++++++++++++ utils/hct/gen_intrin_main.txt | 4 ++ 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/vk.sampledtexture.calculate-lod-unclamped.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/vk.sampledtexture.gather.hlsl diff --git a/tools/clang/include/clang/SPIRV/SpirvBuilder.h b/tools/clang/include/clang/SPIRV/SpirvBuilder.h index 868c978c35..6d02c07866 100644 --- a/tools/clang/include/clang/SPIRV/SpirvBuilder.h +++ b/tools/clang/include/clang/SPIRV/SpirvBuilder.h @@ -339,6 +339,10 @@ class SpirvBuilder { /// \brief Creates SPIR-V instructions for gathering the given image. /// + /// If the of `image` is a sampled image, then that image will be gathered. + /// In this case, `sampler` must be `nullptr`. If `image` is not a sampled + /// image, a sampled image will be created by combining `image` and `sampler`. + /// /// If compareVal is given a non-null value, OpImageDrefGather or /// OpImageSparseDrefGather will be generated; otherwise, OpImageGather or /// OpImageSparseGather will be generated. diff --git a/tools/clang/lib/SPIRV/SpirvBuilder.cpp b/tools/clang/lib/SPIRV/SpirvBuilder.cpp index 7ce0508585..a85916d106 100644 --- a/tools/clang/lib/SPIRV/SpirvBuilder.cpp +++ b/tools/clang/lib/SPIRV/SpirvBuilder.cpp @@ -714,8 +714,15 @@ SpirvInstruction *SpirvBuilder::createImageGather( assert(insertPoint && "null insert point"); // An OpSampledImage is required to do the image sampling. - auto *sampledImage = - createSampledImage(imageType, image, sampler, loc, range); + // Skip creating OpSampledImage if the imageType is a sampled texture. + SpirvInstruction *sampledImage = nullptr; + if (isSampledTexture(imageType)) { + assert(!sampler && + "sampler must be null when sampling from a sampled texture"); + sampledImage = image; + } else { + sampledImage = createSampledImage(imageType, image, sampler, loc, range); + } // TODO: Update ImageGather to accept minLod if necessary. const auto mask = composeImageOperandsMask( diff --git a/tools/clang/test/CodeGenSPIRV/vk.sampledtexture.calculate-lod-unclamped.hlsl b/tools/clang/test/CodeGenSPIRV/vk.sampledtexture.calculate-lod-unclamped.hlsl new file mode 100644 index 0000000000..aebc57738b --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/vk.sampledtexture.calculate-lod-unclamped.hlsl @@ -0,0 +1,21 @@ +// RUN: %dxc -T ps_6_8 -E main -fcgl %s -spirv | FileCheck %s + +// CHECK: OpCapability ImageQuery + +vk::SampledTexture2D t1 : register(t0); + +// CHECK: %type_2d_image = OpTypeImage %float 2D 0 0 0 1 Unknown +// CHECK: %type_sampled_image = OpTypeSampledImage %type_2d_image +// CHECK: [[ptr:%[a-zA-Z0-9_]+]] = OpTypePointer UniformConstant %type_sampled_image + +// CHECK: %t1 = OpVariable [[ptr]] UniformConstant + +void main() { + float2 xy = float2(0.5, 0.5); + +//CHECK: [[tex1:%[a-zA-Z0-9_]+]] = OpLoad %type_sampled_image %t1 +//CHECK-NEXT: [[xy_load:%[a-zA-Z0-9_]+]] = OpLoad %v2float %xy +//CHECK-NEXT: [[query:%[a-zA-Z0-9_]+]] = OpImageQueryLod %v2float [[tex1]] [[xy_load]] +//CHECK-NEXT: {{%[0-9]+}} = OpCompositeExtract %float [[query]] 1 + float lod1 = t1.CalculateLevelOfDetailUnclamped(xy); +} \ No newline at end of file diff --git a/tools/clang/test/CodeGenSPIRV/vk.sampledtexture.gather.hlsl b/tools/clang/test/CodeGenSPIRV/vk.sampledtexture.gather.hlsl new file mode 100644 index 0000000000..f7a26c153a --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/vk.sampledtexture.gather.hlsl @@ -0,0 +1,42 @@ +// RUN: %dxc -T ps_6_7 -E main -fcgl %s -spirv | FileCheck %s + +// CHECK: OpCapability SparseResidency + +// CHECK: [[v2fc:%[0-9]+]] = OpConstantComposite %v2float %float_0_5 %float_0_25 +// CHECK: [[v2ic:%[0-9]+]] = OpConstantComposite %v2int %int_2 %int_3 + +// CHECK: [[type_2d_image_1:%[a-zA-Z0-9_]+]] = OpTypeImage %float 2D 0 0 0 1 Unknown +// CHECK: [[type_sampled_image_1:%[a-zA-Z0-9_]+]] = OpTypeSampledImage [[type_2d_image_1]] +// CHECK: [[ptr_type_1:%[a-zA-Z0-9_]+]] = OpTypePointer UniformConstant [[type_sampled_image_1]] + +// CHECK: [[type_2d_image_2:%[a-zA-Z0-9_]+]] = OpTypeImage %uint 2D 0 0 0 1 Unknown +// CHECK: [[type_sampled_image_2:%[a-zA-Z0-9_]+]] = OpTypeSampledImage [[type_2d_image_2]] +// CHECK: [[ptr_type_2:%[a-zA-Z0-9_]+]] = OpTypePointer UniformConstant [[type_sampled_image_2]] + +// CHECK: %SparseResidencyStruct = OpTypeStruct %uint %v4float + +// CHECK: [[tex1:%[a-zA-Z0-9_]+]] = OpVariable [[ptr_type_1]] UniformConstant +// CHECK: [[tex2:%[a-zA-Z0-9_]+]] = OpVariable [[ptr_type_2]] UniformConstant + +vk::SampledTexture2D tex1 : register(t1); +vk::SampledTexture2D tex2 : register(t2); + +float4 main() : SV_Target { + +// CHECK: [[tex1_load:%[a-zA-Z0-9_]+]] = OpLoad [[type_sampled_image_1]] [[tex1]] +// CHECK: [[val1:%[a-zA-Z0-9_]+]] = OpImageGather %v4float [[tex1_load]] [[v2fc]] %int_0 None + float4 val1 = tex1.Gather(float2(0.5, 0.25)); + +// CHECK: [[tex2_load:%[a-zA-Z0-9_]+]] = OpLoad [[type_sampled_image_2]] [[tex2]] +// CHECK: [[val2:%[a-zA-Z0-9_]+]] = OpImageGather %v4uint [[tex2_load]] [[v2fc]] %int_0 ConstOffset [[v2ic]] + uint4 val2 = tex2.Gather(float2(0.5, 0.25), int2(2, 3)); + +// CHECK: [[tex3_load:%[a-zA-Z0-9_]+]] = OpLoad [[type_sampled_image_1]] [[tex1]] +// CHECK: [[val3:%[a-zA-Z0-9_]+]] = OpImageSparseGather %SparseResidencyStruct [[tex3_load]] [[v2fc]] %int_0 ConstOffset [[v2ic]] +// CHECK: [[status_0:%[a-zA-Z0-9_]+]] = OpCompositeExtract %uint [[val3]] 0 +// CHECK: OpStore %status [[status_0]] + uint status; + float4 val3 = tex1.Gather(float2(0.5, 0.25), int2(2, 3), status); + + return 1.0; +} diff --git a/utils/hct/gen_intrin_main.txt b/utils/hct/gen_intrin_main.txt index 1a0d3b7a3b..4d5d5126c8 100644 --- a/utils/hct/gen_intrin_main.txt +++ b/utils/hct/gen_intrin_main.txt @@ -1239,4 +1239,8 @@ namespace VkSampledTexture2DMethods { $classT [[ro]] Sample(in float<2> x, in int<2> o, in float clamp) : tex2d_t_o_cl; $classT [[]] Sample(in float<2> x, in int<2> o, in float clamp, out uint_only status) : tex2d_t_o_cl_s; float [[ro]] CalculateLevelOfDetail(in float<2> x) : tex2d_t_calc_lod; + float [[ro]] CalculateLevelOfDetailUnclamped(in float<2> x) : tex2d_t_calc_lod_unclamped; + $match<0, -1> void<4> [[ro]] Gather(in float<2> x) : tex2d_t_gather; + $match<0, -1> void<4> [[ro]] Gather(in float<2> x, in int<2> o) : tex2d_t_gather_o; + $match<0, -1> void<4> [[]] Gather(in float<2> x, in int<2> o, out uint_only status) : tex2d_t_gather_o_s; } namespace From a6d6c73c500c9fa8c5bc44f96a6f6452422e7267 Mon Sep 17 00:00:00 2001 From: luciechoi Date: Sat, 17 Jan 2026 13:08:41 +0000 Subject: [PATCH 2/2] GetDimensions Implement `GetDimensions` based on new method. --- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 22 ++++- .../vk.sampledtexture.get-dimensions.hlsl | 94 +++++++++++++++++++ utils/hct/gen_intrin_main.txt | 4 + 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/vk.sampledtexture.get-dimensions.hlsl diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index f77e9ac82b..bfeab5f376 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -4328,7 +4328,18 @@ SpirvEmitter::processBufferTextureGetDimensions(const CXXMemberCallExpr *expr) { const Expr *mipLevel = nullptr, *numLevels = nullptr, *numSamples = nullptr; assert(isTexture(type) || isRWTexture(type) || isBuffer(type) || - isRWBuffer(type)); + isRWBuffer(type) || isSampledTexture(type)); + if (isSampledTexture(type)) { + LowerTypeVisitor lowerTypeVisitor(astContext, spvContext, spirvOptions, + spvBuilder); + const SpirvType *spvType = lowerTypeVisitor.lowerType( + type, SpirvLayoutRule::Void, llvm::None, expr->getExprLoc()); + // Get image type based on type, assuming type is a sampledimage type + const auto *sampledType = cast(spvType); + const SpirvType *imgType = sampledType->getImageType(); + objectInstr = spvBuilder.createUnaryOp(spv::Op::OpImage, imgType, + objectInstr, expr->getExprLoc()); + } // For Texture1D, arguments are either: // a) width @@ -4362,6 +4373,9 @@ SpirvEmitter::processBufferTextureGetDimensions(const CXXMemberCallExpr *expr) { // a) width, height, elements // b) MipLevel, width, height, elements, NumLevels + // SampledTexture types follow the same rules above, as + // this method doesn't require a Sampler argument. + // Note: SPIR-V Spec requires return type of OpImageQuerySize(Lod) to be a // scalar/vector of integers. SPIR-V Spec also requires return type of // OpImageQueryLevels and OpImageQuerySamples to be scalar integers. @@ -4379,6 +4393,7 @@ SpirvEmitter::processBufferTextureGetDimensions(const CXXMemberCallExpr *expr) { if ((typeName == "Texture1D" && numArgs > 1) || (typeName == "Texture2D" && numArgs > 2) || + (typeName == "SampledTexture2D" && numArgs > 2) || (typeName == "TextureCube" && numArgs > 2) || (typeName == "Texture3D" && numArgs > 3) || (typeName == "Texture1DArray" && numArgs > 2) || @@ -4417,7 +4432,7 @@ SpirvEmitter::processBufferTextureGetDimensions(const CXXMemberCallExpr *expr) { // Only Texture types use ImageQuerySizeLod. // TextureMS, RWTexture, Buffers, RWBuffers use ImageQuerySize. SpirvInstruction *lod = nullptr; - if (isTexture(type) && !numSamples) { + if ((isTexture(type) || isSampledTexture(type)) && !numSamples) { if (mipLevel) { // For Texture types when mipLevel argument is present. lod = doExpr(mipLevel, range); @@ -6433,7 +6448,8 @@ SpirvInstruction * SpirvEmitter::processGetDimensions(const CXXMemberCallExpr *expr) { const auto objectType = expr->getImplicitObjectArgument()->getType(); if (isTexture(objectType) || isRWTexture(objectType) || - isBuffer(objectType) || isRWBuffer(objectType)) { + isBuffer(objectType) || isRWBuffer(objectType) || + isSampledTexture(objectType)) { return processBufferTextureGetDimensions(expr); } else if (isByteAddressBuffer(objectType) || isRWByteAddressBuffer(objectType) || diff --git a/tools/clang/test/CodeGenSPIRV/vk.sampledtexture.get-dimensions.hlsl b/tools/clang/test/CodeGenSPIRV/vk.sampledtexture.get-dimensions.hlsl new file mode 100644 index 0000000000..cf98bd8649 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/vk.sampledtexture.get-dimensions.hlsl @@ -0,0 +1,94 @@ +// RUN: %dxc -T ps_6_0 -E main -fcgl %s -spirv | FileCheck %s +// RUN: not %dxc -T ps_6_0 -E main -fcgl %s -spirv -DERROR 2>&1 | FileCheck %s --check-prefix=ERROR + +// CHECK: OpCapability ImageQuery + +vk::SampledTexture2D t1; + +void main() { + uint mipLevel = 1; + uint width, height, numLevels; + +// CHECK: [[t1_load:%[0-9]+]] = OpLoad %type_sampled_image %t1 +// CHECK-NEXT: [[image1:%[0-9]+]] = OpImage %type_2d_image [[t1_load]] +// CHECK-NEXT: [[query1:%[0-9]+]] = OpImageQuerySizeLod %v2uint [[image1]] %int_0 +// CHECK-NEXT: [[query1_0:%[0-9]+]] = OpCompositeExtract %uint [[query1]] 0 +// CHECK-NEXT: OpStore %width [[query1_0]] +// CHECK-NEXT: [[query1_1:%[0-9]+]] = OpCompositeExtract %uint [[query1]] 1 +// CHECK-NEXT: OpStore %height [[query1_1]] + t1.GetDimensions(width, height); + +// CHECK: [[t1_load:%[0-9]+]] = OpLoad %type_sampled_image %t1 +// CHECK-NEXT: [[image2:%[0-9]+]] = OpImage %type_2d_image [[t1_load]] +// CHECK-NEXT: [[mip:%[0-9]+]] = OpLoad %uint %mipLevel +// CHECK-NEXT: [[query2:%[0-9]+]] = OpImageQuerySizeLod %v2uint [[image2]] [[mip]] +// CHECK-NEXT: [[query2_0:%[0-9]+]] = OpCompositeExtract %uint [[query2]] 0 +// CHECK-NEXT: OpStore %width [[query2_0]] +// CHECK-NEXT: [[query2_1:%[0-9]+]] = OpCompositeExtract %uint [[query2]] 1 +// CHECK-NEXT: OpStore %height [[query2_1]] +// CHECK-NEXT: [[query_level_2:%[0-9]+]] = OpImageQueryLevels %uint [[image2]] +// CHECK-NEXT: OpStore %numLevels [[query_level_2]] + t1.GetDimensions(mipLevel, width, height, numLevels); + + float f_width, f_height, f_numLevels; +// CHECK: [[t1_load:%[0-9]+]] = OpLoad %type_sampled_image %t1 +// CHECK-NEXT: [[image1:%[0-9]+]] = OpImage %type_2d_image [[t1_load]] +// CHECK-NEXT: [[query1:%[0-9]+]] = OpImageQuerySizeLod %v2uint [[image1]] %int_0 +// CHECK-NEXT: [[query1_0:%[0-9]+]] = OpCompositeExtract %uint [[query1]] 0 +// CHECK-NEXT: [[f_query1_0:%[0-9]+]] = OpConvertUToF %float [[query1_0]] +// CHECK-NEXT: OpStore %f_width [[f_query1_0]] +// CHECK-NEXT: [[query1_1:%[0-9]+]] = OpCompositeExtract %uint [[query1]] 1 +// CHECK-NEXT: [[f_query1_1:%[0-9]+]] = OpConvertUToF %float [[query1_1]] +// CHECK-NEXT: OpStore %f_height [[f_query1_1]] + t1.GetDimensions(f_width, f_height); + +// CHECK: [[t1_load:%[0-9]+]] = OpLoad %type_sampled_image %t1 +// CHECK-NEXT: [[image2:%[0-9]+]] = OpImage %type_2d_image [[t1_load]] +// CHECK-NEXT: [[mip:%[0-9]+]] = OpLoad %uint %mipLevel +// CHECK-NEXT: [[query2:%[0-9]+]] = OpImageQuerySizeLod %v2uint [[image2]] [[mip]] +// CHECK-NEXT: [[query2_0:%[0-9]+]] = OpCompositeExtract %uint [[query2]] 0 +// CHECK-NEXT: [[f_query2_0:%[0-9]+]] = OpConvertUToF %float [[query2_0]] +// CHECK-NEXT: OpStore %f_width [[f_query2_0]] +// CHECK-NEXT: [[query2_1:%[0-9]+]] = OpCompositeExtract %uint [[query2]] 1 +// CHECK-NEXT: [[f_query2_1:%[0-9]+]] = OpConvertUToF %float [[query2_1]] +// CHECK-NEXT: OpStore %f_height [[f_query2_1]] +// CHECK-NEXT: [[query_level_2:%[0-9]+]] = OpImageQueryLevels %uint [[image2]] +// CHECK-NEXT: [[f_query_level_2:%[0-9]+]] = OpConvertUToF %float [[query_level_2]] +// CHECK-NEXT: OpStore %f_numLevels [[f_query_level_2]] + t1.GetDimensions(mipLevel, f_width, f_height, f_numLevels); + + int i_width, i_height, i_numLevels; +// CHECK: [[t1_load:%[0-9]+]] = OpLoad %type_sampled_image %t1 +// CHECK-NEXT: [[image1:%[0-9]+]] = OpImage %type_2d_image [[t1_load]] +// CHECK-NEXT: [[query1:%[0-9]+]] = OpImageQuerySizeLod %v2uint [[image1]] %int_0 +// CHECK-NEXT: [[query1_0:%[0-9]+]] = OpCompositeExtract %uint [[query1]] 0 +// CHECK-NEXT: [[query_0_int:%[0-9]+]] = OpBitcast %int [[query1_0]] +// CHECK-NEXT: OpStore %i_width [[query_0_int]] +// CHECK-NEXT: [[query1_1:%[0-9]+]] = OpCompositeExtract %uint [[query1]] 1 +// CHECK-NEXT: [[query_1_int:%[0-9]+]] = OpBitcast %int [[query1_1]] +// CHECK-NEXT: OpStore %i_height [[query_1_int]] + t1.GetDimensions(i_width, i_height); + +// CHECK: [[t1_load:%[0-9]+]] = OpLoad %type_sampled_image %t1 +// CHECK-NEXT: [[image2:%[0-9]+]] = OpImage %type_2d_image [[t1_load]] +// CHECK-NEXT: [[mip:%[0-9]+]] = OpLoad %uint %mipLevel +// CHECK-NEXT: [[query2:%[0-9]+]] = OpImageQuerySizeLod %v2uint [[image2]] [[mip]] +// CHECK-NEXT: [[query2_0:%[0-9]+]] = OpCompositeExtract %uint [[query2]] 0 +// CHECK-NEXT: [[query_0_int:%[0-9]+]] = OpBitcast %int [[query2_0]] +// CHECK-NEXT: OpStore %i_width [[query_0_int]] +// CHECK-NEXT: [[query2_1:%[0-9]+]] = OpCompositeExtract %uint [[query2]] 1 +// CHECK-NEXT: [[query_1_int:%[0-9]+]] = OpBitcast %int [[query2_1]] +// CHECK-NEXT: OpStore %i_height [[query_1_int]] +// CHECK-NEXT: [[query_level_2:%[0-9]+]] = OpImageQueryLevels %uint [[image2]] +// CHECK-NEXT: [[query_level_2_int:%[0-9]+]] = OpBitcast %int [[query_level_2]] +// CHECK-NEXT: OpStore %i_numLevels [[query_level_2_int]] + t1.GetDimensions(mipLevel, i_width, i_height, i_numLevels); + +#ifdef ERROR +// ERROR: error: Output argument must be an l-value + t1.GetDimensions(mipLevel, 0, height, numLevels); + +// ERROR: error: Output argument must be an l-value + t1.GetDimensions(width, 20); +#endif +} \ No newline at end of file diff --git a/utils/hct/gen_intrin_main.txt b/utils/hct/gen_intrin_main.txt index 4d5d5126c8..899124e896 100644 --- a/utils/hct/gen_intrin_main.txt +++ b/utils/hct/gen_intrin_main.txt @@ -1243,4 +1243,8 @@ namespace VkSampledTexture2DMethods { $match<0, -1> void<4> [[ro]] Gather(in float<2> x) : tex2d_t_gather; $match<0, -1> void<4> [[ro]] Gather(in float<2> x, in int<2> o) : tex2d_t_gather_o; $match<0, -1> void<4> [[]] Gather(in float<2> x, in int<2> o, out uint_only status) : tex2d_t_gather_o_s; + void [[]] GetDimensions(in uint x, out uint_only width, out $type2 height, out $type2 levels) : resinfo_uint; + void [[]] GetDimensions(in uint x, out float_like width, out $type2 height, out $type2 levels) : resinfo; + void [[]] GetDimensions(out uint_only width, out $type1 height) : resinfo_uint_o; + void [[]] GetDimensions(out float_like width, out $type1 height) : resinfo_o; } namespace