diff options
Diffstat (limited to 'tools/gfx/vulkan/render-vk.cpp')
| -rw-r--r-- | tools/gfx/vulkan/render-vk.cpp | 1901 |
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; } |
