diff options
Diffstat (limited to 'tools/gfx/vulkan/render-vk.cpp')
| -rw-r--r-- | tools/gfx/vulkan/render-vk.cpp | 395 |
1 files changed, 282 insertions, 113 deletions
diff --git a/tools/gfx/vulkan/render-vk.cpp b/tools/gfx/vulkan/render-vk.cpp index 53c210a20..b9cc82469 100644 --- a/tools/gfx/vulkan/render-vk.cpp +++ b/tools/gfx/vulkan/render-vk.cpp @@ -91,6 +91,9 @@ public: protected: + /// Flush state from descriptor set bindings into `commandBuffer` + void _flushBindingState(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint); + class Buffer { public: @@ -280,11 +283,57 @@ public: VkDescriptorSetLayout m_descriptorSetLayout = VK_NULL_HANDLE; VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE; + // Vulkan descriptor sets are the closest in design to what + // the `Renderer` abstraction exposes as a `DescriptorSet`. + // The main difference is that a `DescriptorSet` can include + // root constant ranges, while under Vulkan push constant + // ranges are part of the `VkPipelineLayout`, but not part + // of any `VkDescriptorSetLayout`. + // + // Information about each descriptor slot range in the + // original `Desc` will be stored as `RangeInfo` values, + // which store the relevant information from the `Desc` + // as well as additional information specific to the + // Vulkan implementation path. + // struct RangeInfo { - VkDescriptorType descriptorType; + /// The type of descriptor slot range from the original `Desc` + DescriptorSlotType type; + + /// The start index of the range in the appropriate type-specific array + Index arrayIndex; + + /// The equivalent Vulkan descriptor type, where applicable + VkDescriptorType vkDescriptorType; + + /// The Vulkan `binding` index for this range + uint32_t vkBindingIndex; }; List<RangeInfo> m_ranges; + + // Because root constant ranges aren't part of a `VkDescriptorSetLayout`, + // we store additional data to represent the ranges so that + // we can store their data on a `DescriptorSetImpl` and then + // bind it to the API later. + // + struct RootConstantRangeInfo + { + /// The offset of the range's data in the backing storage. + Index offset; + + /// The size of the range's data. + Index size; + }; + Slang::List<RootConstantRangeInfo> m_rootConstantRanges; + + /// The total size, in bytes, or root constant data for this descriptor set. + uint32_t m_rootConstantDataSize = 0; + + /// The total number of reference counted objects that can be bound + /// to descriptor sets described by this layout. + /// + Index m_totalBoundObjectCount = 0; }; class PipelineLayoutImpl : public PipelineLayout @@ -306,28 +355,14 @@ public: VulkanApi const* m_api; VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; UInt m_descriptorSetCount = 0; + + /// For each descriptor set, stores the start offset of that set's root constant data in the pipeline layout + List<uint32_t> m_descriptorSetRootConstantOffsets; }; class DescriptorSetImpl : public DescriptorSet { public: - // Record the view binding - struct Binding - { - enum class Type : uint8_t - { - Unknown, - ResourceView, - SamplerState, - BufferResource, - CountOf, - }; - Type type; - uint32_t range; - uint32_t index; - RefPtr<RefObject> obj; - }; - DescriptorSetImpl(VKRenderer* renderer) : m_renderer(renderer) { @@ -345,15 +380,21 @@ public: UInt index, ResourceView* textureView, SamplerState* sampler) override; - - static Binding::Type _getBindingType(RefObject* ptr); - void _setBinding(Binding::Type type, UInt range, UInt index, RefObject* ptr); + virtual void setRootConstants( + UInt range, + UInt offset, + UInt size, + void const* data) override; VKRenderer* m_renderer = nullptr; ///< Weak pointer, can't be strong, because if set will become circular reference RefPtr<DescriptorSetLayoutImpl> m_layout; VkDescriptorSet m_descriptorSet = VK_NULL_HANDLE; - List<Binding> m_bindings; ///< Records entities are bound to this descriptor set, and keeps the associated resources/views/state in scope + /// Records entities that are bound to this descriptor set, and keeps the associated resources/views/state in scope + List<RefPtr<RefObject>> m_boundObjects; + + /// Backing storage for root constant ranges belonging to this descriptor set + List<char> m_rootConstantData; }; struct BoundVertexBuffer @@ -2019,6 +2060,46 @@ void VKRenderer::setPipelineState(PipelineType pipelineType, PipelineState* stat m_currentPipeline = (PipelineStateImpl*)state; } +void VKRenderer::_flushBindingState(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint) +{ + auto pipeline = m_currentPipeline; + + // We start by binding the pipeline state. + // + m_api.vkCmdBindPipeline(commandBuffer, pipelineBindPoint, pipeline->m_pipeline); + + // Next we bind all the descriptor sets that were set in the `VKRenderer`. + // + auto pipelineLayoutImpl = pipeline->m_pipelineLayout.Ptr(); + auto vkPipelineLayout = pipelineLayoutImpl->m_pipelineLayout; + auto descriptorSetCount = pipelineLayoutImpl->m_descriptorSetCount; + m_api.vkCmdBindDescriptorSets(commandBuffer, pipelineBindPoint, vkPipelineLayout, + 0, uint32_t(descriptorSetCount), + &m_currentDescriptorSets[0], + 0, nullptr); + + // For any descriptor sets with root-constant ranges, we need to + // bind the relevant data to the context. + // + for(gfx::UInt ii = 0; ii < descriptorSetCount; ++ii) + { + auto descriptorSet = m_currentDescriptorSetImpls[ii]; + auto descriptorSetLayout = descriptorSet->m_layout; + auto size = descriptorSetLayout->m_rootConstantDataSize; + if(size == 0) + continue; + auto data = descriptorSet->m_rootConstantData.getBuffer(); + + // The absolute offset of the descriptor set's data in + // the push-constant data for the entire pipeline was + // computed and cached in the pipeline layout. + // + uint32_t offset = pipelineLayoutImpl->m_descriptorSetRootConstantOffsets[ii]; + + m_api.vkCmdPushConstants(commandBuffer, vkPipelineLayout, VK_SHADER_STAGE_ALL, offset, size, data); + } +} + void VKRenderer::draw(UInt vertexCount, UInt startVertex = 0) { auto pipeline = m_currentPipeline; @@ -2033,13 +2114,7 @@ void VKRenderer::draw(UInt vertexCount, UInt startVertex = 0) // Also create descriptor sets based on the given pipeline layout VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); - m_api.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->m_pipeline); - - auto pipelineLayoutImpl = pipeline->m_pipelineLayout.Ptr(); - m_api.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayoutImpl->m_pipelineLayout, - 0, uint32_t(pipelineLayoutImpl->m_descriptorSetCount), - &m_currentDescriptorSets[0], - 0, nullptr); + _flushBindingState(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS); // Bind the vertex buffer if (m_boundVertexBuffers.getCount() > 0 && m_boundVertexBuffers[0].m_buffer) @@ -2073,13 +2148,7 @@ void VKRenderer::dispatchCompute(int x, int y, int z) // Also create descriptor sets based on the given pipeline layout VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer(); - m_api.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->m_pipeline); - - auto pipelineLayoutImpl = pipeline->m_pipelineLayout.Ptr(); - m_api.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayoutImpl->m_pipelineLayout, - 0, uint32_t(pipelineLayoutImpl->m_descriptorSetCount), - &m_currentDescriptorSets[0], - 0, nullptr); + _flushBindingState(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE); m_api.vkCmdDispatch(commandBuffer, x, y, z); } @@ -2284,10 +2353,61 @@ Result VKRenderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc& de { auto& srcRange = desc.slotRanges[rr]; + if(srcRange.type == DescriptorSlotType::RootConstant) + { + // Root constant ranges are a special case, since they + // don't actually map to `VkDescriptorSetLayoutBinding`s + // like the other cases. + + // We start by computing the offset of the range within + // the backing storage for the descriptor set, while + // also updating the computed total size of root constant + // data needed by the set. + // + auto size = uint32_t(srcRange.count); + auto offset = descriptorSetLayoutImpl->m_rootConstantDataSize; + descriptorSetLayoutImpl->m_rootConstantDataSize += size; + + // We will keep track of the information for this + // range as part of the descriptor set layout. + // + DescriptorSetLayoutImpl::RootConstantRangeInfo rootConstantRangeInfo; + rootConstantRangeInfo.offset = offset; + rootConstantRangeInfo.size = size; + + auto rootConstantRangeIndex = descriptorSetLayoutImpl->m_rootConstantRanges.getCount(); + descriptorSetLayoutImpl->m_rootConstantRanges.add(rootConstantRangeInfo); + + // We will also add a `RangeInfo` to reprsent this + // range, even though it doesn't map to a VK-level + // descriptor range. + // + DescriptorSetLayoutImpl::RangeInfo rangeInfo; + rangeInfo.type = srcRange.type; + rangeInfo.vkDescriptorType = VkDescriptorType(-1); + rangeInfo.arrayIndex = rootConstantRangeIndex; + descriptorSetLayoutImpl->m_ranges.add(rangeInfo); + + // Finally, we bail out instead of performing + // the logic that applies to the other descriptor + // range types. + // + continue; + } + + // Note: Because of the existence of root constant ranges, + // we cannot assume that the `binding` for a range is + // the same as its index in the input array of ranges. + // + // Instead, the `binding` for a range is its index in + // the output array of `VkDescriptorSetLayoutBinding`s. + // + uint32_t bindingIndex = uint32_t(dstBindings.getCount()); + VkDescriptorType dstDescriptorType = translateDescriptorType(srcRange.type); VkDescriptorSetLayoutBinding dstBinding; - dstBinding.binding = uint32_t(rr); + dstBinding.binding = uint32_t(bindingIndex); dstBinding.descriptorType = dstDescriptorType; dstBinding.descriptorCount = uint32_t(srcRange.count); dstBinding.stageFlags = VK_SHADER_STAGE_ALL; @@ -2297,8 +2417,20 @@ Result VKRenderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc& de dstBindings.add(dstBinding); + UInt boundObjectCount = srcRange.count; + if( srcRange.type == DescriptorSlotType::CombinedImageSampler ) + { + boundObjectCount = 2 * srcRange.count; + } + + auto boundObjectArrayIndex = descriptorSetLayoutImpl->m_totalBoundObjectCount; + descriptorSetLayoutImpl->m_totalBoundObjectCount += boundObjectCount; + DescriptorSetLayoutImpl::RangeInfo rangeInfo; - rangeInfo.descriptorType = dstDescriptorType; + rangeInfo.type = srcRange.type; + rangeInfo.vkDescriptorType = dstDescriptorType; + rangeInfo.vkBindingIndex = bindingIndex; + rangeInfo.arrayIndex = boundObjectArrayIndex; descriptorSetLayoutImpl->m_ranges.add(rangeInfo); } @@ -2344,15 +2476,47 @@ Result VKRenderer::createPipelineLayout(const PipelineLayout::Desc& desc, Pipeli UInt descriptorSetCount = desc.descriptorSetCount; VkDescriptorSetLayout descriptorSetLayouts[kMaxDescriptorSets]; + uint32_t descriptorSetRootConstantOffsets[kMaxDescriptorSets]; + uint32_t totalRootConstantSize = 0; for(UInt ii = 0; ii < descriptorSetCount; ++ii) { - descriptorSetLayouts[ii] = ((DescriptorSetLayoutImpl*) desc.descriptorSets[ii].layout)->m_descriptorSetLayout; + auto descriptorSetLayoutImpl = (DescriptorSetLayoutImpl*) desc.descriptorSets[ii].layout; + descriptorSetLayouts[ii] = descriptorSetLayoutImpl->m_descriptorSetLayout; + + descriptorSetRootConstantOffsets[ii] = totalRootConstantSize; + totalRootConstantSize += descriptorSetLayoutImpl->m_rootConstantDataSize; } VkPipelineLayoutCreateInfo pipelineLayoutInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; pipelineLayoutInfo.setLayoutCount = uint32_t(desc.descriptorSetCount); pipelineLayoutInfo.pSetLayouts = &descriptorSetLayouts[0]; + // Our abstraction allows the user to specify any number of root-constant + // ranges across all of their descriptor sets, but Vulkan has a restriction + // that a pipeline layout may only include a single push constant range + // accessible from a given stage. (In other words, the only situation where + // multiple push-constant ranges are allowed is if you want to have, say, + // distinct ranges for the vertex and fragment stages to access). + // + // We handle this by declaring at most one push constant range, which + // represents the concatenation of the data from all ranges that the + // user might have asked for. + // + // Note: The Slang compiler doesn't yet have logic to concatenate multiple + // push-constant ranges in this way, but if/when it does, it should hopefully + // Just Work with this logic. + // + VkPushConstantRange pushConstantRange; + if( totalRootConstantSize ) + { + pushConstantRange.offset = 0; + pushConstantRange.size = totalRootConstantSize; + pushConstantRange.stageFlags = VK_SHADER_STAGE_ALL; + + pipelineLayoutInfo.pushConstantRangeCount = 1; + pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; + } + VkPipelineLayout pipelineLayout; SLANG_VK_CHECK(m_api.vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &pipelineLayout)); @@ -2360,6 +2524,12 @@ Result VKRenderer::createPipelineLayout(const PipelineLayout::Desc& desc, Pipeli pipelineLayoutImpl->m_pipelineLayout = pipelineLayout; pipelineLayoutImpl->m_descriptorSetCount = descriptorSetCount; + for(UInt ii = 0; ii < descriptorSetCount; ++ii) + { + pipelineLayoutImpl->m_descriptorSetRootConstantOffsets.add( + descriptorSetRootConstantOffsets[ii]); + } + *outLayout = pipelineLayoutImpl.detach(); return SLANG_OK; } @@ -2379,73 +2549,23 @@ Result VKRenderer::createDescriptorSet(DescriptorSetLayout* layout, DescriptorSe RefPtr<DescriptorSetImpl> descriptorSetImpl = new DescriptorSetImpl(this); descriptorSetImpl->m_layout = layoutImpl; descriptorSetImpl->m_descriptorSet = descriptorSet; - *outDescriptorSet = descriptorSetImpl.detach(); - return SLANG_OK; -} -/* static */VKRenderer::DescriptorSetImpl::Binding::Type VKRenderer::DescriptorSetImpl::_getBindingType(RefObject* ptr) -{ - typedef Binding::Type Type; + descriptorSetImpl->m_rootConstantData.setCount(layoutImpl->m_rootConstantDataSize); + descriptorSetImpl->m_boundObjects.setCount(layoutImpl->m_totalBoundObjectCount); - if (ptr) - { - if (dynamic_cast<ResourceView*>(ptr)) - { - return Type::ResourceView; - } - else if (dynamic_cast<BufferResource*>(ptr)) - { - return Type::BufferResource; - } - else if (dynamic_cast<SamplerState*>(ptr)) - { - return Type::SamplerState; - } - } - return Type::Unknown; -} - -void VKRenderer::DescriptorSetImpl::_setBinding(Binding::Type type, UInt range, UInt index, RefObject* ptr) -{ - SLANG_ASSERT(ptr == nullptr || _getBindingType(ptr) == type); - - const Index numBindings = m_bindings.getCount(); - for (Index i = 0; i < numBindings; ++i) - { - Binding& binding = m_bindings[i]; - - if (binding.type == type && binding.range == uint32_t(range) && binding.index == uint32_t(index)) - { - if (ptr) - { - binding.obj = ptr; - } - else - { - m_bindings.removeAt(i); - } - - return; - } - } - - // If an entry is not found, and we have a pointer, create an entry - if (ptr) - { - Binding binding; - binding.type = type; - binding.range = uint32_t(range); - binding.index = uint32_t(index); - binding.obj = ptr; - - m_bindings.add(binding); - } + *outDescriptorSet = descriptorSetImpl.detach(); + return SLANG_OK; } void VKRenderer::DescriptorSetImpl::setConstantBuffer(UInt range, UInt index, BufferResource* buffer) { auto bufferImpl = (BufferResourceImpl*)buffer; + SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount())); + auto& rangeInfo = m_layout->m_ranges[range]; + auto bindingIndex = rangeInfo.vkBindingIndex; + auto boundObjectIndex = rangeInfo.arrayIndex + index; + VkDescriptorBufferInfo bufferInfo = {}; bufferInfo.buffer = bufferImpl->m_buffer.m_buffer; bufferInfo.offset = 0; @@ -2453,19 +2573,24 @@ void VKRenderer::DescriptorSetImpl::setConstantBuffer(UInt range, UInt index, Bu VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; writeInfo.dstSet = m_descriptorSet; - writeInfo.dstBinding = uint32_t(range); + writeInfo.dstBinding = uint32_t(bindingIndex); writeInfo.dstArrayElement = uint32_t(index); writeInfo.descriptorCount = 1; - writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType; + writeInfo.descriptorType = rangeInfo.vkDescriptorType; writeInfo.pBufferInfo = &bufferInfo; m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr); - - _setBinding(Binding::Type::BufferResource, range, index, buffer); + m_boundObjects[boundObjectIndex] = buffer; } void VKRenderer::DescriptorSetImpl::setResource(UInt range, UInt index, ResourceView* view) { + SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount())); + auto& rangeInfo = m_layout->m_ranges[range]; + auto bindingIndex = rangeInfo.vkBindingIndex; + auto boundObjectIndex = rangeInfo.arrayIndex + index; + auto descriptorType = rangeInfo.vkDescriptorType; + auto viewImpl = (ResourceViewImpl*)view; switch (viewImpl->m_type) { @@ -2479,10 +2604,10 @@ void VKRenderer::DescriptorSetImpl::setResource(UInt range, UInt index, Resource VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; writeInfo.dstSet = m_descriptorSet; - writeInfo.dstBinding = uint32_t(range); + writeInfo.dstBinding = uint32_t(bindingIndex); writeInfo.dstArrayElement = uint32_t(index); writeInfo.descriptorCount = 1; - writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType; + writeInfo.descriptorType = descriptorType; writeInfo.pImageInfo = &imageInfo; m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr); @@ -2495,10 +2620,10 @@ void VKRenderer::DescriptorSetImpl::setResource(UInt range, UInt index, Resource VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; writeInfo.dstSet = m_descriptorSet; - writeInfo.dstBinding = uint32_t(range); + writeInfo.dstBinding = uint32_t(bindingIndex); writeInfo.dstArrayElement = uint32_t(index); writeInfo.descriptorCount = 1; - writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType; + writeInfo.descriptorType = descriptorType; writeInfo.pTexelBufferView = &bufferViewImpl->m_view; m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr); @@ -2516,25 +2641,32 @@ void VKRenderer::DescriptorSetImpl::setResource(UInt range, UInt index, Resource VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; writeInfo.dstSet = m_descriptorSet; - writeInfo.dstBinding = uint32_t(range); + writeInfo.dstBinding = uint32_t(bindingIndex); writeInfo.dstArrayElement = uint32_t(index); writeInfo.descriptorCount = 1; - writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType; + writeInfo.descriptorType = descriptorType; writeInfo.pBufferInfo = &bufferInfo; m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr); } break; - } - _setBinding(Binding::Type::ResourceView, range, index, view); + m_boundObjects[boundObjectIndex] = view; } void VKRenderer::DescriptorSetImpl::setSampler(UInt range, UInt index, SamplerState* sampler) { + SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount())); + auto& rangeInfo = m_layout->m_ranges[range]; + SLANG_ASSERT(rangeInfo.type == DescriptorSlotType::Sampler); + auto bindingIndex = rangeInfo.vkBindingIndex; + auto boundObjectIndex = rangeInfo.arrayIndex + index; + auto descriptorType = rangeInfo.vkDescriptorType; + + // TODO: Actually bind it! - _setBinding(Binding::Type::SamplerState, range, index, sampler); + m_boundObjects[boundObjectIndex] = sampler; } void VKRenderer::DescriptorSetImpl::setCombinedTextureSampler( @@ -2543,9 +2675,46 @@ void VKRenderer::DescriptorSetImpl::setCombinedTextureSampler( ResourceView* textureView, SamplerState* sampler) { + SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount())); + auto& rangeInfo = m_layout->m_ranges[range]; + SLANG_ASSERT(rangeInfo.type == DescriptorSlotType::CombinedImageSampler); + auto bindingIndex = rangeInfo.vkBindingIndex; + auto descriptorType = rangeInfo.vkDescriptorType; + + // TODO: Actually bind it! + + // Note: Each entry in a combined texture/sampler range consumes + // two entries in the `m_boundObjects` array, since we have + // to keep both the texture view and the sampler object live. + // + auto boundObjectIndex = rangeInfo.arrayIndex + 2 * index; + m_boundObjects[boundObjectIndex + 0] = textureView; + m_boundObjects[boundObjectIndex + 1] = sampler; +} + +void VKRenderer::DescriptorSetImpl::setRootConstants( + UInt range, + UInt offset, + UInt size, + void const* data) +{ + // The `range` variabel is the index of one of the descriptor + // slot ranges, which had better be a `RootConstant` range. + // + SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount())); + auto& rangeInfo = m_layout->m_ranges[range]; + SLANG_ASSERT(rangeInfo.type == DescriptorSlotType::RootConstant); + + // The `arrayIndex` for the descriptor slot range will refer + // to a root constant range, which is the range to be set. + // + auto rootConstantIndex = rangeInfo.arrayIndex; + SLANG_ASSERT(rootConstantIndex >= 0); + SLANG_ASSERT(rootConstantIndex < m_layout->m_rootConstantRanges.getCount()); + auto& rootConstantRangeInfo = m_layout->m_rootConstantRanges[rootConstantIndex]; + SLANG_ASSERT(offset + size <= rootConstantRangeInfo.size); - _setBinding(Binding::Type::SamplerState, range, index, sampler); - _setBinding(Binding::Type::ResourceView, range, index, textureView); + memcpy(m_rootConstantData.getBuffer() + rootConstantRangeInfo.offset + offset, data, size); } void VKRenderer::setDescriptorSet(PipelineType pipelineType, PipelineLayout* layout, UInt index, DescriptorSet* descriptorSet) |
