diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 7b89d63..dd5a483 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -24,6 +24,7 @@ target_compile_definitions(${ENGINE_NAME} PUBLIC target_link_libraries(${ENGINE_NAME} PUBLIC vulkan + vma shaderc glfw glm diff --git a/engine/src/vulkan/buffer.cpp b/engine/src/vulkan/buffer.cpp index 88428af..8054ee1 100644 --- a/engine/src/vulkan/buffer.cpp +++ b/engine/src/vulkan/buffer.cpp @@ -28,26 +28,32 @@ Buffer::Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount, alignmentSize = getAlignment(instanceSize, minOffsetAlignment); bufferSize = alignmentSize * instanceCount; device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer, - memory); + allocation); } Buffer::~Buffer() { unmap(); - this->device.get()->destroyBuffer(this->buffer); - this->device.get()->freeMemory(this->memory); + vmaDestroyBuffer(this->device.getAllocator(), this->buffer, this->allocation); } +// VMA always maps the whole allocation, so `size` only exists to keep the +// interface; `offset` is applied to the returned pointer. vk::Result Buffer::map(vk::DeviceSize size, vk::DeviceSize offset) { - SOPHIA_ASSERT(buffer && memory, "Called map on buffer before create"); + SOPHIA_ASSERT(buffer && allocation, "Called map on buffer before create"); + (void)size; - vk::MemoryMapFlags emptyFlag{}; - return this->device.get()->mapMemory(this->memory, offset, size, emptyFlag, - &this->mapped); + void* base = nullptr; + vk::Result result = static_cast( + vmaMapMemory(this->device.getAllocator(), this->allocation, &base)); + if (result == vk::Result::eSuccess) { + this->mapped = static_cast(base) + offset; + } + return result; } void Buffer::unmap() { if (mapped) { - this->device.get()->unmapMemory(this->memory); + vmaUnmapMemory(this->device.getAllocator(), this->allocation); mapped = nullptr; } } @@ -66,19 +72,13 @@ void Buffer::writeToBuffer(void* data, vk::DeviceSize size, } vk::Result Buffer::flush(vk::DeviceSize size, vk::DeviceSize offset) { - vk::MappedMemoryRange mappedRange = {}; - mappedRange.memory = memory; - mappedRange.offset = offset; - mappedRange.size = size; - return this->device.get()->flushMappedMemoryRanges(1, &mappedRange); + return static_cast(vmaFlushAllocation( + this->device.getAllocator(), this->allocation, offset, size)); } vk::Result Buffer::invalidate(vk::DeviceSize size, vk::DeviceSize offset) { - vk::MappedMemoryRange mappedRange = {}; - mappedRange.memory = memory; - mappedRange.offset = offset; - mappedRange.size = size; - return this->device.get()->invalidateMappedMemoryRanges(1, &mappedRange); + return static_cast(vmaInvalidateAllocation( + this->device.getAllocator(), this->allocation, offset, size)); } vk::DescriptorBufferInfo Buffer::descriptorInfo(vk::DeviceSize size, diff --git a/engine/src/vulkan/buffer.hpp b/engine/src/vulkan/buffer.hpp index 2320ab4..f4cfbb1 100644 --- a/engine/src/vulkan/buffer.hpp +++ b/engine/src/vulkan/buffer.hpp @@ -55,7 +55,7 @@ class Buffer { Device& device; void* mapped = nullptr; vk::Buffer buffer = VK_NULL_HANDLE; - vk::DeviceMemory memory = VK_NULL_HANDLE; + VmaAllocation allocation = VK_NULL_HANDLE; vk::DeviceSize bufferSize; uint32_t instanceCount; diff --git a/engine/src/vulkan/device.cpp b/engine/src/vulkan/device.cpp index 2e8fc9c..fe1fbaa 100644 --- a/engine/src/vulkan/device.cpp +++ b/engine/src/vulkan/device.cpp @@ -69,10 +69,14 @@ Device::Device(Window* window) } pickPhysicalDevice(); createLogicalDevice(); + createAllocator(); createCommandPool(); } Device::~Device() { + vmaDestroyAllocator(this->allocator); + log::trace("destroyed VmaAllocator"); + if (this->enableValidationLayers) { destroyDebugUtilsMessengerEXT(this->instance.get(), this->debugMessenger, nullptr); @@ -88,22 +92,6 @@ Device::~Device() { } } -u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) { - vk::PhysicalDeviceMemoryProperties memoryProperties = - this->physicalDevice.getMemoryProperties(); - - for (u32 i = 0; i < memoryProperties.memoryTypeCount; i++) { - if ((typeFilter & (1 << i)) && - (memoryProperties.memoryTypes[i].propertyFlags & properties) == - properties) { - return i; - } - } - - log::fatal("failed to find suitable memory type"); - throw std::runtime_error("failed to find suitable memory type"); -} - vk::Format Device::findSupportedFormat( const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features) { @@ -125,35 +113,26 @@ vk::Format Device::findSupportedFormat( void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, - vk::Buffer& buffer, vk::DeviceMemory& bufferMemory) { + vk::Buffer& buffer, VmaAllocation& allocation) { vk::BufferCreateInfo bufferInfo = {}; bufferInfo.size = size; bufferInfo.usage = usage; bufferInfo.sharingMode = vk::SharingMode::eExclusive; - try { - buffer = this->device->createBuffer(bufferInfo); - } catch (const vk::SystemError& error) { - log::fatal("failed to create vertex buffer"); - throw std::runtime_error("failed to create vertex buffer"); + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.requiredFlags = static_cast(properties); + + VkBuffer rawBuffer = VK_NULL_HANDLE; + VkResult result = vmaCreateBuffer( + this->allocator, &static_cast(bufferInfo), + &allocInfo, &rawBuffer, &allocation, nullptr); + if (result != VK_SUCCESS) { + log::fatal("failed to create buffer. Error: ", + vk::to_string(static_cast(result))); + throw std::runtime_error("failed to create buffer"); } - vk::MemoryRequirements memoryRequirements = - this->device->getBufferMemoryRequirements(buffer); - - vk::MemoryAllocateInfo allocInfo = {}; - allocInfo.allocationSize = memoryRequirements.size; - allocInfo.memoryTypeIndex = - findMemoryType(memoryRequirements.memoryTypeBits, properties); - - try { - bufferMemory = this->device->allocateMemory(allocInfo); - } catch (const vk::SystemError& error) { - log::fatal("failed to allocate vertex buffer memory"); - throw std::runtime_error("failed to allocate vertex buffer memory"); - } - - this->device->bindBufferMemory(buffer, bufferMemory, 0); + buffer = rawBuffer; } vk::CommandBuffer Device::beginSingleTimeCommands() { @@ -220,43 +199,21 @@ void Device::copyBuffer(vk::Buffer sourceBuffer, vk::Buffer destinationBuffer, void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo, vk::MemoryPropertyFlags properties, - vk::Image& image, - vk::DeviceMemory& imageMemory) { - try { - vk::Result result = this->device->createImage(&imageInfo, nullptr, &image); - if (result != vk::Result::eSuccess) { - throw vk::SystemError(result); - } - } catch (const vk::SystemError& error) { - log::fatal("failed to create image. Error: ", error.what()); + vk::Image& image, VmaAllocation& allocation) { + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.requiredFlags = static_cast(properties); + + VkImage rawImage = VK_NULL_HANDLE; + VkResult result = vmaCreateImage( + this->allocator, &static_cast(imageInfo), + &allocInfo, &rawImage, &allocation, nullptr); + if (result != VK_SUCCESS) { + log::fatal("failed to create image. Error: ", + vk::to_string(static_cast(result))); throw std::runtime_error("failed to create image"); } - vk::MemoryRequirements memoryRequirements = - this->device->getImageMemoryRequirements(image); - - vk::MemoryAllocateInfo allocateInfo{}; - allocateInfo.allocationSize = memoryRequirements.size; - allocateInfo.memoryTypeIndex = - findMemoryType(memoryRequirements.memoryTypeBits, properties); - - try { - vk::Result result = - this->device->allocateMemory(&allocateInfo, nullptr, &imageMemory); - if (result != vk::Result::eSuccess) { - throw vk::SystemError(result); - } - } catch (const vk::SystemError& error) { - log::fatal("failed to allocate image memory. Error: ", error.what()); - throw std::runtime_error("failed to allocate image memory"); - } - - try { - this->device->bindImageMemory(image, imageMemory, 0); - } catch (const vk::SystemError& error) { - log::fatal("failed to bind image memory. Error: ", error.what()); - throw std::runtime_error("failed to bind image memory"); - } + image = rawImage; } void Device::populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo) { @@ -514,6 +471,20 @@ void Device::createLogicalDevice() { } } +void Device::createAllocator() { + VmaAllocatorCreateInfo createInfo = {}; + createInfo.vulkanApiVersion = SOPHIA_VULKAN_API_VERSION; + createInfo.instance = this->instance.get(); + createInfo.physicalDevice = this->physicalDevice; + createInfo.device = this->device.get(); + + if (vmaCreateAllocator(&createInfo, &this->allocator) != VK_SUCCESS) { + log::fatal("failed to create VmaAllocator"); + throw std::runtime_error("failed to create VmaAllocator"); + } + log::trace("created VmaAllocator"); +} + SwapchainSupportDetails Device::querySwapchainSupport( vk::PhysicalDevice device) { SwapchainSupportDetails details; diff --git a/engine/src/vulkan/device.hpp b/engine/src/vulkan/device.hpp index a690f3b..9967f24 100644 --- a/engine/src/vulkan/device.hpp +++ b/engine/src/vulkan/device.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -47,6 +48,7 @@ class Device { void waitIdle() { this->device->waitIdle(); } + VmaAllocator getAllocator() const { return this->allocator; } vk::CommandPool getCommandPool() const { return this->commandPool; } vk::SurfaceKHR getSurface() const { return surface; } vk::Queue getGraphicsQueue() const { return graphicsQueue; } @@ -60,15 +62,13 @@ class Device { return querySwapchainSupport(this->physicalDevice); } - u32 findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties); - vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features); void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::Buffer& buffer, - vk::DeviceMemory& bufferMemory); + VmaAllocation& allocation); vk::CommandBuffer beginSingleTimeCommands(); @@ -79,7 +79,7 @@ class Device { void createImageWithInfo(const vk::ImageCreateInfo& imageInfo, vk::MemoryPropertyFlags properties, vk::Image& image, - vk::DeviceMemory& imageMemory); + VmaAllocation& allocation); void populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo); @@ -100,6 +100,7 @@ class Device { SwapchainSupportDetails querySwapchainSupport(vk::PhysicalDevice device); void createLogicalDevice(); + void createAllocator(); void createCommandPool(); vk::UniqueInstance instance; @@ -111,6 +112,8 @@ class Device { vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE; vk::UniqueDevice device; + VmaAllocator allocator = VK_NULL_HANDLE; + vk::Queue graphicsQueue; vk::Queue presentQueue; diff --git a/engine/src/vulkan/image.cpp b/engine/src/vulkan/image.cpp index 5ea66c1..8d8b93c 100644 --- a/engine/src/vulkan/image.cpp +++ b/engine/src/vulkan/image.cpp @@ -3,11 +3,35 @@ #include #include #include +#include #include "vulkan/device.hpp" namespace sophia { +Image::Image(Image&& other) noexcept + : device{other.device}, + externalImage{other.externalImage}, + image{std::exchange(other.image, nullptr)}, + allocation{std::exchange(other.allocation, VK_NULL_HANDLE)}, + view{std::move(other.view)}, + sampler{std::move(other.sampler)}, + format{other.format}, + extent{other.extent}, + aspectMask{other.aspectMask}, + mipLevels{other.mipLevels}, + arrayLayers{other.arrayLayers}, + layout{other.layout} {} + +Image::~Image() { + this->sampler.reset(); + this->view.reset(); + + if (this->image) { + vmaDestroyImage(this->device.getAllocator(), this->image, this->allocation); + } +} + vk::ImageSubresourceRange Image::getSubresourceRange() const { return vk::ImageSubresourceRange{this->aspectMask, 0, this->mipLevels, 0, this->arrayLayers}; @@ -194,35 +218,26 @@ std::unique_ptr Image::Builder::build() { imageInfo.sharingMode = vk::SharingMode::eExclusive; imageInfo.samples = this->samples; - result->image = this->device.get()->createImageUnique(imageInfo); - - vk::MemoryRequirements memoryRequirements = - this->device.get()->getImageMemoryRequirements(result->image.get()); - - vk::MemoryAllocateInfo allocInfo{}; - allocInfo.allocationSize = memoryRequirements.size; - allocInfo.memoryTypeIndex = this->device.findMemoryType( - memoryRequirements.memoryTypeBits, this->memoryProperties); - - result->memory = this->device.get()->allocateMemoryUnique(allocInfo); - this->device.get()->bindImageMemory(result->image.get(), - result->memory.get(), 0); + this->device.createImageWithInfo(imageInfo, this->memoryProperties, + result->image, result->allocation); } if (!this->isExternalImage && this->initialData != nullptr) { + VmaAllocator allocator = this->device.getAllocator(); + vk::Buffer stagingBuffer; - vk::DeviceMemory stagingMemory; + VmaAllocation stagingAllocation; this->device.createBuffer(this->initialDataSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, - stagingBuffer, stagingMemory); + stagingBuffer, stagingAllocation); - void* mapped = - this->device.get()->mapMemory(stagingMemory, 0, this->initialDataSize); + void* mapped = nullptr; + vmaMapMemory(allocator, stagingAllocation, &mapped); memcpy(mapped, this->initialData, static_cast(this->initialDataSize)); - this->device.get()->unmapMemory(stagingMemory); + vmaUnmapMemory(allocator, stagingAllocation); vk::BufferImageCopy region{}; region.bufferOffset = 0; @@ -247,8 +262,7 @@ std::unique_ptr Image::Builder::build() { } this->device.endSingleTimeCommands(commandBuffer); - this->device.get()->destroyBuffer(stagingBuffer); - this->device.get()->freeMemory(stagingMemory); + vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); } if (this->createView) { diff --git a/engine/src/vulkan/image.hpp b/engine/src/vulkan/image.hpp index 5f8647f..15e091a 100644 --- a/engine/src/vulkan/image.hpp +++ b/engine/src/vulkan/image.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -16,11 +18,12 @@ class Image { Image(const Image&) = delete; Image& operator=(const Image&) = delete; - Image(Image&&) = default; + Image(Image&& other) noexcept; Image& operator=(Image&&) = delete; + ~Image(); vk::Image get() const { - return this->image ? this->image.get() : this->externalImage; + return this->image ? this->image : this->externalImage; } vk::ImageView getView() const { return this->view.get(); } vk::Sampler getSampler() const { return this->sampler.get(); } @@ -56,8 +59,8 @@ class Image { Device& device; vk::Image externalImage; - vk::UniqueImage image; - vk::UniqueDeviceMemory memory; + vk::Image image; + VmaAllocation allocation = VK_NULL_HANDLE; vk::UniqueImageView view; vk::UniqueSampler sampler; diff --git a/engine/src/vulkan/vma.cpp b/engine/src/vulkan/vma.cpp new file mode 100644 index 0000000..7a6837c --- /dev/null +++ b/engine/src/vulkan/vma.cpp @@ -0,0 +1,2 @@ +#define VMA_IMPLEMENTATION +#include diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 6e31ae1..6c9df76 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -17,6 +17,11 @@ target_link_libraries(vulkan INTERFACE Vulkan::shaderc_combined ) +# 2. VMA (header-only, ships with the Vulkan SDK) +add_library(vma INTERFACE) +target_include_directories(vma INTERFACE ${Vulkan_INCLUDE_DIRS}/vma) +target_link_libraries(vma INTERFACE vulkan) + # 3. GLFW (static) option(GLFW_BUILD_EXAMPLES "Build the GLFW example programs" OFF) option(GLFW_BUILD_TESTS "Build the GLFW test programs" OFF)