Skip to content

Commit 4a3a5da

Browse files
authored
[SPIR-V] Add support for D3D12 descriptor heaps (microsoft#6650)
This commit adds support for Sampler/Resource descriptor heaps in DXC. Support for those heaps on the SPIR-V side requires no other extension than SPV_EXT_descriptor_indexing. On the Vulkan side, the VK_EXT_mutable_descriptor_type will be required as multiple descriptor types must be allowed on the same binding. When loading a type from a heap, DXC generates a new OpRuntimeArray of the correct type, and binds it to `set=0, binding=<BindingNumberOfTheHeap>`. This means multiple OpRuntimeArrays will share the same binding. This is why VK_EXT_mutable_descriptor_type is required. This implementation uses at most 3 bindings: - N OpRuntimeArray as binding A for the ResourceDescriptorHeap - N OpRuntimeArray as binding B for the SamplerDescriptorHeap - 1 OpRuntimeArray %counter_type for the ResourceDescriptorHeap counters. The bindings are only allocated if used. If only the SamplerDescriptorHeap is used, a single binding is required. The binding allocation logic is: 1. allocate bindings for every resources, excluding heaps. 2. If ResourceDescriptorHeap is used, find the first unused binding in set=0 and use it. 3. Same for the SamplerDescriptorHeap 4. Same for the counters. UAV counters are not always created, only if used. When used, they are stored in an OpRuntimeArray. The index of a counter in that array is equal to the index of the associated resource in its own OpRuntimeArray. ```hlsl RWStructuredBuffer a = ResourceDescriptorHeap[2]; a.IncrementCounter(); // buffer in descriptorSet 0, binding 0, OpRuntimeArray[index=2] // counter in descriptorSet 0, binding 1, OpRuntimeArray[index=2] ``` As-is, this PR doesn't allow resource heaps to alias regular resources, or to overlap. A follow-up PR will add 3 flags to override each binding/set pairs: - 'fvk-bind-resource-heap <set> <binding>' - 'fvk-bind-sampler-heap <set> <binding>' - 'fvk-bind-counter-heap <set> <binding>' --------- Signed-off-by: Nathan Gauër <[email protected]>
1 parent 3508cdc commit 4a3a5da

28 files changed

+901
-32
lines changed

docs/SPIR-V.rst

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,6 +1721,22 @@ attached to a RW/append/consume structured buffers to specify the binding number
17211721
for the associated counter to ``Z``. Note that the set number of the counter is
17221722
always the same as the main buffer.
17231723

1724+
.. warning::
1725+
When a RW/append/consume structured buffer is accessed through a resource
1726+
heap, its associated counter is in its own binding, but shares the same
1727+
index in the binding as its associated resource.
1728+
1729+
Example:
1730+
- ResourceDescriptorHeap -> binding 0, set 0
1731+
- No other resources are used.
1732+
1733+
- RWStructuredBuffer buff = ResourceDescriptorHeap[3]
1734+
- buff.IncrementCounter()
1735+
1736+
- buff will be at index 3 of the array at binding 0, set 0.
1737+
buff.counter will be at index 3 of the array at binding 1, set 0
1738+
1739+
17241740
Implicit binding number assignment
17251741
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17261742

@@ -1923,6 +1939,81 @@ Example 3: (compiled with ``-fvk-bind-globals 2 1``)
19231939
Note that if the developer chooses to use this command line option, it is their
19241940
responsibility to provide proper numbers and avoid binding overlaps.
19251941

1942+
ResourceDescriptorHeaps & SamplerDescriptorHeaps
1943+
------------------------------------------------
1944+
1945+
The SPIR-V backend supported SM6.6 resource heaps, using 2 extensions:
1946+
- `SPV_EXT_descriptor_indexing`
1947+
- `VK_EXT_mutable_descriptor_type`
1948+
1949+
Each type loaded from a heap is considered to be an unbounded RuntimeArray
1950+
bound to the descriptor set 0.
1951+
1952+
Each heap uses at most 1 binding in that set. Meaning if 2 types are loaded
1953+
from the same heap, DXC will generate 2 RuntimeArray, one for each type, and
1954+
will bind them to the same binding/set.
1955+
(This requires `VK_EXT_mutable_descriptor_type`).
1956+
1957+
For resources with counters, like RW/Append/Consume structured buffers,
1958+
DXC generates another RuntimeArray of counters, and binds it to a new
1959+
binding in the set 0.
1960+
1961+
This means Resource/Sampler heaps can use at most 3 bindings:
1962+
- 1 for all RuntimeArrays associated with the ResourceDescriptorHeap.
1963+
- 1 for all RuntimeArrays associated with the SamplerDescriptorHeaps.
1964+
- 1 for UAV counters.
1965+
1966+
The index of a counter in the counters RuntimeArray matches the index of the
1967+
associated ResourceDescriptorHeap RuntimeArray.
1968+
1969+
The selection of the binding indices for those RuntimeArrays is done once all
1970+
other resources are bound to their respective bindings/sets.
1971+
DXC takes the first 3 unused bindings in the set 0, and distributes them in
1972+
that order:
1973+
1. Resource heap.
1974+
2. Sampler heap.
1975+
3. Resouce heap counters.
1976+
1977+
Bindings are lazily allocated: if only the sampler heap is used,
1978+
1 binding will be used.
1979+
1980+
.. code:: hlsl
1981+
Texture2D tex = ResourceDescriptorHeap[10];
1982+
// tex is in the descriptor set 0, binding 0.
1983+
1984+
.. code:: hlsl
1985+
[[vk::binding(0, 0)]]
1986+
Texture2D Texture;
1987+
// Texture is using set=0, binding=0
1988+
1989+
Texture2D tex = ResourceDescriptorHeap[0];
1990+
// tex is in the descriptor set 0, binding 1.
1991+
1992+
.. code:: hlsl
1993+
[[vk::binding(0, 0)]]
1994+
RWStructuredBuffer<int> buffer;
1995+
// Texture is using set=0, binding=0
1996+
1997+
RWStructuredBuffer<int> tmp = ResourceDescriptorHeap[0];
1998+
tmp.IncrementCounter();
1999+
// tmp is in the descriptor set 0, binding 1.
2000+
// tmp.counter is in the descriptor set 0, binding 2
2001+
2002+
.. code:: hlsl
2003+
[[vk::binding(1, 0)]]
2004+
RWStructuredBuffer<int> buffer;
2005+
// Texture is using set=0, binding=1
2006+
2007+
RWStructuredBuffer<int> tmp = ResourceDescriptorHeap[0];
2008+
tmp.IncrementCounter();
2009+
// tmp is in the descriptor set 0, binding 0.
2010+
// tmp.counter is in the descriptor set 0, binding 2
2011+
2012+
.. code:: hlsl
2013+
RWStructuredBuffer buffer = ResourceDescriptorHeap[2];
2014+
// buffer is in the descriptor set 0, binding 0.
2015+
// Counter not generated, because unused.
2016+
19262017
HLSL Expressions
19272018
================
19282019

tools/clang/include/clang/SPIRV/AstTypeProbe.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ bool isRWStructuredBuffer(QualType type);
221221
/// StructuredBuffer type.
222222
bool isRWAppendConsumeSBuffer(QualType type);
223223

224+
/// \brief Returns true if the given type is a ResourceDescriptorHeap.
225+
bool isResourceDescriptorHeap(QualType type);
226+
227+
/// \brief Returns true if the given type is a SamplerDescriptorHeap.
228+
bool isSamplerDescriptorHeap(QualType type);
229+
224230
/// \brief Returns true if the given type is the HLSL ByteAddressBufferType.
225231
bool isByteAddressBuffer(QualType type);
226232

tools/clang/lib/SPIRV/AstTypeProbe.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,20 @@ bool isRWAppendConsumeSBuffer(QualType type) {
995995
isAppendStructuredBuffer(type);
996996
}
997997

998+
bool isResourceDescriptorHeap(QualType type) {
999+
if (const auto *rt = type->getAs<RecordType>()) {
1000+
return rt->getDecl()->getName() == ".Resource";
1001+
}
1002+
return false;
1003+
}
1004+
1005+
bool isSamplerDescriptorHeap(QualType type) {
1006+
if (const auto *rt = type->getAs<RecordType>()) {
1007+
return rt->getDecl()->getName() == ".Sampler";
1008+
}
1009+
return false;
1010+
}
1011+
9981012
bool isAKindOfStructuredOrByteBuffer(QualType type) {
9991013
// Strip outer arrayness first
10001014
while (type->isArrayType())
@@ -1007,7 +1021,8 @@ bool isAKindOfStructuredOrByteBuffer(QualType type) {
10071021
name == "ByteAddressBuffer" || name == "RWByteAddressBuffer" ||
10081022
name == "RasterizerOrderedByteAddressBuffer" ||
10091023
name == "AppendStructuredBuffer" ||
1010-
name == "ConsumeStructuredBuffer";
1024+
name == "ConsumeStructuredBuffer" || name == ".Resource" ||
1025+
name == ".Sampler";
10111026
}
10121027
return false;
10131028
}
@@ -1022,7 +1037,8 @@ bool isOrContainsAKindOfStructuredOrByteBuffer(QualType type) {
10221037
name == "RasterizerOrderedStructuredBuffer" ||
10231038
name == "ByteAddressBuffer" || name == "RWByteAddressBuffer" ||
10241039
name == "RasterizerOrderedByteAddressBuffer" ||
1025-
name == "AppendStructuredBuffer" || name == "ConsumeStructuredBuffer")
1040+
name == "AppendStructuredBuffer" || name == "ConsumeStructuredBuffer" ||
1041+
name == ".Resource" || name == ".Sampler")
10261042
return true;
10271043

