1
0
Fork 0
Univerxel/src/client/render/vk/Allocator.cpp

282 lines
12 KiB
C++

#include "Allocator.hpp"
#include "PhysicalDeviceInfo.hpp"
#include <TracyVulkan.hpp>
#include "buffer/VertexData.hpp"
#include <memory.h>
using namespace render::vk;
const auto NO_DELETER = Allocator::MemoryDeleter(nullptr);
Allocator::memory_ptr get_null_ptr() { return Allocator::memory_ptr(nullptr, NO_DELETER); }
Allocator::Allocator(VkDevice device, const PhysicalDeviceInfo &info) : device(device), indexedBufferMemory(get_null_ptr()) {
vkGetPhysicalDeviceMemoryProperties(info.device, &properties);
{
if (!info.queueIndices.transferFamily.has_value()) {
LOG_W("No transfer queue family. Using graphics one");
}
const auto family = info.queueIndices.transferFamily.value_or(info.queueIndices.graphicsFamily.value());
vkGetDeviceQueue(device, family, 0, &transferQueue);
VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = family;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
if (vkCreateCommandPool(device, &poolInfo, ALLOC, &transferPool) != VK_SUCCESS) {
FATAL("Failed to create transfer pool!");
}
}
{
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = transferPool;
allocInfo.commandBufferCount = 1;
vkAllocateCommandBuffers(device, &allocInfo, &transferBuffer);
tracyCtx = TracyVkContext(info.device, device, transferQueue, transferBuffer);
}
{
size_t vertexSize = sizeof(buffer::vk::vertices[0]) * buffer::vk::vertices.size();
size_t indexSize = sizeof(buffer::vk::indices[0]) * buffer::vk::indices.size();
size_t stagingSize = std::max(vertexSize, indexSize);
buffer_info stagingBuffer;
if(auto stagingMemory = createBuffer(stagingSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, stagingBuffer)) {
std::vector<buffer_requirement> requirements = {
{indexSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT},
{vertexSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT}
};
if(std::vector<buffer_info> out; indexedBufferMemory = createBuffers(requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, out)) {
indexBuffer = out[0];
vertexBuffer = out[1];
} else {
FATAL("Cannot allocate buffer memory");
}
stagingMemory->write(buffer::vk::vertices.data(), vertexSize);
copyBuffer(stagingBuffer, vertexBuffer, vertexSize);
stagingMemory->write(buffer::vk::indices.data(), indexSize);
copyBuffer(stagingBuffer, indexBuffer, indexSize);
vkDestroyBuffer(device, stagingBuffer.buffer, ALLOC); //TODO: move to buffer
} else {
FATAL("Cannot allocate staging memory");
}
}
}
Allocator::~Allocator() {
vkDestroyBuffer(device, indexBuffer.buffer, ALLOC);
vkDestroyBuffer(device, vertexBuffer.buffer, ALLOC);
indexedBufferMemory.reset();
TracyVkDestroy(tracyCtx);
vkFreeCommandBuffers(device, transferPool, 1, &transferBuffer);
vkDestroyCommandPool(device, transferPool, ALLOC);
//NOTE: all allocations are delete by ~vector
}
void Allocator::setTracyZone(const char* name) {
TracyVkCollect(tracyCtx, transferBuffer);
TracyVkZone(tracyCtx, transferBuffer, name);
(void)name;
}
Allocator::memory_ptr Allocator::createBuffer(VkDeviceSize size, VkMemoryPropertyFlags properties, VkBufferUsageFlags usage, buffer_info& out) {
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(device, &bufferInfo, ALLOC, &out.buffer) != VK_SUCCESS) {
LOG_E("Failed to create buffer");
return get_null_ptr();
}
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, out.buffer, &memRequirements);
if (auto memory = allocate(memRequirements, properties)) {
if(vkBindBufferMemory(device, out.buffer, memory->ref, memory->offset) == VK_SUCCESS)
return memory;
}
LOG_E("Failed to allocate buffer memory");
return get_null_ptr();
}
Allocator::memory_ptr Allocator::createBuffers(const std::vector<buffer_requirement>& requirements, VkMemoryPropertyFlags properties, std::vector<buffer_info>& out) {
assert(!requirements.empty());
out.resize(requirements.size());
// Create buffers
VkMemoryRequirements memRequirements = {0, 0, UINT32_MAX};
std::vector<std::pair<VkDeviceSize, VkDeviceSize>> ranges;
ranges.resize(requirements.size());
for (size_t i = 0; i < requirements.size(); i++) {
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = requirements[i].size;
bufferInfo.usage = requirements[i].usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(device, &bufferInfo, ALLOC, &out[i].buffer) != VK_SUCCESS) {
LOG_E("Failed to create buffer");
return get_null_ptr();
}
VkMemoryRequirements individualMemRequirements;
vkGetBufferMemoryRequirements(device, out[i].buffer, &individualMemRequirements);
memRequirements.alignment = std::max(memRequirements.alignment, individualMemRequirements.alignment);
memRequirements.memoryTypeBits &= individualMemRequirements.memoryTypeBits;
ranges[i].first = individualMemRequirements.size;
}
// Align blocks
auto aligned = [&](VkDeviceSize offset) {
if (offset % memRequirements.alignment == 0)
return offset;
return offset + memRequirements.alignment - (offset % memRequirements.alignment);
};
ranges[0].second = 0;
for (size_t i = 1; i < requirements.size(); i++) {
ranges[i].second = aligned(ranges[i-1].second + ranges[i-1].first);
}
memRequirements.size = aligned(ranges.back().second + ranges.back().first);
// Bind memory
if (auto memory = allocate(memRequirements, properties)) {
for (size_t i = 0; i < requirements.size(); i++) {
if (vkBindBufferMemory(device, out[i].buffer, memory->ref, memory->offset + ranges[i].second) != VK_SUCCESS) {
LOG_E("Failed to bind buffer");
return get_null_ptr();
}
}
return memory;
}
LOG_E("Failed to allocate buffers");
return get_null_ptr();
}
Allocator::memory_ptr Allocator::allocate(VkMemoryRequirements requirements, VkMemoryPropertyFlags properties) {
//TODO: search for existing allocation
//TODO: allocate more ???
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = requirements.size;
if (const auto memIdx = findMemory(requirements.memoryTypeBits, properties, requirements.size)) {
//TODO: check budget
allocInfo.memoryTypeIndex = memIdx.value();
} else {
LOG_E("No suitable memory heap");
return get_null_ptr();
}
VkDeviceMemory memory;
if (vkAllocateMemory(device, &allocInfo, ALLOC, &memory) != VK_SUCCESS) {
LOG_E("Failed to allocate memory!");
return get_null_ptr();
}
void *ptr = nullptr;
if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, &ptr);
}
auto allocation = allocations.emplace_back(new Allocation(device, memory, allocInfo.allocationSize, allocInfo.memoryTypeIndex, ptr)).get();
allocation->areas.push_back({allocInfo.allocationSize, 0});
return memory_ptr(new memory_area{memory, requirements.size, 0, ptr}, allocation->deleter);
}
void Allocator::copyBuffer(buffer_info src, buffer_info dst, VkDeviceSize size) {
//FIXME: assert no out of range
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(transferBuffer, &beginInfo);
VkBufferCopy copyRegion{};
copyRegion.srcOffset = 0;
copyRegion.dstOffset = 0;
copyRegion.size = size;
vkCmdCopyBuffer(transferBuffer, src.buffer, dst.buffer, 1, &copyRegion);
vkEndCommandBuffer(transferBuffer);
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &transferBuffer;
vkQueueSubmit(transferQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(transferQueue); //MAYBE: use fences
vkResetCommandBuffer(transferBuffer, 0);
}
std::optional<uint32_t> Allocator::findMemory(uint32_t typeFilter, VkMemoryPropertyFlags requirement, VkDeviceSize size) const {
#if DEBUG
LOG_D("available memory:");
for (uint32_t i = 0; i < properties.memoryTypeCount; i++) {
LOG_D('\t' << i << ": " << ((properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ? "local " : "")
<< ((properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ? "visible " : "")
<< ((properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) ? "coherent " : "")
<< ((properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) ? "cached " : "")
<< ((properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) ? "lazy " : "")
<< ((properties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_PROTECTED_BIT) ? "protected " : "")
<< properties.memoryHeaps[properties.memoryTypes[i].heapIndex].size);
}
#endif
for (uint32_t i = 0; i < properties.memoryTypeCount; i++) {
if ((typeFilter & (1 << i)) && (properties.memoryTypes[i].propertyFlags & requirement) == requirement) {
VkDeviceSize usage = size;
for(const auto& alloc: allocations) {
if(alloc->memoryType == i)
usage += alloc->size;
}
VkDeviceSize budget = properties.memoryHeaps[properties.memoryTypes[i].heapIndex].size;
//TODO: use memory budjet extension
if(budget >= usage) {
return i;
}
}
}
return {};
}
void Allocator::memory_area::write(const void* data, size_t data_size, size_t write_offset) {
assert(ptr != nullptr && size >= write_offset + data_size);
memcpy(ptr + write_offset, data, data_size);
}
void Allocator::MemoryDeleter::operator()(memory_area* area) {
assert(area != nullptr && "Deleting null area");
if(owner != nullptr) {
for (auto it = owner->areas.begin(); it != owner->areas.end(); ++it) {
if(it->offset == area->offset) {
assert(it->size == area->size);
owner->areas.erase(it);
delete area;
return;
}
}
}
LOG_E("Allocation area not found");
delete area;
}
Allocator::Allocation::Allocation(VkDevice device, VkDeviceMemory memory, VkDeviceSize size, uint32_t memoryType, void *ptr):
device(device), memory(memory), size(size), memoryType(memoryType), ptr(ptr), deleter(this) { }
Allocator::Allocation::~Allocation() {
if(!areas.empty())
LOG_E("Freeing " << areas.size() << " floating buffers");
if(ptr != nullptr)
vkUnmapMemory(device, memory);
vkFreeMemory(device, memory, ALLOC);
}