Index: ps/trunk/source/renderer/backend/vulkan/DescriptorManager.cpp =================================================================== --- ps/trunk/source/renderer/backend/vulkan/DescriptorManager.cpp (revision 27808) +++ ps/trunk/source/renderer/backend/vulkan/DescriptorManager.cpp (revision 27809) @@ -1,383 +1,387 @@ /* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "DescriptorManager.h" #include "lib/hash.h" #include "ps/containers/StaticVector.h" #include "renderer/backend/vulkan/Device.h" #include "renderer/backend/vulkan/Mapping.h" #include "renderer/backend/vulkan/Texture.h" #include "renderer/backend/vulkan/Utilities.h" #include #include namespace Renderer { namespace Backend { namespace Vulkan { CDescriptorManager::CDescriptorManager(CDevice* device, const bool useDescriptorIndexing) : m_Device(device), m_UseDescriptorIndexing(useDescriptorIndexing), m_ErrorTexture(device->CreateTexture( "DescriptorManagerErrorTexture", ITexture::Type::TEXTURE_2D, ITexture::Usage::TRANSFER_DST | ITexture::Usage::SAMPLED, Format::R8G8B8A8_UNORM, 1, 1, Sampler::MakeDefaultSampler(Sampler::Filter::NEAREST, Sampler::AddressMode::REPEAT), 1, 1)) { if (useDescriptorIndexing) { VkDescriptorPoolSize descriptorPoolSize{}; descriptorPoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; descriptorPoolSize.descriptorCount = DESCRIPTOR_INDEXING_BINDING_SIZE * NUMBER_OF_BINDINGS_PER_DESCRIPTOR_INDEXING_SET; VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{}; descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; descriptorPoolCreateInfo.poolSizeCount = 1; descriptorPoolCreateInfo.pPoolSizes = &descriptorPoolSize; descriptorPoolCreateInfo.maxSets = 1; descriptorPoolCreateInfo.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT; ENSURE_VK_SUCCESS(vkCreateDescriptorPool( device->GetVkDevice(), &descriptorPoolCreateInfo, nullptr, &m_DescriptorIndexingPool)); const VkShaderStageFlags stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT; const std::array bindings{{ {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DESCRIPTOR_INDEXING_BINDING_SIZE, stageFlags}, {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DESCRIPTOR_INDEXING_BINDING_SIZE, stageFlags}, {2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DESCRIPTOR_INDEXING_BINDING_SIZE, stageFlags} }}; const VkDescriptorBindingFlagsEXT baseBindingFlags = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT | VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT_EXT; const std::array bindingFlags{{ baseBindingFlags, baseBindingFlags, baseBindingFlags }}; VkDescriptorSetLayoutBindingFlagsCreateInfoEXT bindingFlagsCreateInfo{}; bindingFlagsCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT; bindingFlagsCreateInfo.bindingCount = bindingFlags.size(); bindingFlagsCreateInfo.pBindingFlags = bindingFlags.data(); VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{}; descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; descriptorSetLayoutCreateInfo.bindingCount = bindings.size(); descriptorSetLayoutCreateInfo.pBindings = bindings.data(); descriptorSetLayoutCreateInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT; descriptorSetLayoutCreateInfo.pNext = &bindingFlagsCreateInfo; ENSURE_VK_SUCCESS(vkCreateDescriptorSetLayout( device->GetVkDevice(), &descriptorSetLayoutCreateInfo, nullptr, &m_DescriptorIndexingSetLayout)); m_DescriptorSetLayouts.emplace_back(m_DescriptorIndexingSetLayout); VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{}; descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; descriptorSetAllocateInfo.descriptorPool = m_DescriptorIndexingPool; descriptorSetAllocateInfo.descriptorSetCount = 1; descriptorSetAllocateInfo.pSetLayouts = &m_DescriptorIndexingSetLayout; ENSURE_VK_SUCCESS(vkAllocateDescriptorSets( device->GetVkDevice(), &descriptorSetAllocateInfo, &m_DescriptorIndexingSet)); for (DescriptorIndexingBindingMap& bindingMap : m_DescriptorIndexingBindings) { bindingMap.firstFreeIndex = 0; bindingMap.elements.resize(DESCRIPTOR_INDEXING_BINDING_SIZE); std::iota(bindingMap.elements.begin(), std::prev(bindingMap.elements.end()), 1); bindingMap.elements.back() = -1; } } // Currently we hard-code the layout for uniforms. const VkDescriptorSetLayoutBinding bindings[] = { {0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT} }; VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{}; descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; descriptorSetLayoutCreateInfo.bindingCount = std::size(bindings); descriptorSetLayoutCreateInfo.pBindings = bindings; ENSURE_VK_SUCCESS(vkCreateDescriptorSetLayout( device->GetVkDevice(), &descriptorSetLayoutCreateInfo, nullptr, &m_UniformDescriptorSetLayout)); m_DescriptorSetLayouts.emplace_back(m_UniformDescriptorSetLayout); } CDescriptorManager::~CDescriptorManager() { VkDevice device = m_Device->GetVkDevice(); for (auto& pair: m_SingleTypePools) { for (SingleTypePool& pool : pair.second) { if (pool.pool != VK_NULL_HANDLE) vkDestroyDescriptorPool(device, pool.pool, nullptr); if (pool.layout != VK_NULL_HANDLE) vkDestroyDescriptorSetLayout(device, pool.layout, nullptr); } } m_SingleTypePools.clear(); for (VkDescriptorSetLayout descriptorSetLayout : m_DescriptorSetLayouts) vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); m_DescriptorSetLayouts.clear(); if (m_DescriptorIndexingPool != VK_NULL_HANDLE) vkDestroyDescriptorPool(device, m_DescriptorIndexingPool, nullptr); } CDescriptorManager::SingleTypePool& CDescriptorManager::GetSingleTypePool( const VkDescriptorType type, const uint32_t size) { ENSURE(size > 0 && size <= 16); std::vector& pools = m_SingleTypePools[type]; if (pools.size() <= size) pools.resize(size + 1); SingleTypePool& pool = pools[size]; if (pool.pool == VK_NULL_HANDLE) { constexpr uint32_t maxSets = 16384; VkDescriptorPoolSize descriptorPoolSize{}; - descriptorPoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorPoolSize.type = type; descriptorPoolSize.descriptorCount = maxSets * size; VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{}; descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; descriptorPoolCreateInfo.poolSizeCount = 1; descriptorPoolCreateInfo.pPoolSizes = &descriptorPoolSize; descriptorPoolCreateInfo.maxSets = maxSets; ENSURE_VK_SUCCESS(vkCreateDescriptorPool( m_Device->GetVkDevice(), &descriptorPoolCreateInfo, nullptr, &pool.pool)); const VkPipelineStageFlags stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_COMPUTE_BIT; PS::StaticVector bindings; for (uint32_t index = 0; index < size; ++index) bindings.emplace_back(VkDescriptorSetLayoutBinding{index, type, 1, stageFlags}); VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{}; descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; descriptorSetLayoutCreateInfo.bindingCount = size; descriptorSetLayoutCreateInfo.pBindings = bindings.data(); ENSURE_VK_SUCCESS(vkCreateDescriptorSetLayout( m_Device->GetVkDevice(), &descriptorSetLayoutCreateInfo, nullptr, &pool.layout)); pool.firstFreeIndex = 0; pool.elements.reserve(maxSets); for (uint32_t index = 0; index < maxSets; ++index) pool.elements.push_back({VK_NULL_HANDLE, 1, static_cast(index + 1)}); pool.elements.back().nextFreeIndex = SingleTypePool::INVALID_INDEX; } return pool; } VkDescriptorSetLayout CDescriptorManager::GetSingleTypeDescritorSetLayout( VkDescriptorType type, const uint32_t size) { return GetSingleTypePool(type, size).layout; } size_t CDescriptorManager::SingleTypeCacheKeyHash::operator()(const SingleTypeCacheKey& key) const { size_t seed = 0; hash_combine(seed, key.first); for (CTexture::UID uid : key.second) hash_combine(seed, uid); return seed; } VkDescriptorSet CDescriptorManager::GetSingleTypeDescritorSet( VkDescriptorType type, VkDescriptorSetLayout layout, const std::vector& texturesUID, const std::vector& textures) { ENSURE(texturesUID.size() == textures.size()); ENSURE(!texturesUID.empty()); const SingleTypeCacheKey key{layout, texturesUID}; auto it = m_SingleTypeSets.find(key); if (it == m_SingleTypeSets.end()) { SingleTypePool& pool = GetSingleTypePool(type, texturesUID.size()); const int16_t elementIndex = pool.firstFreeIndex; ENSURE(elementIndex != SingleTypePool::INVALID_INDEX); SingleTypePool::Element& element = pool.elements[elementIndex]; ENSURE(pool.firstFreeIndex != element.nextFreeIndex); pool.firstFreeIndex = element.nextFreeIndex; ++element.version; // Occupy the index. element.nextFreeIndex = SingleTypePool::INVALID_INDEX; if (element.set == VK_NULL_HANDLE) { VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{}; descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; descriptorSetAllocateInfo.descriptorPool = pool.pool; descriptorSetAllocateInfo.descriptorSetCount = 1; descriptorSetAllocateInfo.pSetLayouts = &layout; ENSURE_VK_SUCCESS(vkAllocateDescriptorSets( m_Device->GetVkDevice(), &descriptorSetAllocateInfo, &element.set)); } it = m_SingleTypeSets.emplace(key, element.set).first; for (const CTexture::UID uid : texturesUID) if (uid != CTexture::INVALID_UID) m_TextureSingleTypePoolMap[uid].push_back({type, element.version, elementIndex, static_cast(texturesUID.size())}); PS::StaticVector infos; for (CTexture* texture : textures) { if (!texture) + { + // We can use a default texture only for read-only bindings. + ENSURE(type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE); texture = m_ErrorTexture->As(); + } ENSURE(texture->GetUsage() & ITexture::Usage::SAMPLED); VkDescriptorImageInfo descriptorImageInfo{}; descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; descriptorImageInfo.imageView = texture->GetSamplerImageView(); descriptorImageInfo.sampler = texture->GetSampler(); infos.emplace_back(std::move(descriptorImageInfo)); } VkWriteDescriptorSet writeDescriptorSet{}; writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSet.dstSet = element.set; writeDescriptorSet.dstBinding = 0; writeDescriptorSet.dstArrayElement = 0; writeDescriptorSet.descriptorType = type; writeDescriptorSet.descriptorCount = static_cast(infos.size()); writeDescriptorSet.pImageInfo = infos.data(); vkUpdateDescriptorSets( m_Device->GetVkDevice(), 1, &writeDescriptorSet, 0, nullptr); } return it->second; } uint32_t CDescriptorManager::GetUniformSet() const { return m_UseDescriptorIndexing ? 1 : 0; } uint32_t CDescriptorManager::GetTextureDescriptor(CTexture* texture) { ENSURE(m_UseDescriptorIndexing); ENSURE(texture->GetUsage() & ITexture::Usage::SAMPLED); uint32_t binding = 0; if (texture->GetType() == ITexture::Type::TEXTURE_2D && IsDepthFormat(texture->GetFormat()) && texture->IsCompareEnabled()) binding = 2; else if (texture->GetType() == ITexture::Type::TEXTURE_CUBE) binding = 1; DescriptorIndexingBindingMap& bindingMap = m_DescriptorIndexingBindings[binding]; auto it = bindingMap.map.find(texture->GetUID()); if (it != bindingMap.map.end()) return it->second; m_TextureToBindingMap[texture->GetUID()] = binding; ENSURE(bindingMap.firstFreeIndex != -1); uint32_t descriptorSetIndex = bindingMap.firstFreeIndex; bindingMap.firstFreeIndex = bindingMap.elements[bindingMap.firstFreeIndex]; ENSURE(texture->GetType() != ITexture::Type::TEXTURE_2D_MULTISAMPLE); VkDescriptorImageInfo descriptorImageInfo{}; descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; descriptorImageInfo.imageView = texture->GetSamplerImageView(); descriptorImageInfo.sampler = texture->GetSampler(); VkWriteDescriptorSet writeDescriptorSet{}; writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSet.dstSet = m_DescriptorIndexingSet; writeDescriptorSet.dstBinding = binding; writeDescriptorSet.dstArrayElement = descriptorSetIndex; writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; writeDescriptorSet.descriptorCount = 1; writeDescriptorSet.pImageInfo = &descriptorImageInfo; vkUpdateDescriptorSets( m_Device->GetVkDevice(), 1, &writeDescriptorSet, 0, nullptr); bindingMap.map[texture->GetUID()] = descriptorSetIndex; return descriptorSetIndex; } void CDescriptorManager::OnTextureDestroy(const CTexture::UID uid) { ENSURE(uid != CTexture::INVALID_UID); if (m_UseDescriptorIndexing) { DescriptorIndexingBindingMap& bindingMap = m_DescriptorIndexingBindings[m_TextureToBindingMap[uid]]; auto it = bindingMap.map.find(uid); // It's possible to not have the texture in the map. Because a texture will // be added to it only in case of usage. if (it == bindingMap.map.end()) return; const int16_t index = it->second; bindingMap.elements[index] = bindingMap.firstFreeIndex; bindingMap.firstFreeIndex = index; bindingMap.map.erase(it); } else { auto it = m_TextureSingleTypePoolMap.find(uid); if (it == m_TextureSingleTypePoolMap.end()) return; for (const auto& entry : it->second) { SingleTypePool& pool = GetSingleTypePool(entry.type, entry.size); SingleTypePool::Element& element = pool.elements[entry.elementIndex]; // Multiple textures might be used by the same descriptor set and // we don't need to reset it if it was already. if (element.version == entry.version && element.nextFreeIndex == SingleTypePool::INVALID_INDEX) { ENSURE(pool.firstFreeIndex != entry.elementIndex); element.nextFreeIndex = pool.firstFreeIndex; pool.firstFreeIndex = entry.elementIndex; } } m_TextureSingleTypePoolMap.erase(it); } } } // namespace Vulkan } // namespace Backend } // namespace Renderer