moved allocator logic to separate class

This commit is contained in:
matthias@arch 2022-11-10 16:57:01 +01:00
parent 17033bf104
commit b5b1f345bc
4 changed files with 191 additions and 105 deletions

View File

@ -0,0 +1,92 @@
#include "dynamic_size_block_allocator.hpp"
#include "exceptions.hpp"
#include "vulkan_settings.hpp"
#include <gz-util/string/conversion.hpp>
namespace gz::vlk {
std::string Block::toString() const {
return std::string("<offset: " + gz::toHexString(offset, 0) + ", size: " + gz::toHexString(size, 0) + ", free: " + gz::toString(free) + ">");
}
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

View File

@ -0,0 +1,51 @@
#pragma once
/* #include <gz-util/log.hpp> */
#include <initializer_list>
#include <list>
#include <map>
#include <cstdint>
#include <string>
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<Block>::iterator;
const Iterator begin() { return blocks.begin(); }
const Iterator end() { return blocks.end(); }
std::list<Block>::size_type blockCount() const { return blocks.size(); }
bool empty() const { return blocks.size() == 1 and blocks.front().free; }
private:
std::list<Block> blocks;
/* Log aLog; */
}; // class DynBlockAllocator
} // namespace gz::vlk

View File

@ -1,22 +1,15 @@
#include "vulkan_allocator.hpp"
#include "dynamic_size_block_allocator.hpp"
#include "exceptions.hpp"
#include "vulkan_instance.hpp"
#include <glm/vector_relational.hpp>
#include <gz-util/util/string_conversion.hpp>
#include <gz-util/string/conversion.hpp>
namespace gz::vlk {
std::string MemoryBlock::toString() const {
return std::string("<offset: " + gz::toHexString(offset, 0) + ", size: " + gz::toHexString(size, 0) + ", free: " + gz::toString(free) + ">");
}
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;
try {
uint32_t offset = deviceMemory->dynBlockAllocator.allocate(allocI.allocationSize, memReq.memoryRequirements.alignment);
memoryInfo.memory = deviceMemory->memory;
memoryInfo.offset = block->offset;
memoryInfo.offset = offset;
memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex;
/* aLog.log0("allocate: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") Blocks:", deviceMemory->blocks); */
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
try {
uint32_t offset = deviceMemory.dynBlockAllocator.allocate(allocI.allocationSize, memReq.memoryRequirements.alignment);
memoryInfo.memory = deviceMemory.memory;
memoryInfo.offset = 0;
memoryInfo.offset = offset;
memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex;
/* aLog.log0("allocate: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(memory[memoryInfo.memoryTypeIndex].size()) + ") Blocks:", deviceMemory.blocks); */
return;
}
catch (VkException& e) {
// should never happen
throw e;
}
}
@ -107,37 +75,18 @@ 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;
try {
deviceMemory->dynBlockAllocator.free(memoryInfo.offset);
}
catch (VkUserError&) {
throw VkUserError("Trying to free block that was not allocated from this VulkanAllocator", "VulkanAllocator::free");
}
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) {
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);
@ -145,8 +94,6 @@ namespace gz::vlk {
return;
}
}
throw VkUserError("Trying to free block that was not allocated from this VulkanAllocator", "VulkanAllocator::free");
}
void VulkanAllocator::cleanup() {
@ -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);
}

View File

@ -1,6 +1,11 @@
#pragma once
#include "vulkan_util.hpp"
#include "dynamic_size_block_allocator.hpp"
#define LOG_SUBLOGS
#include <gz-util/log.hpp>
#include <initializer_list>
@ -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<MemoryBlock> blocks;
DynBlockAllocator dynBlockAllocator;
};