[llvm] [SPIRV] Add reads from image buffer for shaders. (PR #115178)
Steven Perron via llvm-commits
llvm-commits at lists.llvm.org
Wed Nov 6 12:57:48 PST 2024
https://github.com/s-perron updated https://github.com/llvm/llvm-project/pull/115178
>From 22b15f3b476f30f4f307cfba7b56a9ce1a13c1e8 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Fri, 20 Sep 2024 11:02:12 -0400
Subject: [PATCH 1/2] [SPIRV] Add reads from image buffer for shaders.
This commit adds an intrinsic that will read from an image buffer. We
chose to match the name of the DXIL intrinsic for simplicity in clang.
We cannot reuse the existing openCL readimage function because that is
not a reserved name in HLSL.
I considered trying to refactor generateReadImageInst, so that we could
share code between the two implementations. However, most of the code in
generateReadImageInst is concerned with trying to figure out which type
of image read is being done. Once we factor out the code that will be
common, then we end up with just a single call to the MIRBuilder being
common.
---
llvm/include/llvm/IR/IntrinsicsSPIRV.td | 6 +
llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp | 20 ++++
llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h | 9 ++
.../Target/SPIRV/SPIRVInstructionSelector.cpp | 108 ++++++++++++++++++
llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp | 20 ++++
.../SPIRV/hlsl-resources/BufferLoad.ll | 66 +++++++++++
...lslBufferLoad.ll => ScalarResourceType.ll} | 0
.../SPIRV/hlsl-resources/UnknownBufferLoad.ll | 30 +++++
8 files changed, 259 insertions(+)
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/BufferLoad.ll
rename llvm/test/CodeGen/SPIRV/hlsl-resources/{HlslBufferLoad.ll => ScalarResourceType.ll} (100%)
create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-resources/UnknownBufferLoad.ll
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 72fee94908db72..0fc297e2b5505c 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -99,4 +99,10 @@ let TargetPrefix = "spv" in {
[llvm_any_ty],
[llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i1_ty],
[IntrNoMem]>;
+
+ // Read a value from the image buffer. It does not translate directly to a
+ // single OpImageRead because the result type is not necessarily a 4 element
+ // vector.
+ def int_spv_typedBufferLoad
+ : DefaultAttrsIntrinsic<[llvm_any_ty], [llvm_any_ty, llvm_i32_ty]>;
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
index af0df4d6e5d563..406c9f2e486846 100644
--- a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
@@ -1056,6 +1056,11 @@ SPIRVGlobalRegistry::getSPIRVTypeForVReg(Register VReg,
return nullptr;
}
+SPIRVType *SPIRVGlobalRegistry::getResultType(Register VReg) {
+ MachineInstr *Instr = CurMF->getRegInfo().getVRegDef(VReg);
+ return getSPIRVTypeForVReg(Instr->getOperand(1).getReg());
+}
+
SPIRVType *SPIRVGlobalRegistry::getOrCreateSPIRVType(
const Type *Ty, MachineIRBuilder &MIRBuilder,
SPIRV::AccessQualifier::AccessQualifier AccessQual, bool EmitIR) {
@@ -1126,6 +1131,21 @@ SPIRVGlobalRegistry::getScalarOrVectorComponentCount(SPIRVType *Type) const {
: 1;
}
+SPIRVType *
+SPIRVGlobalRegistry::getScalarOrVectorComponentType(Register VReg) const {
+ return getScalarOrVectorComponentType(getSPIRVTypeForVReg(VReg));
+}
+
+SPIRVType *
+SPIRVGlobalRegistry::getScalarOrVectorComponentType(SPIRVType *Type) const {
+ if (!Type)
+ return nullptr;
+ Register ScalarReg = Type->getOpcode() == SPIRV::OpTypeVector
+ ? Type->getOperand(1).getReg()
+ : Type->getOperand(0).getReg();
+ return getSPIRVTypeForVReg(ScalarReg);
+}
+
unsigned
SPIRVGlobalRegistry::getScalarOrVectorBitWidth(const SPIRVType *Type) const {
assert(Type && "Invalid Type pointer");
diff --git a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
index a95b488960c4c3..0cf174d0e45afa 100644
--- a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
+++ b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
@@ -353,6 +353,9 @@ class SPIRVGlobalRegistry {
SPIRVType *getSPIRVTypeForVReg(Register VReg,
const MachineFunction *MF = nullptr) const;
+ // Return the result type of the instruction defining the register.
+ SPIRVType *getResultType(Register VReg);
+
// Whether the given VReg has a SPIR-V type mapped to it yet.
bool hasSPIRVTypeForVReg(Register VReg) const {
return getSPIRVTypeForVReg(VReg) != nullptr;
@@ -388,6 +391,12 @@ class SPIRVGlobalRegistry {
unsigned getScalarOrVectorComponentCount(Register VReg) const;
unsigned getScalarOrVectorComponentCount(SPIRVType *Type) const;
+ // Return the component type in a vector if the argument is associated with
+ // a vector type. Returns the argument itself for a scalar type, and nullptr
+ // for a missing type.
+ SPIRVType *getScalarOrVectorComponentType(Register VReg) const;
+ SPIRVType *getScalarOrVectorComponentType(SPIRVType *Type) const;
+
// For vectors or scalars of booleans, integers and floats, return the scalar
// type's bitwidth. Otherwise calls llvm_unreachable().
unsigned getScalarOrVectorBitWidth(const SPIRVType *Type) const;
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 7aa5f4f2b1a8f1..475a890b8c6035 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -245,6 +245,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
void selectHandleFromBinding(Register &ResVReg, const SPIRVType *ResType,
MachineInstr &I) const;
+ void selectReadImageIntrinsic(Register &ResVReg, const SPIRVType *ResType,
+ MachineInstr &I) const;
+
// Utilities
Register buildI32Constant(uint32_t Val, MachineInstr &I,
const SPIRVType *ResType = nullptr) const;
@@ -269,6 +272,12 @@ class SPIRVInstructionSelector : public InstructionSelector {
uint32_t Binding, uint32_t ArraySize,
Register IndexReg, bool IsNonUniform,
MachineIRBuilder MIRBuilder) const;
+ SPIRVType *getCorrespondingVec4Type(const SPIRVType *Type,
+ MachineInstr &I) const;
+ void extractScalarOrVectorFromVector(Register &ResultReg,
+ const SPIRVType *ResType,
+ Register &InputReg,
+ MachineInstr &InsertionPoint) const;
};
} // end anonymous namespace
@@ -2717,6 +2726,10 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
selectHandleFromBinding(ResVReg, ResType, I);
return true;
}
+ case Intrinsic::spv_typedBufferLoad: {
+ selectReadImageIntrinsic(ResVReg, ResType, I);
+ return true;
+ }
default: {
std::string DiagMsg;
raw_string_ostream OS(DiagMsg);
@@ -2753,6 +2766,83 @@ void SPIRVInstructionSelector::selectHandleFromBinding(Register &ResVReg,
.addUse(VarReg);
}
+void SPIRVInstructionSelector::selectReadImageIntrinsic(
+ Register &ResVReg, const SPIRVType *ResType, MachineInstr &I) const {
+
+ // If the load of the image is in a different basic block, then
+ // this will generate invalid code. A proper solution is to move
+ // the OpLoad from selectHandleFromBinding here. However, to do
+ // that we will need to change the return type of the intrinsic.
+ // We will do that when we can, but for now trying to move forward with other
+ // issues.
+ Register ImageReg = I.getOperand(2).getReg();
+
+ SPIRVType *ReadType = getCorrespondingVec4Type(ResType, I);
+ Register ReadReg = MRI->createVirtualRegister(GR.getRegClass(ReadType));
+ BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpImageRead))
+ .addDef(ReadReg)
+ .addUse(GR.getSPIRVTypeID(ReadType))
+ .addUse(ImageReg)
+ .addUse(I.getOperand(3).getReg());
+
+ extractScalarOrVectorFromVector(ResVReg, ResType, ReadReg, I);
+}
+
+void SPIRVInstructionSelector::extractScalarOrVectorFromVector(
+ Register &ResultReg, const SPIRVType *ResType, Register &InputReg,
+ MachineInstr &InsertionPoint) const {
+ SPIRVType *InputType = GR.getResultType(InputReg);
+ assert(InputType->getOpcode() == SPIRV::OpTypeVector);
+
+ if (ResType->getOpcode() != SPIRV::OpTypeVector) {
+ assert(ResType == GR.getScalarOrVectorComponentType(InputType));
+ BuildMI(*InsertionPoint.getParent(), InsertionPoint,
+ InsertionPoint.getDebugLoc(), TII.get(SPIRV::OpCompositeExtract))
+ .addDef(ResultReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ .addUse(InputReg)
+ .addImm(0);
+ return;
+ }
+
+ uint64_t InputSize = GR.getScalarOrVectorComponentCount(InputType);
+ uint64_t VectorSize = GR.getScalarOrVectorComponentCount(ResType);
+ if (VectorSize == InputSize) {
+ BuildMI(*InsertionPoint.getParent(), InsertionPoint,
+ InsertionPoint.getDebugLoc(), TII.get(SPIRV::OpCopyObject))
+ .addDef(ResultReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ .addUse(InputReg);
+ return;
+ }
+
+ assert(VectorSize < InputSize &&
+ "Cannot extract more element than there are in the input.");
+ SmallVector<Register> ComponentRegisters;
+ SPIRVType *ScalarType = GR.getScalarOrVectorComponentType(ResType);
+ const TargetRegisterClass *ScalarRegClass = GR.getRegClass(ScalarType);
+ for (uint64_t i = 0; i < VectorSize; i++) {
+ Register ComponentReg = MRI->createVirtualRegister(ScalarRegClass);
+ BuildMI(*InsertionPoint.getParent(), InsertionPoint,
+ InsertionPoint.getDebugLoc(), TII.get(SPIRV::OpCompositeExtract))
+ .addDef(ComponentReg)
+ .addUse(ScalarType->getOperand(0).getReg())
+ .addUse(InputReg)
+ .addImm(i);
+ ComponentRegisters.emplace_back(ComponentReg);
+ }
+
+ MachineInstrBuilder MIB = BuildMI(*InsertionPoint.getParent(), InsertionPoint,
+ InsertionPoint.getDebugLoc(),
+ TII.get(SPIRV::OpCompositeConstruct))
+ .addDef(ResultReg)
+ .addUse(GR.getSPIRVTypeID(ResType));
+
+ for (Register ComponentReg : ComponentRegisters) {
+ MIB.addUse(ComponentReg);
+ }
+}
+
Register SPIRVInstructionSelector::buildPointerToResource(
const SPIRVType *ResType, uint32_t Set, uint32_t Binding,
uint32_t ArraySize, Register IndexReg, bool IsNonUniform,
@@ -3101,6 +3191,24 @@ bool SPIRVInstructionSelector::selectSpvThreadId(Register ResVReg,
return MIB.constrainAllUses(TII, TRI, RBI);
}
+SPIRVType *
+SPIRVInstructionSelector::getCorrespondingVec4Type(const SPIRVType *Type,
+ MachineInstr &I) const {
+ MachineIRBuilder MIRBuilder(I);
+ if (Type->getOpcode() != SPIRV::OpTypeVector) {
+ return GR.getOrCreateSPIRVVectorType(Type, 4, MIRBuilder);
+ }
+
+ uint64_t VectorSize = Type->getOperand(2).getImm();
+ if (VectorSize == 4) {
+ return Type;
+ }
+
+ Register ScalarTypeReg = Type->getOperand(1).getReg();
+ const SPIRVType *ScalarType = GR.getSPIRVTypeForVReg(ScalarTypeReg);
+ return GR.getOrCreateSPIRVVectorType(ScalarType, 4, MIRBuilder);
+}
+
namespace llvm {
InstructionSelector *
createSPIRVInstructionSelector(const SPIRVTargetMachine &TM,
diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
index e8641b3a105dec..673fde96ea19fd 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -719,6 +719,11 @@ void RequirementHandler::initAvailableCapabilitiesForVulkan(
Capability::UniformTexelBufferArrayNonUniformIndexingEXT,
Capability::StorageTexelBufferArrayNonUniformIndexingEXT});
}
+
+ // Became core in Vulkan 1.3
+ if (ST.isAtLeastSPIRVVer(VersionTuple(1, 6))) {
+ addAvailableCaps({Capability::StorageImageReadWithoutFormat});
+ }
}
} // namespace SPIRV
@@ -1005,6 +1010,13 @@ void addOpAccessChainReqs(const MachineInstr &Instr,
}
}
+static bool imageTypeHasUnknownFormat(SPIRVType *TypeInst) {
+ if (TypeInst->getOpcode() != SPIRV::OpTypeImage)
+ return false;
+ assert(TypeInst->getOperand(7).isImm() && "The image format must be an imm.");
+ return TypeInst->getOperand(7).getImm() == 0;
+}
+
static void AddDotProductRequirements(const MachineInstr &MI,
SPIRV::RequirementHandler &Reqs,
const SPIRVSubtarget &ST) {
@@ -1411,6 +1423,14 @@ void addInstrRequirements(const MachineInstr &MI,
case SPIRV::OpUDot:
AddDotProductRequirements(MI, Reqs, ST);
break;
+ case SPIRV::OpImageRead: {
+ Register ImageReg = MI.getOperand(2).getReg();
+ SPIRVType *TypeDef = ST.getSPIRVGlobalRegistry()->getResultType(ImageReg);
+ if (imageTypeHasUnknownFormat(TypeDef))
+ Reqs.addCapability(SPIRV::Capability::StorageImageReadWithoutFormat);
+ break;
+ }
+
default:
break;
}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/BufferLoad.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/BufferLoad.ll
new file mode 100644
index 00000000000000..27c66eeb238a10
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/BufferLoad.ll
@@ -0,0 +1,66 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-vulkan-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-library %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-NOT: OpCapability StorageImageReadWithoutFormat
+
+; CHECK-DAG: OpDecorate [[IntBufferVar:%[0-9]+]] DescriptorSet 16
+; CHECK-DAG: OpDecorate [[IntBufferVar]] Binding 7
+
+; CHECK-DAG: [[int:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[zero:%[0-9]+]] = OpConstant [[int]] 0
+; CHECK-DAG: [[v4_int:%[0-9]+]] = OpTypeVector [[int]] 4
+; CHECK-DAG: [[v2_int:%[0-9]+]] = OpTypeVector [[int]] 2
+; CHECK-DAG: [[RWBufferTypeInt:%[0-9]+]] = OpTypeImage [[int]] Buffer 2 0 0 2 R32i {{$}}
+; CHECK-DAG: [[IntBufferPtrType:%[0-9]+]] = OpTypePointer UniformConstant [[RWBufferTypeInt]]
+; CHECK-DAG: [[IntBufferVar]] = OpVariable [[IntBufferPtrType]] UniformConstant
+
+; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
+; CHECK-NEXT: OpLabel
+define void @RWBufferLoad_Vec4_I32() #0 {
+; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
+ %buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24)
+ @llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_24(
+ i32 16, i32 7, i32 1, i32 0, i1 false)
+
+; CHECK: OpImageRead [[v4_int]] [[buffer]] [[zero]]
+ %data0 = call <4 x i32> @llvm.spv.typedBufferLoad(
+ target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer0, i32 0)
+
+ ret void
+}
+
+; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
+; CHECK-NEXT: OpLabel
+define void @RWBufferLoad_I32() #0 {
+; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
+ %buffer1 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24)
+ @llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_24(
+ i32 16, i32 7, i32 1, i32 0, i1 false)
+
+; CHECK: [[V:%[0-9]+]] = OpImageRead [[v4_int]] [[buffer]] [[zero]]
+; CHECK: OpCompositeExtract [[int]] [[V]] 0
+ %data1 = call i32 @llvm.spv.typedBufferLoad(
+ target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer1, i32 0)
+
+ ret void
+}
+
+; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
+; CHECK-NEXT: OpLabel
+define void @RWBufferLoad_Vec2_I32() #0 {
+; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
+ %buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 24)
+ @llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_24(
+ i32 16, i32 7, i32 1, i32 0, i1 false)
+
+; CHECK: [[V:%[0-9]+]] = OpImageRead [[v4_int]] [[buffer]] [[zero]]
+; CHECK: [[e0:%[0-9]+]] = OpCompositeExtract [[int]] [[V]] 0
+; CHECK: [[e1:%[0-9]+]] = OpCompositeExtract [[int]] [[V]] 1
+; CHECK: OpCompositeConstruct [[v2_int]] [[e0]] [[e1]]
+ %data0 = call <2 x i32> @llvm.spv.typedBufferLoad(
+ target("spirv.Image", i32, 5, 2, 0, 0, 2, 24) %buffer0, i32 0)
+
+ ret void
+}
+
+attributes #0 = { convergent noinline norecurse "frame-pointer"="all" "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/HlslBufferLoad.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/ScalarResourceType.ll
similarity index 100%
rename from llvm/test/CodeGen/SPIRV/hlsl-resources/HlslBufferLoad.ll
rename to llvm/test/CodeGen/SPIRV/hlsl-resources/ScalarResourceType.ll
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/UnknownBufferLoad.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/UnknownBufferLoad.ll
new file mode 100644
index 00000000000000..7f9c6f7da2859e
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/UnknownBufferLoad.ll
@@ -0,0 +1,30 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: OpCapability StorageImageReadWithoutFormat
+; CHECK-DAG: OpDecorate [[IntBufferVar:%[0-9]+]] DescriptorSet 16
+; CHECK-DAG: OpDecorate [[IntBufferVar]] Binding 7
+
+; CHECK-DAG: [[int:%[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: [[zero:%[0-9]+]] = OpConstant [[int]] 0
+; CHECK-DAG: [[v4_int:%[0-9]+]] = OpTypeVector [[int]] 4
+; CHECK-DAG: [[RWBufferTypeInt:%[0-9]+]] = OpTypeImage [[int]] Buffer 2 0 0 2 Unknown {{$}}
+; CHECK-DAG: [[IntBufferPtrType:%[0-9]+]] = OpTypePointer UniformConstant [[RWBufferTypeInt]]
+; CHECK-DAG: [[IntBufferVar]] = OpVariable [[IntBufferPtrType]] UniformConstant
+
+; CHECK: {{%[0-9]+}} = OpFunction {{%[0-9]+}} DontInline {{%[0-9]+}}
+; CHECK-NEXT: OpLabel
+define void @RWBufferLoad_Vec4_I32() #0 {
+; CHECK: [[buffer:%[0-9]+]] = OpLoad [[RWBufferTypeInt]] [[IntBufferVar]]
+ %buffer0 = call target("spirv.Image", i32, 5, 2, 0, 0, 2, 0)
+ @llvm.spv.handle.fromBinding.tspirv.Image_f32_5_2_0_0_2_0(
+ i32 16, i32 7, i32 1, i32 0, i1 false)
+
+; CHECK: OpImageRead [[v4_int]] [[buffer]] [[zero]]
+ %data0 = call <4 x i32> @llvm.spv.typedBufferLoad(
+ target("spirv.Image", i32, 5, 2, 0, 0, 2, 0) %buffer0, i32 0)
+
+ ret void
+}
+
+attributes #0 = { convergent noinline norecurse "frame-pointer"="all" "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
>From c2b517bd74f1e2d61857cf290ad389b370a2af0b Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Wed, 6 Nov 2024 15:57:35 -0500
Subject: [PATCH 2/2] Update SpirvUsage.rst.
---
llvm/docs/SPIRVUsage.rst | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/llvm/docs/SPIRVUsage.rst b/llvm/docs/SPIRVUsage.rst
index 0fcaa366c8a3e0..277b9c15292c53 100644
--- a/llvm/docs/SPIRVUsage.rst
+++ b/llvm/docs/SPIRVUsage.rst
@@ -392,6 +392,14 @@ SPIR-V backend, along with their descriptions and argument details.
If `arraySize > 1`, then the binding represents an array of resources\
of the given size, and the handle for the resource at the given index is returned.\
If the index is possibly non-uniform, then `isUniformIndex` must get set to true.
+ * - `int_spv_typeBufferLoad`
+ - Scalar or vector
+ - `[spirv.Image ImageBuffer, 32-bit Integer coordinate]`
+ - Loads a value from a Vulkan image buffer at the given coordinate. The \
+ image buffer data is assumed to be stored as a 4-element vector. If the \
+ return type is a scalar, then the first element of the vector is \
+ returned. If the return type is an n-element vector, then the first \
+ n-elements of the 4-element vector are returned.
.. _spirv-builtin-functions:
More information about the llvm-commits
mailing list