Added allocator for buffers

This commit is contained in:
matthias@arch 2022-10-21 21:58:12 +02:00
parent 4b500bed23
commit 63b08cd219
2 changed files with 194 additions and 0 deletions

136
src/vulkan_allocator.cpp Normal file
View File

@ -0,0 +1,136 @@
#include "vulkan_allocator.hpp"
#include "exceptions.hpp"
#include "vulkan_instance.hpp"
#include <glm/vector_relational.hpp>
#include <vulkan/vulkan_core.h>
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<MemoryBlock> 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

58
src/vulkan_allocator.hpp Normal file
View File

@ -0,0 +1,58 @@
#pragma once
/* #include "vulkan_util.hpp" */
#include <gz-util/log.hpp>
#include <initializer_list>
#include <vulkan/vulkan.hpp>
#include <vulkan/vulkan_core.h>
#include <list>
#include <map>
/* #include <vulkan/vulkan_core.h> */
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<uint32_t, std::vector<DeviceMemory>> memory;
Log aLog;
VulkanInstance& vk;
}; // class VulkanAllocator
} // namespace gz::vk