diff --git a/src/Graphics/Renderer.cpp b/src/Graphics/Renderer.cpp index be0f434..cda483a 100644 --- a/src/Graphics/Renderer.cpp +++ b/src/Graphics/Renderer.cpp @@ -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"); @@ -52,6 +53,7 @@ namespace Dynamo::Graphics { _materials.destroy(); _shaders.destroy(); _meshes.destroy(); + _memory.destroy(); _swapchain.destroy(); // Vulkan core objects @@ -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); } diff --git a/src/Graphics/Renderer.hpp b/src/Graphics/Renderer.hpp index 2ae39ef..ebe1b6a 100644 --- a/src/Graphics/Renderer.hpp +++ b/src/Graphics/Renderer.hpp @@ -40,6 +40,7 @@ namespace Dynamo::Graphics { VkCommandPool _graphics_pool; VkCommandPool _transfer_pool; + MemoryPool _memory; MeshRegistry _meshes; ShaderRegistry _shaders; MaterialRegistry _materials; @@ -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: diff --git a/src/Graphics/Vulkan/MemoryPool.cpp b/src/Graphics/Vulkan/MemoryPool.cpp new file mode 100644 index 0000000..f69d084 --- /dev/null +++ b/src/Graphics/Vulkan/MemoryPool.cpp @@ -0,0 +1,138 @@ +#include +#include +#include +#include + +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 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(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 \ No newline at end of file diff --git a/src/Graphics/Vulkan/MemoryPool.hpp b/src/Graphics/Vulkan/MemoryPool.hpp new file mode 100644 index 0000000..96f3092 --- /dev/null +++ b/src/Graphics/Vulkan/MemoryPool.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include + +#include +#include +#include + +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; + + struct Allocation { + VkDeviceMemory memory; + unsigned offset; + unsigned type; + unsigned index; + void *mapped; + }; + + VkDevice _device; + const PhysicalDevice *_physical; + std::vector _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 \ No newline at end of file diff --git a/src/Graphics/Vulkan/TextureRegistry.cpp b/src/Graphics/Vulkan/TextureRegistry.cpp index bfc676f..aac2ca0 100644 --- a/src/Graphics/Vulkan/TextureRegistry.cpp +++ b/src/Graphics/Vulkan/TextureRegistry.cpp @@ -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 @@ -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; @@ -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); } @@ -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); diff --git a/src/Graphics/Vulkan/TextureRegistry.hpp b/src/Graphics/Vulkan/TextureRegistry.hpp index 7fd10ad..4a5419d 100644 --- a/src/Graphics/Vulkan/TextureRegistry.hpp +++ b/src/Graphics/Vulkan/TextureRegistry.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -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;