diff options
author | r.kuznetsov <r.kuznetsov@corp.mail.ru> | 2019-01-23 12:48:24 +0300 |
---|---|---|
committer | Daria Volvenkova <d.volvenkova@corp.mail.ru> | 2019-03-01 10:45:24 +0300 |
commit | cc4222a69fdb0776abba626a3389018084b549f0 (patch) | |
tree | 12332d25d15ca58b48555ff2e57f8ba3aed7c19a /drape | |
parent | f53013310a9ce31e976e7bae78461e57b12cc0d2 (diff) |
[vulkan] Implemented memory and objects managers
Diffstat (limited to 'drape')
-rw-r--r-- | drape/vulkan/vulkan_memory_manager.cpp | 275 | ||||
-rw-r--r-- | drape/vulkan/vulkan_memory_manager.hpp | 82 | ||||
-rw-r--r-- | drape/vulkan/vulkan_object_manager.cpp | 128 | ||||
-rw-r--r-- | drape/vulkan/vulkan_object_manager.hpp | 48 |
4 files changed, 531 insertions, 2 deletions
diff --git a/drape/vulkan/vulkan_memory_manager.cpp b/drape/vulkan/vulkan_memory_manager.cpp index 896c6c6591..1b4c3cfcba 100644 --- a/drape/vulkan/vulkan_memory_manager.cpp +++ b/drape/vulkan/vulkan_memory_manager.cpp @@ -1,9 +1,284 @@ #include "drape/vulkan/vulkan_memory_manager.hpp" +#include "drape/vulkan/vulkan_utils.hpp" + +#include "base/assert.hpp" + +#include <algorithm> +#include <limits> namespace dp { namespace vulkan { +namespace +{ +std::array<uint32_t, VulkanMemoryManager::kResourcesCount> const kMinBlockSizeInBytes = +{ + 64 * 1024, // Geometry + 4 * 1024, // Uniform + 0, // Staging + 0, // Image +}; + +std::array<uint32_t, VulkanMemoryManager::kResourcesCount> const kDesiredSizeInBytes = +{ + 50 * 1024 * 1024, // Geometry + std::numeric_limits<uint32_t>::max(), // Uniform (unlimited) + 10 * 1024 * 1024, // Staging + std::numeric_limits<uint32_t>::max(), // Image (unlimited) +}; + +VkMemoryPropertyFlags GetMemoryPropertyFlags(VulkanMemoryManager::ResourceType resourceType, + boost::optional<VkMemoryPropertyFlags> & fallbackTypeBits) +{ + switch (resourceType) + { + case VulkanMemoryManager::ResourceType::Geometry: + case VulkanMemoryManager::ResourceType::Staging: + fallbackTypeBits = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + case VulkanMemoryManager::ResourceType::Uniform: + // No fallback. + return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + + case VulkanMemoryManager::ResourceType::Image: + // No fallback. + return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + case VulkanMemoryManager::ResourceType::Count: + CHECK(false, ()); + } + return 0; +} + +uint32_t GetAligned(uint32_t value, uint32_t alignment) +{ + if (alignment == 0) + return value; + return (value + alignment - 1) & ~(alignment - 1); +} +} // namespace + +VulkanMemoryManager::~VulkanMemoryManager() +{ + for (size_t i = 0; i < kResourcesCount; ++i) + { + for (auto const & b : m_freeBlocks[i]) + vkFreeMemory(m_device, b.m_memory, nullptr); + + for (auto const & p : m_memory[i]) + { + for (auto const & b : p.second) + vkFreeMemory(m_device, b.m_memory, nullptr); + } + } +} + +boost::optional<uint32_t> VulkanMemoryManager::GetMemoryTypeIndex(uint32_t typeBits, + VkMemoryPropertyFlags properties) const +{ + for (uint32_t i = 0; i < m_memoryProperties.memoryTypeCount; i++) + { + if ((typeBits & 1) == 1) + { + if ((m_memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) + return i; + } + typeBits >>= 1; + } + return {}; +} + +uint32_t VulkanMemoryManager::GetOffsetAlignment(ResourceType resourceType) const +{ + if (resourceType == ResourceType::Uniform) + return static_cast<uint32_t>(m_deviceLimits.minUniformBufferOffsetAlignment); + + return static_cast<uint32_t>(m_deviceLimits.minMemoryMapAlignment); +} + +VulkanMemoryManager::AllocationPtr VulkanMemoryManager::Allocate(ResourceType resourceType, + VkMemoryRequirements memReqs, + uint64_t blockHash) +{ + auto const alignedSize = GetAligned(static_cast<uint32_t>(memReqs.size), + static_cast<uint32_t>(memReqs.alignment)); + // Looking for an existed block. + { + auto & m = m_memory[static_cast<size_t>(resourceType)]; + auto const it = m.find(blockHash); + if (it != m.end()) + { + CHECK(!it->second.empty(), ()); + auto & block = it->second.back(); + auto const alignedOffset = GetAligned(block.m_freeOffset, GetOffsetAlignment(resourceType)); + + // There is space in the current block. + if (block.m_blockSize <= alignedOffset + alignedSize) + { + block.m_freeOffset = alignedOffset + alignedSize; + block.m_allocationCounter++; + return std::make_shared<Allocation>(resourceType, blockHash, block.m_memory, + alignedOffset, alignedSize, block.m_isCoherent); + } + } + + // Looking for a block in free ones. + auto & fm = m_freeBlocks[static_cast<size_t>(resourceType)]; + // Free blocks array must be sorted by size. + MemoryBlock refBlock; + refBlock.m_blockSize = alignedSize; + auto const freeBlockIt = std::upper_bound(fm.begin(), fm.end(), refBlock); + if (freeBlockIt != fm.end()) + { + MemoryBlock freeBlock = *freeBlockIt; + CHECK_EQUAL(freeBlock.m_allocationCounter, 0, ()); + CHECK_EQUAL(freeBlock.m_freeOffset, 0, ()); + CHECK_LESS_OR_EQUAL(alignedSize, freeBlock.m_blockSize, ()); + fm.erase(freeBlockIt); + + freeBlock.m_freeOffset = alignedSize; + freeBlock.m_allocationCounter++; + auto p = std::make_shared<Allocation>(resourceType, blockHash, freeBlock.m_memory, + 0, alignedSize, freeBlock.m_isCoherent); + m[blockHash].push_back(std::move(freeBlock)); + return p; + } + } + + // Looking for memory index by memory properties. + boost::optional<VkMemoryPropertyFlags> fallbackFlags; + auto flags = GetMemoryPropertyFlags(resourceType, fallbackFlags); + auto memoryTypeIndex = GetMemoryTypeIndex(memReqs.memoryTypeBits, flags); + if (!memoryTypeIndex && fallbackFlags) + { + flags = fallbackFlags.value(); + memoryTypeIndex = GetMemoryTypeIndex(memReqs.memoryTypeBits, flags); + if (!memoryTypeIndex) + CHECK(false, ("Unsupported memory allocation configuration.")); + } + else + { + CHECK(false, ("Unsupported memory allocation configuration.")); + } + + // Create new memory block. + auto const blockSize = std::max(kMinBlockSizeInBytes[static_cast<size_t>(resourceType)], + alignedSize); + VkDeviceMemory memory = {}; + VkMemoryAllocateInfo memAllocInfo = {}; + memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllocInfo.pNext = nullptr; + memAllocInfo.allocationSize = blockSize; + memAllocInfo.memoryTypeIndex = memoryTypeIndex.value(); + CHECK_VK_CALL(vkAllocateMemory(m_device, &memAllocInfo, nullptr, &memory)); + m_sizes[static_cast<size_t>(resourceType)] += blockSize; + + // Attach block. + auto & m = m_memory[static_cast<size_t>(resourceType)]; + + MemoryBlock newBlock; + newBlock.m_memory = memory; + newBlock.m_blockSize = blockSize; + newBlock.m_freeOffset = alignedSize; + newBlock.m_allocationCounter++; + newBlock.m_isCoherent = ((flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0); + + auto p = std::make_shared<Allocation>(resourceType, blockHash, newBlock.m_memory, + 0, alignedSize, newBlock.m_isCoherent); + m[blockHash].push_back(std::move(newBlock)); + return p; +} + +void VulkanMemoryManager::BeginDeallocationSession() +{ + m_isInDeallocationSession = true; + m_deallocationSessionMask = 0; +} + +void VulkanMemoryManager::Deallocate(AllocationPtr ptr) +{ + CHECK(ptr, ()); + auto const kResourceIndex = static_cast<size_t>(ptr->m_resourceType); + auto & m = m_memory[kResourceIndex]; + auto const it = m.find(ptr->m_blockHash); + CHECK(it != m.end(), ()); + auto blockIt = std::find_if(it->second.begin(), it->second.end(), + [&ptr](MemoryBlock const & b) + { + return b.m_memory == ptr->m_memory; + }); + CHECK(blockIt != it->second.end(), ()); + CHECK_GREATER(blockIt->m_allocationCounter, 0, ()); + blockIt->m_allocationCounter--; + + if (blockIt->m_allocationCounter == 0) + { + if (m_isInDeallocationSession) + { + // Here we set a bit in the deallocation mask to skip the processing of untouched + // resource collections. + m_deallocationSessionMask |= (1 << kResourceIndex); + } + else + { + MemoryBlock memoryBlock = *blockIt; + it->second.erase(blockIt); + if (m_sizes[kResourceIndex] > kDesiredSizeInBytes[kResourceIndex]) + { + CHECK_LESS_OR_EQUAL(memoryBlock.m_blockSize, m_sizes[kResourceIndex], ()); + m_sizes[kResourceIndex] -= memoryBlock.m_blockSize; + vkFreeMemory(m_device, memoryBlock.m_memory, nullptr); + } + else + { + memoryBlock.m_freeOffset = 0; + auto & fm = m_freeBlocks[kResourceIndex]; + fm.push_back(std::move(memoryBlock)); + std::sort(fm.begin(), fm.end()); + } + } + } +} + +void VulkanMemoryManager::EndDeallocationSession() +{ + if (!m_isInDeallocationSession) + return; + + m_isInDeallocationSession = false; + + for (size_t i = 0; i < kResourcesCount; ++i) + { + if (((m_deallocationSessionMask >> i) & 1) == 0) + continue; + auto & fm = m_freeBlocks[i]; + auto & m = m_memory[i]; + m[i].erase(std::remove_if(m[i].begin(), m[i].end(), + [this, &fm, i](MemoryBlock const & b) + { + if (b.m_allocationCounter == 0) + { + if (m_sizes[i] > kDesiredSizeInBytes[i]) + { + CHECK_LESS_OR_EQUAL(b.m_blockSize, m_sizes[i], ()); + m_sizes[i] -= b.m_blockSize; + vkFreeMemory(m_device, b.m_memory, nullptr); + } + else + { + MemoryBlock block = b; + block.m_freeOffset = 0; + fm.push_back(std::move(block)); + } + return true; + } + return false; + }), m[i].end()); + std::sort(fm.begin(), fm.end()); + } +} } // namespace vulkan } // namespace dp diff --git a/drape/vulkan/vulkan_memory_manager.hpp b/drape/vulkan/vulkan_memory_manager.hpp index 35f6031d2f..18449c0838 100644 --- a/drape/vulkan/vulkan_memory_manager.hpp +++ b/drape/vulkan/vulkan_memory_manager.hpp @@ -3,19 +3,97 @@ #include <vulkan_wrapper.h> #include <vulkan/vulkan.h> +#include <boost/optional.hpp> + +#include <array> +#include <cstdint> +#include <memory> +#include <mutex> +#include <vector> +#include <unordered_map> + namespace dp { namespace vulkan { +// NOTE: The class is not thread safe and must be externally synchronized. class VulkanMemoryManager { public: - explicit VulkanMemoryManager(VkDevice device) : m_device(device) {} + VulkanMemoryManager(VkDevice device, VkPhysicalDeviceLimits const & deviceLimits, + VkPhysicalDeviceMemoryProperties const & memoryProperties) + : m_device(device) + , m_deviceLimits(deviceLimits) + , m_memoryProperties(memoryProperties) + {} + + ~VulkanMemoryManager(); + + enum class ResourceType : uint8_t + { + Geometry = 0, + Uniform, + Staging, + Image, + + Count + }; + static size_t constexpr kResourcesCount = + static_cast<uint32_t>(VulkanMemoryManager::ResourceType::Count); + + struct Allocation + { + uint64_t const m_blockHash; + VkDeviceMemory const m_memory; + uint32_t const m_offset; + uint32_t const m_size; + ResourceType const m_resourceType; + bool const m_isCoherent; - //VkDeviceMemory + Allocation(ResourceType resourceType, uint64_t blockHash, VkDeviceMemory memory, + uint32_t offset, uint32_t size, bool isCoherent) + : m_blockHash(blockHash) + , m_memory(memory) + , m_offset(offset) + , m_size(size) + , m_resourceType(resourceType) + , m_isCoherent(isCoherent) + {} + }; + + using AllocationPtr = std::shared_ptr<Allocation>; + + AllocationPtr Allocate(ResourceType resourceType, VkMemoryRequirements memReqs, + uint64_t blockHash); + void BeginDeallocationSession(); + void Deallocate(AllocationPtr ptr); + void EndDeallocationSession(); private: + boost::optional<uint32_t> GetMemoryTypeIndex(uint32_t typeBits, + VkMemoryPropertyFlags properties) const; + uint32_t GetOffsetAlignment(ResourceType resourceType) const; + VkDevice const m_device; + VkPhysicalDeviceLimits const m_deviceLimits; + VkPhysicalDeviceMemoryProperties const m_memoryProperties; + bool m_isInDeallocationSession = false; + uint32_t m_deallocationSessionMask = 0; + + struct MemoryBlock + { + VkDeviceMemory m_memory = {}; + uint32_t m_blockSize = 0; + uint32_t m_freeOffset = 0; + uint32_t m_allocationCounter = 0; + bool m_isCoherent = false; + + bool operator<(MemoryBlock const & b) const { return m_blockSize < b.m_blockSize; } + }; + + std::array<std::unordered_map<uint64_t, std::vector<MemoryBlock>>, kResourcesCount> m_memory; + std::array<std::vector<MemoryBlock>, kResourcesCount> m_freeBlocks; + std::array<uint32_t, kResourcesCount> m_sizes = {}; }; } // namespace vulkan } // namespace dp diff --git a/drape/vulkan/vulkan_object_manager.cpp b/drape/vulkan/vulkan_object_manager.cpp new file mode 100644 index 0000000000..c7e86e4312 --- /dev/null +++ b/drape/vulkan/vulkan_object_manager.cpp @@ -0,0 +1,128 @@ +#include "drape/vulkan/vulkan_object_manager.hpp" +#include "drape/vulkan/vulkan_utils.hpp" + +namespace dp +{ +namespace vulkan +{ +VulkanObjectManager::VulkanObjectManager(VkDevice device, VkPhysicalDeviceLimits const & deviceLimits, + VkPhysicalDeviceMemoryProperties const & memoryProperties, + uint32_t queueFamilyIndex) + : m_device(device) + , m_queueFamilyIndex(queueFamilyIndex) + , m_memoryManager(device, deviceLimits, memoryProperties) +{ + m_queueToDestroy.reserve(50); +} + +VulkanObjectManager::~VulkanObjectManager() +{ + CollectObjects(); +} + +VulkanObject VulkanObjectManager::CreateBuffer(VulkanMemoryManager::ResourceType resourceType, + uint32_t sizeInBytes, uint64_t batcherHash) +{ + std::lock_guard<std::mutex> lock(m_mutex); + + VulkanObject result; + VkBufferCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + info.pNext = nullptr; + info.flags = 0; + info.size = sizeInBytes; + if (resourceType == VulkanMemoryManager::ResourceType::Geometry) + info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + else if (resourceType == VulkanMemoryManager::ResourceType::Uniform) + info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + else if (resourceType == VulkanMemoryManager::ResourceType::Staging) + info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + else + CHECK(false, ("Unsupported resource type.")); + + info.usage = VK_SHARING_MODE_EXCLUSIVE; + info.queueFamilyIndexCount = 1; + info.pQueueFamilyIndices = &m_queueFamilyIndex; + CHECK_VK_CALL(vkCreateBuffer(m_device, &info, nullptr, &result.m_buffer)); + + VkMemoryRequirements memReqs = {}; + vkGetBufferMemoryRequirements(m_device, result.m_buffer, &memReqs); + + result.m_allocation = m_memoryManager.Allocate(resourceType, memReqs, batcherHash); + return result; +} + +VulkanObject VulkanObjectManager::CreateImage(VkImageUsageFlagBits usageFlagBits, VkFormat format, + VkImageAspectFlags aspectFlags, uint32_t width, uint32_t height) +{ + std::lock_guard<std::mutex> lock(m_mutex); + + VulkanObject result; + VkImageCreateInfo imageCreateInfo = {}; + imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageCreateInfo.pNext = nullptr; + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = format; + imageCreateInfo.mipLevels = 1; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.extent = { width, height, 1 }; + imageCreateInfo.usage = usageFlagBits | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + CHECK_VK_CALL(vkCreateImage(m_device, &imageCreateInfo, nullptr, &result.m_image)); + + VkImageViewCreateInfo viewCreateInfo = {}; + viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewCreateInfo.pNext = nullptr; + viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewCreateInfo.format = format; + viewCreateInfo.components = {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, + VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; + viewCreateInfo.subresourceRange.aspectMask = aspectFlags; + viewCreateInfo.subresourceRange.baseMipLevel = 0; + viewCreateInfo.subresourceRange.levelCount = 1; + viewCreateInfo.subresourceRange.baseArrayLayer = 0; + viewCreateInfo.subresourceRange.layerCount = 1; + viewCreateInfo.image = result.m_image; + CHECK_VK_CALL(vkCreateImageView(m_device, &viewCreateInfo, nullptr, &result.m_imageView)); + + VkMemoryRequirements memReqs = {}; + vkGetImageMemoryRequirements(m_device, result.m_image, &memReqs); + + result.m_allocation = m_memoryManager.Allocate(VulkanMemoryManager::ResourceType::Image, + memReqs, 0 /* blockHash */); + return result; +} + +void VulkanObjectManager::DestroyObject(VulkanObject object) +{ + std::lock_guard<std::mutex> lock(m_mutex); + m_queueToDestroy.push_back(std::move(object)); +} + +void VulkanObjectManager::CollectObjects() +{ + std::lock_guard<std::mutex> lock(m_mutex); + if (m_queueToDestroy.empty()) + return; + + m_memoryManager.BeginDeallocationSession(); + for (size_t i = 0; i < m_queueToDestroy.size(); ++i) + { + if (m_queueToDestroy[i].m_buffer != 0) + vkDestroyBuffer(m_device, m_queueToDestroy[i].m_buffer, nullptr); + if (m_queueToDestroy[i].m_imageView != 0) + vkDestroyImageView(m_device, m_queueToDestroy[i].m_imageView, nullptr); + if (m_queueToDestroy[i].m_image != 0) + vkDestroyImage(m_device, m_queueToDestroy[i].m_image, nullptr); + + if (m_queueToDestroy[i].m_allocation) + m_memoryManager.Deallocate(m_queueToDestroy[i].m_allocation); + } + m_memoryManager.EndDeallocationSession(); + m_queueToDestroy.clear(); +} +} // namespace vulkan +} // namespace dp diff --git a/drape/vulkan/vulkan_object_manager.hpp b/drape/vulkan/vulkan_object_manager.hpp new file mode 100644 index 0000000000..560faffd93 --- /dev/null +++ b/drape/vulkan/vulkan_object_manager.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "drape/vulkan/vulkan_memory_manager.hpp" + +#include <vulkan_wrapper.h> +#include <vulkan/vulkan.h> + +#include <cstdint> +#include <memory> +#include <mutex> +#include <vector> + +namespace dp +{ +namespace vulkan +{ +struct VulkanObject +{ + VkBuffer m_buffer = {}; + VkImage m_image = {}; + VkImageView m_imageView = {}; + VulkanMemoryManager::AllocationPtr m_allocation; +}; + +class VulkanObjectManager +{ +public: + VulkanObjectManager(VkDevice device, VkPhysicalDeviceLimits const & deviceLimits, + VkPhysicalDeviceMemoryProperties const & memoryProperties, + uint32_t queueFamilyIndex); + ~VulkanObjectManager(); + + VulkanObject CreateBuffer(VulkanMemoryManager::ResourceType resourceType, + uint32_t sizeInBytes, uint64_t batcherHash); + VulkanObject CreateImage(VkImageUsageFlagBits usageFlagBits, VkFormat format, + VkImageAspectFlags aspectFlags, uint32_t width, uint32_t height); + void DestroyObject(VulkanObject object); + void CollectObjects(); + +private: + VkDevice const m_device; + uint32_t const m_queueFamilyIndex; + VulkanMemoryManager m_memoryManager; + std::vector<VulkanObject> m_queueToDestroy; + std::mutex m_mutex; +}; +} // namespace vulkan +} // namespace dp |