Skip to content

Commit

Permalink
Tmp - Implement MemoryPool for VkImage and VkBuffer
Browse files Browse the repository at this point in the history
  • Loading branch information
SirBob01 committed Oct 31, 2024
1 parent 9b92f7d commit 1393605
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 49 deletions.
6 changes: 5 additions & 1 deletion src/Graphics/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace Dynamo::Graphics {
_transfer_pool = VkCommandPool_create(_device, _physical.transfer_queues);

// Vulkan object registries
_memory = MemoryPool(_device, _physical);
_meshes = MeshRegistry(_device, _physical, _transfer_pool);
_shaders = ShaderRegistry(_device);
_materials = MaterialRegistry(_device, root_asset_directory + "/vulkan_cache.bin");
Expand Down Expand Up @@ -52,6 +53,7 @@ namespace Dynamo::Graphics {
_materials.destroy();
_shaders.destroy();
_meshes.destroy();
_memory.destroy();
_swapchain.destroy();

// Vulkan core objects
Expand Down Expand Up @@ -89,7 +91,9 @@ namespace Dynamo::Graphics {

void Renderer::destroy_shader(Shader shader) { _shaders.destroy(shader); }

Texture Renderer::build_texture(const TextureDescriptor &descriptor) { return _textures.build(descriptor); }
Texture Renderer::build_texture(const TextureDescriptor &descriptor) {
return _textures.build(descriptor, _memory);
}

void Renderer::destroy_texture(Texture texture) { _textures.destroy(texture); }

Expand Down
6 changes: 1 addition & 5 deletions src/Graphics/Renderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace Dynamo::Graphics {
VkCommandPool _graphics_pool;
VkCommandPool _transfer_pool;

MemoryPool _memory;
MeshRegistry _meshes;
ShaderRegistry _shaders;
MaterialRegistry _materials;
Expand All @@ -53,11 +54,6 @@ namespace Dynamo::Graphics {
VkClearValue _clear;

// TODO - Fixes:
// * Buffer has a lot of problems... Resizing will invalidate handles used by other resources
// - Instead of Buffer, build a MemoryPool that tracks allocated VkBuffers / VkImages
// - A VkBuffer should persist throughout program lifetime to prevent messy rebuilding of allocated resources
// - If a VkBuffer is full, create a new one. Need a sane minimum size to prevent overallocation (limit 4096)
// - Allow passing in fallback memory types when allocating memory
// * Pre-defined render pass ---- Define a default render pass to handle the no-draw case

// TODO - Features:
Expand Down
138 changes: 138 additions & 0 deletions src/Graphics/Vulkan/MemoryPool.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#include <Graphics/Vulkan/MemoryPool.hpp>
#include <Graphics/Vulkan/Utils.hpp>
#include <Utils/Log.hpp>
#include <vulkan/vulkan_core.h>

namespace Dynamo::Graphics::Vulkan {
MemoryPool::MemoryPool(VkDevice device, const PhysicalDevice &physical) :
_device(device), _physical(&physical), _groups(physical.memory.memoryTypeCount) {}

unsigned MemoryPool::find_type_index(const VkMemoryRequirements &requirements,
VkMemoryPropertyFlags properties) const {
unsigned type_index = 0;
while (type_index < _physical->memory.memoryTypeCount) {
VkMemoryType type = _physical->memory.memoryTypes[type_index];
bool has_type = requirements.memoryTypeBits & (1 << type_index);
bool has_properties = (properties & type.propertyFlags) == properties;
if (has_type && has_properties) {
break;
}
type_index++;
}
DYN_ASSERT(type_index < _groups.size());
return type_index;
}

MemoryPool::Allocation MemoryPool::allocate_memory(const VkMemoryRequirements &requirements,
VkMemoryPropertyFlags properties) {
Allocation allocation;
allocation.type = find_type_index(requirements, properties);
allocation.mapped = nullptr;

MemoryGroup &group = _groups[allocation.type];
for (allocation.index = 0; allocation.index < group.size(); allocation.index++) {
Memory &memory = group[allocation.index];

std::optional<unsigned> result = memory.allocator.reserve(requirements.size, requirements.alignment);
if (result.has_value()) {
allocation.memory = memory.handle;
allocation.offset = result.value();
if (memory.mapped) {
allocation.mapped = static_cast<char *>(memory.mapped) + allocation.offset;
}
}
}

// None found, allocate new memory
VkDeviceSize heap_size = std::max(requirements.size, MEMORY_ALLOCATION_SIZE);
allocation.memory = VkDeviceMemory_allocate(_device, allocation.type, heap_size);
if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
VkResult_check("Map Memory", vkMapMemory(_device, allocation.memory, 0, heap_size, 0, &allocation.mapped));
}
group.push_back({allocation.memory, heap_size, allocation.mapped});

Memory &memory = group.back();
allocation.offset = memory.allocator.reserve(requirements.size, requirements.alignment).value();
return allocation;
}

BufferT MemoryPool::build(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, unsigned size) {
// Create the buffer
BufferT buffer;
buffer.handle = VkBuffer_create(_device, usage, size, nullptr, 0);

// Allocate memory and bind to buffer
VkMemoryRequirements requirements;
vkGetBufferMemoryRequirements(_device, buffer.handle, &requirements);

Allocation allocation = allocate_memory(requirements, properties);
buffer.memory = allocation.memory;
buffer.memory_offset = allocation.offset;
buffer.memory_type = allocation.type;
buffer.group_index = allocation.index;
buffer.mapped = allocation.mapped;

vkBindBufferMemory(_device, buffer.handle, buffer.memory, buffer.memory_offset);
return buffer;
}

Image MemoryPool::build(const TextureDescriptor &descriptor) {
// Create the image
VkExtent3D extent;
extent.width = descriptor.width;
extent.height = descriptor.height;
extent.depth = 1;

VkFormat format = convert_texture_format(descriptor.format);
VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;

Image image;
image.handle = VkImage_create(_device,
extent,
format,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_TYPE_2D,
VK_IMAGE_TILING_OPTIMAL,
usage,
VK_SAMPLE_COUNT_1_BIT,
1,
1,
nullptr,
0);

// Allocate memory and bind to image
VkMemoryRequirements requirements;
vkGetImageMemoryRequirements(_device, image.handle, &requirements);

Allocation allocation = allocate_memory(requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
image.memory = allocation.memory;
image.memory_offset = allocation.offset;
image.memory_type = allocation.type;
image.group_index = allocation.index;
image.mapped = allocation.mapped;

vkBindImageMemory(_device, image.handle, image.memory, image.memory_offset);
return image;
}

void MemoryPool::free(const BufferT &buffer) {
vkDestroyBuffer(_device, buffer.handle, nullptr);
Memory &memory = _groups[buffer.memory_type][buffer.group_index];
memory.allocator.free(buffer.memory_offset);
}

void MemoryPool::free(const Image &image) {
vkDestroyImage(_device, image.handle, nullptr);
Memory &memory = _groups[image.memory_type][image.group_index];
memory.allocator.free(image.memory_offset);
}

void MemoryPool::destroy() {
for (const MemoryGroup &group : _groups) {
for (const Memory &memory : group) {
vkFreeMemory(_device, memory.handle, nullptr);
}
}
_groups.clear();
}
} // namespace Dynamo::Graphics::Vulkan
74 changes: 74 additions & 0 deletions src/Graphics/Vulkan/MemoryPool.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#pragma once

#include <vulkan/vulkan_core.h>

#include <Graphics/Texture.hpp>
#include <Graphics/Vulkan/PhysicalDevice.hpp>
#include <Utils/Allocator.hpp>

namespace Dynamo::Graphics::Vulkan {
/**
* @brief 256M minimum buffer allocation size.
*
* We only have 4096 guaranteed allocations. 256M * 4096 is approx. 1T, so this should be enough.
*
*/
constexpr VkDeviceSize MEMORY_ALLOCATION_SIZE = 256 * (1 << 20);

struct BufferT {
VkBuffer handle;
VkDeviceMemory memory;
unsigned memory_offset;
unsigned memory_type;
unsigned group_index;
void *mapped;
};

struct Image {
VkImage handle;
VkDeviceMemory memory;
unsigned memory_offset;
unsigned memory_type;
unsigned group_index;
void *mapped;
};

class MemoryPool {
struct Memory {
VkDeviceMemory handle;
Allocator allocator;
void *mapped;
};
using MemoryGroup = std::vector<Memory>;

struct Allocation {
VkDeviceMemory memory;
unsigned offset;
unsigned type;
unsigned index;
void *mapped;
};

VkDevice _device;
const PhysicalDevice *_physical;
std::vector<MemoryGroup> _groups;

unsigned find_type_index(const VkMemoryRequirements &requirements, VkMemoryPropertyFlags properties) const;

Allocation allocate_memory(const VkMemoryRequirements &requirements, VkMemoryPropertyFlags properties);

public:
MemoryPool(VkDevice device, const PhysicalDevice &physical);
MemoryPool() = default;

BufferT build(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, unsigned size);

Image build(const TextureDescriptor &descriptor);

void free(const BufferT &allocation);

void free(const Image &allocation);

void destroy();
};
}; // namespace Dynamo::Graphics::Vulkan
51 changes: 9 additions & 42 deletions src/Graphics/Vulkan/TextureRegistry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Dynamo::Graphics::Vulkan {
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
}

Texture TextureRegistry::build(const TextureDescriptor &descriptor) {
Texture TextureRegistry::build(const TextureDescriptor &descriptor, MemoryPool &memory) {
TextureInstance instance;

// Build sampler
Expand All @@ -47,47 +47,9 @@ namespace Dynamo::Graphics::Vulkan {
_samplers.emplace(sampler_settings, instance.sampler);
}

// TODO: What a mess.
// Build image
VkExtent3D extent;
extent.width = descriptor.width;
extent.height = descriptor.height;
extent.depth = 1;

VkFormat format = convert_texture_format(descriptor.format);
VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;

instance.image = VkImage_create(_device,
extent,
format,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_TYPE_2D,
VK_IMAGE_TILING_OPTIMAL,
usage,
VK_SAMPLE_COUNT_1_BIT,
1,
1,
nullptr,
0);

// TODO: This needs to be abstracted away
VkMemoryRequirements requirements;
vkGetImageMemoryRequirements(_device, instance.image, &requirements);

unsigned type_index;
for (type_index = 0; type_index < _physical->memory.memoryTypeCount; type_index++) {
VkMemoryType type = _physical->memory.memoryTypes[type_index];
if ((requirements.memoryTypeBits & (1 << type_index)) &&
(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT & type.propertyFlags)) {
break;
}
}
if (type_index == _physical->memory.memoryTypeCount) {
Log::error("Vulkan could not find suitable memory type for image.");
}

VkDeviceMemory memory = VkDeviceMemory_allocate(_device, type_index, requirements.size);
vkBindImageMemory(_device, instance.image, memory, 0);
Image image = memory.build(descriptor);
instance.image = image.handle;

VkImageSubresourceRange subresources;
subresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
Expand Down Expand Up @@ -135,7 +97,11 @@ namespace Dynamo::Graphics::Vulkan {
subresources);

// Build image view
instance.view = VkImageView_create(_device, instance.image, format, VK_IMAGE_VIEW_TYPE_2D, subresources);
instance.view = VkImageView_create(_device,
instance.image,
convert_texture_format(descriptor.format),
VK_IMAGE_VIEW_TYPE_2D,
subresources);

return _instances.insert(instance);
}
Expand All @@ -150,6 +116,7 @@ namespace Dynamo::Graphics::Vulkan {
}

void TextureRegistry::destroy() {
_staging.destroy();
_instances.foreach ([&](TextureInstance &instance) {
vkDestroyImageView(_device, instance.view, nullptr);
vkDestroyImage(_device, instance.image, nullptr);
Expand Down
3 changes: 2 additions & 1 deletion src/Graphics/Vulkan/TextureRegistry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <Graphics/Texture.hpp>
#include <Graphics/Vulkan/Buffer.hpp>
#include <Graphics/Vulkan/MemoryPool.hpp>
#include <Graphics/Vulkan/PhysicalDevice.hpp>
#include <Utils/SparseArray.hpp>

Expand Down Expand Up @@ -59,7 +60,7 @@ namespace Dynamo::Graphics::Vulkan {
TextureRegistry(VkDevice device, const PhysicalDevice &physical, VkCommandPool transfer_pool);
TextureRegistry() = default;

Texture build(const TextureDescriptor &descriptor);
Texture build(const TextureDescriptor &descriptor, MemoryPool &memory);

const TextureInstance &get(Texture texture) const;

Expand Down

0 comments on commit 1393605

Please sign in to comment.