10281044
for (const auto *field : recordType->getDecl()->fields()) {

tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -976,14 +976,6 @@ SpirvInstruction *DeclResultIdMapper::getDeclEvalInfo(const ValueDecl *decl,
976976
decl->getType(), spv::StorageClass::Output, loc);
977977
}
978978

979-
if (hlsl::IsHLSLDynamicResourceType(decl->getType()) ||
980-
hlsl::IsHLSLDynamicSamplerType(decl->getType())) {
981-
emitError("HLSL object %0 not yet supported with -spirv",
982-
decl->getLocation())
983-
<< decl->getName();
984-
return nullptr;
985-
}
986-
987979
const DeclSpirvInfo *info = getDeclSpirvInfo(decl);
988980

989981
// If DeclSpirvInfo is not found for this decl, it might be because it is an
@@ -1150,8 +1142,19 @@ DeclResultIdMapper::createFileVar(const VarDecl *var,
11501142
return varInstr;
11511143
}
11521144

1145+
SpirvVariable *DeclResultIdMapper::createResourceHeap(const VarDecl *var,
1146+
QualType ResourceType) {
1147+
QualType ResourceArrayType = astContext.getIncompleteArrayType(
1148+
ResourceType, clang::ArrayType::Normal, 0);
1149+
return createExternVar(var, ResourceArrayType);
1150+
}
1151+
11531152
SpirvVariable *DeclResultIdMapper::createExternVar(const VarDecl *var) {
1154-
const auto type = var->getType();
1153+
return createExternVar(var, var->getType());
1154+
}
1155+
1156+
SpirvVariable *DeclResultIdMapper::createExternVar(const VarDecl *var,
1157+
QualType type) {
11551158
const bool isGroupShared = var->hasAttr<HLSLGroupSharedAttr>();
11561159
const bool isACSBuffer =
11571160
isAppendStructuredBuffer(type) || isConsumeStructuredBuffer(type);
@@ -1262,12 +1265,9 @@ SpirvVariable *DeclResultIdMapper::createExternVar(const VarDecl *var) {
12621265
if (storageClass == spv::StorageClass::Workgroup)
12631266
return varInstr;
12641267

1265-
const auto *regAttr = getResourceBinding(var);
12661268
const auto *bindingAttr = var->getAttr<VKBindingAttr>();
1267-
const auto *counterBindingAttr = var->getAttr<VKCounterBindingAttr>();
1268-
1269-
resourceVars.emplace_back(varInstr, var, loc, regAttr, bindingAttr,
1270-
counterBindingAttr);
1269+
resourceVars.emplace_back(varInstr, var, loc, getResourceBinding(var),
1270+
bindingAttr, var->getAttr<VKCounterBindingAttr>());
12711271

12721272
if (const auto *inputAttachment = var->getAttr<VKInputAttachmentIndexAttr>())
12731273
spvBuilder.decorateInputAttachmentIndex(varInstr,
@@ -1819,6 +1819,10 @@ void DeclResultIdMapper::createCounterVar(
18191819
assert(declType->isIncompleteArrayType());
18201820
counterType = spvContext.getRuntimeArrayType(counterType, arrayStride);
18211821
}
1822+
} else if (isResourceDescriptorHeap(decl->getType()) ||
1823+
isSamplerDescriptorHeap(decl->getType())) {
1824+
counterType =
1825+
spvContext.getRuntimeArrayType(counterType, /* arrayStride= */ 4);
18221826
}
18231827

18241828
// {RW|Append|Consume}StructuredBuffer are all in Uniform storage class.
@@ -2002,9 +2006,11 @@ class LocationSet {
20022006
uint32_t nextAvailableLocation[kMaxIndex];
20032007
};
20042008

2009+
} // namespace
2010+
20052011
/// A class for managing resource bindings to avoid duplicate uses of the same
20062012
/// set and binding number.
2007-
class BindingSet {
2013+
class DeclResultIdMapper::BindingSet {
20082014
public:
20092015
/// Uses the given set and binding number. Returns false if the binding number
20102016
/// was already occupied in the set, and returns true otherwise.
@@ -2070,7 +2076,6 @@ class BindingSet {
20702076
///< set number -> set of used binding number
20712077
llvm::DenseMap<uint32_t, std::set<uint32_t>> usedBindings;
20722078
};
2073-
} // namespace
20742079

20752080
bool DeclResultIdMapper::checkSemanticDuplication(bool forInput) {
20762081
// Mapping from entry points to the corresponding set of semantics.
@@ -2619,6 +2624,13 @@ bool DeclResultIdMapper::decorateResourceBindings() {
26192624
}
26202625
}
26212626

2627+
if (var.getDeclaration()) {
2628+
const VarDecl *decl = dyn_cast<VarDecl>(var.getDeclaration());
2629+
if (decl && (isResourceDescriptorHeap(decl->getType()) ||
2630+
isSamplerDescriptorHeap(decl->getType())))
2631+
continue;
2632+
}
2633+
26222634
if (var.isCounter()) {
26232635

26242636
if (!var.getCounterBinding()) {
@@ -2673,9 +2685,61 @@ bool DeclResultIdMapper::decorateResourceBindings() {
26732685
}
26742686
}
26752687

2688+
decorateResourceHeapsBindings(bindingSet);
26762689
return true;
26772690
}
26782691

2692+
void DeclResultIdMapper::decorateResourceHeapsBindings(BindingSet &bindingSet) {
2693+
bool hasResource = false;
2694+
bool hasSamplers = false;
2695+
bool hasCounters = false;
2696+
2697+
// Determine which type of heap resource is used to lazily allocation
2698+
// bindings.
2699+
for (const auto &var : resourceVars) {
2700+
if (!var.getDeclaration())
2701+
continue;
2702+
const VarDecl *decl = dyn_cast<VarDecl>(var.getDeclaration());
2703+
if (!decl)
2704+
continue;
2705+
2706+
const bool isResourceHeap = isResourceDescriptorHeap(decl->getType());
2707+
const bool isSamplerHeap = isSamplerDescriptorHeap(decl->getType());
2708+
2709+
assert(!(var.isCounter() && isSamplerHeap));
2710+
2711+
hasResource |= isResourceHeap;
2712+
hasSamplers |= isSamplerHeap;
2713+
hasCounters |= isResourceHeap && var.isCounter();
2714+
}
2715+
2716+
// Allocate bindings only for used resources. The order of this allocation is
2717+
// important:
2718+
// - First resource heaps, then sampler heaps, and finally counter heaps.
2719+
const uint32_t resourceBinding =
2720+
hasResource ? bindingSet.useNextBinding(0) : 0;
2721+
const uint32_t samplersBinding =
2722+
hasSamplers ? bindingSet.useNextBinding(0) : 0;
2723+
const uint32_t countersBinding =
2724+
hasCounters ? bindingSet.useNextBinding(0) : 0;
2725+
2726+
for (const auto &var : resourceVars) {
2727+
if (!var.getDeclaration())
2728+
continue;
2729+
const VarDecl *decl = dyn_cast<VarDecl>(var.getDeclaration());
2730+
if (!decl)
2731+
continue;
2732+
2733+
if (isResourceDescriptorHeap(decl->getType()))
2734+
spvBuilder.decorateDSetBinding(var.getSpirvInstr(), /* set= */ 0,
2735+
var.isCounter() ? countersBinding
2736+
: resourceBinding);
2737+
else if (isSamplerDescriptorHeap(decl->getType()))
2738+
spvBuilder.decorateDSetBinding(var.getSpirvInstr(), /* set= */ 0,
2739+
samplersBinding);
2740+
}
2741+
}
2742+
26792743
bool DeclResultIdMapper::decorateResourceCoherent() {
26802744
for (const auto &var : resourceVars) {
26812745
if (const auto *decl = var.getDeclaration()) {

tools/clang/lib/SPIRV/DeclResultIdMapper.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ class CounterVarFields {
189189
/// type, the fields with attached semantics will need to be translated into
190190
/// stage variables per Vulkan's requirements.
191191
class DeclResultIdMapper {
192+
/// \brief An internal class to handle binding number allocation.
193+
class BindingSet;
194+
192195
public:
193196
inline DeclResultIdMapper(ASTContext &context, SpirvContext &spirvContext,
194197
SpirvBuilder &spirvBuilder, SpirvEmitter &emitter,
@@ -277,9 +280,17 @@ class DeclResultIdMapper {
277280
SpirvVariable *createFileVar(const VarDecl *var,
278281
llvm::Optional<SpirvInstruction *> init);
279282

283+
/// Creates a global variable for resource heaps containing elements of type
284+
/// |type|.
285+
SpirvVariable *createResourceHeap(const VarDecl *var, QualType type);
286+
280287
/// \brief Creates an external-visible variable and returns its instruction.
281288
SpirvVariable *createExternVar(const VarDecl *var);
282289

290+
/// \brief Creates an external-visible variable of type |type| and returns its
291+
/// instruction.
292+
SpirvVariable *createExternVar(const VarDecl *var, QualType type);
293+
283294
/// \brief Returns an OpString instruction that represents the given VarDecl.
284295
/// VarDecl must be a variable of string type.
285296
///
@@ -637,6 +648,10 @@ class DeclResultIdMapper {
637648
llvm::DenseSet<StageVariableLocationInfo, StageVariableLocationInfo>
638649
*stageVariableLocationInfo);
639650

651+
/// \bried Decorates used Resource/Sampler descriptor heaps with the correct
652+
/// binding/set decorations.
653+
void decorateResourceHeapsBindings(BindingSet &bindingSet);
654+
640655
/// \brief Returns a map that divides all of the shader stage variables into
641656
/// separate vectors for each entry point.
642657
llvm::DenseMap<const SpirvFunction *, SmallVector<StageVar, 8>>

0 commit comments

Comments
 (0)