From 63b08cd219d5a14571a13e2dd0e7764fc4cc4e04 Mon Sep 17 00:00:00 2001 From: "matthias@arch" Date: Fri, 21 Oct 2022 21:58:12 +0200 Subject: [PATCH] Added allocator for buffers --- src/vulkan_allocator.cpp | 136 +++++++++++++++++++++++++++++++++++++++ src/vulkan_allocator.hpp | 58 +++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 src/vulkan_allocator.cpp create mode 100644 src/vulkan_allocator.hpp diff --git a/src/vulkan_allocator.cpp b/src/vulkan_allocator.cpp new file mode 100644 index 0000000..1dc2d36 --- /dev/null +++ b/src/vulkan_allocator.cpp @@ -0,0 +1,136 @@ +#include "vulkan_allocator.hpp" + +#include "exceptions.hpp" +#include "vulkan_instance.hpp" + +#include +#include + +namespace gz::vk { + struct MemoryBlock { + size_t size; + size_t offset; + bool free; + }; + + struct DeviceMemory { + DeviceMemory() = delete; + DeviceMemory(VkDeviceSize size_) { + size = size_; + memory = VK_NULL_HANDLE; + blocks.emplace_back(MemoryBlock{size, 0, true}); + }; + + VkDeviceSize size; + VkDeviceMemory memory; + std::list blocks; + }; + + + VulkanAllocator::VulkanAllocator(VulkanInstance& instance) + : vk(instance) + { + LogCreateInfo logCI{}; + logCI.logfile = "vulkan_allocator.log"; + logCI.storeLog = false; + logCI.prefix = "Allocator"; + logCI.prefixColor = Color::BLACK; + logCI.timeColor = VULKAN_MESSAGE_TIME_COLOR; + aLog = Log(std::move(logCI)); + + } + + void VulkanAllocator::allocate(const VkMemoryAllocateInfo& allocI, MemoryInfo& memoryInfo) { + // go through allocated memories with matching memoryType + for (auto& deviceMemory : memory[allocI.memoryTypeIndex]) { + /* auto bestBlockIt = deviceMemory.blocks.end(); */ + // go through blocks in memory + for (auto block = deviceMemory.blocks.begin(); block != deviceMemory.blocks.end(); block++) { + if (!block->free) { continue; } + if (block->size >= allocI.allocationSize) { + aLog.log0("allocate: memoryTypeIndex:", allocI.memoryTypeIndex, + "size:", allocI.allocationSize, "block size:", block->size, "block offset:", block->offset); + // if the block is larger than the needed size, split the block + if (block->size > allocI.allocationSize) { + // emplace free part of block after used part of block + auto newBlock = ++block; + block--; + deviceMemory.blocks.emplace(newBlock, + MemoryBlock{ block->size - allocI.allocationSize, block->offset + allocI.allocationSize, true }); + block--; + block->size = allocI.allocationSize; + } + block->free = false; + memoryInfo.memory = deviceMemory.memory; + memoryInfo.offset = block->offset; + memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex; + return; + } + } + } + + // if nothing is found, allocate new memory + memory[allocI.memoryTypeIndex].emplace_back(DeviceMemory(allocI.allocationSize * TODO_ALLOCATION_SIZE_MULTIPLIIER)); + VkMemoryAllocateInfo allocI_{}; + allocI_.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocI_.memoryTypeIndex = allocI.memoryTypeIndex; + allocI_.allocationSize = memory[allocI.memoryTypeIndex].back().size; + aLog.log0("allocate: Allocating new memory of size:", allocI_.allocationSize, " and memoryTypeIndex:", allocI.memoryTypeIndex); + VkResult result = vkAllocateMemory(vk.getDevice(), &allocI_, NO_ALLOC, &memory[allocI.memoryTypeIndex].back().memory); + if (result != VK_SUCCESS) { + throw getVkException(result, "Failed to allocate memory", "VulkanAllocator::allocate"); + } + + // return block of new memory + DeviceMemory& deviceMemory = memory[allocI.memoryTypeIndex].back(); + deviceMemory.blocks.front().offset = allocI.allocationSize; + deviceMemory.blocks.front().size -= allocI.allocationSize; + deviceMemory.blocks.emplace_front(MemoryBlock{ allocI.allocationSize, 0, false }); + + memoryInfo.memory = deviceMemory.memory; + memoryInfo.offset = 0; + memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex; + } + + + void VulkanAllocator::free(MemoryInfo& memoryInfo) { + // go through allocated memories with matching memoryType + for (auto deviceMemory = memory[memoryInfo.memoryTypeIndex].begin(); deviceMemory != memory[memoryInfo.memoryTypeIndex].end(); deviceMemory++) { + if (deviceMemory->memory != memoryInfo.memory) { continue; } + // go through blocks in memory + for (auto block = deviceMemory->blocks.begin(); block != deviceMemory->blocks.end(); block++) { + if (block->offset != memoryInfo.offset) { continue; } + aLog.log0("free: Freeing block at offset", memoryInfo.offset); + block->free = true; + // merge with previous block, if free + auto otherBlock = block; + otherBlock--; + if (otherBlock->free) { + otherBlock->size += block->size; + deviceMemory->blocks.erase(block); + block = otherBlock; + } + // merge with next block, if free + otherBlock = block; + otherBlock++; + if (otherBlock->free) { + block->size += otherBlock->size; + deviceMemory->blocks.erase(otherBlock); + } + memoryInfo.memory = VK_NULL_HANDLE; + memoryInfo.offset = 0; + + // if now there is only one free block, everything is free -> deallocate memory + if (deviceMemory->blocks.size() == 1) { + aLog.log0("free: Deallocting memory of size:", deviceMemory->size, "and memoryTypeIndex:", memoryInfo.memoryTypeIndex); + vkFreeMemory(vk.getDevice(), deviceMemory->memory, NO_ALLOC); + memory[memoryInfo.memoryTypeIndex].erase(deviceMemory); + } + return; + } + } + throw VkUserError("Trying to free block that was not allocated from this VulkanAllocator", "VulkanAllocator::free"); + } + + +} // namespace gz::vk diff --git a/src/vulkan_allocator.hpp b/src/vulkan_allocator.hpp new file mode 100644 index 0000000..956b81d --- /dev/null +++ b/src/vulkan_allocator.hpp @@ -0,0 +1,58 @@ +#pragma once + +/* #include "vulkan_util.hpp" */ +#include + +#include +#include +#include + +#include +#include + +/* #include */ + +namespace gz::vk { + struct MemoryInfo { + VkDeviceMemory memory = VK_NULL_HANDLE; + VkDeviceSize offset = 0; + uint32_t memoryTypeIndex = 0; + }; + // if no memory is available, allocate a chunk of multiplier * requested size + constexpr VkDeviceSize TODO_ALLOCATION_SIZE_MULTIPLIIER = 10; + + + // defined in vulkan_instance.hpp + class VulkanInstance; + // defined in vulkan_allocator.cpp + struct DeviceMemory; + + class VulkanAllocator { + public: + VulkanAllocator(VulkanInstance& instance); + /** + * @brief Get a block from a VkDeviceMemory + * @details + * Get a block of allocI.allocationSize of a VkDeviceMemory that was allocated with allocI.memoryTypeIndex. + * + * When this function returns, memoryInfo will contain the information about the available block. + * You can then bind something of size allocI.allocationSize to memoryInfo.memory at offset memoryInfo.offset. + * @throws VkException when a call to vkAllocateMemory is needed and fails + * @todo Determine the size of new allocations + */ + void allocate(const VkMemoryAllocateInfo& allocI, MemoryInfo& memoryInfo); + /** + * @brief Free a block allocated with allocate() + * + * When this function returns, memoryInfo will be reset to default values + * @throws VkUserError if memoryInfo was not allocated from this allocator + */ + void free(MemoryInfo& memoryInfo); + private: + /// allocated memory for memoryIndexType + std::map> memory; + Log aLog; + VulkanInstance& vk; + + }; // class VulkanAllocator +} // namespace gz::vk