From b5b1f345bcf645820e1ceb1e8619a57063b2533d Mon Sep 17 00:00:00 2001 From: "matthias@arch" Date: Thu, 10 Nov 2022 16:57:01 +0100 Subject: [PATCH] moved allocator logic to separate class --- src/dynamic_size_block_allocator.cpp | 92 ++++++++++++++++++ src/dynamic_size_block_allocator.hpp | 51 ++++++++++ src/vulkan_allocator.cpp | 135 ++++++++------------------- src/vulkan_allocator.hpp | 18 ++-- 4 files changed, 191 insertions(+), 105 deletions(-) create mode 100644 src/dynamic_size_block_allocator.cpp create mode 100644 src/dynamic_size_block_allocator.hpp diff --git a/src/dynamic_size_block_allocator.cpp b/src/dynamic_size_block_allocator.cpp new file mode 100644 index 0000000..71ddf56 --- /dev/null +++ b/src/dynamic_size_block_allocator.cpp @@ -0,0 +1,92 @@ +#include "dynamic_size_block_allocator.hpp" + +#include "exceptions.hpp" +#include "vulkan_settings.hpp" + +#include + +namespace gz::vlk { + std::string Block::toString() const { + return std::string(""); + } + + + DynBlockAllocator::DynBlockAllocator(uint32_t size) { + /* LogCreateInfo logCI { */ + /* .logfile = "DynBlockAllocator.log", */ + /* .storeLog = false, */ + /* .prefix = "BlockAllocator", */ + /* .prefixColor = Color::LI_GREEN, */ + /* .timeColor = settings::VULKAN_MESSAGE_TIME_COLOR, */ + /* }; */ + /* aLog = Log(std::move(logCI)); */ + blocks.emplace_front(Block{ .size = size, .offset = 0, .free = true }); + } + + uint32_t DynBlockAllocator::allocate(uint32_t size, uint32_t alignment) { + // go through blocks in memory + for (auto block = blocks.begin(); block != blocks.end(); block++) { + if (!block->free) { continue; } + if (block->size >= size) { + // check if alignment is ok + if (block->offset % alignment != 0) { + // move non-aligned space to previous block + // new offset === offset + (align - offset % align) + size_t moveToPreviousBlock = alignment - block->offset % alignment; + // check if still large enough + if (block->size - moveToPreviousBlock < size) { continue; } + block--; + block->size += moveToPreviousBlock; + block++; + block->offset += moveToPreviousBlock; + block->size -= moveToPreviousBlock; + } + // if the block is larger than the needed size, split the block + if (block->size > size) { + // emplace free part of block after used part of block + auto newBlock = ++block; + block--; + blocks.emplace(newBlock, + Block{ block->size - size, block->offset + size, true }); + /* block--; */ + block->size = size; + } + block->free = false; + /* aLog.log0("allocate: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") Blocks:", deviceMemory->blocks); */ + return block->offset; + } + } + // if no space, throw exception + throw VkException("Out of memory", "DynBlockAllocator::allocate"); + } + + + void DynBlockAllocator::free(uint32_t offset) { + // go through blocks in memory + for (auto block = blocks.begin(); block != blocks.end(); block++) { + if (block->offset != offset) { continue; } + block->free = true; + if (block != blocks.begin()) { + // merge with previous block, if free + auto otherBlock = block; + otherBlock--; + if (otherBlock->free) { + otherBlock->size += block->size; + blocks.erase(block); + block = otherBlock; + } + } + if (block != --(blocks.end())) { + // merge with next block, if free + auto otherBlock = block; + otherBlock++; + if (otherBlock->free) { + block->size += otherBlock->size; + blocks.erase(otherBlock); + } + } + return; + } + throw VkUserError("Trying to free block that was not allocated from this DynBlockAllocator", "DynBlockAllocator::free"); + } +} // namespace gz::vlk diff --git a/src/dynamic_size_block_allocator.hpp b/src/dynamic_size_block_allocator.hpp new file mode 100644 index 0000000..23a6813 --- /dev/null +++ b/src/dynamic_size_block_allocator.hpp @@ -0,0 +1,51 @@ +#pragma once + +/* #include */ + +#include + +#include +#include +#include +#include + + +namespace gz::vlk { + /** + * @brief Information on a single block of memory + */ + struct Block { + size_t size = 0; + size_t offset = 0; + bool free = true; + std::string toString() const; + }; + + /** + * @brief General purpose dynamic sized block allocator + * @note Does not actually allocate anything itself + */ + class DynBlockAllocator { + public: + DynBlockAllocator(uint32_t size); + /** + * @brief Allocate block of size with alignment + * @throws VkException if the block can not be allocated + */ + [[ nodiscard ]] uint32_t allocate(uint32_t size, uint32_t alignment); + /** + * @brief Free a block allocated with allocate() + */ + void free(uint32_t offset); + + using Iterator = std::list::iterator; + const Iterator begin() { return blocks.begin(); } + const Iterator end() { return blocks.end(); } + std::list::size_type blockCount() const { return blocks.size(); } + bool empty() const { return blocks.size() == 1 and blocks.front().free; } + + private: + std::list blocks; + /* Log aLog; */ + }; // class DynBlockAllocator +} // namespace gz::vlk diff --git a/src/vulkan_allocator.cpp b/src/vulkan_allocator.cpp index 165bd0d..a1e3c3f 100644 --- a/src/vulkan_allocator.cpp +++ b/src/vulkan_allocator.cpp @@ -1,22 +1,15 @@ #include "vulkan_allocator.hpp" +#include "dynamic_size_block_allocator.hpp" #include "exceptions.hpp" #include "vulkan_instance.hpp" #include -#include +#include namespace gz::vlk { - std::string MemoryBlock::toString() const { - return std::string(""); - } - - - DeviceMemory::DeviceMemory(vk::DeviceSize size_) { - size = size_; - memory = VK_NULL_HANDLE; - blocks.emplace_back(MemoryBlock{size, 0, true}); - }; + DeviceMemory::DeviceMemory(vk::DeviceSize size_) + : size(size_), memory(VK_NULL_HANDLE), dynBlockAllocator(size_) {}; VulkanAllocator::VulkanAllocator(VulkanInstance& instance) @@ -39,42 +32,15 @@ namespace gz::vlk { "), ( alignment", toHexString(memReq.memoryRequirements.alignment), ")"); // go through allocated memories with matching memoryType for (auto deviceMemory = memory[allocI.memoryTypeIndex].begin(); deviceMemory != memory[allocI.memoryTypeIndex].end(); deviceMemory++) { - /* 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) { - // check if alignment is ok - if (block->offset % memReq.memoryRequirements.alignment != 0) { - // move non-aligned space to previous block - // new offset === offset + (align - offset % align) - size_t moveToPreviousBlock = memReq.memoryRequirements.alignment - block->offset % memReq.memoryRequirements.alignment; - // check if still large enough - if (block->size - moveToPreviousBlock < allocI.allocationSize) { continue; } - block--; - block->size += moveToPreviousBlock; - block++; - block->offset += moveToPreviousBlock; - block->size -= moveToPreviousBlock; - } - // 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; - - /* aLog.log0("allocate: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") Blocks:", deviceMemory->blocks); */ - return; - } + try { + uint32_t offset = deviceMemory->dynBlockAllocator.allocate(allocI.allocationSize, memReq.memoryRequirements.alignment); + memoryInfo.memory = deviceMemory->memory; + memoryInfo.offset = offset; + memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex; + return; + } + catch (VkException& e) { + continue; } } @@ -91,15 +57,17 @@ namespace gz::vlk { // 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 }); - - // alignment always satisfied with vk::allocateMemory() and offset 0 - memoryInfo.memory = deviceMemory.memory; - memoryInfo.offset = 0; - memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex; - /* aLog.log0("allocate: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(memory[memoryInfo.memoryTypeIndex].size()) + ") Blocks:", deviceMemory.blocks); */ + try { + uint32_t offset = deviceMemory.dynBlockAllocator.allocate(allocI.allocationSize, memReq.memoryRequirements.alignment); + memoryInfo.memory = deviceMemory.memory; + memoryInfo.offset = offset; + memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex; + return; + } + catch (VkException& e) { + // should never happen + throw e; + } } @@ -107,45 +75,24 @@ namespace gz::vlk { // 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: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") Freeing block at offset:", gz::toHexString(memoryInfo.offset, 0)); - block->free = true; - if (block != deviceMemory->blocks.begin()) { - // merge with previous block, if free - auto otherBlock = block; - otherBlock--; - if (otherBlock->free) { - otherBlock->size += block->size; - deviceMemory->blocks.erase(block); - block = otherBlock; - } - } - if (block != --(deviceMemory->blocks.end())) { - // merge with next block, if free - auto otherBlock = block; - otherBlock++; - if (otherBlock->free) { - block->size += otherBlock->size; - deviceMemory->blocks.erase(otherBlock); - } - } - - memoryInfo.memory = VK_NULL_HANDLE; - memoryInfo.offset = 0; - - /* aLog.log0("free: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") Blocks:", deviceMemory->blocks); */ - // if now there is only one free block, everything is free -> deallocate memory - if (deviceMemory->blocks.size() == 1) { - aLog.log0("free: Deallocating (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") of size:", gz::toHexString(deviceMemory->size, 0)); - vk.getDevice().freeMemory(deviceMemory->memory, NO_ALLOC); - memory[memoryInfo.memoryTypeIndex].erase(deviceMemory); - } - return; + try { + deviceMemory->dynBlockAllocator.free(memoryInfo.offset); } + catch (VkUserError&) { + throw VkUserError("Trying to free block that was not allocated from this VulkanAllocator", "VulkanAllocator::free"); + } + memoryInfo.memory = VK_NULL_HANDLE; + memoryInfo.offset = 0; + + /* aLog.log0("free: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") Blocks:", deviceMemory->blocks); */ + // if now there is only one free block, everything is free -> deallocate memory + if (deviceMemory->dynBlockAllocator.blockCount() == 1) { + aLog.log0("free: Deallocating (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") of size:", gz::toHexString(deviceMemory->size, 0)); + vk.getDevice().freeMemory(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"); } @@ -154,7 +101,7 @@ namespace gz::vlk { for (auto memType = memory.begin(); memType != memory.end(); memType++) { for (auto deviceMemory = memType->second.begin(); deviceMemory != memType->second.end(); deviceMemory++) { // check if all blocks are free - for (auto block = deviceMemory->blocks.begin(); block != deviceMemory->blocks.end(); block++) { + for (auto block = deviceMemory->dynBlockAllocator.begin(); block != deviceMemory->dynBlockAllocator.end(); block++) { if (block->free) { continue; } aLog.warning("cleanup: (memory", memType->first, "-", gz::toString(deviceMemory - memType->second.begin()) + ") Block not freed: ", *block); } diff --git a/src/vulkan_allocator.hpp b/src/vulkan_allocator.hpp index 8af4508..88a95e9 100644 --- a/src/vulkan_allocator.hpp +++ b/src/vulkan_allocator.hpp @@ -1,6 +1,11 @@ #pragma once + #include "vulkan_util.hpp" + +#include "dynamic_size_block_allocator.hpp" + +#define LOG_SUBLOGS #include #include @@ -32,16 +37,6 @@ namespace gz::vlk { uint32_t memoryTypeIndex = 0; }; - /** - * @brief Information on a single block of memory - */ - struct MemoryBlock { - size_t size; - size_t offset; - bool free; - std::string toString() const; - }; - /** * @brief Manage a single vk::DeviceMemory chunk */ @@ -51,7 +46,8 @@ namespace gz::vlk { vk::DeviceSize size; vk::DeviceMemory memory; - std::list blocks; + + DynBlockAllocator dynBlockAllocator; };