diff --git a/src/dxvk/dxvk_pipelayout.cpp b/src/dxvk/dxvk_pipelayout.cpp index 72b319a1..7ad5e7cf 100644 --- a/src/dxvk/dxvk_pipelayout.cpp +++ b/src/dxvk/dxvk_pipelayout.cpp @@ -1,11 +1,344 @@ #include +#include "dxvk_device.h" #include "dxvk_descriptor.h" #include "dxvk_limits.h" #include "dxvk_pipelayout.h" namespace dxvk { + uint32_t DxvkBindingInfo::computeSetIndex() const { + if (stages & (VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT)) { + // For fragment shaders, create a separate set for UBOs + if (descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + || descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) + return DxvkDescriptorSets::FsBuffers; + + return DxvkDescriptorSets::FsViews; + } else { + // Put all vertex shader resources into the last set. + // Vertex shader UBOs are usually updated every draw, + // and other resource types are rarely used. + return DxvkDescriptorSets::VsAll; + } + } + + + bool DxvkBindingInfo::canMerge(const DxvkBindingInfo& binding) const { + if ((stages & VK_SHADER_STAGE_FRAGMENT_BIT) != (binding.stages & VK_SHADER_STAGE_FRAGMENT_BIT)) + return false; + + return descriptorType == binding.descriptorType + && resourceBinding == binding.resourceBinding + && viewType == binding.viewType; + } + + + void DxvkBindingInfo::merge(const DxvkBindingInfo& binding) { + stages |= binding.stages; + access |= binding.access; + } + + + bool DxvkBindingInfo::eq(const DxvkBindingInfo& other) const { + return descriptorType == other.descriptorType + && resourceBinding == other.resourceBinding + && viewType == other.viewType + && stages == other.stages + && access == other.access; + } + + + size_t DxvkBindingInfo::hash() const { + DxvkHashState hash; + hash.add(descriptorType); + hash.add(resourceBinding); + hash.add(viewType); + hash.add(stages); + hash.add(access); + return hash; + } + + + DxvkBindingList::DxvkBindingList() { + + } + + + DxvkBindingList::~DxvkBindingList() { + + } + + + void DxvkBindingList::addBinding(const DxvkBindingInfo& binding) { + for (auto& b : m_bindings) { + if (b.canMerge(binding)) { + b.merge(binding); + return; + } + } + + m_bindings.push_back(binding); + } + + + void DxvkBindingList::merge(const DxvkBindingList& list) { + for (const auto& binding : list.m_bindings) + addBinding(binding); + } + + + bool DxvkBindingList::eq(const DxvkBindingList& other) const { + if (getBindingCount() != other.getBindingCount()) + return false; + + for (uint32_t i = 0; i < getBindingCount(); i++) { + if (!getBinding(i).eq(other.getBinding(i))) + return false; + } + + return true; + } + + + size_t DxvkBindingList::hash() const { + DxvkHashState hash; + + for (const auto& binding : m_bindings) + hash.add(binding.hash()); + + return hash; + } + + + DxvkBindingSetLayoutKey::DxvkBindingSetLayoutKey(const DxvkBindingList& list) { + m_bindings.resize(list.getBindingCount()); + + for (uint32_t i = 0; i < list.getBindingCount(); i++) { + m_bindings[i].descriptorType = list.getBinding(i).descriptorType; + m_bindings[i].stages = list.getBinding(i).stages; + } + } + + + DxvkBindingSetLayoutKey::~DxvkBindingSetLayoutKey() { + + } + + + bool DxvkBindingSetLayoutKey::eq(const DxvkBindingSetLayoutKey& other) const { + if (m_bindings.size() != other.m_bindings.size()) + return false; + + for (size_t i = 0; i < m_bindings.size(); i++) { + if (m_bindings[i].descriptorType != other.m_bindings[i].descriptorType + || m_bindings[i].stages != other.m_bindings[i].stages) + return false; + } + + return true; + } + + + size_t DxvkBindingSetLayoutKey::hash() const { + DxvkHashState hash; + + for (size_t i = 0; i < m_bindings.size(); i++) { + hash.add(m_bindings[i].descriptorType); + hash.add(m_bindings[i].stages); + } + + return hash; + } + + + DxvkBindingSetLayout::DxvkBindingSetLayout( + DxvkDevice* device, + const DxvkBindingSetLayoutKey& key) + : m_device(device) { + auto vk = m_device->vkd(); + + std::array bindingInfos; + std::array templateInfos; + + VkDescriptorSetLayoutCreateInfo layoutInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; + layoutInfo.bindingCount = key.getBindingCount(); + layoutInfo.pBindings = bindingInfos.data(); + + for (uint32_t i = 0; i < key.getBindingCount(); i++) { + auto entry = key.getBinding(i); + + VkDescriptorSetLayoutBinding& bindingInfo = bindingInfos[i]; + bindingInfo.binding = i; + bindingInfo.descriptorType = entry.descriptorType; + bindingInfo.descriptorCount = 1; + bindingInfo.stageFlags = entry.stages; + bindingInfo.pImmutableSamplers = nullptr; + + VkDescriptorUpdateTemplateEntry& templateInfo = templateInfos[i]; + templateInfo.dstBinding = i; + templateInfo.dstArrayElement = 0; + templateInfo.descriptorCount = 1; + templateInfo.descriptorType = entry.descriptorType; + templateInfo.offset = sizeof(DxvkDescriptorInfo) * i; + templateInfo.stride = sizeof(DxvkDescriptorInfo); + } + + if (vk->vkCreateDescriptorSetLayout(vk->device(), &layoutInfo, nullptr, &m_layout) != VK_SUCCESS) + throw DxvkError("DxvkBindingSetLayoutKey: Failed to create descriptor set layout"); + + if (layoutInfo.bindingCount) { + VkDescriptorUpdateTemplateCreateInfo templateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO }; + templateInfo.descriptorUpdateEntryCount = layoutInfo.bindingCount; + templateInfo.pDescriptorUpdateEntries = templateInfos.data(); + templateInfo.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET; + templateInfo.descriptorSetLayout = m_layout; + + if (vk->vkCreateDescriptorUpdateTemplate(vk->device(), &templateInfo, nullptr, &m_template) != VK_SUCCESS) + throw DxvkError("DxvkBindingLayoutObjects: Failed to create descriptor update template"); + } + } + + + DxvkBindingSetLayout::~DxvkBindingSetLayout() { + auto vk = m_device->vkd(); + + vk->vkDestroyDescriptorSetLayout(vk->device(), m_layout, nullptr); + vk->vkDestroyDescriptorUpdateTemplate(vk->device(), m_template, nullptr); + } + + + DxvkBindingLayout::DxvkBindingLayout() + : m_pushConst { 0, 0, 0 } { + + } + + + DxvkBindingLayout::~DxvkBindingLayout() { + + } + + + void DxvkBindingLayout::addBinding(const DxvkBindingInfo& binding) { + uint32_t set = binding.computeSetIndex(); + m_bindings[set].addBinding(binding); + } + + + void DxvkBindingLayout::addPushConstantRange(VkPushConstantRange range) { + uint32_t oldEnd = m_pushConst.offset + m_pushConst.size; + uint32_t newEnd = range.offset + range.size; + + m_pushConst.stageFlags |= range.stageFlags; + m_pushConst.offset = std::min(m_pushConst.offset, range.offset); + m_pushConst.size = std::max(oldEnd, newEnd) - m_pushConst.offset; + } + + + void DxvkBindingLayout::merge(const DxvkBindingLayout& layout) { + for (uint32_t i = 0; i < layout.m_bindings.size(); i++) + m_bindings[i].merge(layout.m_bindings[i]); + + addPushConstantRange(layout.m_pushConst); + } + + + bool DxvkBindingLayout::eq(const DxvkBindingLayout& other) const { + for (uint32_t i = 0; i < m_bindings.size(); i++) { + if (!m_bindings[i].eq(other.m_bindings[i])) + return false; + } + + if (m_pushConst.stageFlags != other.m_pushConst.stageFlags + || m_pushConst.offset != other.m_pushConst.offset + || m_pushConst.size != other.m_pushConst.size) + return false; + + return true; + } + + + size_t DxvkBindingLayout::hash() const { + DxvkHashState hash; + + for (uint32_t i = 0; i < m_bindings.size(); i++) + hash.add(m_bindings[i].hash()); + + hash.add(m_pushConst.stageFlags); + hash.add(m_pushConst.offset); + hash.add(m_pushConst.size); + return hash; + } + + + DxvkBindingLayoutObjects::DxvkBindingLayoutObjects( + DxvkDevice* device, + const DxvkBindingLayout& layout, + const DxvkBindingSetLayout** setObjects) + : m_device(device), m_layout(layout) { + auto vk = m_device->vkd(); + + uint32_t constId = 0; + + std::array setLayouts; + + for (uint32_t i = 0; i < DxvkDescriptorSets::SetCount; i++) { + m_bindingOffsets[i] = constId; + m_bindingObjects[i] = setObjects[i]; + setLayouts[i] = setObjects[i]->getSetLayout(); + + uint32_t bindingCount = m_layout.getBindingCount(i); + + for (uint32_t j = 0; j < bindingCount; j++) { + const DxvkBindingInfo& binding = m_layout.getBinding(i, j); + + DxvkBindingMapping mapping; + mapping.set = i; + mapping.binding = j; + mapping.constId = constId++; + + m_mapping.insert({ binding.resourceBinding, mapping }); + } + + if (bindingCount) + m_setMask |= 1u << i; + } + + VkPushConstantRange pushConst = m_layout.getPushConstantRange(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; + pipelineLayoutInfo.setLayoutCount = setLayouts.size(); + pipelineLayoutInfo.pSetLayouts = setLayouts.data(); + + if (pushConst.stageFlags && pushConst.size) { + pipelineLayoutInfo.pushConstantRangeCount = 1; + pipelineLayoutInfo.pPushConstantRanges = &pushConst; + } + + if (vk->vkCreatePipelineLayout(vk->device(), &pipelineLayoutInfo, nullptr, &m_pipelineLayout)) + throw DxvkError("DxvkBindingLayoutObjects: Failed to create pipeline layout"); + } + + + DxvkBindingLayoutObjects::~DxvkBindingLayoutObjects() { + auto vk = m_device->vkd(); + + vk->vkDestroyPipelineLayout(vk->device(), m_pipelineLayout, nullptr); + } + + + VkAccessFlags DxvkBindingLayoutObjects::getAccessFlags() const { + VkAccessFlags flags = 0; + + for (uint32_t i = 0; i < DxvkDescriptorSets::SetCount; i++) { + for (uint32_t j = 0; j < m_layout.getBindingCount(i); j++) + flags |= m_layout.getBinding(i, j).access; + } + + return flags; + } + + DxvkDescriptorSlotMapping:: DxvkDescriptorSlotMapping() { } DxvkDescriptorSlotMapping::~DxvkDescriptorSlotMapping() { } diff --git a/src/dxvk/dxvk_pipelayout.h b/src/dxvk/dxvk_pipelayout.h index 9e96b34b..ea43eb4c 100644 --- a/src/dxvk/dxvk_pipelayout.h +++ b/src/dxvk/dxvk_pipelayout.h @@ -1,11 +1,475 @@ #pragma once +#include +#include #include +#include "dxvk_hash.h" #include "dxvk_include.h" namespace dxvk { + class DxvkDevice; + + /** + * \brief Descriptor set indices + */ + struct DxvkDescriptorSets { + static constexpr uint32_t FsViews = 0; + static constexpr uint32_t FsBuffers = 1; + static constexpr uint32_t VsAll = 2; + static constexpr uint32_t SetCount = 3; + }; + + /** + * \brief Binding info + * + * Stores metadata for a single binding in + * a given shader, or for the whole pipeline. + */ + struct DxvkBindingInfo { + VkDescriptorType descriptorType; ///< Vulkan descriptor type + uint32_t resourceBinding; ///< API binding slot for the resource + VkImageViewType viewType; ///< Image view type + VkShaderStageFlags stages; ///< Shader stage mask + VkAccessFlags access; ///< Access mask for the resource + + /** + * \brief Computes descriptor set index for the given binding + * + * This is determines based on the shader stages that use the binding. + * \returns Descriptor set index + */ + uint32_t computeSetIndex() const; + + /** + * \brief Checks whether bindings can be merged + * + * Bindings can be merged if they access the same resource with + * the same view and descriptor type and are part of the same + * descriptor set. + * \param [in] binding The binding to probe + * \returns \c true if the bindings can be merged + */ + bool canMerge(const DxvkBindingInfo& binding) const; + + /** + * \brief Merges bindings + * + * Merges the stage and access flags of two + * otherwise identical binding declarations. + * \param [in] binding The binding to merge + */ + void merge(const DxvkBindingInfo& binding); + + /** + * \brief Checks for equality + * + * \param [in] other Binding to compare to + * \returns \c true if both bindings are equal + */ + bool eq(const DxvkBindingInfo& other) const; + + /** + * \brief Hashes binding info + * \returns Binding hash + */ + size_t hash() const; + + }; + + /** + * \brief Binding list + * + * Linear structure that can be used to look + * up descriptor set objects. + */ + class DxvkBindingList { + + public: + + DxvkBindingList(); + ~DxvkBindingList(); + + /** + * \brief Number of Vulkan bindings + * \returns Binding count + */ + uint32_t getBindingCount() const { + return uint32_t(m_bindings.size()); + } + + /** + * \brief Retrieves binding info + * + * \param [in] idx Binding index + * \returns Binding info + */ + const DxvkBindingInfo& getBinding(uint32_t index) const { + return m_bindings[index]; + } + + /** + * \brief Adds a binding to the list + * \param [in] binding Binding info + */ + void addBinding(const DxvkBindingInfo& binding); + + /** + * \brief Merges binding lists + * + * Adds bindings from another list to the current list. Useful + * when creating descriptor set layouts for pipelines consisting + * of multiple shader stages. + * \param [in] layout Binding list to merge + */ + void merge(const DxvkBindingList& list); + + /** + * \brief Checks for equality + * + * \param [in] other Binding layout to compare to + * \returns \c true if both binding layouts are equal + */ + bool eq(const DxvkBindingList& other) const; + + /** + * \brief Hashes binding layout + * \returns Binding layout hash + */ + size_t hash() const; + + private: + + std::vector m_bindings; + + }; + + + /** + * \brief Binding set layout key entry + * + * Stores unique info for a single binding. + */ + struct DxvkBindingSetLayoutKeyEntry { + VkDescriptorType descriptorType; + VkShaderStageFlags stages; + }; + + + /** + * \brief Binding set layout key + * + * Stores relevant information to look + * up unique descriptor set layouts. + */ + class DxvkBindingSetLayoutKey { + + public: + + DxvkBindingSetLayoutKey(const DxvkBindingList& list); + ~DxvkBindingSetLayoutKey(); + + /** + * \brief Retrieves binding count + * \returns Binding count + */ + uint32_t getBindingCount() const { + return uint32_t(m_bindings.size()); + } + + /** + * \brief Retrieves binding info + * + * \param [in] index Binding index + * \returns Binding info + */ + DxvkBindingSetLayoutKeyEntry getBinding(uint32_t index) const { + return m_bindings[index]; + } + + /** + * \brief Checks for equality + * + * \param [in] other Binding layout to compare to + * \returns \c true if both binding layouts are equal + */ + bool eq(const DxvkBindingSetLayoutKey& other) const; + + /** + * \brief Hashes binding layout + * \returns Binding layout hash + */ + size_t hash() const; + + private: + + std::vector m_bindings; + + }; + + + /** + * \brief Binding list objects + * + * Manages a Vulkan descriptor set layout + * object for a given binding list. + */ + class DxvkBindingSetLayout { + + public: + + DxvkBindingSetLayout( + DxvkDevice* device, + const DxvkBindingSetLayoutKey& key); + + ~DxvkBindingSetLayout(); + + /** + * \brief Queries descriptor set layout + * \returns Descriptor set layout + */ + VkDescriptorSetLayout getSetLayout() const { + return m_layout; + } + + /** + * \brief Queries descriptor template + * \returns Descriptor set template + */ + VkDescriptorUpdateTemplate getSetUpdateTemplate() const { + return m_template; + } + + private: + + DxvkDevice* m_device; + VkDescriptorSetLayout m_layout = VK_NULL_HANDLE; + VkDescriptorUpdateTemplate m_template = VK_NULL_HANDLE; + + }; + + + /** + * \brief Binding layout + * + * Convenience class to map out shader bindings for use in + * descriptor set layouts and pipeline layouts. If possible, + * bindings that only differ in stage will be merged. + */ + class DxvkBindingLayout { + + public: + + DxvkBindingLayout(); + ~DxvkBindingLayout(); + + /** + * \brief Number of Vulkan bindings per set + * + * \param [in] set Descriptor set index + * \returns Binding count for the given set + */ + uint32_t getBindingCount(uint32_t set) const { + return m_bindings[set].getBindingCount(); + } + + /** + * \brief Retrieves binding info + * + * \param [in] set Descriptor set index + * \param [in] idx Binding index + * \returns Binding info + */ + const DxvkBindingInfo& getBinding(uint32_t set, uint32_t idx) const { + return m_bindings[set].getBinding(idx); + } + + /** + * \brief Retrieves binding list for a given set + * + * Use convenience methods above to gather info about + * individual descriptors. This is intended to be used + * for descriptor set lookup primarily. + */ + const DxvkBindingList& getBindingList(uint32_t set) const { + return m_bindings[set]; + } + + /** + * \brief Retrieves push constant range + * \returns Push constant range + */ + VkPushConstantRange getPushConstantRange() const { + return m_pushConst; + } + + /** + * \brief Adds a binding to the layout + * \param [in] binding Binding info + */ + void addBinding(const DxvkBindingInfo& binding); + + /** + * \brief Adds push constant range + * \param [in] range Push constant range + */ + void addPushConstantRange(VkPushConstantRange range); + + /** + * \brief Merges binding layouts + * + * Adds bindings and push constant range from another layout to + * the current layout. Useful when creating pipeline layouts and + * descriptor set layouts for pipelines consisting of multiple + * shader stages. + * \param [in] layout Binding layout to merge + */ + void merge(const DxvkBindingLayout& layout); + + /** + * \brief Checks for equality + * + * \param [in] other Binding layout to compare to + * \returns \c true if both binding layouts are equal + */ + bool eq(const DxvkBindingLayout& other) const; + + /** + * \brief Hashes binding layout + * \returns Binding layout hash + */ + size_t hash() const; + + private: + + std::array m_bindings; + VkPushConstantRange m_pushConst; + + }; + + /** + * \brief Descriptor set and binding number + */ + struct DxvkBindingMapping { + uint32_t set; + uint32_t binding; + uint32_t constId; + }; + + /** + * \brief Pipeline and descriptor set layouts for a given binding layout + * + * Creates the following Vulkan objects for a given binding layout: + * - A descriptor set layout for each required descriptor set + * - A descriptor update template for each set with non-zero binding count + * - A pipeline layout referencing all descriptor sets and the push constant ranges + */ + class DxvkBindingLayoutObjects { + + public: + + DxvkBindingLayoutObjects( + DxvkDevice* device, + const DxvkBindingLayout& layout, + const DxvkBindingSetLayout** setObjects); + + ~DxvkBindingLayoutObjects(); + + /** + * \brief Binding layout + * \returns Binding layout + */ + const DxvkBindingLayout& layout() const { + return m_layout; + } + + /** + * \brief Queries active descriptor set mask + * \returns Bit mask of non-empty descriptor sets + */ + uint32_t getSetMask() const { + return m_setMask; + } + + /** + * \brief Queries first binding number for a given set + * + * This is relevant for generating binding masks. + * \param [in] set Descriptor set index + * \returns First binding in the given set + */ + uint32_t getFirstBinding(uint32_t set) const { + return m_bindingOffsets[set]; + } + + /** + * \brief Retrieves descriptor set layout for a given set + * + * \param [in] set Descriptor set index + * \returns Vulkan descriptor set layout + */ + VkDescriptorSetLayout getSetLayout(uint32_t set) const { + return m_bindingObjects[set]->getSetLayout(); + } + + /** + * \brief Retrieves descriptor update template for a given set + * + * \param [in] set Descriptor set index + * \returns Vulkan descriptor update template + */ + VkDescriptorUpdateTemplate getSetUpdateTemplate(uint32_t set) const { + return m_bindingObjects[set]->getSetUpdateTemplate(); + } + + /** + * \brief Retrieves pipeline layout + * \returns Pipeline layout + */ + VkPipelineLayout getPipelineLayout() const { + return m_pipelineLayout; + } + + /** + * \brief Looks up set and binding number by resource binding + * + * \param [in] index Resource binding index + * \returns Descriptor set and binding number + */ + std::optional lookupBinding(uint32_t index) const { + auto entry = m_mapping.find(index); + + if (entry != m_mapping.end()) + return entry->second; + + return std::nullopt; + } + + /** + * \brief Queries accumulated resource access flags + * + * Can be used to determine whether the pipeline + * reads or writes any resources. + */ + VkAccessFlags getAccessFlags() const; + + private: + + DxvkDevice* m_device; + DxvkBindingLayout m_layout; + VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; + + uint32_t m_setMask = 0; + + std::array m_bindingObjects = { }; + std::array m_bindingOffsets = { }; + + std::unordered_map m_mapping; + + }; + + /** * \brief Resource slot *