diff --git a/.gitmodules b/.gitmodules index b69121770227..3c341c517785 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ url = https://github.com/randy408/libspng.git branch = v0.7.2 shallow = true +[submodule "Externals/VulkanMemoryAllocator"] + path = Externals/VulkanMemoryAllocator + url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git diff --git a/Externals/VulkanMemoryAllocator b/Externals/VulkanMemoryAllocator new file mode 160000 index 000000000000..c35169249051 --- /dev/null +++ b/Externals/VulkanMemoryAllocator @@ -0,0 +1 @@ +Subproject commit c351692490513cdb0e5a2c925aaf7ea4a9b672f4 diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt index 4489ca9e1069..5fd6aa1ed734 100644 --- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt +++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt @@ -52,6 +52,7 @@ PRIVATE target_include_directories(videovulkan PRIVATE ${CMAKE_SOURCE_DIR}/Externals/Vulkan/Include + ${CMAKE_SOURCE_DIR}/Externals/VulkanMemoryAllocator/include ) if(MSVC) diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp index d8ef681b561d..46712693a569 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp @@ -525,13 +525,6 @@ void CommandBufferManager::BeginCommandBuffer() m_current_cmd_buffer = next_buffer_index; } -void CommandBufferManager::DeferBufferDestruction(VkBuffer object) -{ - CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources(); - cmd_buffer_resources.cleanup_resources.push_back( - [object]() { vkDestroyBuffer(g_vulkan_context->GetDevice(), object, nullptr); }); -} - void CommandBufferManager::DeferBufferViewDestruction(VkBufferView object) { CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources(); @@ -539,11 +532,12 @@ void CommandBufferManager::DeferBufferViewDestruction(VkBufferView object) [object]() { vkDestroyBufferView(g_vulkan_context->GetDevice(), object, nullptr); }); } -void CommandBufferManager::DeferDeviceMemoryDestruction(VkDeviceMemory object) +void CommandBufferManager::DeferBufferDestruction(VkBuffer buffer, VmaAllocation alloc) { CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources(); - cmd_buffer_resources.cleanup_resources.push_back( - [object]() { vkFreeMemory(g_vulkan_context->GetDevice(), object, nullptr); }); + cmd_buffer_resources.cleanup_resources.push_back([buffer, alloc]() { + vmaDestroyBuffer(g_vulkan_context->GetMemoryAllocator(), buffer, alloc); + }); } void CommandBufferManager::DeferFramebufferDestruction(VkFramebuffer object) @@ -553,11 +547,11 @@ void CommandBufferManager::DeferFramebufferDestruction(VkFramebuffer object) [object]() { vkDestroyFramebuffer(g_vulkan_context->GetDevice(), object, nullptr); }); } -void CommandBufferManager::DeferImageDestruction(VkImage object) +void CommandBufferManager::DeferImageDestruction(VkImage image, VmaAllocation alloc) { CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources(); cmd_buffer_resources.cleanup_resources.push_back( - [object]() { vkDestroyImage(g_vulkan_context->GetDevice(), object, nullptr); }); + [image, alloc]() { vmaDestroyImage(g_vulkan_context->GetMemoryAllocator(), image, alloc); }); } void CommandBufferManager::DeferImageViewDestruction(VkImageView object) diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h index 85e0ec8741d0..109079e698c4 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h @@ -86,11 +86,10 @@ class CommandBufferManager // Schedule a vulkan resource for destruction later on. This will occur when the command buffer // is next re-used, and the GPU has finished working with the specified resource. - void DeferBufferDestruction(VkBuffer object); void DeferBufferViewDestruction(VkBufferView object); - void DeferDeviceMemoryDestruction(VkDeviceMemory object); + void DeferBufferDestruction(VkBuffer buffer, VmaAllocation alloc); void DeferFramebufferDestruction(VkFramebuffer object); - void DeferImageDestruction(VkImage object); + void DeferImageDestruction(VkImage object, VmaAllocation alloc); void DeferImageViewDestruction(VkImageView object); private: diff --git a/Source/Core/VideoBackends/Vulkan/StagingBuffer.cpp b/Source/Core/VideoBackends/Vulkan/StagingBuffer.cpp index 503035028261..19ca7dc8dd16 100644 --- a/Source/Core/VideoBackends/Vulkan/StagingBuffer.cpp +++ b/Source/Core/VideoBackends/Vulkan/StagingBuffer.cpp @@ -10,12 +10,13 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/VulkanContext.h" +#include "VideoCommon/DriverDetails.h" namespace Vulkan { -StagingBuffer::StagingBuffer(STAGING_BUFFER_TYPE type, VkBuffer buffer, VkDeviceMemory memory, - VkDeviceSize size, bool coherent) - : m_type(type), m_buffer(buffer), m_memory(memory), m_size(size), m_coherent(coherent) +StagingBuffer::StagingBuffer(STAGING_BUFFER_TYPE type, VkBuffer buffer, VmaAllocation alloc, + VkDeviceSize size, char* map_ptr) + : m_type(type), m_buffer(buffer), m_alloc(alloc), m_size(size), m_map_pointer(map_ptr) { } @@ -25,8 +26,7 @@ StagingBuffer::~StagingBuffer() if (m_map_pointer) Unmap(); - g_command_buffer_mgr->DeferDeviceMemoryDestruction(m_memory); - g_command_buffer_mgr->DeferBufferDestruction(m_buffer); + g_command_buffer_mgr->DeferBufferDestruction(m_buffer, m_alloc); } void StagingBuffer::BufferMemoryBarrier(VkCommandBuffer command_buffer, VkBuffer buffer, @@ -51,49 +51,21 @@ void StagingBuffer::BufferMemoryBarrier(VkCommandBuffer command_buffer, VkBuffer &buffer_info, 0, nullptr); } -bool StagingBuffer::Map(VkDeviceSize offset, VkDeviceSize size) +bool StagingBuffer::Map() { - m_map_offset = offset; - if (size == VK_WHOLE_SIZE) - m_map_size = m_size - offset; - else - m_map_size = size; - - ASSERT(!m_map_pointer); - ASSERT(m_map_offset + m_map_size <= m_size); - - void* map_pointer; - VkResult res = vkMapMemory(g_vulkan_context->GetDevice(), m_memory, m_map_offset, m_map_size, 0, - &map_pointer); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkMapMemory failed: "); - return false; - } - - m_map_pointer = reinterpret_cast(map_pointer); + // The staging buffer is permanently mapped and VMA handles the mapping for us return true; } void StagingBuffer::Unmap() { - ASSERT(m_map_pointer); - - vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); - m_map_pointer = nullptr; - m_map_offset = 0; - m_map_size = 0; + // The staging buffer is permanently mapped and VMA handles the unmapping for us } void StagingBuffer::FlushCPUCache(VkDeviceSize offset, VkDeviceSize size) { - ASSERT(offset >= m_map_offset); - if (m_coherent) - return; - - VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, m_memory, - offset - m_map_offset, size}; - vkFlushMappedMemoryRanges(g_vulkan_context->GetDevice(), 1, &range); + // vmaFlushAllocation checks whether the allocation uses a coherent memory type internally + vmaFlushAllocation(g_vulkan_context->GetMemoryAllocator(), m_alloc, offset, size); } void StagingBuffer::InvalidateGPUCache(VkCommandBuffer command_buffer, @@ -101,7 +73,9 @@ void StagingBuffer::InvalidateGPUCache(VkCommandBuffer command_buffer, VkPipelineStageFlagBits dest_pipeline_stage, VkDeviceSize offset, VkDeviceSize size) { - if (m_coherent) + VkMemoryPropertyFlags flags = 0; + vmaGetAllocationMemoryProperties(g_vulkan_context->GetMemoryAllocator(), m_alloc, &flags); + if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) [[likely]] return; ASSERT((offset + size) <= m_size || (offset < m_size && size == VK_WHOLE_SIZE)); @@ -114,7 +88,9 @@ void StagingBuffer::PrepareForGPUWrite(VkCommandBuffer command_buffer, VkPipelineStageFlagBits dst_pipeline_stage, VkDeviceSize offset, VkDeviceSize size) { - if (m_coherent) + VkMemoryPropertyFlags flags = 0; + vmaGetAllocationMemoryProperties(g_vulkan_context->GetMemoryAllocator(), m_alloc, &flags); + if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) [[likely]] return; ASSERT((offset + size) <= m_size || (offset < m_size && size == VK_WHOLE_SIZE)); @@ -126,7 +102,9 @@ void StagingBuffer::FlushGPUCache(VkCommandBuffer command_buffer, VkAccessFlagBi VkPipelineStageFlagBits src_pipeline_stage, VkDeviceSize offset, VkDeviceSize size) { - if (m_coherent) + VkMemoryPropertyFlags flags = 0; + vmaGetAllocationMemoryProperties(g_vulkan_context->GetMemoryAllocator(), m_alloc, &flags); + if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) [[likely]] return; ASSERT((offset + size) <= m_size || (offset < m_size && size == VK_WHOLE_SIZE)); @@ -136,39 +114,32 @@ void StagingBuffer::FlushGPUCache(VkCommandBuffer command_buffer, VkAccessFlagBi void StagingBuffer::InvalidateCPUCache(VkDeviceSize offset, VkDeviceSize size) { - ASSERT(offset >= m_map_offset); - if (m_coherent) - return; - - VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, m_memory, - offset - m_map_offset, size}; - vkInvalidateMappedMemoryRanges(g_vulkan_context->GetDevice(), 1, &range); + // vmaInvalidateAllocation checks whether the allocation uses a coherent memory type internally + vmaInvalidateAllocation(g_vulkan_context->GetMemoryAllocator(), m_alloc, offset, size); } void StagingBuffer::Read(VkDeviceSize offset, void* data, size_t size, bool invalidate_caches) { ASSERT((offset + size) <= m_size); - ASSERT(offset >= m_map_offset && size <= (m_map_size + (offset - m_map_offset))); if (invalidate_caches) InvalidateCPUCache(offset, size); - memcpy(data, m_map_pointer + (offset - m_map_offset), size); + memcpy(data, m_map_pointer + offset, size); } void StagingBuffer::Write(VkDeviceSize offset, const void* data, size_t size, bool invalidate_caches) { ASSERT((offset + size) <= m_size); - ASSERT(offset >= m_map_offset && size <= (m_map_size + (offset - m_map_offset))); - memcpy(m_map_pointer + (offset - m_map_offset), data, size); + memcpy(m_map_pointer + offset, data, size); if (invalidate_caches) FlushCPUCache(offset, size); } bool StagingBuffer::AllocateBuffer(STAGING_BUFFER_TYPE type, VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer* out_buffer, - VkDeviceMemory* out_memory, bool* out_coherent) + VmaAllocation* out_alloc, char** out_map_ptr) { VkBufferCreateInfo buffer_create_info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, // VkStructureType sType @@ -180,46 +151,60 @@ bool StagingBuffer::AllocateBuffer(STAGING_BUFFER_TYPE type, VkDeviceSize size, 0, // uint32_t queueFamilyIndexCount nullptr // const uint32_t* pQueueFamilyIndices }; - VkResult res = - vkCreateBuffer(g_vulkan_context->GetDevice(), &buffer_create_info, nullptr, out_buffer); - if (res != VK_SUCCESS) + + VmaAllocationCreateInfo alloc_create_info = {}; + alloc_create_info.flags = + VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; + alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO; + alloc_create_info.pool = VK_NULL_HANDLE; + alloc_create_info.pUserData = nullptr; + alloc_create_info.priority = 0.0; + alloc_create_info.preferredFlags = 0; + alloc_create_info.requiredFlags = 0; + + if (DriverDetails::HasBug(DriverDetails::BUG_SLOW_CACHED_READBACK_MEMORY)) [[unlikely]] { - LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: "); - return false; + // If there is no memory type that is both CACHED and COHERENT, + // pick the one that is COHERENT + alloc_create_info.usage = VMA_MEMORY_USAGE_UNKNOWN; + alloc_create_info.requiredFlags = + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + alloc_create_info.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + } + else + { + if (type == STAGING_BUFFER_TYPE_UPLOAD) + alloc_create_info.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + else + alloc_create_info.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; } - VkMemoryRequirements requirements; - vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), *out_buffer, &requirements); + VmaAllocationInfo alloc_info; + VkResult res = vmaCreateBuffer(g_vulkan_context->GetMemoryAllocator(), &buffer_create_info, + &alloc_create_info, out_buffer, out_alloc, &alloc_info); - u32 type_index; if (type == STAGING_BUFFER_TYPE_UPLOAD) - type_index = g_vulkan_context->GetUploadMemoryType(requirements.memoryTypeBits, out_coherent); - else - type_index = g_vulkan_context->GetReadbackMemoryType(requirements.memoryTypeBits, out_coherent); - - VkMemoryAllocateInfo memory_allocate_info = { - VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - requirements.size, // VkDeviceSize allocationSize - type_index // uint32_t memoryTypeIndex - }; - res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_allocate_info, nullptr, out_memory); - if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: "); - vkDestroyBuffer(g_vulkan_context->GetDevice(), *out_buffer, nullptr); - return false; + VkMemoryPropertyFlags flags = 0; + vmaGetMemoryTypeProperties(g_vulkan_context->GetMemoryAllocator(), alloc_info.memoryType, + &flags); + if (!(flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + { + WARN_LOG_FMT(VIDEO, "Vulkan: Failed to find a coherent memory type for uploads, this will " + "affect performance."); + } } - res = vkBindBufferMemory(g_vulkan_context->GetDevice(), *out_buffer, *out_memory, 0); + *out_map_ptr = reinterpret_cast(alloc_info.pMappedData); + if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkBindBufferMemory failed: "); - vkDestroyBuffer(g_vulkan_context->GetDevice(), *out_buffer, nullptr); - vkFreeMemory(g_vulkan_context->GetDevice(), *out_memory, nullptr); + LOG_VULKAN_ERROR(res, "vmaCreateBuffer failed: "); return false; } + VkMemoryPropertyFlags flags = 0; + vmaGetAllocationMemoryProperties(g_vulkan_context->GetMemoryAllocator(), *out_alloc, &flags); return true; } @@ -227,12 +212,12 @@ std::unique_ptr StagingBuffer::Create(STAGING_BUFFER_TYPE type, V VkBufferUsageFlags usage) { VkBuffer buffer; - VkDeviceMemory memory; - bool coherent; - if (!AllocateBuffer(type, size, usage, &buffer, &memory, &coherent)) + VmaAllocation alloc; + char* map_ptr; + if (!AllocateBuffer(type, size, usage, &buffer, &alloc, &map_ptr)) return nullptr; - return std::make_unique(type, buffer, memory, size, coherent); + return std::make_unique(type, buffer, alloc, size, map_ptr); } } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/StagingBuffer.h b/Source/Core/VideoBackends/Vulkan/StagingBuffer.h index 4676863bc833..e1546a8e512e 100644 --- a/Source/Core/VideoBackends/Vulkan/StagingBuffer.h +++ b/Source/Core/VideoBackends/Vulkan/StagingBuffer.h @@ -13,8 +13,8 @@ namespace Vulkan class StagingBuffer { public: - StagingBuffer(STAGING_BUFFER_TYPE type, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize size, - bool coherent); + StagingBuffer(STAGING_BUFFER_TYPE type, VkBuffer buffer, VmaAllocation allocation, + VkDeviceSize size, char* map_ptr); virtual ~StagingBuffer(); STAGING_BUFFER_TYPE GetType() const { return m_type; } @@ -23,9 +23,7 @@ class StagingBuffer bool IsMapped() const { return m_map_pointer != nullptr; } const char* GetMapPointer() const { return m_map_pointer; } char* GetMapPointer() { return m_map_pointer; } - VkDeviceSize GetMapOffset() const { return m_map_offset; } - VkDeviceSize GetMapSize() const { return m_map_size; } - bool Map(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE); + bool Map(); void Unmap(); // Upload part 1: Prepare from device read from the CPU side @@ -60,7 +58,7 @@ class StagingBuffer // Allocates the resources needed to create a staging buffer. static bool AllocateBuffer(STAGING_BUFFER_TYPE type, VkDeviceSize size, VkBufferUsageFlags usage, - VkBuffer* out_buffer, VkDeviceMemory* out_memory, bool* out_coherent); + VkBuffer* out_buffer, VmaAllocation* out_alloc, char** out_map_ptr); // Wrapper for creating an barrier on a buffer static void BufferMemoryBarrier(VkCommandBuffer command_buffer, VkBuffer buffer, @@ -72,12 +70,9 @@ class StagingBuffer protected: STAGING_BUFFER_TYPE m_type; VkBuffer m_buffer; - VkDeviceMemory m_memory; + VmaAllocation m_alloc; VkDeviceSize m_size; - bool m_coherent; char* m_map_pointer = nullptr; - VkDeviceSize m_map_offset = 0; - VkDeviceSize m_map_size = 0; }; } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp index 3138a31602a6..a182bd84837e 100644 --- a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp @@ -20,8 +20,7 @@ VKBoundingBox::~VKBoundingBox() { if (m_gpu_buffer != VK_NULL_HANDLE) { - vkDestroyBuffer(g_vulkan_context->GetDevice(), m_gpu_buffer, nullptr); - vkFreeMemory(g_vulkan_context->GetDevice(), m_gpu_memory, nullptr); + vmaDestroyBuffer(g_vulkan_context->GetMemoryAllocator(), m_gpu_buffer, m_gpu_allocation); } } @@ -118,47 +117,27 @@ bool VKBoundingBox::CreateGPUBuffer() nullptr // const uint32_t* pQueueFamilyIndices }; - VkBuffer buffer; - VkResult res = vkCreateBuffer(g_vulkan_context->GetDevice(), &info, nullptr, &buffer); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: "); - return false; - } - - VkMemoryRequirements memory_requirements; - vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), buffer, &memory_requirements); - - uint32_t memory_type_index = g_vulkan_context - ->GetMemoryType(memory_requirements.memoryTypeBits, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, false) - .value_or(0); - VkMemoryAllocateInfo memory_allocate_info = { - VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - memory_requirements.size, // VkDeviceSize allocationSize - memory_type_index // uint32_t memoryTypeIndex - }; - VkDeviceMemory memory; - res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_allocate_info, nullptr, &memory); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: "); - vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); - return false; - } + VmaAllocationCreateInfo alloc_create_info = {}; + alloc_create_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; + alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + alloc_create_info.pool = VK_NULL_HANDLE; + alloc_create_info.pUserData = nullptr; + alloc_create_info.priority = 0.0; + alloc_create_info.requiredFlags = 0; + alloc_create_info.preferredFlags = 0; - res = vkBindBufferMemory(g_vulkan_context->GetDevice(), buffer, memory, 0); + VkBuffer buffer; + VmaAllocation alloc; + VkResult res = vmaCreateBuffer(g_vulkan_context->GetMemoryAllocator(), &info, &alloc_create_info, + &buffer, &alloc, nullptr); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkBindBufferMemory failed: "); - vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); - vkFreeMemory(g_vulkan_context->GetDevice(), memory, nullptr); + LOG_VULKAN_ERROR(res, "vmaCreateBuffer failed: "); return false; } m_gpu_buffer = buffer; - m_gpu_memory = memory; + m_gpu_allocation = alloc; return true; } diff --git a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h index 7ab429a5da87..15a5a172d991 100644 --- a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h +++ b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.h @@ -32,7 +32,7 @@ class VKBoundingBox final : public BoundingBox bool CreateReadbackBuffer(); VkBuffer m_gpu_buffer = VK_NULL_HANDLE; - VkDeviceMemory m_gpu_memory = VK_NULL_HANDLE; + VmaAllocation m_gpu_allocation = VK_NULL_HANDLE; static constexpr size_t BUFFER_SIZE = sizeof(BBoxType) * NUM_BBOX_VALUES; diff --git a/Source/Core/VideoBackends/Vulkan/VKMain.cpp b/Source/Core/VideoBackends/Vulkan/VKMain.cpp index bc76aa561077..e81c5d066b0a 100644 --- a/Source/Core/VideoBackends/Vulkan/VKMain.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKMain.cpp @@ -35,8 +35,9 @@ void VideoBackend::InitBackendInfo() if (LoadVulkanLibrary()) { - VkInstance temp_instance = - VulkanContext::CreateVulkanInstance(WindowSystemType::Headless, false, false); + u32 vk_api_version = 0; + VkInstance temp_instance = VulkanContext::CreateVulkanInstance(WindowSystemType::Headless, + false, false, &vk_api_version); if (temp_instance) { if (LoadVulkanInstanceFunctions(temp_instance)) @@ -114,8 +115,9 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) // We use this instance to fill in backend info, then re-use it for the actual device. bool enable_surface = wsi.type != WindowSystemType::Headless; bool enable_debug_reports = ShouldEnableDebugReports(enable_validation_layer); - VkInstance instance = - VulkanContext::CreateVulkanInstance(wsi.type, enable_debug_reports, enable_validation_layer); + u32 vk_api_version = 0; + VkInstance instance = VulkanContext::CreateVulkanInstance( + wsi.type, enable_debug_reports, enable_validation_layer, &vk_api_version); if (instance == VK_NULL_HANDLE) { PanicAlertFmt("Failed to create Vulkan instance."); @@ -171,8 +173,9 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) } // Now we can create the Vulkan device. VulkanContext takes ownership of the instance and surface. - g_vulkan_context = VulkanContext::Create(instance, gpu_list[selected_adapter_index], surface, - enable_debug_reports, enable_validation_layer); + g_vulkan_context = + VulkanContext::Create(instance, gpu_list[selected_adapter_index], surface, + enable_debug_reports, enable_validation_layer, vk_api_version); if (!g_vulkan_context) { PanicAlertFmt("Failed to create Vulkan device"); diff --git a/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.cpp b/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.cpp index f1967307005a..3efa00f6faa0 100644 --- a/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.cpp @@ -22,13 +22,9 @@ StreamBuffer::StreamBuffer(VkBufferUsageFlags usage, u32 size) : m_usage(usage), StreamBuffer::~StreamBuffer() { - if (m_host_pointer) - vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); - + // VMA_ALLOCATION_CREATE_MAPPED_BIT automatically handles unmapping for us if (m_buffer != VK_NULL_HANDLE) - g_command_buffer_mgr->DeferBufferDestruction(m_buffer); - if (m_memory != VK_NULL_HANDLE) - g_command_buffer_mgr->DeferDeviceMemoryDestruction(m_memory); + g_command_buffer_mgr->DeferBufferDestruction(m_buffer, m_alloc); } std::unique_ptr StreamBuffer::Create(VkBufferUsageFlags usage, u32 size) @@ -54,74 +50,38 @@ bool StreamBuffer::AllocateBuffer() nullptr // const uint32_t* pQueueFamilyIndices }; - VkBuffer buffer = VK_NULL_HANDLE; - VkResult res = - vkCreateBuffer(g_vulkan_context->GetDevice(), &buffer_create_info, nullptr, &buffer); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateBuffer failed: "); - return false; - } - - // Get memory requirements (types etc) for this buffer - VkMemoryRequirements memory_requirements; - vkGetBufferMemoryRequirements(g_vulkan_context->GetDevice(), buffer, &memory_requirements); - - // Aim for a coherent mapping if possible. - u32 memory_type_index = g_vulkan_context->GetUploadMemoryType(memory_requirements.memoryTypeBits, - &m_coherent_mapping); + VmaAllocationCreateInfo alloc_create_info = {}; + alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT; + alloc_create_info.usage = + VMA_MEMORY_USAGE_AUTO_PREFER_HOST; // Host visible VRAM is slower in practice + alloc_create_info.pool = VK_NULL_HANDLE; + alloc_create_info.pUserData = nullptr; + alloc_create_info.priority = 0.0; + alloc_create_info.requiredFlags = 0; + alloc_create_info.preferredFlags = 0; - // Allocate memory for backing this buffer - VkMemoryAllocateInfo memory_allocate_info = { - VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // VkStructureType sType - nullptr, // const void* pNext - memory_requirements.size, // VkDeviceSize allocationSize - memory_type_index // uint32_t memoryTypeIndex - }; - VkDeviceMemory memory = VK_NULL_HANDLE; - res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_allocate_info, nullptr, &memory); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: "); - vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); - return false; - } - - // Bind memory to buffer - res = vkBindBufferMemory(g_vulkan_context->GetDevice(), buffer, memory, 0); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkBindBufferMemory failed: "); - vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); - vkFreeMemory(g_vulkan_context->GetDevice(), memory, nullptr); - return false; - } - - // Map this buffer into user-space - void* mapped_ptr = nullptr; - res = vkMapMemory(g_vulkan_context->GetDevice(), memory, 0, m_size, 0, &mapped_ptr); + VkBuffer buffer = VK_NULL_HANDLE; + VmaAllocation alloc = VK_NULL_HANDLE; + VmaAllocationInfo alloc_info; + VkResult res = vmaCreateBuffer(g_vulkan_context->GetMemoryAllocator(), &buffer_create_info, + &alloc_create_info, &buffer, &alloc, &alloc_info); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkMapMemory failed: "); - vkDestroyBuffer(g_vulkan_context->GetDevice(), buffer, nullptr); - vkFreeMemory(g_vulkan_context->GetDevice(), memory, nullptr); + LOG_VULKAN_ERROR(res, "vmaCreateBuffer failed: "); return false; } - // Unmap current host pointer (if there was a previous buffer) - if (m_host_pointer) - vkUnmapMemory(g_vulkan_context->GetDevice(), m_memory); - // Destroy the backings for the buffer after the command buffer executes + // VMA_ALLOCATION_CREATE_MAPPED_BIT automatically handles unmapping for us if (m_buffer != VK_NULL_HANDLE) - g_command_buffer_mgr->DeferBufferDestruction(m_buffer); - if (m_memory != VK_NULL_HANDLE) - g_command_buffer_mgr->DeferDeviceMemoryDestruction(m_memory); + g_command_buffer_mgr->DeferBufferDestruction(m_buffer, m_alloc); // Replace with the new buffer m_buffer = buffer; - m_memory = memory; - m_host_pointer = reinterpret_cast(mapped_ptr); + m_alloc = alloc; + m_host_pointer = reinterpret_cast(alloc_info.pMappedData); m_current_offset = 0; m_current_gpu_position = 0; m_tracked_fences.clear(); @@ -201,12 +161,9 @@ void StreamBuffer::CommitMemory(u32 final_num_bytes) ASSERT(final_num_bytes <= m_last_allocation_size); // For non-coherent mappings, flush the memory range - if (!m_coherent_mapping) - { - VkMappedMemoryRange range = {VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, nullptr, m_memory, - m_current_offset, final_num_bytes}; - vkFlushMappedMemoryRanges(g_vulkan_context->GetDevice(), 1, &range); - } + // vmaFlushAllocation checks whether the allocation uses a coherent memory type internally + vmaFlushAllocation(g_vulkan_context->GetMemoryAllocator(), m_alloc, m_current_offset, + final_num_bytes); m_current_offset += final_num_bytes; } diff --git a/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.h b/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.h index 1528d3289c30..61979c6f3fce 100644 --- a/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.h +++ b/Source/Core/VideoBackends/Vulkan/VKStreamBuffer.h @@ -20,7 +20,6 @@ class StreamBuffer ~StreamBuffer(); VkBuffer GetBuffer() const { return m_buffer; } - VkDeviceMemory GetDeviceMemory() const { return m_memory; } u8* GetHostPointer() const { return m_host_pointer; } u8* GetCurrentHostPointer() const { return m_host_pointer + m_current_offset; } u32 GetCurrentSize() const { return m_size; } @@ -45,13 +44,11 @@ class StreamBuffer u32 m_last_allocation_size = 0; VkBuffer m_buffer = VK_NULL_HANDLE; - VkDeviceMemory m_memory = VK_NULL_HANDLE; + VmaAllocation m_alloc = VK_NULL_HANDLE; u8* m_host_pointer = nullptr; // List of fences and the corresponding positions in the buffer std::deque> m_tracked_fences; - - bool m_coherent_mapping = false; }; } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp index ff2f2cc2d125..412deaf5f9cf 100644 --- a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp @@ -26,10 +26,10 @@ namespace Vulkan { -VKTexture::VKTexture(const TextureConfig& tex_config, VkDeviceMemory device_memory, VkImage image, +VKTexture::VKTexture(const TextureConfig& tex_config, VmaAllocation alloc, VkImage image, std::string_view name, VkImageLayout layout /* = VK_IMAGE_LAYOUT_UNDEFINED */, ComputeImageLayout compute_layout /* = ComputeImageLayout::Undefined */) - : AbstractTexture(tex_config), m_device_memory(device_memory), m_image(image), m_layout(layout), + : AbstractTexture(tex_config), m_alloc(alloc), m_image(image), m_layout(layout), m_compute_layout(compute_layout), m_name(name) { if (!m_name.empty() && g_ActiveConfig.backend_info.bSupportsSettingObjectNames) @@ -49,10 +49,9 @@ VKTexture::~VKTexture() g_command_buffer_mgr->DeferImageViewDestruction(m_view); // If we don't have device memory allocated, the image is not owned by us (e.g. swapchain) - if (m_device_memory != VK_NULL_HANDLE) + if (m_alloc != VK_NULL_HANDLE) { - g_command_buffer_mgr->DeferImageDestruction(m_image); - g_command_buffer_mgr->DeferDeviceMemoryDestruction(m_device_memory); + g_command_buffer_mgr->DeferImageDestruction(m_image, m_alloc); } } @@ -85,46 +84,28 @@ std::unique_ptr VKTexture::Create(const TextureConfig& tex_config, st nullptr, VK_IMAGE_LAYOUT_UNDEFINED}; - VkImage image = VK_NULL_HANDLE; - VkResult res = vkCreateImage(g_vulkan_context->GetDevice(), &image_info, nullptr, &image); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateImage failed: "); - return nullptr; - } - - // Allocate memory to back this texture, we want device local memory in this case - VkMemoryRequirements memory_requirements; - vkGetImageMemoryRequirements(g_vulkan_context->GetDevice(), image, &memory_requirements); - - VkMemoryAllocateInfo memory_info = { - VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, nullptr, memory_requirements.size, - g_vulkan_context - ->GetMemoryType(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - false) - .value_or(0)}; + VmaAllocationCreateInfo alloc_create_info = {}; + alloc_create_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; + alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + alloc_create_info.pool = VK_NULL_HANDLE; + alloc_create_info.pUserData = nullptr; + alloc_create_info.priority = + tex_config.IsComputeImage() || tex_config.IsRenderTarget() ? 1.0 : 0.0; + alloc_create_info.requiredFlags = 0; + alloc_create_info.preferredFlags = 0; - VkDeviceMemory device_memory; - res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_info, nullptr, &device_memory); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: "); - vkDestroyImage(g_vulkan_context->GetDevice(), image, nullptr); - return nullptr; - } - - res = vkBindImageMemory(g_vulkan_context->GetDevice(), image, device_memory, 0); + VkImage image = VK_NULL_HANDLE; + VmaAllocation alloc = VK_NULL_HANDLE; + VkResult res = vmaCreateImage(g_vulkan_context->GetMemoryAllocator(), &image_info, + &alloc_create_info, &image, &alloc, nullptr); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkBindImageMemory failed: "); - vkDestroyImage(g_vulkan_context->GetDevice(), image, nullptr); - vkFreeMemory(g_vulkan_context->GetDevice(), device_memory, nullptr); + LOG_VULKAN_ERROR(res, "vmaCreateImage failed: "); return nullptr; } - std::unique_ptr texture = - std::make_unique(tex_config, device_memory, image, name, VK_IMAGE_LAYOUT_UNDEFINED, - ComputeImageLayout::Undefined); + std::unique_ptr texture = std::make_unique( + tex_config, alloc, image, name, VK_IMAGE_LAYOUT_UNDEFINED, ComputeImageLayout::Undefined); if (!texture->CreateView(VK_IMAGE_VIEW_TYPE_2D_ARRAY)) return nullptr; @@ -135,7 +116,7 @@ std::unique_ptr VKTexture::CreateAdopted(const TextureConfig& tex_con VkImageViewType view_type, VkImageLayout layout) { std::unique_ptr texture = std::make_unique( - tex_config, VkDeviceMemory(VK_NULL_HANDLE), image, "", layout, ComputeImageLayout::Undefined); + tex_config, VmaAllocation(VK_NULL_HANDLE), image, "", layout, ComputeImageLayout::Undefined); if (!texture->CreateView(view_type)) return nullptr; @@ -701,9 +682,9 @@ void VKTexture::TransitionToLayout(VkCommandBuffer command_buffer, VKStagingTexture::VKStagingTexture(PrivateTag, StagingTextureType type, const TextureConfig& config, std::unique_ptr buffer, VkImage linear_image, - VkDeviceMemory linear_image_memory) + VmaAllocation linear_image_alloc) : AbstractStagingTexture(type, config), m_staging_buffer(std::move(buffer)), - m_linear_image(linear_image), m_linear_image_memory(linear_image_memory) + m_linear_image(linear_image), m_linear_image_alloc(linear_image_alloc) { } @@ -711,8 +692,7 @@ VKStagingTexture::~VKStagingTexture() { if (m_linear_image != VK_NULL_HANDLE) { - g_command_buffer_mgr->DeferImageDestruction(m_linear_image); - g_command_buffer_mgr->DeferDeviceMemoryDestruction(m_linear_image_memory); + g_command_buffer_mgr->DeferImageDestruction(m_linear_image, m_linear_image_alloc); } } @@ -741,28 +721,27 @@ std::unique_ptr VKStagingTexture::Create(StagingTextureType ty } VkBuffer buffer; - VkDeviceMemory memory; - bool coherent; - if (!StagingBuffer::AllocateBuffer(buffer_type, buffer_size, buffer_usage, &buffer, &memory, - &coherent)) + VmaAllocation alloc; + char* map_ptr; + if (!StagingBuffer::AllocateBuffer(buffer_type, buffer_size, buffer_usage, &buffer, &alloc, + &map_ptr)) { return nullptr; } // Linear image VkImage linear_image = VK_NULL_HANDLE; - VkDeviceMemory linear_image_device_memory = VK_NULL_HANDLE; + VmaAllocation linear_image_alloc = VK_NULL_HANDLE; if (DriverDetails::HasBug(DriverDetails::BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY) && type == StagingTextureType::Readback && config.samples == 1) { - std::tie(linear_image, linear_image_device_memory) = CreateLinearImage(type, config); + std::tie(linear_image, linear_image_alloc) = CreateLinearImage(type, config); } std::unique_ptr staging_buffer = - std::make_unique(buffer_type, buffer, memory, buffer_size, coherent); - std::unique_ptr staging_tex = - std::make_unique(PrivateTag{}, type, config, std::move(staging_buffer), - linear_image, linear_image_device_memory); + std::make_unique(buffer_type, buffer, alloc, buffer_size, map_ptr); + std::unique_ptr staging_tex = std::make_unique( + PrivateTag{}, type, config, std::move(staging_buffer), linear_image, linear_image_alloc); // Use persistent mapping. if (!staging_tex->m_staging_buffer->Map()) @@ -772,8 +751,8 @@ std::unique_ptr VKStagingTexture::Create(StagingTextureType ty return staging_tex; } -std::pair VKStagingTexture::CreateLinearImage(StagingTextureType type, - const TextureConfig& config) +std::pair VKStagingTexture::CreateLinearImage(StagingTextureType type, + const TextureConfig& config) { // Create a intermediate texture with linear tiling VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, @@ -802,43 +781,25 @@ std::pair VKStagingTexture::CreateLinearImage(StagingTe return std::make_pair(VK_NULL_HANDLE, VK_NULL_HANDLE); } - VkImage image; - res = vkCreateImage(g_vulkan_context->GetDevice(), &image_info, nullptr, &image); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkCreateImage failed: "); - return std::make_pair(VK_NULL_HANDLE, VK_NULL_HANDLE); - } - - // Allocate memory to back this texture, we want device local memory in this case - VkMemoryRequirements memory_requirements; - vkGetImageMemoryRequirements(g_vulkan_context->GetDevice(), image, &memory_requirements); - - VkMemoryAllocateInfo memory_info = { - VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, nullptr, memory_requirements.size, - g_vulkan_context - ->GetMemoryType(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - false) - .value_or(0)}; + VmaAllocationCreateInfo alloc_create_info = {}; + alloc_create_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; + alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + alloc_create_info.pool = VK_NULL_HANDLE; + alloc_create_info.pUserData = nullptr; + alloc_create_info.priority = 0.0; + alloc_create_info.requiredFlags = 0; + alloc_create_info.preferredFlags = 0; - VkDeviceMemory device_memory; - res = vkAllocateMemory(g_vulkan_context->GetDevice(), &memory_info, nullptr, &device_memory); - if (res != VK_SUCCESS) - { - LOG_VULKAN_ERROR(res, "vkAllocateMemory failed: "); - vkDestroyImage(g_vulkan_context->GetDevice(), image, nullptr); - return std::make_pair(VK_NULL_HANDLE, VK_NULL_HANDLE); - } - - res = vkBindImageMemory(g_vulkan_context->GetDevice(), image, device_memory, 0); + VkImage image; + VmaAllocation alloc; + res = vmaCreateImage(g_vulkan_context->GetMemoryAllocator(), &image_info, &alloc_create_info, + &image, &alloc, nullptr); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkBindImageMemory failed: "); - vkDestroyImage(g_vulkan_context->GetDevice(), image, nullptr); - vkFreeMemory(g_vulkan_context->GetDevice(), device_memory, nullptr); + LOG_VULKAN_ERROR(res, "vmaCreateImage failed: "); return std::make_pair(VK_NULL_HANDLE, VK_NULL_HANDLE); } - return std::make_pair(image, device_memory); + return std::make_pair(image, alloc); } void VKStagingTexture::CopyFromTexture(const AbstractTexture* src, diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.h b/Source/Core/VideoBackends/Vulkan/VKTexture.h index be74442ba2ca..f2eae6d80c84 100644 --- a/Source/Core/VideoBackends/Vulkan/VKTexture.h +++ b/Source/Core/VideoBackends/Vulkan/VKTexture.h @@ -30,7 +30,7 @@ class VKTexture final : public AbstractTexture }; VKTexture() = delete; - VKTexture(const TextureConfig& tex_config, VkDeviceMemory device_memory, VkImage image, + VKTexture(const TextureConfig& tex_config, VmaAllocation alloc, VkImage image, std::string_view name, VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED, ComputeImageLayout compute_layout = ComputeImageLayout::Undefined); ~VKTexture(); @@ -51,11 +51,10 @@ class VKTexture final : public AbstractTexture void FinishedRendering() override; VkImage GetImage() const { return m_image; } - VkDeviceMemory GetDeviceMemory() const { return m_device_memory; } VkImageView GetView() const { return m_view; } VkImageLayout GetLayout() const { return m_layout; } VkFormat GetVkFormat() const { return GetVkFormatForHostTextureFormat(m_config.format); } - bool IsAdopted() const { return m_device_memory != VkDeviceMemory(VK_NULL_HANDLE); } + bool IsAdopted() const { return m_alloc != VmaAllocation(VK_NULL_HANDLE); } static std::unique_ptr Create(const TextureConfig& tex_config, std::string_view name); static std::unique_ptr @@ -74,7 +73,7 @@ class VKTexture final : public AbstractTexture private: bool CreateView(VkImageViewType type); - VkDeviceMemory m_device_memory; + VmaAllocation m_alloc; VkImage m_image; VkImageView m_view = VK_NULL_HANDLE; mutable VkImageLayout m_layout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -92,7 +91,7 @@ class VKStagingTexture final : public AbstractStagingTexture VKStagingTexture() = delete; VKStagingTexture(PrivateTag, StagingTextureType type, const TextureConfig& config, std::unique_ptr buffer, VkImage linear_image, - VkDeviceMemory linear_image_memory); + VmaAllocation linear_image_alloc); ~VKStagingTexture(); @@ -110,8 +109,8 @@ class VKStagingTexture final : public AbstractStagingTexture static std::unique_ptr Create(StagingTextureType type, const TextureConfig& config); - static std::pair CreateLinearImage(StagingTextureType type, - const TextureConfig& config); + static std::pair CreateLinearImage(StagingTextureType type, + const TextureConfig& config); private: void CopyFromTextureToLinearImage(const VKTexture* src_tex, @@ -120,7 +119,7 @@ class VKStagingTexture final : public AbstractStagingTexture std::unique_ptr m_staging_buffer; VkImage m_linear_image = VK_NULL_HANDLE; - VkDeviceMemory m_linear_image_memory = VK_NULL_HANDLE; + VmaAllocation m_linear_image_alloc = VK_NULL_HANDLE; u64 m_flush_fence_counter = 0; }; diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp index bc9cc1169b7b..4d6b5c890344 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp @@ -40,6 +40,8 @@ VulkanContext::VulkanContext(VkInstance instance, VkPhysicalDevice physical_devi VulkanContext::~VulkanContext() { + if (m_allocator != VK_NULL_HANDLE) + vmaDestroyAllocator(m_allocator); if (m_device != VK_NULL_HANDLE) vkDestroyDevice(m_device, nullptr); @@ -86,7 +88,8 @@ bool VulkanContext::CheckValidationLayerAvailablility() } VkInstance VulkanContext::CreateVulkanInstance(WindowSystemType wstype, bool enable_debug_report, - bool enable_validation_layer) + bool enable_validation_layer, + u32* out_vk_api_version) { std::vector enabled_extensions; if (!SelectInstanceExtensions(&enabled_extensions, wstype, enable_debug_report)) @@ -114,6 +117,8 @@ VkInstance VulkanContext::CreateVulkanInstance(WindowSystemType wstype, bool ena } } + *out_vk_api_version = app_info.apiVersion; + VkInstanceCreateInfo instance_create_info = {}; instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_create_info.pNext = nullptr; @@ -429,10 +434,9 @@ void VulkanContext::PopulateBackendInfoMultisampleModes( config->backend_info.AAModes.emplace_back(64); } -std::unique_ptr VulkanContext::Create(VkInstance instance, VkPhysicalDevice gpu, - VkSurfaceKHR surface, - bool enable_debug_reports, - bool enable_validation_layer) +std::unique_ptr +VulkanContext::Create(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, + bool enable_debug_reports, bool enable_validation_layer, u32 vk_api_version) { std::unique_ptr context = std::make_unique(instance, gpu); @@ -445,7 +449,8 @@ std::unique_ptr VulkanContext::Create(VkInstance instance, VkPhys context->EnableDebugReports(); // Attempt to create the device. - if (!context->CreateDevice(surface, enable_validation_layer)) + if (!context->CreateDevice(surface, enable_validation_layer) || + !context->CreateAllocator(vk_api_version)) { // Since we are destroying the instance, we're also responsible for destroying the surface. if (surface != VK_NULL_HANDLE) @@ -508,6 +513,9 @@ bool VulkanContext::SelectDeviceExtensions(bool enable_surface) INFO_LOG_FMT(VIDEO, "Using VK_EXT_full_screen_exclusive for exclusive fullscreen."); #endif + AddExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false); + AddExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, false); + return true; } @@ -695,6 +703,34 @@ bool VulkanContext::CreateDevice(VkSurfaceKHR surface, bool enable_validation_la return true; } +bool VulkanContext::CreateAllocator(u32 vk_api_version) +{ + VmaAllocatorCreateInfo allocator_info = {}; + allocator_info.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; + allocator_info.physicalDevice = m_physical_device; + allocator_info.device = m_device; + allocator_info.preferredLargeHeapBlockSize = 64 << 20; + allocator_info.pAllocationCallbacks = nullptr; + allocator_info.pDeviceMemoryCallbacks = nullptr; + allocator_info.pHeapSizeLimit = nullptr; + allocator_info.pVulkanFunctions = nullptr; + allocator_info.instance = m_instance; + allocator_info.vulkanApiVersion = vk_api_version; + allocator_info.pTypeExternalMemoryHandleTypes = nullptr; + + if (SupportsDeviceExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME)) + allocator_info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; + + VkResult res = vmaCreateAllocator(&allocator_info, &m_allocator); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vmaCreateAllocator failed: "); + return false; + } + + return true; +} + static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, @@ -756,109 +792,6 @@ void VulkanContext::DisableDebugReports() } } -std::optional VulkanContext::GetMemoryType(u32 bits, VkMemoryPropertyFlags properties, - bool strict, bool* is_coherent) -{ - static constexpr u32 ALL_MEMORY_PROPERTY_FLAGS = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | - VK_MEMORY_PROPERTY_HOST_CACHED_BIT; - - const u32 mask = strict ? ALL_MEMORY_PROPERTY_FLAGS : properties; - - for (u32 i = 0; i < VK_MAX_MEMORY_TYPES; i++) - { - if ((bits & (1 << i)) != 0) - { - const VkMemoryPropertyFlags type_flags = - m_device_memory_properties.memoryTypes[i].propertyFlags; - const VkMemoryPropertyFlags supported = type_flags & mask; - if (supported == properties) - { - if (is_coherent) - *is_coherent = (type_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0; - return i; - } - } - } - - return std::nullopt; -} - -u32 VulkanContext::GetUploadMemoryType(u32 bits, bool* is_coherent) -{ - static constexpr VkMemoryPropertyFlags COHERENT_FLAGS = - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - - // Try for coherent memory. Some drivers (looking at you, Adreno) have the cached type before the - // uncached type, so use a strict check first. - std::optional type_index = GetMemoryType(bits, COHERENT_FLAGS, true, is_coherent); - if (type_index) - return type_index.value(); - - // Try for coherent memory, with any other bits set. - type_index = GetMemoryType(bits, COHERENT_FLAGS, false, is_coherent); - if (type_index) - { - WARN_LOG_FMT(VIDEO, - "Strict check for upload memory properties failed, this may affect performance"); - return type_index.value(); - } - - // Fall back to non-coherent memory. - WARN_LOG_FMT( - VIDEO, - "Vulkan: Failed to find a coherent memory type for uploads, this will affect performance."); - type_index = GetMemoryType(bits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, false, is_coherent); - if (type_index) - return type_index.value(); - - // Shouldn't happen, there should be at least one host-visible heap. - PanicAlertFmt("Unable to get memory type for upload."); - return 0; -} - -u32 VulkanContext::GetReadbackMemoryType(u32 bits, bool* is_coherent) -{ - std::optional type_index; - - // Mali driver appears to be significantly slower for readbacks when using cached memory. - if (DriverDetails::HasBug(DriverDetails::BUG_SLOW_CACHED_READBACK_MEMORY)) - { - type_index = GetMemoryType( - bits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, true, - is_coherent); - if (type_index) - return type_index.value(); - } - - // Optimal config uses cached+coherent. - type_index = - GetMemoryType(bits, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - true, is_coherent); - if (type_index) - return type_index.value(); - - // Otherwise, prefer cached over coherent if we must choose one. - type_index = - GetMemoryType(bits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, - false, is_coherent); - if (type_index) - return type_index.value(); - - WARN_LOG_FMT(VIDEO, "Vulkan: Failed to find a cached memory type for readbacks, this will affect " - "performance."); - type_index = GetMemoryType(bits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, false, is_coherent); - *is_coherent = false; - if (type_index) - return type_index.value(); - - // We should have at least one host visible memory type... - PanicAlertFmt("Unable to get memory type for upload."); - return 0; -} - bool VulkanContext::SupportsDeviceExtension(const char* name) const { return std::any_of(m_device_extensions.begin(), m_device_extensions.end(), diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.h b/Source/Core/VideoBackends/Vulkan/VulkanContext.h index 7f5d72a7d951..6ba48a34f68e 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.h +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.h @@ -26,7 +26,7 @@ class VulkanContext // Helper method to create a Vulkan instance. static VkInstance CreateVulkanInstance(WindowSystemType wstype, bool enable_debug_report, - bool enable_validation_layer); + bool enable_validation_layer, u32* out_vk_api_version); // Returns a list of Vulkan-compatible GPUs. using GPUList = std::vector; @@ -47,7 +47,7 @@ class VulkanContext // been called for the specified VideoConfig. static std::unique_ptr Create(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, bool enable_debug_reports, - bool enable_validation_layer); + bool enable_validation_layer, u32 api_version); // Enable/disable debug message runtime. bool EnableDebugReports(); @@ -98,14 +98,6 @@ class VulkanContext return m_device_properties.limits.bufferImageGranularity; } float GetMaxSamplerAnisotropy() const { return m_device_properties.limits.maxSamplerAnisotropy; } - // Finds a memory type index for the specified memory properties and the bits returned by - // vkGetImageMemoryRequirements - std::optional GetMemoryType(u32 bits, VkMemoryPropertyFlags properties, bool strict, - bool* is_coherent = nullptr); - - // Finds a memory type for upload or readback buffers. - u32 GetUploadMemoryType(u32 bits, bool* is_coherent = nullptr); - u32 GetReadbackMemoryType(u32 bits, bool* is_coherent = nullptr); // Returns true if the specified extension is supported and enabled. bool SupportsDeviceExtension(const char* name) const; @@ -113,6 +105,8 @@ class VulkanContext // Returns true if exclusive fullscreen is supported for the given surface. bool SupportsExclusiveFullscreen(const WindowSystemInfo& wsi, VkSurfaceKHR surface); + VmaAllocator GetMemoryAllocator() const { return m_allocator; } + #ifdef WIN32 // Returns the platform-specific exclusive fullscreen structure. VkSurfaceFullScreenExclusiveWin32InfoEXT @@ -127,10 +121,12 @@ class VulkanContext bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer); void InitDriverDetails(); void PopulateShaderSubgroupSupport(); + bool CreateAllocator(u32 vk_api_version); VkInstance m_instance = VK_NULL_HANDLE; VkPhysicalDevice m_physical_device = VK_NULL_HANDLE; VkDevice m_device = VK_NULL_HANDLE; + VmaAllocator m_allocator = VK_NULL_HANDLE; VkQueue m_graphics_queue = VK_NULL_HANDLE; u32 m_graphics_queue_family_index = 0; diff --git a/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl b/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl index 8c6db283c9c5..3bb21c41e360 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl +++ b/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl @@ -37,6 +37,7 @@ VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceSurfaceSupportKHR, false) VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR, false) VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceSurfaceFormatsKHR, false) VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceSurfacePresentModesKHR, false) +VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceMemoryProperties2, false) #if defined(VK_USE_PLATFORM_WIN32_KHR) VULKAN_INSTANCE_ENTRY_POINT(vkCreateWin32SurfaceKHR, false) @@ -192,6 +193,10 @@ VULKAN_DEVICE_ENTRY_POINT(vkDestroySwapchainKHR, false) VULKAN_DEVICE_ENTRY_POINT(vkGetSwapchainImagesKHR, false) VULKAN_DEVICE_ENTRY_POINT(vkAcquireNextImageKHR, false) VULKAN_DEVICE_ENTRY_POINT(vkQueuePresentKHR, false) +VULKAN_DEVICE_ENTRY_POINT(vkGetBufferMemoryRequirements2, false) +VULKAN_DEVICE_ENTRY_POINT(vkGetImageMemoryRequirements2, false) +VULKAN_DEVICE_ENTRY_POINT(vkBindBufferMemory2, false) +VULKAN_DEVICE_ENTRY_POINT(vkBindImageMemory2, false) #ifdef SUPPORTS_VULKAN_EXCLUSIVE_FULLSCREEN VULKAN_DEVICE_ENTRY_POINT(vkAcquireFullScreenExclusiveModeEXT, false) diff --git a/Source/Core/VideoBackends/Vulkan/VulkanLoader.cpp b/Source/Core/VideoBackends/Vulkan/VulkanLoader.cpp index c554f07314a0..9b62244dfd82 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanLoader.cpp +++ b/Source/Core/VideoBackends/Vulkan/VulkanLoader.cpp @@ -1,6 +1,7 @@ // Copyright 2016 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#define VMA_IMPLEMENTATION #include "VideoBackends/Vulkan/VulkanLoader.h" #include diff --git a/Source/Core/VideoBackends/Vulkan/VulkanLoader.h b/Source/Core/VideoBackends/Vulkan/VulkanLoader.h index 2d6843953b6c..c9b92f5ac8e0 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanLoader.h +++ b/Source/Core/VideoBackends/Vulkan/VulkanLoader.h @@ -37,6 +37,42 @@ #undef VULKAN_INSTANCE_ENTRY_POINT #undef VULKAN_MODULE_ENTRY_POINT +// Include vma allocator globally since including it before the vulkan headers causes +// errors +#ifdef _MSVC_LANG +#pragma warning(push, 4) +#pragma warning(disable : 4189) // local variable is initialized but not referenced + +#endif // #ifdef _MSVC_LANG + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-variable" +#endif // #ifdef __clang__ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif // #ifdef __GNUC__ + +#define VMA_VULKAN_VERSION 1001000 +#define VMA_STATIC_VULKAN_FUNCTIONS 1 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 +#undef VK_NO_PROTOTYPES +#include "vk_mem_alloc.h" + +#ifdef _MSVC_LANG +#pragma warning(pop) +#endif // #ifdef _MSVC_LANG + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif // #ifdef __clang__ + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif // #ifdef __GNUC__ + #include "Common/Logging/Log.h" namespace Vulkan diff --git a/Source/VSProps/Base.Dolphin.props b/Source/VSProps/Base.Dolphin.props index aadb04d5ddc4..263d5d5fcf3f 100644 --- a/Source/VSProps/Base.Dolphin.props +++ b/Source/VSProps/Base.Dolphin.props @@ -14,6 +14,7 @@ $(ExternalsDir)OpenAL\include;%(AdditionalIncludeDirectories) $(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories) $(ExternalsDir)Vulkan\include;%(AdditionalIncludeDirectories) + $(ExternalsDir)VulkanMemoryAllocator\include;%(AdditionalIncludeDirectories) $(ExternalsDir)WIL\include;%(AdditionalIncludeDirectories) WIL_SUPPRESS_EXCEPTIONS;%(PreprocessorDefinitions)