moved allocator logic to separate class
This commit is contained in:
parent
17033bf104
commit
b5b1f345bc
92
src/dynamic_size_block_allocator.cpp
Normal file
92
src/dynamic_size_block_allocator.cpp
Normal 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
|
51
src/dynamic_size_block_allocator.hpp
Normal file
51
src/dynamic_size_block_allocator.hpp
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user