summaryrefslogtreecommitdiffstats
path: root/tools/gfx/vulkan/render-vk.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2021-04-29 15:27:24 -0700
committerGitHub <noreply@github.com>2021-04-29 15:27:24 -0700
commit37a341775e410c02df5072244becdc416fd15c86 (patch)
tree4f8110596c22140fd529c56915e60283493eaa63 /tools/gfx/vulkan/render-vk.cpp
parentaba8ec619de2bcf908901fa33677bb1d35296df8 (diff)
Update gfx back-ends to handle static specialization (#1826)
* Update gfx back-ends to handle static specialization The main goal here is to make the D3D11, D3D12 and Vulkan back-ends support static specialization of interface types in the case where the data for the type won't "fit" in the pre-allocated space for existential values. This includes all cases where the concrete type being specialized to has resources/samplers/etc., as well as any cases where its ordinary/uniform data exceeds the space available. (Note that the CPU and CUDA targets don't need this work since they can (in theory) support arbitrary-size data in the fixed-size existential payload by using pointer indirection. Actually supporting indirection in those cases should be a distinct change) The Slang compiler already performs layout for programs that have this kind of data that doesn't "fit," and it lays them out using an idea of "pending" type layouts. Basically, a type that contains some amount of specialized interface-type fields will produce both a "primary" type layout that just covers the data for the unspecialized case, as well as "pending" type layout that describes the layout for all the extra data needed by specialization. When laying out a `ConstantBuffer<X>` or `ParameterBlocK<X>` ("CB" or "PB"), the front-end will try to place as much of that "pending" data into the layout of the buffer/block itself as is possible. That means that both CBs and PBs will be able to allocate trailing bytes for any ordinary data in the "pending" layout. PBs will be able to allocate any trailing resources/samplers into their layout, but for CBs they will spill out to be part of the pending layout for the buffer itself. In order for the back-ends to properly handle pending data, they need to *either* assume the exact layout rules used by the front-end and try to reproduce them (e.g., by iterating over binding ranges and sub-objects in the exact same order that front-end layout would enumerate them), *or* they need to respect the reflection information produced by the front-end. This change takes the latter approach, trying to make only minimal assumptions about the layout rules being used. This choice is motivated by wanting to decouple the `gfx` implementation from the compiler front-end, especially insofar as this work has made me question whether the current layout rules are the best ones possible. A common theme across all the implementations is to have a fixed-size type that can represent "binding offsets" for the chosen back-end. The offset type has fields that depend on the API-specific way bindings are indexed; e.g., for D3D11 it has offsets for CBV, SRV, UAV, and sampler bindings. This fixed-size offset type can be filled in based on Slang reflecton information, and then used to compute derived offsets with just a few add operations. The simple offset type for each API is then extended to produce an offset type that includes both the offsets for "primary" data and also the offsets for "pending" data. Most logic that traffics in offsets doesn't have to know about this more complicated representation. Making consistent use of these offsets required that I pretty much rewrite the logic that actually applies shader objects to the API state. Doing so might be lowering the efficiency of the system in the near term, but the increase in clarity was important for getting the work done, and it seems like it will also be important if/when we start trying to perform special-case optimizations around root and entry-point parameter setting. While there are many API-specific differences, we can identify a repeated pattern where many steps, whether applying parameters to the pipeline stage or constructing signatures / layouts, can be broken down into three main operations on `ShaderObject`s or their layouts: * `*AsValue()` is the core operation, and is the one used for the `ExistentialValue` case most of the time. It ignores the ordinary data in the object, and instead processes all nested binding ranges (for resources/smaplers) and sub-objects. * `*AsConstantBuffer()` handles the `ConstntBuffer<X>` case, by dealing with the implicit buffer for ordinary data (if it is needed) and then delegates to the `*AsValue()` case. * `*AsParameterBlock()` handles the `ParameterBlock<X>` case, by allocating/preparing/etc. any descriptor tables/sets that would be required for the current object/layout and then delegating to `*AsConstantBuffer()` to do the rest The idea is that by having the parameter block case delegate to the constant buffer case, which delegates to the value/existential case, we can streamline a lot of the logic so that it doesn't seem quite as full of special cases. Note: When preparing this pull request I spent a reasonable amount of time trying to clean up the D3D11 and Vulkan implementations, so they are probably the easiest to read and understand when it comes to the new code. Doing the cleanup work also helped to work out some weird corner case bugs/issues. In contrast, the D3D12 path hasn't had as much attention given to cleanliness and comments, so it really needs some attention down the line to get things into a state that is easier to understand. * fixup: remove debugging code spotted in review
Diffstat (limited to 'tools/gfx/vulkan/render-vk.cpp')
-rw-r--r--tools/gfx/vulkan/render-vk.cpp1901
1 files changed, 1409 insertions, 492 deletions
diff --git a/tools/gfx/vulkan/render-vk.cpp b/tools/gfx/vulkan/render-vk.cpp
index b3f99ff59..a0860f0e5 100644
--- a/tools/gfx/vulkan/render-vk.cpp
+++ b/tools/gfx/vulkan/render-vk.cpp
@@ -660,28 +660,230 @@ public:
VkPipeline m_pipeline = VK_NULL_HANDLE;
};
+ // In order to bind shader parameters to the correct locations, we need to
+ // be able to describe those locations. Most shader parameters in Vulkan
+ // simply consume a single `binding`, but we also need to deal with
+ // parameters that represent push-constant ranges.
+ //
+ // In more complex cases we might be binding an entire "sub-object" like
+ // a parameter block, an entry point, etc. For the general case, we need
+ // to be able to represent a composite offset that includes offsets for
+ // each of the cases that Vulkan supports.
+
+ /// A "simple" binding offset that records `binding`, `set`, etc. offsets
+ struct SimpleBindingOffset
+ {
+ /// An offset in GLSL/SPIR-V `binding`s
+ uint32_t binding = 0;
+
+ /// The descriptor `set` that the `binding` field should be understood as an index into
+ uint32_t bindingSet = 0;
+
+ /// The starting index for any "child" descriptor sets to start at
+ uint32_t childSet = 0;
+
+ // The distinction between `bindingSet` and `childSet` above is subtle, but
+ // potentially very important when objects contain nested parameter blocks.
+ // Consider:
+ //
+ // struct Stuff { ... }
+ // struct Things
+ // {
+ // Texture2D t;
+ // ParameterBlock<Stuff> stuff;
+ // }
+ //
+ // ParameterBlock<Stuff> gStuff;
+ // Texture2D gTex;
+ // ConstantBuffer<Things> gThings;
+ //
+ // In this example, the global-scope parameters like `gTex` and `gThings`
+ // are expected to be laid out in `set=0`, and we also expect `gStuff`
+ // to be laid out as `set=1`. As a result we expect that `gThings.t`
+ // will be laid out as `binding=1,set=0` (right after `gTex`), but
+ // `gThings.stuff` should be laid out as `set=2`.
+ //
+ // In this case, when binding `gThings` we would want a binding offset
+ // that has a `binding` or 1, a `bindingSet` of 0, and a `childSet` of 2.
+ //
+ // TODO: Validate that any of this works as intended.
+
+ /// The offset in push-constant ranges (not bytes)
+ uint32_t pushConstantRange = 0;
+
+ /// Create a default (zero) offset
+ SimpleBindingOffset()
+ {}
+
+ /// Create an offset based on offset information in the given Slang `varLayout`
+ SimpleBindingOffset(slang::VariableLayoutReflection* varLayout)
+ {
+ if(varLayout)
+ {
+ bindingSet = (uint32_t) varLayout->getBindingSpace(SLANG_PARAMETER_CATEGORY_DESCRIPTOR_TABLE_SLOT);
+ binding = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_DESCRIPTOR_TABLE_SLOT);
+
+ childSet = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_REGISTER_SPACE);
+
+ pushConstantRange = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_PUSH_CONSTANT_BUFFER);
+ }
+ }
+
+ /// Add any values in the given `offset`
+ void operator+=(SimpleBindingOffset const& offset)
+ {
+ binding += offset.binding;
+ bindingSet += offset.bindingSet;
+ childSet += offset.childSet;
+ pushConstantRange += offset.pushConstantRange;
+ }
+ };
+
+ // While a "simple" binding offset representation will work in many cases,
+ // once we need to deal with layout for programs with interface-type parameters
+ // that have been statically specialized, we also need to track the offset
+ // for where to bind any "pending" data that arises from the process of static
+ // specialization.
+ //
+ // In order to conveniently track both the "primary" and "pending" offset information,
+ // we will define a more complete `BindingOffset` type that combines simple
+ // binding offsets for the primary and pending parts.
+
+ /// A representation of the offset at which to bind a shader parameter or sub-object
+ struct BindingOffset : SimpleBindingOffset
+ {
+ // Offsets for "primary" data are stored directly in the `BindingOffset`
+ // via the inheritance from `SimpleBindingOffset`.
+
+ /// Offset for any "pending" data
+ SimpleBindingOffset pending;
+
+ /// Create a default (zero) offset
+ BindingOffset()
+ {}
+
+ /// Create an offset from a simple offset
+ explicit BindingOffset(SimpleBindingOffset const& offset)
+ : SimpleBindingOffset(offset)
+ {}
+
+ /// Create an offset based on offset information in the given Slang `varLayout`
+ BindingOffset(slang::VariableLayoutReflection* varLayout)
+ : SimpleBindingOffset(varLayout)
+ , pending(varLayout->getPendingDataLayout())
+ {}
+
+ /// Add any values in the given `offset`
+ void operator+=(SimpleBindingOffset const& offset)
+ {
+ SimpleBindingOffset::operator+=(offset);
+ }
+
+ /// Add any values in the given `offset`
+ void operator+=(BindingOffset const& offset)
+ {
+ SimpleBindingOffset::operator+=(offset);
+ pending += offset.pending;
+ }
+ };
+
class ShaderObjectLayoutImpl : public ShaderObjectLayoutBase
{
public:
+ // A shader object comprises three main kinds of state:
+ //
+ // * Zero or more bytes of ordinary ("uniform") data
+ // * Zero or more *bindings* for textures, buffers, and samplers
+ // * Zero or more *sub-objects* representing nested parameter blocks, etc.
+ //
+ // A shader object *layout* stores information that can be used to
+ // organize these different kinds of state and optimize access to them.
+ //
+ // For example, both texture/buffer/sampler bindings and sub-objects
+ // are organized into logical *binding ranges* by the Slang reflection
+ // API, and a shader object layout will store information about those
+ // ranges in a form that is usable for the Vulkan API:
+
struct BindingRangeInfo
{
slang::BindingType bindingType;
Index count;
Index baseIndex;
- Index descriptorSetIndex;
- Index rangeIndexInDescriptorSet;
- // Returns true if this binding range consumes a specialization argument slot.
- bool isSpecializationArg() const
+ /// The `binding` offset to apply for this range
+ uint32_t bindingOffset;
+
+ /// The `set` offset to apply for this range
+ uint32_t setOffset;
+
+ // Note: The 99% case is that `setOffset` will be zero. For any shader object
+ // that was allocated from an ordinary Slang type (anything other than a root
+ // shader object in fact), all of the bindings will have been allocated into
+ // a single logical descriptor set.
+ //
+ // TODO: Ideally we could refactor so that only the root shader object layout
+ // stores a set offset for its binding ranges, and all other objects skip
+ // storing a field that never actually matters.
+ };
+
+ // Sometimes we just want to iterate over the ranges that represnet
+ // sub-objects while skipping over the others, because sub-object
+ // ranges often require extra handling or more state.
+ //
+ // For that reason we also store pre-computed information about each
+ // sub-object range.
+
+ /// Offset information for a sub-object range
+ struct SubObjectRangeOffset : BindingOffset
+ {
+ SubObjectRangeOffset()
+ {}
+
+ SubObjectRangeOffset(slang::VariableLayoutReflection* varLayout)
+ : BindingOffset(varLayout)
+ {
+ if(auto pendingLayout = varLayout->getPendingDataLayout())
+ {
+ pendingOrdinaryData = (uint32_t) pendingLayout->getOffset(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ }
+ }
+
+ /// The offset for "pending" ordinary data related to this range
+ uint32_t pendingOrdinaryData = 0;
+ };
+
+ /// Stride information for a sub-object range
+ struct SubObjectRangeStride
+ {
+ SubObjectRangeStride()
+ {}
+
+ SubObjectRangeStride(slang::TypeLayoutReflection* typeLayout)
{
- return bindingType == slang::BindingType::ExistentialValue;
+ if(auto pendingLayout = typeLayout->getPendingDataTypeLayout())
+ {
+ pendingOrdinaryData = (uint32_t) pendingLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ }
}
+
+ /// The strid for "pending" ordinary data related to this range
+ uint32_t pendingOrdinaryData = 0;
};
+ /// Information about a logical binding range as reported by Slang reflection
struct SubObjectRangeInfo
{
- RefPtr<ShaderObjectLayoutImpl> layout;
+ /// The index of the binding range that corresponds to this sub-object range
Index bindingRangeIndex;
+
+ /// The layout expected for objects bound to this range (if known)
+ RefPtr<ShaderObjectLayoutImpl> layout;
+
+ /// The offset to use when binding the first object in this range
+ SubObjectRangeOffset offset;
+
+ /// Stride between consecutive objects in this range
+ SubObjectRangeStride stride;
};
struct DescriptorSetInfo
@@ -710,10 +912,23 @@ public:
Index m_subObjectCount = 0;
Index m_varyingInputCount = 0;
Index m_varyingOutputCount = 0;
- uint32_t m_pushConstantSize = 0;
List<DescriptorSetInfo> m_descriptorSetBuildInfos;
Dictionary<Index, Index> m_mapSpaceToDescriptorSetIndex;
+ /// The number of descriptor sets allocated by child/descendent objects
+ uint32_t m_childDescriptorSetCount = 0;
+
+ /// The total number of `binding`s consumed by this object and its children/descendents
+ uint32_t m_totalBindingCount = 0;
+
+ /// The push-constant ranges that belong to this object itself (if any)
+ List<VkPushConstantRange> m_ownPushConstantRanges;
+
+ /// The number of push-constant ranges owned by child/descendent objects
+ uint32_t m_childPushConstantRangeCount = 0;
+
+ uint32_t m_totalOrdinaryDataSize = 0;
+
Index findOrAddDescriptorSet(Index space)
{
Index index;
@@ -765,45 +980,71 @@ public:
}
}
- Result _addDescriptorSets(
- slang::TypeLayoutReflection* typeLayout,
- bool createImplicitConstantBufferForUniforms,
- slang::VariableLayoutReflection* varLayout = nullptr)
+ /// Add any descriptor ranges implied by this object containing a leaf
+ /// sub-object described by `typeLayout`, at the given `offset`.
+ void _addDescriptorRangesAsValue(
+ slang::TypeLayoutReflection* typeLayout,
+ BindingOffset const& offset)
{
- SlangInt descriptorSetCount = typeLayout->getDescriptorSetCount();
- SlangInt defaultDescriptorSetIndex;
- // If the type has ordinary uniform data fields, we need to make sure to create
- // a descriptor set with a constant buffer binding in the case that the shader
- // object is bound as a stand alone parameter block.
- uint32_t bindingOffset = 0;
- if (createImplicitConstantBufferForUniforms && typeLayout->getSize() != 0)
+ // First we will scan through all the descriptor sets that the Slang reflection
+ // information believes go into making up the given type.
+ //
+ // Note: We are initializing the sets in order so that their order in our
+ // internal data structures should be deterministically based on the order
+ // in which they are listed in Slang's reflection information.
+ //
+ Index descriptorSetCount = typeLayout->getDescriptorSetCount();
+ for (Index i = 0; i < descriptorSetCount; ++i)
{
- defaultDescriptorSetIndex = findOrAddDescriptorSet(0);
- auto& descriptorSetInfo = m_descriptorSetBuildInfos[defaultDescriptorSetIndex];
- VkDescriptorSetLayoutBinding vkBindingRangeDesc = {};
- vkBindingRangeDesc.binding = 0;
- bindingOffset = 1;
- vkBindingRangeDesc.descriptorCount = 1;
- vkBindingRangeDesc.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
- vkBindingRangeDesc.stageFlags = VK_SHADER_STAGE_ALL;
- descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
+ SlangInt descriptorRangeCount = typeLayout->getDescriptorSetDescriptorRangeCount(i);
+ if (descriptorRangeCount == 0)
+ continue;
+ auto descriptorSetIndex = findOrAddDescriptorSet(offset.bindingSet + typeLayout->getDescriptorSetSpaceOffset(i));
}
- for (SlangInt s = 0; s < descriptorSetCount; ++s)
+ // For actually populating the descriptor sets we prefer to enumerate
+ // the binding ranges of the type instead of the descriptor sets.
+ //
+ Index bindRangeCount = typeLayout->getBindingRangeCount();
+ for( Index i = 0; i < bindRangeCount; ++i )
{
- SlangInt descriptorRangeCount =
- typeLayout->getDescriptorSetDescriptorRangeCount(s);
+ auto bindingRangeIndex = i;
+ auto bindingRangeType = typeLayout->getBindingRangeType(bindingRangeIndex);
+ switch(bindingRangeType)
+ {
+ default:
+ break;
+
+ // We will skip over ranges that represent sub-objects for now, and handle
+ // them in a separate pass.
+ //
+ case slang::BindingType::ParameterBlock:
+ case slang::BindingType::ConstantBuffer:
+ case slang::BindingType::ExistentialValue:
+ case slang::BindingType::PushConstant:
+ continue;
+ }
+
+ // Given a binding range we are interested in, we will then enumerate
+ // its contained descriptor ranges.
+
+ Index descriptorRangeCount = typeLayout->getBindingRangeDescriptorRangeCount(bindingRangeIndex);
if (descriptorRangeCount == 0)
continue;
- auto descriptorSetIndex =
- findOrAddDescriptorSet(typeLayout->getDescriptorSetSpaceOffset(s));
+ auto slangDescriptorSetIndex = typeLayout->getBindingRangeDescriptorSetIndex(bindingRangeIndex);
+ auto descriptorSetIndex = findOrAddDescriptorSet(offset.bindingSet + typeLayout->getDescriptorSetSpaceOffset(slangDescriptorSetIndex));
auto& descriptorSetInfo = m_descriptorSetBuildInfos[descriptorSetIndex];
- for (SlangInt r = 0; r < descriptorRangeCount; ++r)
+
+ Index firstDescriptorRangeIndex = typeLayout->getBindingRangeFirstDescriptorRangeIndex(bindingRangeIndex);
+ for(Index j = 0; j < descriptorRangeCount; ++j)
{
- auto slangBindingType =
- typeLayout->getDescriptorSetDescriptorRangeType(s, r);
+ Index descriptorRangeIndex = firstDescriptorRangeIndex + j;
+ auto slangDescriptorType = typeLayout->getDescriptorSetDescriptorRangeType(slangDescriptorSetIndex, descriptorRangeIndex);
- switch (slangBindingType)
+ // Certain kinds of descriptor ranges reflected by Slang do not
+ // manifest as descriptors at the Vulkan level, so we will skip those.
+ //
+ switch (slangDescriptorType)
{
case slang::BindingType::ExistentialValue:
case slang::BindingType::InlineUniformData:
@@ -813,50 +1054,183 @@ public:
break;
}
- auto vkDescriptorType = _mapDescriptorType(slangBindingType);
+ auto vkDescriptorType = _mapDescriptorType(slangDescriptorType);
VkDescriptorSetLayoutBinding vkBindingRangeDesc = {};
- vkBindingRangeDesc.binding = bindingOffset +
- (uint32_t)typeLayout->getDescriptorSetDescriptorRangeIndexOffset(s, r);
- vkBindingRangeDesc.descriptorCount =
- (uint32_t)typeLayout->getDescriptorSetDescriptorRangeDescriptorCount(
- s, r);
+ vkBindingRangeDesc.binding = offset.binding + (uint32_t)typeLayout->getDescriptorSetDescriptorRangeIndexOffset(slangDescriptorSetIndex, descriptorRangeIndex);
+ vkBindingRangeDesc.descriptorCount = (uint32_t)typeLayout->getDescriptorSetDescriptorRangeDescriptorCount(slangDescriptorSetIndex, descriptorRangeIndex);
vkBindingRangeDesc.descriptorType = vkDescriptorType;
vkBindingRangeDesc.stageFlags = VK_SHADER_STAGE_ALL;
- if (varLayout)
+
+ descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
+ }
+ }
+
+ // We skipped over the sub-object ranges when adding descriptors above,
+ // and now we will address that oversight by iterating over just
+ // the sub-object ranges.
+ //
+ Index subObjectRangeCount = typeLayout->getSubObjectRangeCount();
+ for(Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; ++subObjectRangeIndex)
+ {
+ auto bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(subObjectRangeIndex);
+ auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
+
+ auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ SLANG_ASSERT(subObjectTypeLayout);
+
+ BindingOffset subObjectRangeOffset = offset;
+ subObjectRangeOffset += BindingOffset(typeLayout->getSubObjectRangeOffset(subObjectRangeIndex));
+
+ switch(bindingType)
+ {
+ // A `ParameterBlock<X>` never contributes descripto ranges to the
+ // decriptor sets of a parent object.
+ //
+ case slang::BindingType::ParameterBlock:
+ default:
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ // An interest/existential-typed sub-object range will only contribute descriptor
+ // ranges to a parent object in the case where it has been specialied, which
+ // is precisely the case where the Slang reflection information will tell us
+ // about its "pending" layout.
+ //
+ if(auto pendingTypeLayout = subObjectTypeLayout->getPendingDataTypeLayout())
{
- auto category =
- typeLayout->getDescriptorSetDescriptorRangeCategory(s, r);
- vkBindingRangeDesc.binding += (uint32_t)varLayout->getOffset(category);
+ BindingOffset pendingOffset = BindingOffset(subObjectRangeOffset.pending);
+ _addDescriptorRangesAsValue(pendingTypeLayout, pendingOffset);
}
- descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
+ break;
+
+ case slang::BindingType::ConstantBuffer:
+ {
+ // A `ConstantBuffer<X>` range will contribute any nested descriptor
+ // ranges in `X`, along with a leading descriptor range for a
+ // uniform buffer to hold ordinary/uniform data, if there is any.
+
+ SLANG_ASSERT(subObjectTypeLayout);
+
+ auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
+ SLANG_ASSERT(containerVarLayout);
+
+ auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
+ SLANG_ASSERT(elementVarLayout);
+
+ auto elementTypeLayout = elementVarLayout->getTypeLayout();
+ SLANG_ASSERT(elementTypeLayout);
+
+ BindingOffset containerOffset = subObjectRangeOffset;
+ containerOffset += BindingOffset(subObjectTypeLayout->getContainerVarLayout());
+
+ BindingOffset elementOffset = subObjectRangeOffset;
+ elementOffset += BindingOffset(elementVarLayout);
+
+ _addDescriptorRangesAsConstantBuffer(elementTypeLayout, containerOffset, elementOffset);
+ }
+ break;
+
+ case slang::BindingType::PushConstant:
+ {
+ // This case indicates a `ConstantBuffer<X>` that was marked as being
+ // used for push constants.
+ //
+ // Much of the handling is the same as for an ordinary `ConstantBuffer<X>`,
+ // but of course we need to handle the ordinary data part differently.
+
+ SLANG_ASSERT(subObjectTypeLayout);
+
+ auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
+ SLANG_ASSERT(containerVarLayout);
+
+ auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
+ SLANG_ASSERT(elementVarLayout);
+
+ auto elementTypeLayout = elementVarLayout->getTypeLayout();
+ SLANG_ASSERT(elementTypeLayout);
+
+ BindingOffset containerOffset = subObjectRangeOffset;
+ containerOffset += BindingOffset(subObjectTypeLayout->getContainerVarLayout());
+
+ BindingOffset elementOffset = subObjectRangeOffset;
+ elementOffset += BindingOffset(elementVarLayout);
+
+ _addDescriptorRangesAsPushConstantBuffer(elementTypeLayout, containerOffset, elementOffset);
+ }
+ break;
}
+
}
- return SLANG_OK;
}
- Result setElementTypeLayout(
- slang::TypeLayoutReflection* typeLayout,
- bool buildDescriptorSetLayout)
+ /// Add the descriptor ranges implied by a `ConstantBuffer<X>` where `X` is
+ /// described by `elementTypeLayout`.
+ ///
+ /// The `containerOffset` and `elementOffset` are the binding offsets that
+ /// should apply to the buffer itself and the contents of the buffer, respectively.
+ ///
+ void _addDescriptorRangesAsConstantBuffer(
+ slang::TypeLayoutReflection* elementTypeLayout,
+ BindingOffset const& containerOffset,
+ BindingOffset const& elementOffset)
{
- // First we will use the Slang layout information to allocate
- // the descriptor set layout(s) required to store values
- // of the given type.
- //
- if (buildDescriptorSetLayout)
+ // If the type has ordinary uniform data fields, we need to make sure to create
+ // a descriptor set with a constant buffer binding in the case that the shader
+ // object is bound as a stand alone parameter block.
+ if (elementTypeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM) != 0)
{
- SLANG_RETURN_ON_FAIL(_addDescriptorSets(typeLayout, true));
+ auto descriptorSetIndex = findOrAddDescriptorSet(containerOffset.bindingSet);
+ auto& descriptorSetInfo = m_descriptorSetBuildInfos[descriptorSetIndex];
+ VkDescriptorSetLayoutBinding vkBindingRangeDesc = {};
+ vkBindingRangeDesc.binding = containerOffset.binding;
+ vkBindingRangeDesc.descriptorCount = 1;
+ vkBindingRangeDesc.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ vkBindingRangeDesc.stageFlags = VK_SHADER_STAGE_ALL;
+ descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
}
- typeLayout = _unwrapParameterGroups(typeLayout);
+ _addDescriptorRangesAsValue(elementTypeLayout, elementOffset);
+ }
- m_elementTypeLayout = typeLayout;
+ /// Add the descriptor ranges implied by a `PushConstantBuffer<X>` where `X` is
+ /// described by `elementTypeLayout`.
+ ///
+ /// The `containerOffset` and `elementOffset` are the binding offsets that
+ /// should apply to the buffer itself and the contents of the buffer, respectively.
+ ///
+ void _addDescriptorRangesAsPushConstantBuffer(
+ slang::TypeLayoutReflection* elementTypeLayout,
+ BindingOffset const& containerOffset,
+ BindingOffset const& elementOffset)
+ {
+ // If the type has ordinary uniform data fields, we need to make sure to create
+ // a descriptor set with a constant buffer binding in the case that the shader
+ // object is bound as a stand alone parameter block.
+ auto ordinaryDataSize = (uint32_t) elementTypeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ if (ordinaryDataSize != 0)
+ {
+ auto pushConstantRangeIndex = containerOffset.pushConstantRange;
+ VkPushConstantRange vkPushConstantRange = {};
+ vkPushConstantRange.size = ordinaryDataSize;
+ vkPushConstantRange.stageFlags = VK_SHADER_STAGE_ALL; // TODO: be more clever
- // Next we will compute the binding ranges that are used to store
- // the logical contents of the object in memory. These will relate
- // to the descriptor ranges in the various sets, but not always
- // in a one-to-one fashion.
+ while(m_ownPushConstantRanges.getCount() <= pushConstantRangeIndex)
+ {
+ VkPushConstantRange emptyRange = { 0 };
+ m_ownPushConstantRanges.add(emptyRange);
+ }
+
+ m_ownPushConstantRanges[pushConstantRangeIndex] = vkPushConstantRange;
+ }
+
+ _addDescriptorRangesAsValue(elementTypeLayout, elementOffset);
+ }
+ /// Add binding ranges to this shader object layout, as implied by the given `typeLayout`
+ void addBindingRanges(
+ slang::TypeLayoutReflection* typeLayout)
+ {
SlangInt bindingRangeCount = typeLayout->getBindingRangeCount();
for (SlangInt r = 0; r < bindingRangeCount; ++r)
{
@@ -865,10 +1239,6 @@ public:
slang::TypeLayoutReflection* slangLeafTypeLayout =
typeLayout->getBindingRangeLeafTypeLayout(r);
- SlangInt descriptorSetIndex = typeLayout->getBindingRangeDescriptorSetIndex(r);
- SlangInt rangeIndexInDescriptorSet =
- typeLayout->getBindingRangeFirstDescriptorRangeIndex(r);
-
Index baseIndex = 0;
switch (slangBindingType)
{
@@ -882,11 +1252,13 @@ public:
case slang::BindingType::Sampler:
baseIndex = m_samplerCount;
m_samplerCount += count;
+ m_totalBindingCount += 1;
break;
case slang::BindingType::CombinedTextureSampler:
baseIndex = m_combinedTextureSamplerCount;
m_combinedTextureSamplerCount += count;
+ m_totalBindingCount += 1;
break;
case slang::BindingType::VaryingInput:
@@ -901,6 +1273,7 @@ public:
default:
baseIndex = m_resourceViewCount;
m_resourceViewCount += count;
+ m_totalBindingCount += 1;
break;
}
@@ -908,8 +1281,32 @@ public:
bindingRangeInfo.bindingType = slangBindingType;
bindingRangeInfo.count = count;
bindingRangeInfo.baseIndex = baseIndex;
- bindingRangeInfo.descriptorSetIndex = descriptorSetIndex;
- bindingRangeInfo.rangeIndexInDescriptorSet = rangeIndexInDescriptorSet;
+
+ // We'd like to extract the information on the GLSL/SPIR-V
+ // `binding` that this range should bind into (or whatever
+ // other specific kind of offset/index is appropriate to it).
+ //
+ // A binding range represents a logical member of the shader
+ // object type, and it may encompass zero or more *descriptor
+ // ranges* that describe how it is physically bound to pipeline
+ // state.
+ //
+ // If the current bindign range is backed by at least one descriptor
+ // range then we can query the binding offset of that descriptor
+ // range. We expect that in the common case there will be exactly
+ // one descriptor range, and we can extract the information easily.
+ //
+ if(typeLayout->getBindingRangeDescriptorRangeCount(r) != 0)
+ {
+ SlangInt descriptorSetIndex = typeLayout->getBindingRangeDescriptorSetIndex(r);
+ SlangInt descriptorRangeIndex = typeLayout->getBindingRangeFirstDescriptorRangeIndex(r);
+
+ auto set = typeLayout->getDescriptorSetSpaceOffset(descriptorSetIndex);
+ auto bindingOffset = typeLayout->getDescriptorSetDescriptorRangeIndexOffset(descriptorSetIndex, descriptorRangeIndex);
+
+ bindingRangeInfo.setOffset = uint32_t(set);
+ bindingRangeInfo.bindingOffset = uint32_t(bindingOffset);
+ }
m_bindingRanges.add(bindingRangeInfo);
}
@@ -918,6 +1315,7 @@ public:
for (SlangInt r = 0; r < subObjectRangeCount; ++r)
{
SlangInt bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(r);
+ auto& bindingRange = m_bindingRanges[bindingRangeIndex];
auto slangBindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
slang::TypeLayoutReflection* slangLeafTypeLayout =
typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
@@ -930,20 +1328,103 @@ public:
// know the appropraite type/layout of sub-object to allocate.
//
RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
- if (slangBindingType != slang::BindingType::ExistentialValue)
+ switch(slangBindingType)
{
- ShaderObjectLayoutImpl::createForElementType(
- m_renderer,
- slangLeafTypeLayout->getElementTypeLayout(),
- true,
- subObjectLayout.writeRef());
+ default:
+ {
+ auto elementTypeLayout = slangLeafTypeLayout->getElementTypeLayout();
+ ShaderObjectLayoutImpl::createForElementType(
+ m_renderer,
+ elementTypeLayout,
+ subObjectLayout.writeRef());
+ }
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ if(auto pendingTypeLayout = slangLeafTypeLayout->getPendingDataTypeLayout())
+ {
+ ShaderObjectLayoutImpl::createForElementType(
+ m_renderer,
+ pendingTypeLayout,
+ subObjectLayout.writeRef());
+ }
+ break;
}
SubObjectRangeInfo subObjectRange;
subObjectRange.bindingRangeIndex = bindingRangeIndex;
subObjectRange.layout = subObjectLayout;
+
+ // We will use Slang reflection infromation to extract the offset information
+ // for each sub-object range.
+ //
+ // TODO: We should also be extracting the uniform offset here.
+ //
+ subObjectRange.offset = SubObjectRangeOffset(typeLayout->getSubObjectRangeOffset(r));
+ subObjectRange.stride = SubObjectRangeStride(slangLeafTypeLayout);
+
+ switch(slangBindingType)
+ {
+ case slang::BindingType::ParameterBlock:
+ m_childDescriptorSetCount += subObjectLayout->getTotalDescriptorSetCount();
+ m_childPushConstantRangeCount += subObjectLayout->getTotalPushConstantRangeCount();
+ break;
+
+ case slang::BindingType::ConstantBuffer:
+ m_childDescriptorSetCount += subObjectLayout->getChildDescriptorSetCount();
+ m_totalBindingCount += subObjectLayout->getTotalBindingCount();
+ m_childPushConstantRangeCount += subObjectLayout->getTotalPushConstantRangeCount();
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ if(subObjectLayout)
+ {
+ m_childDescriptorSetCount += subObjectLayout->getChildDescriptorSetCount();
+ m_totalBindingCount += subObjectLayout->getTotalBindingCount();
+ m_childPushConstantRangeCount += subObjectLayout->getTotalPushConstantRangeCount();
+
+ // An interface-type range that includes ordinary data can
+ // increase the size of the ordinary data buffer we need to
+ // allocate for the parent object.
+ //
+ uint32_t ordinaryDataEnd = subObjectRange.offset.pendingOrdinaryData
+ + (uint32_t) bindingRange.count * subObjectRange.stride.pendingOrdinaryData;
+
+ if(ordinaryDataEnd > m_totalOrdinaryDataSize)
+ {
+ m_totalOrdinaryDataSize = ordinaryDataEnd;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
m_subObjectRanges.add(subObjectRange);
}
+ }
+
+ Result setElementTypeLayout(
+ slang::TypeLayoutReflection* typeLayout)
+ {
+ typeLayout = _unwrapParameterGroups(typeLayout);
+ m_elementTypeLayout = typeLayout;
+
+ m_totalOrdinaryDataSize = (uint32_t) typeLayout->getSize();
+
+ // Next we will compute the binding ranges that are used to store
+ // the logical contents of the object in memory. These will relate
+ // to the descriptor ranges in the various sets, but not always
+ // in a one-to-one fashion.
+
+ addBindingRanges(typeLayout);
+
+ // Note: This routine does not take responsibility for
+ // adding descriptor ranges at all, because the exact way
+ // that descriptor ranges need to be added varies between
+ // ordinary shader objects, root shader objects, and entry points.
+
return SLANG_OK;
}
@@ -960,11 +1441,53 @@ public:
static Result createForElementType(
VKDevice* renderer,
slang::TypeLayoutReflection* elementType,
- bool createConstantBufferForOrdinaryData,
ShaderObjectLayoutImpl** outLayout)
{
Builder builder(renderer);
- builder.setElementTypeLayout(elementType, createConstantBufferForOrdinaryData);
+ builder.setElementTypeLayout(elementType);
+
+ // When constructing a shader object layout directly from a reflected
+ // type in Slang, we want to compute the descriptor sets and ranges
+ // that would be used if this object were bound as a parameter block.
+ //
+ // It might seem like we need to deal with the other cases for how
+ // the shader object might be bound, but the descriptor ranges we
+ // compute here will only ever be used in parameter-block case.
+ //
+ // One important wrinkle is that we know that the parameter block
+ // allocated for `elementType` will potentially need a buffer `binding`
+ // for any ordinary data it contains.
+
+ bool needsOrdinaryDataBuffer = elementType->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM) != 0;
+ uint32_t ordinaryDataBufferCount = needsOrdinaryDataBuffer ? 1 : 0;
+
+ // When binding the object, we know that the ordinary data buffer will
+ // always use a the first available `binding`, so its offset will be
+ // all zeroes.
+ //
+ BindingOffset containerOffset;
+
+ // In contrast, the `binding`s used by all the other entries in the
+ // parameter block will need to be offset by one if there was
+ // an ordinary data buffer.
+ //
+ BindingOffset elementOffset;
+ elementOffset.binding = ordinaryDataBufferCount;
+
+ // Furthermore, any `binding`s that arise due to "pending" data
+ // in the type of the object (due to specialization for existential types)
+ // will need to come after all the other `binding`s that were
+ // part of the "primary" (unspecialized) data.
+ //
+ uint32_t primaryDescriptorCount = ordinaryDataBufferCount
+ + (uint32_t) elementType->getSize(SLANG_PARAMETER_CATEGORY_DESCRIPTOR_TABLE_SLOT);
+ elementOffset.pending.binding = primaryDescriptorCount;
+
+ // Once we've computed the offset information, we simply add the
+ // descriptor ranges as if things were declared as a `ConstantBuffer<X>`,
+ // since that is how things will be laid out inside the parameter block.
+ //
+ builder._addDescriptorRangesAsConstantBuffer(elementType, containerOffset, elementOffset);
return builder.build(outLayout);
}
@@ -977,9 +1500,55 @@ public:
}
}
- List<DescriptorSetInfo> const& getDescriptorSets() { return m_descriptorSetInfos; }
+ /// Get the number of descriptor sets that are allocated for this object itself
+ /// (if it needed to be bound as a parameter block).
+ ///
+ uint32_t getOwnDescriptorSetCount() { return uint32_t(m_descriptorSetInfos.getCount()); }
+
+ /// Get information about the descriptor sets that would be allocated to
+ /// represent this object itself as a parameter block.
+ ///
+ List<DescriptorSetInfo> const& getOwnDescriptorSets() { return m_descriptorSetInfos; }
+
+ /// Get the number of descriptor sets that would need to be allocated and bound
+ /// to represent the children of this object if it were bound as a parameter
+ /// block.
+ ///
+ /// To a first approximation, this is the number of (transitive) children
+ /// that are declared as `ParameterBlock<X>`.
+ ///
+ uint32_t getChildDescriptorSetCount() { return m_childDescriptorSetCount; }
+
+ /// Get the total number of descriptor sets that would need to be allocated and bound
+ /// to represent this object and its children (transitively) as a parameter block.
+ ///
+ uint32_t getTotalDescriptorSetCount() { return getOwnDescriptorSetCount() + getChildDescriptorSetCount(); }
+
+ /// Get the total number of `binding`s required to represent this type and its
+ /// (transitive) children.
+ ///
+ /// Note that this count does *not* include bindings that would be part of child
+ /// parameter blocks, nor does it include the binding for an ordinary data buffer,
+ /// if one is needed.
+ ///
+ uint32_t getTotalBindingCount() { return m_totalBindingCount; }
+
- uint32_t getPushConstantSize() { return m_pushConstantSize; }
+ /// Get the list of push constant ranges required to bind the state of this object itself.
+ List<VkPushConstantRange> const& getOwnPushConstantRanges() const { return m_ownPushConstantRanges; }
+
+ /// Get the number of push constant ranges required to bind the state of this object itself.
+ uint32_t getOwnPushConstantRangeCount() { return (uint32_t) m_ownPushConstantRanges.getCount(); }
+
+ /// Get the number of push constant ranges required to bind the state of the (transitive)
+ /// children of this object.
+ uint32_t getChildPushConstantRangeCount() { return m_childPushConstantRangeCount; }
+
+ /// Get the total number of push constant ranges required to bind the state of this object
+ /// and its (transitive) children.
+ uint32_t getTotalPushConstantRangeCount() { return getOwnPushConstantRangeCount() + getChildPushConstantRangeCount(); }
+
+ uint32_t getTotalOrdinaryDataSize() const { return m_totalOrdinaryDataSize; }
List<BindingRangeInfo> const& getBindingRanges() { return m_bindingRanges; }
@@ -1014,12 +1583,15 @@ public:
m_bindingRanges = builder->m_bindingRanges;
m_descriptorSetInfos = _Move(builder->m_descriptorSetBuildInfos);
- m_pushConstantSize = builder->m_pushConstantSize;
+ m_ownPushConstantRanges = builder->m_ownPushConstantRanges;
m_resourceViewCount = builder->m_resourceViewCount;
m_samplerCount = builder->m_samplerCount;
m_combinedTextureSamplerCount = builder->m_combinedTextureSamplerCount;
+ m_childDescriptorSetCount = builder->m_childDescriptorSetCount;
+ m_totalBindingCount = builder->m_totalBindingCount;
m_subObjectCount = builder->m_subObjectCount;
m_subObjectRanges = builder->m_subObjectRanges;
+ m_totalOrdinaryDataSize = builder->m_totalOrdinaryDataSize;
// Create VkDescriptorSetLayout for all descriptor sets.
for (auto& descriptorSetInfo : m_descriptorSetInfos)
@@ -1042,7 +1614,13 @@ public:
Index m_samplerCount = 0;
Index m_combinedTextureSamplerCount = 0;
Index m_subObjectCount = 0;
- uint32_t m_pushConstantSize = 0;
+ List<VkPushConstantRange> m_ownPushConstantRanges;
+ uint32_t m_childPushConstantRangeCount = 0;
+
+ uint32_t m_childDescriptorSetCount = 0;
+ uint32_t m_totalBindingCount = 0;
+ uint32_t m_totalOrdinaryDataSize = 0;
+
List<SubObjectRangeInfo> m_subObjectRanges;
};
@@ -1069,15 +1647,18 @@ public:
void addEntryPointParams(slang::EntryPointLayout* entryPointLayout)
{
m_slangEntryPointLayout = entryPointLayout;
- setElementTypeLayout(entryPointLayout->getTypeLayout(), false);
- m_pushConstantSize = (uint32_t)_unwrapParameterGroups(entryPointLayout->getTypeLayout())
- ->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
- m_stage = VulkanUtil::getShaderStage(entryPointLayout->getStage());
+ setElementTypeLayout(entryPointLayout->getTypeLayout());
+ m_shaderStageFlag = VulkanUtil::getShaderStage(entryPointLayout->getStage());
+
+ // Note: we do not bother adding any descriptor sets/ranges here,
+ // because the descriptor ranges of an entry point will simply
+ // be allocated as part of the descriptor sets for the root
+ // shader object.
}
slang::EntryPointLayout* m_slangEntryPointLayout = nullptr;
- VkShaderStageFlags m_stage;
+ VkShaderStageFlags m_shaderStageFlag;
};
Result _init(Builder const* builder)
@@ -1087,16 +1668,16 @@ public:
SLANG_RETURN_ON_FAIL(Super::_init(builder));
m_slangEntryPointLayout = builder->m_slangEntryPointLayout;
- m_stage = builder->m_stage;
+ m_shaderStageFlag = builder->m_shaderStageFlag;
return SLANG_OK;
}
- VkShaderStageFlags getStage() const { return m_stage; }
+ VkShaderStageFlags getShaderStageFlag() const { return m_shaderStageFlag; }
slang::EntryPointLayout* getSlangLayout() const { return m_slangEntryPointLayout; };
slang::EntryPointLayout* m_slangEntryPointLayout;
- VkShaderStageFlags m_stage;
+ VkShaderStageFlags m_shaderStageFlag;
};
class RootShaderObjectLayout : public ShaderObjectLayoutImpl
@@ -1113,10 +1694,14 @@ public:
}
}
+ /// Information stored for each entry point of the program
struct EntryPointInfo
{
+ /// Layout of the entry point
RefPtr<EntryPointLayout> layout;
- Index rangeOffset;
+
+ /// Offset for binding the entry point, relative to the start of the program
+ BindingOffset offset;
};
struct Builder : Super::Builder
@@ -1140,34 +1725,74 @@ public:
void addGlobalParams(slang::VariableLayoutReflection* globalsLayout)
{
- setElementTypeLayout(globalsLayout->getTypeLayout(), true);
+ setElementTypeLayout(globalsLayout->getTypeLayout());
+
+ // We need to populate our descriptor sets/ranges with information
+ // from the layout of the global scope.
+ //
+ // While we expect that the parameter in the global scope start
+ // at an offset of zero, it is also worth querying the offset
+ // information because it could impact the locations assigned
+ // to "pending" data in the case of static specialization.
+ //
+ BindingOffset offset(globalsLayout);
+
+ // Note: We are adding descriptor ranges here based directly on
+ // the type of the global-scope layout. The type layout for the
+ // global scope will either be something like a `struct GlobalParams`
+ // that contains all the global-scope parameters or a `ConstantBuffer<GlobalParams>`
+ // and in either case the `_addDescriptorRangesAsValue` can properly
+ // add all the ranges implied.
+ //
+ // As a result we don't require any special-case logic here to
+ // deal with the possibility of a "default" constant buffer allocated
+ // for global-scope parameters of uniform/ordinary type.
+ //
+ _addDescriptorRangesAsValue(globalsLayout->getTypeLayout(), offset);
+
+ // We want to keep track of the offset that was applied to "pending"
+ // data because we will need it again later when it comes time to
+ // actually bind things.
+ //
+ m_pendingDataOffset = offset.pending;
}
void addEntryPoint(EntryPointLayout* entryPointLayout)
{
+ auto slangEntryPointLayout = entryPointLayout->getSlangLayout();
+ auto entryPointVarLayout = slangEntryPointLayout->getVarLayout();
+
+ // The offset information for each entry point needs to
+ // be adjusted by any offset for "pending" data that
+ // was recorded in the global-scope layout.
+ //
+ // TODO(tfoley): Double-check that this is correct.
+
+ BindingOffset entryPointOffset(entryPointVarLayout);
+ entryPointOffset.pending += m_pendingDataOffset;
+
EntryPointInfo info;
info.layout = entryPointLayout;
+ info.offset = entryPointOffset;
+
+ // Similar to the case for the global scope, we expect the
+ // type layout for the entry point parameters to be either
+ // a `struct EntryPointParams` or a `PushConstantBuffer<EntryPointParams>`.
+ // Rather than deal with the different cases here, we will
+ // trust the `_addDescriptorRangesAsValue` code to handle
+ // either case correctly.
+ //
+ _addDescriptorRangesAsValue(entryPointVarLayout->getTypeLayout(), entryPointOffset);
- if (m_descriptorSetBuildInfos.getCount())
- {
- info.rangeOffset = m_descriptorSetBuildInfos[0].vkBindings.getCount();
- }
- else
- {
- info.rangeOffset = 0;
- }
-
- auto slangEntryPointLayout = entryPointLayout->getSlangLayout();
- _addDescriptorSets(
- _unwrapParameterGroups(slangEntryPointLayout->getTypeLayout()),
- false,
- slangEntryPointLayout->getVarLayout());
m_entryPoints.add(info);
}
slang::IComponentType* m_program;
slang::ProgramLayout* m_programLayout;
List<EntryPointInfo> m_entryPoints;
+
+ /// Offset to apply to "pending" data from this object, sub-objects, and entry points
+ SimpleBindingOffset m_pendingDataOffset;
};
Index findEntryPointIndex(VkShaderStageFlags stage)
@@ -1176,7 +1801,7 @@ public:
for (Index i = 0; i < entryPointCount; ++i)
{
auto entryPoint = m_entryPoints[i];
- if (entryPoint.layout->getStage() == stage)
+ if (entryPoint.layout->getShaderStageFlag() == stage)
return i;
}
return -1;
@@ -1214,9 +1839,14 @@ public:
return SLANG_OK;
}
+ SimpleBindingOffset const& getPendingDataOffset() const { return m_pendingDataOffset; }
+
slang::IComponentType* getSlangProgram() const { return m_program; }
slang::ProgramLayout* getSlangProgramLayout() const { return m_programLayout; }
+ /// Get all of the push constant ranges that will be bound for this object and all (transitive) sub-objects
+ List<VkPushConstantRange> const& getAllPushConstantRanges() { return m_allPushConstantRanges; }
+
protected:
Result _init(Builder const* builder)
{
@@ -1227,75 +1857,156 @@ public:
m_program = builder->m_program;
m_programLayout = builder->m_programLayout;
m_entryPoints = _Move(builder->m_entryPoints);
+ m_pendingDataOffset = builder->m_pendingDataOffset;
m_renderer = renderer;
+ // If the program has unbound specialization parameters,
+ // then we will avoid creating a final Vulkan pipeline layout.
+ //
+ // TODO: We should really create the information necessary
+ // for binding as part of a separate object, so that we have
+ // a clean seperation between what is needed for writing into
+ // a shader object vs. what is needed for binding it to the
+ // pipeline. We eventually need to be able to create bindable
+ // state objects from unspecialized programs, in order to
+ // support dynamic dispatch.
+ //
if (m_program->getSpecializationParamCount() != 0)
return SLANG_OK;
- // For fully specialized shader programs, we create a Vulkan pipeline layout now.
+ // Otherwise, we need to create a final (bindable) layout.
+ //
+ // We will use a recursive walk to collect all the `VkDescriptorSetLayout`s
+ // that are required for the global scope, sub-objects, and entry points.
+ //
+ SLANG_RETURN_ON_FAIL(addAllDescriptorSets());
- // First, collect `VkDescriptorSetLayout`s for the global scope and all sub-objects
- // referenced via a `ParameterBlock` from shader object layouts.
- SLANG_RETURN_ON_FAIL(addDescriptorSetLayoutRec(this));
+ // We will also use a recursive walk to collect all the push-constant
+ // ranges needed for this object, sub-objects, and entry points.
+ //
+ SLANG_RETURN_ON_FAIL(addAllPushConstantRanges());
- // Next, collect push constant ranges. We will use one descriptor range for each
- // entry point that has uniform parameters.
- uint32_t pushConstantOffset = 0;
- for (auto& entryPoint : m_entryPoints)
- {
- auto size = entryPoint.layout->getPushConstantSize();
- if (size)
- {
- VkPushConstantRange pushConstantRange = {};
- pushConstantRange.offset = pushConstantOffset;
- pushConstantRange.size = size;
- pushConstantRange.stageFlags = entryPoint.layout->getStage();
- m_pushConstantRanges.add(pushConstantRange);
- pushConstantOffset += size;
- }
- }
+ // Once we've collected the information across the entire
+ // tree of sub-objects
// Now call Vulkan API to create a pipeline layout.
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {};
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutCreateInfo.setLayoutCount = (uint32_t)m_vkDescriptorSetLayouts.getCount();
pipelineLayoutCreateInfo.pSetLayouts = m_vkDescriptorSetLayouts.getBuffer();
- if (m_pushConstantRanges.getCount())
+ if (m_allPushConstantRanges.getCount())
{
pipelineLayoutCreateInfo.pushConstantRangeCount =
- (uint32_t)m_pushConstantRanges.getCount();
+ (uint32_t)m_allPushConstantRanges.getCount();
pipelineLayoutCreateInfo.pPushConstantRanges =
- m_pushConstantRanges.getBuffer();
+ m_allPushConstantRanges.getBuffer();
}
SLANG_RETURN_ON_FAIL(m_renderer->m_api.vkCreatePipelineLayout(
m_renderer->m_api.m_device, &pipelineLayoutCreateInfo, nullptr, &m_pipelineLayout));
return SLANG_OK;
}
- // Recusively add `VkDescriptorSetLayout` for all descriptor sets used by this and children
- // shader objects and add them to `m_vkDescriptorSetLayouts`.
- Result addDescriptorSetLayoutRec(ShaderObjectLayoutImpl* layout)
+ /// Add all the descriptor sets implied by this root object and sub-objects
+ Result addAllDescriptorSets()
+ {
+ SLANG_RETURN_ON_FAIL(addAllDescriptorSetsRec(this));
+
+ // Note: the descriptor ranges/sets for direct entry point parameters
+ // were already enumerated into the ranges/sets of the root object itself,
+ // so we don't wnat to add them again.
+ //
+ // We do however have to deal with the possibility that an entry
+ // point could introduce "child" descriptor sets, e.g., because it
+ // has a `ParameterBlock<X>` parameter.
+ //
+ for(auto& entryPoint : getEntryPoints())
+ {
+ SLANG_RETURN_ON_FAIL(addChildDescriptorSetsRec(entryPoint.layout));
+ }
+
+ return SLANG_OK;
+ }
+
+ /// Recurisvely add descriptor sets defined by `layout` and sub-objects
+ Result addAllDescriptorSetsRec(ShaderObjectLayoutImpl* layout)
{
- for (auto& descSetInfo : layout->getDescriptorSets())
+ // TODO: This logic assumes that descriptor sets are all contiguous
+ // and have been allocated in a global order that matches the order
+ // of enumeration here.
+
+ for (auto& descSetInfo : layout->getOwnDescriptorSets())
{
m_vkDescriptorSetLayouts.add(descSetInfo.descriptorSetLayout);
}
- // Note: entry point parameters in a `RootShaderObject` has already been included
- // in `layout->getDescriptorSets()` during `RootShaderObjectLayout` construction,
- // so we do not need to enumerate entry point array here.
-
- // However, for sub-objects referenced through `ParameterBlock`s, we do need to
- // add their descriptor sets to our pipeline layout.
- // Binding ranges for sub-objects referenced through `ConstantBuffer`s are also
- // included in this object's layout already, so no need to skip those.
+ SLANG_RETURN_ON_FAIL(addChildDescriptorSetsRec(layout));
+ return SLANG_OK;
+ }
+ /// Recurisvely add descriptor sets defined by sub-objects of `layout`
+ Result addChildDescriptorSetsRec(ShaderObjectLayoutImpl* layout)
+ {
for (auto& subObject : layout->getSubObjectRanges())
{
auto bindingRange = layout->getBindingRange(subObject.bindingRangeIndex);
- if (bindingRange.bindingType == slang::BindingType::ParameterBlock)
+ switch(bindingRange.bindingType)
+ {
+ case slang::BindingType::ParameterBlock:
+ SLANG_RETURN_ON_FAIL(addAllDescriptorSetsRec(subObject.layout));
+ break;
+
+ default:
+ if(auto subObjectLayout = subObject.layout)
+ {
+ SLANG_RETURN_ON_FAIL(addChildDescriptorSetsRec(subObject.layout));
+ }
+ break;
+ }
+ }
+
+ return SLANG_OK;
+ }
+
+ /// Add all the push-constant ranges implied by this root object and sub-objects
+ Result addAllPushConstantRanges()
+ {
+ SLANG_RETURN_ON_FAIL(addAllPushConstantRangesRec(this));
+
+ for(auto& entryPoint : getEntryPoints())
+ {
+ SLANG_RETURN_ON_FAIL(addChildPushConstantRangesRec(entryPoint.layout));
+ }
+
+ return SLANG_OK;
+ }
+
+ /// Recurisvely add push-constant ranges defined by `layout` and sub-objects
+ Result addAllPushConstantRangesRec(ShaderObjectLayoutImpl* layout)
+ {
+ // TODO: This logic assumes that push-constant ranges are all contiguous
+ // and have been allocated in a global order that matches the order
+ // of enumeration here.
+
+ for (auto pushConstantRange : layout->getOwnPushConstantRanges())
+ {
+ pushConstantRange.offset = m_totalPushConstantSize;
+ m_totalPushConstantSize += pushConstantRange.size;
+
+ m_allPushConstantRanges.add(pushConstantRange);
+ }
+
+ SLANG_RETURN_ON_FAIL(addChildPushConstantRangesRec(layout));
+ return SLANG_OK;
+ }
+
+ /// Recurisvely add push-constant ranges defined by sub-objects of `layout`
+ Result addChildPushConstantRangesRec(ShaderObjectLayoutImpl* layout)
+ {
+ for (auto& subObject : layout->getSubObjectRanges())
+ {
+ if(auto subObjectLayout = subObject.layout)
{
- SLANG_RETURN_ON_FAIL(addDescriptorSetLayoutRec(subObject.layout));
+ SLANG_RETURN_ON_FAIL(addAllPushConstantRangesRec(subObject.layout));
}
}
@@ -1308,7 +2019,10 @@ public:
List<EntryPointInfo> m_entryPoints;
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
Array<VkDescriptorSetLayout, kMaxDescriptorSets> m_vkDescriptorSetLayouts;
- Array<VkPushConstantRange, 8> m_pushConstantRanges;
+ List<VkPushConstantRange> m_allPushConstantRanges;
+ uint32_t m_totalPushConstantSize = 0;
+
+ SimpleBindingOffset m_pendingDataOffset;
VKDevice* m_renderer = nullptr;
};
@@ -1441,7 +2155,6 @@ public:
void flushBindingState(VkPipelineBindPoint pipelineBindPoint)
{
auto& api = *m_api;
- bindRootShaderObjectImpl(pipelineBindPoint);
// Get specialized pipeline state and bind it.
//
@@ -1449,6 +2162,9 @@ public:
m_device->maybeSpecializePipeline(
m_currentPipeline, &m_commandBuffer->m_rootObject, newPipeline);
PipelineStateImpl* newPipelineImpl = static_cast<PipelineStateImpl*>(newPipeline.Ptr());
+
+ bindRootShaderObjectImpl(pipelineBindPoint);
+
auto pipelineBindPointId = getBindPointIndex(pipelineBindPoint);
if (m_boundPipelines[pipelineBindPointId] != newPipelineImpl->m_pipeline)
{
@@ -1459,28 +2175,23 @@ public:
}
};
- union VulkanDescriptorInfo
- {
- VkDescriptorBufferInfo bufferInfo;
- VkDescriptorImageInfo imageInfo;
- };
- struct RootBindingState
+ /// Context information required when binding shader objects to the pipeline
+ struct RootBindingContext
{
- ShortList<VkWriteDescriptorSet, 32> descriptorSetWrites;
- ChunkedList<VulkanDescriptorInfo, 32> descriptorInfos;
- ChunkedList<VkBufferView, 8> bufferViews;
- Array<VkDescriptorSet, kMaxDescriptorSets> descriptorSets;
- ArrayView<VkPushConstantRange> pushConstantRanges;
+ /// The pipeline layout being used for binding
VkPipelineLayout pipelineLayout;
+
+ /// An allocator to use for descriptor sets during binding
DescriptorSetAllocator* descriptorSetAllocator;
+
+ /// The dvice being used
VKDevice* device;
- };
- struct BindingOffset
- {
- uint32_t uniformOffset;
- uint32_t pushConstantRangeOffset;
- uint32_t descriptorSetIndexOffset;
- uint32_t descriptorRangeOffset;
+
+ /// The descriptor sets that are being allocated and bound
+ VkDescriptorSet* descriptorSets;
+
+ /// Information about all the push-constant ranges that should be bound
+ ConstArrayView<VkPushConstantRange> pushConstantRanges;
};
class ShaderObjectImpl : public ShaderObjectBase
@@ -1949,10 +2660,8 @@ public:
// layout logic does for complex cases with multiple layers of nested arrays and
// structures.
//
- size_t subObjectRangePendingDataOffset =
- _getSubObjectRangePendingDataOffset(specializedLayout, subObjectRangeIndex);
- size_t subObjectRangePendingDataStride =
- _getSubObjectRangePendingDataStride(specializedLayout, subObjectRangeIndex);
+ size_t subObjectRangePendingDataOffset = subObjectRangeInfo.offset.pendingOrdinaryData;
+ size_t subObjectRangePendingDataStride = subObjectRangeInfo.stride.pendingOrdinaryData;
// If the range doesn't actually need/use the "pending" allocation at all, then
// we need to detect that case and skip such ranges.
@@ -1987,22 +2696,6 @@ public:
return SLANG_OK;
}
- // As discussed in `_writeOrdinaryData()`, these methods are just stubs waiting for
- // the "flat" Slang refelction information to provide access to the relevant data.
- //
- size_t _getSubObjectRangePendingDataOffset(
- ShaderObjectLayoutImpl* specializedLayout,
- Index subObjectRangeIndex)
- {
- return 0;
- }
- size_t _getSubObjectRangePendingDataStride(
- ShaderObjectLayoutImpl* specializedLayout,
- Index subObjectRangeIndex)
- {
- return 0;
- }
-
public:
struct CombinedTextureSamplerSlot
{
@@ -2011,218 +2704,219 @@ public:
operator bool() { return textureView && sampler; }
};
- // A shared template function for composing a VkWriteDescriptorSet structure.
- // The signature for `WriteDescriptorInfoFunc` is
- // `void(VkWriteDescriptorSet&, int startElement, int elementCount)`, which sets up
- // `VkWriteDescriptorSet::pBufferInfo`, `pImageInfo` or `pTexelBufferView` fields.
- template<typename WriteDescriptorInfoFunc, typename TResourceArrayView>
- static void _writeDescriptorRange(
- RootBindingState* bindingState,
- BindingOffset offset,
- VkDescriptorType descriptorType,
- TResourceArrayView resourceViews,
- const WriteDescriptorInfoFunc& writeDescriptorInfo)
+ /// Write a single desriptor using the Vulkan API
+ static inline void writeDescriptor(
+ RootBindingContext& context,
+ VkWriteDescriptorSet const& write)
{
- auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
- bool hasNullBinding = false;
- for (auto& ptr : resourceViews)
- {
- if (!ptr)
- {
- hasNullBinding = true;
- break;
- }
- }
- if (hasNullBinding)
- {
- for (Index i = 0; i < resourceViews.getCount(); i++)
- {
- if (!resourceViews[i])
- continue;
- VkWriteDescriptorSet write = {};
- write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
- write.descriptorCount = 1;
- write.descriptorType = descriptorType;
- write.dstArrayElement = (uint32_t)i;
- write.dstBinding = offset.descriptorRangeOffset;
- write.dstSet = descriptorSet;
- auto infos = bindingState->descriptorInfos.reserveRange(1);
- writeDescriptorInfo(write, (uint32_t)i, 1);
- bindingState->descriptorSetWrites.add(write);
- }
- return;
- }
-
- VkWriteDescriptorSet write = {};
- write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
- write.descriptorCount = (uint32_t)resourceViews.getCount();
- write.descriptorType = descriptorType;
- write.dstArrayElement = 0;
- write.dstBinding = offset.descriptorRangeOffset;
- write.dstSet = descriptorSet;
- writeDescriptorInfo(write, 0, write.descriptorCount);
- bindingState->descriptorSetWrites.add(write);
+ auto device = context.device;
+ device->m_api.vkUpdateDescriptorSets(
+ device->m_device,
+ 1,
+ &write,
+ 0,
+ nullptr);
}
static void writeBufferDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
BufferResourceImpl* buffer,
size_t bufferOffset,
size_t bufferSize)
{
- auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ VkDescriptorBufferInfo bufferInfo = {};
+ bufferInfo.buffer = buffer->m_buffer.m_buffer;
+ bufferInfo.offset = bufferOffset;
+ bufferInfo.range = bufferSize;
+
VkWriteDescriptorSet write = {};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.descriptorCount = 1;
write.descriptorType = descriptorType;
write.dstArrayElement = 0;
- write.dstBinding = offset.descriptorRangeOffset;
+ write.dstBinding = offset.binding;
write.dstSet = descriptorSet;
- auto& bufferInfo = bindingState->descriptorInfos.reserveRange(1)->bufferInfo;
write.pBufferInfo = &bufferInfo;
- bufferInfo.buffer = buffer->m_buffer.m_buffer;
- bufferInfo.offset = bufferOffset;
- bufferInfo.range = bufferSize;
- bindingState->descriptorSetWrites.add(write);
+
+ writeDescriptor(context, write);
}
static void writeBufferDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
BufferResourceImpl* buffer)
{
writeBufferDescriptor(
- bindingState, offset, descriptorType, buffer, 0, buffer->getDesc()->sizeInBytes);
+ context, offset, descriptorType, buffer, 0, buffer->getDesc()->sizeInBytes);
}
+
static void writePlainBufferDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<RefPtr<ResourceViewImpl>> resourceViews)
{
- auto writeDescriptorInfo = [=](VkWriteDescriptorSet& write,
- uint32_t startElement,
- uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = resourceViews.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto infos = bindingState->descriptorInfos.reserveRange(count);
- write.pBufferInfo = (VkDescriptorBufferInfo*)infos;
- for (uint32_t i = startElement; i < count; i++)
+ auto bufferView = static_cast<PlainBufferResourceViewImpl*>(resourceViews[i].Ptr());
+
+ VkDescriptorBufferInfo bufferInfo = {};
+
+ if(bufferView)
{
- auto bufferView =
- static_cast<PlainBufferResourceViewImpl*>(resourceViews[i].Ptr());
- if (bufferView)
- {
- infos[i].bufferInfo.buffer = bufferView->m_buffer->m_buffer.m_buffer;
- infos[i].bufferInfo.offset = 0;
- infos[i].bufferInfo.range = bufferView->m_buffer->getDesc()->sizeInBytes;
- }
+ bufferInfo.buffer = bufferView->m_buffer->m_buffer.m_buffer;
+ bufferInfo.offset = 0;
+ bufferInfo.range = bufferView->m_buffer->getDesc()->sizeInBytes;
}
- };
- _writeDescriptorRange(
- bindingState, offset, descriptorType, resourceViews, writeDescriptorInfo);
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pBufferInfo = &bufferInfo;
+
+ writeDescriptor(context, write);
+ }
}
static void writeTexelBufferDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<RefPtr<ResourceViewImpl>> resourceViews)
{
- auto writeDescriptorInfo = [=](VkWriteDescriptorSet& write,
- uint32_t startElement,
- uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = resourceViews.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto views = bindingState->bufferViews.reserveRange(write.descriptorCount);
- write.pTexelBufferView = views;
- for (uint32_t i = startElement; i < count; i++)
- {
- views[i] =
- static_cast<TexelBufferResourceViewImpl*>(resourceViews[i].Ptr())->m_view;
- }
- };
- _writeDescriptorRange(
- bindingState, offset, descriptorType, resourceViews, writeDescriptorInfo);
+ auto resourceView = static_cast<TexelBufferResourceViewImpl*>(resourceViews[i].Ptr());
+
+ VkBufferView bufferView = resourceView->m_view;
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pTexelBufferView = &bufferView;
+
+ writeDescriptor(context, write);
+ }
}
static void writeTextureSamplerDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<CombinedTextureSamplerSlot> slots)
{
- auto writeDescriptorInfo = [=](VkWriteDescriptorSet& write,
- uint32_t startElement,
- uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = slots.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
- write.pImageInfo = (VkDescriptorImageInfo*)infos;
- for (uint32_t i = startElement; i < count; i++)
- {
- auto texture = slots[i].textureView;
- auto sampler = slots[i].sampler;
- auto& imageInfo = ((VkDescriptorImageInfo*)infos)[i];
- imageInfo.imageView = texture->m_view;
- imageInfo.imageLayout = texture->m_layout;
- imageInfo.sampler = sampler->m_sampler;
- }
- };
- _writeDescriptorRange(bindingState, offset, descriptorType, slots, writeDescriptorInfo);
+ auto texture = slots[i].textureView;
+ auto sampler = slots[i].sampler;
+
+ VkDescriptorImageInfo imageInfo = {};
+ imageInfo.imageView = texture->m_view;
+ imageInfo.imageLayout = texture->m_layout;
+ imageInfo.sampler = sampler->m_sampler;
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pImageInfo = &imageInfo;
+
+ writeDescriptor(context, write);
+ }
}
static void writeTextureDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<RefPtr<ResourceViewImpl>> resourceViews)
{
- auto writeDescriptorInfo =
- [=](VkWriteDescriptorSet& write, uint32_t startElement, uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = resourceViews.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
- write.pImageInfo = (VkDescriptorImageInfo*)infos;
- for (uint32_t i = startElement; i < count; i++)
- {
- auto texture = static_cast<TextureResourceViewImpl*>(resourceViews[i].Ptr());
- auto& imageInfo = ((VkDescriptorImageInfo*)infos)[i];
- imageInfo.imageView = texture->m_view;
- imageInfo.imageLayout = texture->m_layout;
- imageInfo.sampler = 0;
- }
- };
- _writeDescriptorRange(
- bindingState, offset, descriptorType, resourceViews, writeDescriptorInfo);
+ auto texture = static_cast<TextureResourceViewImpl*>(resourceViews[i].Ptr());
+
+ VkDescriptorImageInfo imageInfo = {};
+ imageInfo.imageView = texture->m_view;
+ imageInfo.imageLayout = texture->m_layout;
+ imageInfo.sampler = 0;
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pImageInfo = &imageInfo;
+
+ writeDescriptor(context, write);
+ }
}
static void writeSamplerDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<RefPtr<SamplerStateImpl>> samplers)
{
- auto writeDescriptorInfo =
- [=](VkWriteDescriptorSet& write, uint32_t startElement, uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = samplers.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
- write.pImageInfo = (VkDescriptorImageInfo*)infos;
- for (uint32_t i = startElement; i < count; i++)
- {
- auto texture = samplers[i]->m_sampler;
- auto& imageInfo = ((VkDescriptorImageInfo*)infos)[i];
- imageInfo.imageView = 0;
- imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
- imageInfo.sampler = samplers[i]->m_sampler;
- }
- };
- _writeDescriptorRange(
- bindingState, offset, descriptorType, samplers, writeDescriptorInfo);
+ auto sampler = samplers[i];
+
+ VkDescriptorImageInfo imageInfo = {};
+ imageInfo.imageView = 0;
+ imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+ imageInfo.sampler = sampler->m_sampler;
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pImageInfo = &imageInfo;
+
+ writeDescriptor(context, write);
+ }
}
/// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed
- Result _ensureOrdinaryDataBufferCreatedIfNeeded(PipelineCommandEncoder* encoder)
+ Result _ensureOrdinaryDataBufferCreatedIfNeeded(
+ PipelineCommandEncoder* encoder,
+ ShaderObjectLayoutImpl* specializedLayout)
{
// If we have already created a buffer to hold ordinary data, then we should
// simply re-use that buffer rather than re-create it.
@@ -2238,22 +2932,7 @@ public:
return SLANG_OK;
}
- // Computing the size of the ordinary data buffer is *not* just as simple
- // as using the size of the `m_ordinayData` array that we store. The reason
- // for the added complexity is that interface-type fields may lead to the
- // storage being specialized such that it needs extra appended data to
- // store the concrete values that logically belong in those interface-type
- // fields but wouldn't fit in the fixed-size allocation we gave them.
- //
- // TODO: We need to actually implement that logic by using reflection
- // data computed for the specialized type of this shader object.
- // For now we just make the simple assumption described above despite
- // knowing that it is false.
- //
- RefPtr<ShaderObjectLayoutImpl> specializedLayout;
- SLANG_RETURN_ON_FAIL(_getSpecializedLayout(specializedLayout.writeRef()));
-
- m_constantBufferSize = specializedLayout->getElementTypeLayout()->getSize();
+ m_constantBufferSize = specializedLayout->getTotalOrdinaryDataSize();
if (m_constantBufferSize == 0)
{
m_upToDateConstantBufferHeapVersion =
@@ -2287,187 +2966,344 @@ public:
return SLANG_OK;
}
- /// Bind the buffer for ordinary/uniform data, if needed
- Result _bindOrdinaryDataBufferIfNeeded(
- PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset)
- {
- // We are going to need to tweak the base binding range index
- // used for descriptor-set writes if and only if we actually
- // bind a buffer for ordinary data.
- //
- auto& baseRangeIndex = offset.descriptorRangeOffset;
-
- // We start by ensuring that the buffer is created, if it is needed.
- //
- SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(encoder));
-
- // If we did indeed need/create a buffer, then we must bind it into
- // the given `descriptorSet` and update the base range index for
- // subsequent binding operations to account for it.
- //
- if (m_constantBuffer)
- {
- auto bufferImpl = static_cast<BufferResourceImpl*>(m_constantBuffer);
- writeBufferDescriptor(
- bindingState,
- offset,
- VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
- bufferImpl,
- m_constantBufferOffset,
- m_constantBufferSize);
- offset.descriptorRangeOffset++;
- }
-
- return SLANG_OK;
- }
-
public:
- Result bindDescriptorRanges(
+
+ /// Bind this shader object as a "value"
+ ///
+ /// This is the mode used for binding sub-objects for existential-type
+ /// fields, and is also used as part of the implementation of the
+ /// parameter-block and constant-buffer cases.
+ ///
+ Result bindAsValue(
PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset)
+ RootBindingContext& context,
+ BindingOffset const& offset,
+ ShaderObjectLayoutImpl* specializedLayout)
{
- auto layout = getLayout();
-
- // Fill in the descriptor sets based on binding ranges
+ // We start by iterating over the "simple" (non-sub-object) binding
+ // ranges and writing them to the descriptor sets that are being
+ // passed down.
//
- for (auto bindingRangeInfo : layout->getBindingRanges())
+ for (auto bindingRangeInfo : specializedLayout->getBindingRanges())
{
- auto rangeIndex =
- bindingRangeInfo.rangeIndexInDescriptorSet + offset.descriptorRangeOffset;
+ BindingOffset rangeOffset = offset;
+
auto baseIndex = bindingRangeInfo.baseIndex;
auto count = (uint32_t)bindingRangeInfo.count;
switch (bindingRangeInfo.bindingType)
{
case slang::BindingType::ConstantBuffer:
- for (uint32_t i = 0; i < count; ++i)
- {
- ShaderObjectImpl* subObject = m_objects[baseIndex + i];
- subObject->bindObjectIntoConstantBuffer(encoder, bindingState, offset);
- }
- break;
case slang::BindingType::ParameterBlock:
- for (uint32_t i = 0; i < count; ++i)
- {
- ShaderObjectImpl* subObject = m_objects[baseIndex + i];
- auto newOffset = offset;
- subObject->bindObjectIntoParameterBlock(encoder, bindingState, newOffset);
- offset.pushConstantRangeOffset = newOffset.pushConstantRangeOffset;
- }
+ case slang::BindingType::ExistentialValue:
break;
+
case slang::BindingType::Texture:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTextureDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::MutableTexture:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTextureDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::CombinedTextureSampler:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTextureSamplerDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
m_combinedTextureSamplers.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::Sampler:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeSamplerDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_SAMPLER,
m_samplers.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::RawBuffer:
case slang::BindingType::MutableRawBuffer:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writePlainBufferDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::TypedBuffer:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTexelBufferDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::MutableTypedBuffer:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTexelBufferDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::VaryingInput:
case slang::BindingType::VaryingOutput:
break;
+ default:
+ SLANG_ASSERT(!"unsupported binding type");
+ return SLANG_FAIL;
+ break;
+ }
+ }
+
+ // Once we've handled the simpel binding ranges, we move on to the
+ // sub-object ranges, which are generally more involved.
+ //
+ for( auto const& subObjectRange : specializedLayout->getSubObjectRanges() )
+ {
+ auto const& bindingRangeInfo = specializedLayout->getBindingRange(subObjectRange.bindingRangeIndex);
+ auto count = bindingRangeInfo.count;
+ auto baseIndex = bindingRangeInfo.baseIndex;
+
+ auto subObjectLayout = subObjectRange.layout;
+
+ // The starting offset to use for the sub-object
+ // has already been computed and stored as part
+ // of the layout, so we can get to the starting
+ // offset for the range easily.
+ //
+ BindingOffset rangeOffset = offset;
+ rangeOffset += subObjectRange.offset;
+
+ switch( bindingRangeInfo.bindingType )
+ {
+ case slang::BindingType::ConstantBuffer:
+ {
+ BindingOffset objOffset = rangeOffset;
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ // Binding a constant buffer sub-object is simple enough:
+ // we just call `bindAsConstantBuffer` on it to bind
+ // the ordinary data buffer (if needed) and any other
+ // bindings it recursively contains.
+ //
+ ShaderObjectImpl* subObject = m_objects[baseIndex + i];
+ subObject->bindAsConstantBuffer(encoder, context, objOffset, subObjectLayout);
+
+ // When dealing with arrays of sub-objects, we need to make
+ // sure to increment the offset for each subsequent object
+ // by the appropriate stride.
+ //
+ // TODO: We should pre-compute these and simply have
+ // `subObjectRange.stride` to go with `subObjectRange.offset`.
+ //
+ objOffset.binding += subObjectLayout->getTotalBindingCount();
+ objOffset.childSet += subObjectLayout->getChildDescriptorSetCount();
+ objOffset.pushConstantRange += subObjectLayout->getTotalPushConstantRangeCount();
+ }
+ }
+ break;
+ case slang::BindingType::ParameterBlock:
+ {
+ BindingOffset objOffset = rangeOffset;
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ // The case for `ParameterBlock<X>` is not that different
+ // from `ConstantBuffer<X>`, except that we call `bindAsParameterBlock`
+ // instead (understandably).
+ //
+ ShaderObjectImpl* subObject = m_objects[baseIndex + i];
+ subObject->bindAsParameterBlock(encoder, context, objOffset, subObjectLayout);
+
+ // The logic for striding from one sub-object to another is also
+ // different, given the differences in how constant buffers and
+ // parameter blocks are allocated.
+ //
+ objOffset.childSet += subObjectLayout->getChildDescriptorSetCount();
+ objOffset.pushConstantRange += subObjectLayout->getTotalPushConstantRangeCount();
+ }
+ }
+ break;
+
case slang::BindingType::ExistentialValue:
+ // Interface/existential-type sub-object ranges are the most complicated case.
//
- // TODO: If the existential value is one that "fits" into the storage available,
- // then we should write its data directly into that area. Otherwise, we need
- // to bind its content as "pending" data which will come after any other data
- // beloning to the same set (that is, it's starting descriptorRangeIndex will
- // need to be one after the number of ranges accounted for in the original type)
+ // First, we can only bind things if we have static specialization information
+ // to work with, which is exactly the case where `subObjectLayout` will be non-null.
//
+ if( subObjectLayout )
+ {
+ // Second, the offset where we want to start bindign for existential-type
+ // ranges is a bit different, because we don't wnat to bind at the "primary"
+ // offset that got passed down, but instead at the "pending" offset.
+ //
+ // For the purposes of nested binding, what used to be the pending offset
+ // will now be used as the primary offset.
+ //
+ BindingOffset objOffset = BindingOffset(rangeOffset.pending);
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ // An existential-type sub-object is always bound just as a value,
+ // which handles its nested bindings and descriptor sets, but
+ // does not deal with ordianry data. The ordinary data should
+ // have been handled as part of the buffer for a parent object
+ // already.
+ //
+ ShaderObjectImpl* subObject = m_objects[baseIndex + i];
+ subObject->bindAsValue(encoder, context, objOffset, subObjectLayout);
+
+ objOffset.binding += subObjectLayout->getTotalBindingCount();
+ objOffset.childSet += subObjectLayout->getChildDescriptorSetCount();
+ objOffset.pushConstantRange += subObjectLayout->getTotalPushConstantRangeCount();
+ }
+ }
break;
default:
- SLANG_ASSERT(!"unsupported binding type");
+ SLANG_ASSERT(!"unsupported sub-object type");
return SLANG_FAIL;
break;
}
}
+
+ return SLANG_OK;
+ }
+
+ /// Allocate the descriptor sets needed for binding this object (but not nested parameter blocks)
+ Result allocateDescriptorSets(
+ PipelineCommandEncoder* encoder,
+ RootBindingContext& context,
+ BindingOffset const& offset,
+ ShaderObjectLayoutImpl* specializedLayout)
+ {
+ auto baseDescriptorSetIndex = offset.childSet;
+
+ // The number of sets to allocate and their layouts was already pre-computed
+ // as part of the shader object layout, so we use that information here.
+ //
+ for (auto descriptorSetInfo : specializedLayout->getOwnDescriptorSets())
+ {
+ auto descriptorSetHandle = context.descriptorSetAllocator->allocate(
+ descriptorSetInfo.descriptorSetLayout).handle;
+
+ // For each set, we need to write it into the set of descriptor sets
+ // being used for binding. This is done both so that other steps
+ // in binding can find the set to fill it in, but also so that
+ // we can bind all the descriptor sets to the pipeline when the
+ // time comes.
+ //
+ auto descriptorSetIndex = baseDescriptorSetIndex + descriptorSetInfo.space;
+ context.descriptorSets[descriptorSetIndex] = descriptorSetHandle;
+ }
+
return SLANG_OK;
}
- virtual Result bindObjectIntoConstantBuffer(
+ /// Bind this object as a `ParameterBlock<X>`.
+ Result bindAsParameterBlock(
PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset)
+ RootBindingContext& context,
+ BindingOffset const& inOffset,
+ ShaderObjectLayoutImpl* specializedLayout)
{
- SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(encoder, bindingState, offset));
+ // Because we are binding into a nested parameter block,
+ // any texture/buffer/sampler bindings will now want to
+ // write into the sets we allocate for this object and
+ // not the sets for any parent object(s).
+ //
+ BindingOffset offset = inOffset;
+ offset.bindingSet = offset.childSet;
+ offset.binding = 0;
+
+ // TODO: We should also be writing to `offset.pending` here,
+ // because any resource/sampler bindings related to "pending"
+ // data should *also* be writing into the chosen set.
+ //
+ // The challenge here is that we need to compute the right
+ // value for `offset.pending.binding`, so that it writes after
+ // all the other bindings.
+
+ // Writing the bindings for a parameter block is relatively easy:
+ // we just need to allocate the descriptor set(s) needed for this
+ // object and then fill it in like a `ConstantBuffer<X>`.
+ //
+ SLANG_RETURN_ON_FAIL(allocateDescriptorSets(encoder, context, offset, specializedLayout));
+ SLANG_RETURN_ON_FAIL(bindAsConstantBuffer(encoder, context, offset, specializedLayout));
- SLANG_RETURN_ON_FAIL(bindDescriptorRanges(encoder, bindingState, offset));
return SLANG_OK;
}
- virtual Result bindObjectIntoParameterBlock(
+ /// Bind the ordinary data buffer if needed, and adjust `ioOffset` accordingly
+ Result bindOrdinaryDataBufferIfNeeded(
PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset)
- {
- auto& descriptorSetInfos = getLayout()->getDescriptorSets();
- offset.descriptorSetIndexOffset = (uint32_t)bindingState->descriptorSets.getCount();
- offset.uniformOffset = 0;
- offset.descriptorRangeOffset = 0;
- for (auto info : descriptorSetInfos)
+ RootBindingContext& context,
+ BindingOffset& ioOffset,
+ ShaderObjectLayoutImpl* specializedLayout)
+ {
+ // We start by ensuring that the buffer is created, if it is needed.
+ //
+ SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(encoder, specializedLayout));
+
+ // If we did indeed need/create a buffer, then we must bind it into
+ // the given `descriptorSet` and update the base range index for
+ // subsequent binding operations to account for it.
+ //
+ if (m_constantBuffer)
{
- bindingState->descriptorSets.add(
- bindingState->descriptorSetAllocator->allocate(info.descriptorSetLayout)
- .handle);
+ auto bufferImpl = static_cast<BufferResourceImpl*>(m_constantBuffer);
+ writeBufferDescriptor(
+ context,
+ ioOffset,
+ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ bufferImpl,
+ m_constantBufferOffset,
+ m_constantBufferSize);
+ ioOffset.binding++;
}
- SLANG_RETURN_ON_FAIL(bindObjectIntoConstantBuffer(encoder, bindingState, offset));
+
+ return SLANG_OK;
+ }
+
+ /// Bind this object as a `ConstantBuffer<X>`.
+ Result bindAsConstantBuffer(
+ PipelineCommandEncoder* encoder,
+ RootBindingContext& context,
+ BindingOffset const& inOffset,
+ ShaderObjectLayoutImpl* specializedLayout)
+ {
+ // To bind an object as a constant buffer, we first
+ // need to bind its ordinary data (if any) into an
+ // ordinary data buffer, and then bind it as a "value"
+ // which handles any of its recursively-contained bindings.
+ //
+ // The one detail is taht when binding the ordinary data
+ // buffer we need to adjust the `binding` index used for
+ // subsequent operations based on whether or not an ordinary
+ // data buffer was used (and thus consumed a `binding`).
+ //
+ BindingOffset offset = inOffset;
+ SLANG_RETURN_ON_FAIL(bindOrdinaryDataBufferIfNeeded(encoder, context, /*inout*/ offset, specializedLayout));
+ SLANG_RETURN_ON_FAIL(bindAsValue(encoder, context, offset, specializedLayout));
return SLANG_OK;
}
@@ -2547,28 +3383,60 @@ public:
EntryPointLayout* getLayout() { return static_cast<EntryPointLayout*>(m_layout.Ptr()); }
- virtual Result bindObjectIntoConstantBuffer(
+ /// Bind this shader object as an entry point
+ Result bindAsEntryPoint(
PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset) override
+ RootBindingContext& context,
+ BindingOffset const& inOffset,
+ EntryPointLayout* layout)
{
- // Set data in `m_ordinaryData` into the push constant range.
+ BindingOffset offset = inOffset;
+
+ // Any ordinary data in an entry point is assumed to be allocated
+ // as a push-constant range.
+ //
+ // TODO: Can we make this operation not bake in that assumption?
+ //
+ // TODO: Can/should this function be renamed as just `bindAsPushConstantBuffer`?
+ //
if (m_ordinaryData.getCount())
{
- auto pushConstantRange =
- bindingState->pushConstantRanges[offset.pushConstantRangeOffset];
+ // The index of the push constant range to bind should be
+ // passed down as part of the `offset`, and we will increment
+ // it here so that any further recursively-contained push-constant
+ // ranges use the next index.
+ //
+ auto pushConstantRangeIndex = offset.pushConstantRange++;
+
+ // Information about the push constant ranges (including offsets
+ // and stage flags) was pre-computed for the entire program and
+ // stored on the binding context.
+ //
+ auto const& pushConstantRange = context.pushConstantRanges[pushConstantRangeIndex];
+
+ // We expect that the size of the range as reflected matches the
+ // amount of ordinary data stored on this object.
+ //
+ // TODO: This would not be the case if specialization for interface-type
+ // parameters led to the entry point having "pending" ordinary data.
+ //
+ SLANG_ASSERT(pushConstantRange.size == (uint32_t) m_ordinaryData.getCount());
+
+ auto pushConstantData = m_ordinaryData.getBuffer();
+
encoder->m_api->vkCmdPushConstants(
encoder->m_commandBuffer->m_commandBuffer,
- bindingState->pipelineLayout,
+ context.pipelineLayout,
pushConstantRange.stageFlags,
pushConstantRange.offset,
pushConstantRange.size,
- m_ordinaryData.getBuffer());
- offset.pushConstantRangeOffset++;
+ pushConstantData);
}
- // Process the rest of binding ranges.
- SLANG_RETURN_ON_FAIL(bindDescriptorRanges(encoder, bindingState, offset));
+ // Any remaining bindings in the object can be handled through the
+ // "value" case.
+ //
+ SLANG_RETURN_ON_FAIL(bindAsValue(encoder, context, offset, layout));
return SLANG_OK;
}
@@ -2614,18 +3482,50 @@ public:
return SLANG_OK;
}
- virtual Result bindObjectIntoParameterBlock(
- PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset) override
+ /// Bind this object as a root shader object
+ Result bindAsRoot(
+ PipelineCommandEncoder* encoder,
+ RootBindingContext& context,
+ RootShaderObjectLayout* layout)
{
- SLANG_RETURN_ON_FAIL(Super::bindObjectIntoParameterBlock(encoder, bindingState, offset));
+ BindingOffset offset = {};
+ offset.pending = layout->getPendingDataOffset();
- // Bind all entry points.
- for (auto& entryPoint : m_entryPoints)
+ // Note: the operations here are quite similar to what `bindAsParameterBlock` does.
+ // The key difference in practice is that we do *not* make use of the adjustment
+ // that `bindOrdinaryDataBufferIfNeeded` applied to the offset passed into it.
+ //
+ // The reason for this difference in behavior is that the layout information
+ // for root shader parameters is in practice *already* offset appropriately
+ // (so that it ends up using absolute offsets).
+ //
+ // TODO: One more wrinkle here is that the `ordinaryDataBufferOffset` below
+ // might not be correct if `binding=0,set=0` was already claimed via explicit
+ // binding information. We should really be getting the offset information for
+ // the ordinary data buffer directly from the reflection information for
+ // the global scope.
+
+ SLANG_RETURN_ON_FAIL(allocateDescriptorSets(encoder, context, offset, layout));
+
+ BindingOffset ordinaryDataBufferOffset = offset;
+ SLANG_RETURN_ON_FAIL(bindOrdinaryDataBufferIfNeeded(encoder, context, /*inout*/ ordinaryDataBufferOffset, layout));
+
+ SLANG_RETURN_ON_FAIL(bindAsValue(encoder, context, offset, layout));
+
+ auto entryPointCount = layout->getEntryPoints().getCount();
+ for( Index i = 0; i < entryPointCount; ++i )
{
- entryPoint->bindObjectIntoConstantBuffer(encoder, bindingState, offset);
+ auto entryPoint = m_entryPoints[i];
+ auto const& entryPointInfo = layout->getEntryPoint(i);
+
+ // Note: we do *not* need to add the entry point offset
+ // information to the global `offset` because the
+ // `RootShaderObjectLayout` has already baked any offsets
+ // from the global layout into the `entryPointInfo`.
+
+ entryPoint->bindAsEntryPoint(encoder, context, entryPointInfo.offset, entryPointInfo.layout);
}
+
return SLANG_OK;
}
@@ -3873,33 +4773,50 @@ Result VKDevice::PipelineCommandEncoder::bindRootShaderObjectImpl(
if (!specializedLayout)
return SLANG_FAIL;
- RootBindingState bindState = {};
- bindState.pushConstantRanges = specializedLayout->m_pushConstantRanges.getView();
- bindState.pipelineLayout = specializedLayout->m_pipelineLayout;
- bindState.device = m_device;
- bindState.descriptorSetAllocator = &m_commandBuffer->m_transientHeap->m_descSetAllocator;
-
- // Write bindings into descriptor sets. This step allocate descriptor sets and collects
- // all `VkWriteDescriptorSet` operations in `bindState.descriptorSetWrites`.
- BindingOffset offset = {};
- rootObjectImpl->bindObjectIntoParameterBlock(this, &bindState, offset);
-
- // Execute descriptor writes collected in `bindState.descriptorSetWrites`.
- m_device->m_api.vkUpdateDescriptorSets(
- m_device->m_device,
- (uint32_t)bindState.descriptorSetWrites.getCount(),
- bindState.descriptorSetWrites.getArrayView().arrayView.getBuffer(),
- 0,
- nullptr);
+ // We will set up the context required when binding shader objects
+ // to the pipeline. Note that this is mostly just being packaged
+ // together to minimize the number of parameters that have to
+ // be dealt with in the complex recursive call chains.
+ //
+ RootBindingContext context;
+ context.pipelineLayout = specializedLayout->m_pipelineLayout;
+ context.device = m_device;
+ context.descriptorSetAllocator = &m_commandBuffer->m_transientHeap->m_descSetAllocator;
+ context.pushConstantRanges = specializedLayout->getAllPushConstantRanges().getArrayView();
+
+ // The context includes storage for the descriptor sets we will bind,
+ // and the number of sets we need to make space for is determined
+ // by the specialized program layout.
+ //
+ List<VkDescriptorSet> descriptorSetsStorage;
+ auto descriptorSetCount = specializedLayout->getTotalDescriptorSetCount();
+
+ descriptorSetsStorage.setCount(descriptorSetCount);
+ auto descriptorSets = descriptorSetsStorage.getBuffer();
+
+ context.descriptorSets = descriptorSets;
+
+ // We kick off recursive binding of shader objects to the pipeline (plus
+ // the state in `context`).
+ //
+ // Note: this logic will directly write any push-constant ranges needed,
+ // and will also fill in any descriptor sets. Currently it does not
+ // *bind* the descriptor sets it fills in.
+ //
+ // TODO: It could probably bind the descriptor sets as well.
+ //
+ rootObjectImpl->bindAsRoot(this, context, specializedLayout);
- // Bind descriptor sets.
+ // Once we've filled in all the descriptor sets, we bind them
+ // to the pipeline at once.
+ //
m_device->m_api.vkCmdBindDescriptorSets(
m_commandBuffer->m_commandBuffer,
bindPoint,
specializedLayout->m_pipelineLayout,
0,
- (uint32_t)bindState.descriptorSets.getCount(),
- bindState.descriptorSets.getBuffer(),
+ (uint32_t) descriptorSetCount,
+ descriptorSets,
0,
nullptr);
@@ -5540,7 +6457,7 @@ Result VKDevice::createShaderObjectLayout(
{
RefPtr<ShaderObjectLayoutImpl> layout;
SLANG_RETURN_ON_FAIL(
- ShaderObjectLayoutImpl::createForElementType(this, typeLayout, true, layout.writeRef()));
+ ShaderObjectLayoutImpl::createForElementType(this, typeLayout, layout.writeRef()));
returnRefPtrMove(outLayout, layout);
return SLANG_OK;
}