From 8c8c7c61626ecdda0f61bf13c75ebeaa856e985b Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 31 Jul 2026 19:30:31 -0400 Subject: [PATCH] Adjustments on UMA --- .../app/src/main/res/values/strings.xml | 2 +- src/qt_common/config/shared_translation.cpp | 4 +- src/video_core/buffer_cache/buffer_cache.h | 83 +++++---- .../buffer_cache/buffer_cache_base.h | 2 +- .../renderer_vulkan/vk_buffer_cache.h | 12 +- .../vulkan_common/vulkan_memory_allocator.cpp | 175 ++++++++++-------- .../vulkan_common/vulkan_memory_allocator.h | 27 ++- 7 files changed, 181 insertions(+), 124 deletions(-) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index d31c1db227..b57c610e76 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -525,7 +525,7 @@ Use asynchronous shaders Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches. Unified memory access (UMA) - Lets the GPU read guest memory directly for buffer uploads, skipping the CPU staging copy. Requires driver support for host memory import and is automatically disabled when unsupported. + Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy. GPU Unswizzle Settings Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality. Enable GPU Unswizzle diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 7a9049f3a5..a81e633597 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -231,9 +231,7 @@ std::unique_ptr InitializeTranslations(QObject* parent) { INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), tr("May reduce shader stutter.")); INSERT(Settings, use_unified_memory, tr("Enable unified memory access (UMA)"), - tr("Lets the GPU read guest memory directly for buffer uploads, skipping the CPU " - "staging copy.\nRequires driver support for host memory import and may cause " - "issues in some games.")); + tr("Lets the GPU write buffer readbacks directly into guest memory.")); INSERT(Settings, pipeline_worker_count, tr("Pipeline Worker Threads"), tr("Number of threads used to build Vulkan pipelines.\n" "Higher values speed up compilation at the cost of heat and power.")); diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index ed5eb3c0d5..ca6d65c2b0 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1815,53 +1815,73 @@ void BufferCache

::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer, } template -bool BufferCache

::TryUnifiedUploadMemory([[maybe_unused]] Buffer& buffer, - [[maybe_unused]] std::span copies) { +bool BufferCache

::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer, + [[maybe_unused]] std::span copies) { if constexpr (USE_UNIFIED_MEMORY) { - boost::container::small_vector host_copies; const u8* const physical_base = device_memory.GetPhysicalBase(); const u64 unified_size = runtime.UnifiedMemorySize(); + const u64 window_size = runtime.UnifiedMemoryWindowSize(); + if (window_size == 0) { + return false; + } + boost::container::small_vector window_ids; + boost::container::small_vector, 4> groups; + const auto group_for = [&](u64 window) -> boost::container::small_vector& { + for (size_t i = 0; i < window_ids.size(); ++i) { + if (window_ids[i] == window) { + return groups[i]; + } + } + window_ids.push_back(window); + groups.emplace_back(); + return groups.back(); + }; for (const BufferCopy& copy : copies) { - const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset; - u64 uploaded = 0; - while (uploaded < copy.size) { - const DAddr page_addr = device_addr + uploaded; + const DAddr device_addr = buffer.CpuAddr() + copy.src_offset; + u64 downloaded = 0; + while (downloaded < copy.size) { + const DAddr page_addr = device_addr + downloaded; const u8* const ptr = device_memory.GetPointer(page_addr); if (ptr == nullptr) { return false; } const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK; - const u64 chunk = (std::min)(copy.size - uploaded, - static_cast(Core::DEVICE_PAGESIZE) - page_offset); - const u64 src_offset = static_cast(ptr - physical_base); - if (src_offset + chunk > unified_size) { + u64 chunk = (std::min)(copy.size - downloaded, + static_cast(Core::DEVICE_PAGESIZE) - page_offset); + const u64 phys_offset = static_cast(ptr - physical_base); + if (phys_offset + chunk > unified_size) { return false; } - if (!host_copies.empty()) { - BufferCopy& last = host_copies.back(); - if (last.src_offset + last.size == src_offset && - last.dst_offset + last.size == copy.dst_offset + uploaded) { + const u64 window = phys_offset / window_size; + const u64 local_offset = phys_offset % window_size; + chunk = (std::min)(chunk, window_size - local_offset); + auto& group = group_for(window); + if (!group.empty()) { + BufferCopy& last = group.back(); + if (last.src_offset + last.size == copy.src_offset + downloaded && + last.dst_offset + last.size == local_offset) { last.size += chunk; - uploaded += chunk; + downloaded += chunk; continue; } } - host_copies.push_back(BufferCopy{ - .src_offset = src_offset, - .dst_offset = copy.dst_offset + uploaded, + group.push_back(BufferCopy{ + .src_offset = copy.src_offset + downloaded, + .dst_offset = local_offset, .size = chunk, }); - uploaded += chunk; + downloaded += chunk; } } for (const BufferCopy& copy : copies) { - if (Settings::values.enable_gpu_buffer_readback.GetValue()) { - DownloadBufferMemory(buffer, buffer.CpuAddr() + copy.dst_offset, copy.size); - } + buffer.MarkUsage(copy.src_offset, copy.size); + } + for (size_t i = 0; i < window_ids.size(); ++i) { + const std::span group_span(groups[i].data(), groups[i].size()); + runtime.CopyBuffer(runtime.UnifiedMemoryWindowBuffer(window_ids[i]), buffer, + group_span, true); } - const std::span host_span(host_copies.data(), host_copies.size()); - const bool can_reorder = runtime.CanReorderUpload(buffer, host_span); - runtime.CopyBuffer(buffer, runtime.UnifiedMemoryBuffer(), host_span, true, can_reorder); + runtime.Finish(); return true; } else { return false; @@ -1873,11 +1893,6 @@ void BufferCache

::MappedUploadMemory([[maybe_unused]] Buffer& buffer, [[maybe_unused]] u64 total_size_bytes, [[maybe_unused]] std::span copies) { if constexpr (USE_MEMORY_MAPS) { - if constexpr (USE_UNIFIED_MEMORY) { - if (runtime.HasUnifiedMemory() && TryUnifiedUploadMemory(buffer, copies)) { - return; - } - } auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes); const std::span staging_pointer = upload_staging.mapped_span; for (BufferCopy& copy : copies) { @@ -1976,6 +1991,12 @@ void BufferCache

::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64 } if constexpr (USE_MEMORY_MAPS) { + if constexpr (USE_UNIFIED_MEMORY) { + if (runtime.HasUnifiedMemory() && + TryUnifiedDownloadMemory(buffer, std::span(copies.data(), copies.size()))) { + return; + } + } auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); const u8* const mapped_memory = download_staging.mapped_span.data(); const std::span copies_span(copies.data(), copies.data() + copies.size()); diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 724227e4b6..c3f39d39cb 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -451,7 +451,7 @@ private: void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span copies); - bool TryUnifiedUploadMemory(Buffer& buffer, std::span copies); + bool TryUnifiedDownloadMemory(Buffer& buffer, std::span copies); void DownloadBufferMemory(Buffer& buffer_id); diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 8b18bc3a14..004666a4ec 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -104,14 +104,18 @@ public: return unified_memory != nullptr && unified_memory->IsValid(); } - [[nodiscard]] VkBuffer UnifiedMemoryBuffer() const noexcept { - return unified_memory ? unified_memory->GetBuffer() : VK_NULL_HANDLE; - } - [[nodiscard]] u64 UnifiedMemorySize() const noexcept { return unified_memory ? unified_memory->GetSize() : 0; } + [[nodiscard]] u64 UnifiedMemoryWindowSize() const noexcept { + return unified_memory ? unified_memory->GetWindowSize() : 0; + } + + [[nodiscard]] VkBuffer UnifiedMemoryWindowBuffer(size_t index) const noexcept { + return unified_memory ? unified_memory->GetWindowBuffer(index) : VK_NULL_HANDLE; + } + u64 CurrentTick(); u64 KnownGpuTick(); diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 26879f1c7c..5a9524997e 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -83,52 +83,21 @@ namespace Vulkan { alignment); return; } + using namespace Common::Literals; + VkDeviceSize candidate_window = 1_GiB; const u64 max_buffer_size = device.GetMaxBufferSize(); - if (max_buffer_size != 0 && max_buffer_size < size) { - size = static_cast(Common::AlignDown(max_buffer_size, alignment)); - if (size == 0) { - return; - } - } - const auto &logical = device.GetLogical(); - VkMemoryHostPointerPropertiesEXT host_props{ - .sType = VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT, - .pNext = nullptr, - .memoryTypeBits = 0, - }; - if (logical.GetMemoryHostPointerPropertiesEXT( - VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, base, &host_props) != - VK_SUCCESS || - host_props.memoryTypeBits == 0) { - return; - } - const VkExternalMemoryBufferCreateInfo external_info{ - .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, - .pNext = nullptr, - .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, - }; - const VkBufferCreateInfo buffer_ci{ - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .pNext = &external_info, - .flags = 0, - .size = size, - .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - }; - VkBuffer new_buffer{}; - if (logical.CreateBufferRaw(buffer_ci, &new_buffer) != VK_SUCCESS) { - return; + if (max_buffer_size != 0 && max_buffer_size < candidate_window) { + candidate_window = Common::AlignDown(max_buffer_size, alignment); } - const VkMemoryRequirements requirements = logical.GetBufferMemoryRequirements(new_buffer); - const u32 type_mask = requirements.memoryTypeBits & host_props.memoryTypeBits; - if (type_mask == 0 || requirements.size > size) { - logical.DestroyBufferRaw(new_buffer); + if (candidate_window == 0) { return; } + window_size = candidate_window; + + const auto &logical = device.GetLogical(); const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties; - const auto find_type = [&](VkMemoryPropertyFlags wanted) -> std::optional { + const auto find_type = [&](u32 type_mask, VkMemoryPropertyFlags wanted) + -> std::optional { for (u32 i = 0; i < memory_props.memoryTypeCount; ++i) { if (((type_mask >> i) & 1u) != 0 && (memory_props.memoryTypes[i].propertyFlags & wanted) == wanted) { @@ -137,48 +106,100 @@ namespace Vulkan { } return std::nullopt; }; - auto type_index = find_type(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | - VK_MEMORY_PROPERTY_HOST_CACHED_BIT); - if (!type_index) { - type_index = find_type(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - } - if (!type_index) { - logical.DestroyBufferRaw(new_buffer); - return; - } - const VkImportMemoryHostPointerInfoEXT import_info{ - .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, - .pNext = nullptr, - .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, - .pHostPointer = base, - }; - const VkMemoryAllocateInfo alloc_info{ - .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .pNext = &import_info, - .allocationSize = size, - .memoryTypeIndex = *type_index, - }; - memory = logical.TryAllocateMemory(alloc_info); - if (!memory) { - logical.DestroyBufferRaw(new_buffer); - return; + + for (size_t offset = 0; offset < size; offset += window_size) { + u8 *const window_base = static_cast(base) + offset; + const VkDeviceSize window_len = + (std::min)(static_cast(size - offset), window_size); + VkMemoryHostPointerPropertiesEXT host_props{ + .sType = VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT, + .pNext = nullptr, + .memoryTypeBits = 0, + }; + if (logical.GetMemoryHostPointerPropertiesEXT( + VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, window_base, + &host_props) != VK_SUCCESS || + host_props.memoryTypeBits == 0) { + break; + } + const VkExternalMemoryBufferCreateInfo external_info{ + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, + .pNext = nullptr, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, + }; + const VkBufferCreateInfo buffer_ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = &external_info, + .flags = 0, + .size = window_len, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + VkBuffer new_buffer{}; + if (logical.CreateBufferRaw(buffer_ci, &new_buffer) != VK_SUCCESS) { + break; + } + const VkMemoryRequirements requirements = + logical.GetBufferMemoryRequirements(new_buffer); + const u32 type_mask = requirements.memoryTypeBits & host_props.memoryTypeBits; + if (type_mask == 0 || requirements.size > window_len) { + logical.DestroyBufferRaw(new_buffer); + break; + } + auto type_index = find_type(type_mask, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT); + if (!type_index) { + type_index = find_type(type_mask, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + } + if (!type_index) { + logical.DestroyBufferRaw(new_buffer); + break; + } + const VkImportMemoryHostPointerInfoEXT import_info{ + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, + .pNext = nullptr, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, + .pHostPointer = window_base, + }; + const VkMemoryAllocateInfo alloc_info{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &import_info, + .allocationSize = window_len, + .memoryTypeIndex = *type_index, + }; + vk::DeviceMemory memory = logical.TryAllocateMemory(alloc_info); + if (!memory) { + logical.DestroyBufferRaw(new_buffer); + break; + } + if (logical.BindBufferMemory(new_buffer, *memory, 0) != VK_SUCCESS) { + logical.DestroyBufferRaw(new_buffer); + break; + } + windows.push_back(Window{ + .memory = std::move(memory), + .buffer = new_buffer, + }); + imported_size += static_cast(window_len); } - if (logical.BindBufferMemory(new_buffer, *memory, 0) != VK_SUCCESS) { - logical.DestroyBufferRaw(new_buffer); - memory = vk::DeviceMemory{}; + if (windows.empty()) { + LOG_INFO(Render_Vulkan, "Unified memory disabled, host memory import failed"); return; } - buffer = new_buffer; - imported_size = size; - LOG_INFO(Render_Vulkan, "Imported {} MiB of guest memory for unified memory access", - size >> 20); + LOG_INFO(Render_Vulkan, + "Imported {} MiB of guest memory for unified memory access in {} windows", + imported_size >> 20, windows.size()); } HostMemoryImport::~HostMemoryImport() { - if (buffer != VK_NULL_HANDLE) { - device.GetLogical().DestroyBufferRaw(buffer); + for (Window &window : windows) { + if (window.buffer != VK_NULL_HANDLE) { + device.GetLogical().DestroyBufferRaw(window.buffer); + } } } diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h index ecf515112e..57d5286ff8 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.h +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h @@ -51,21 +51,34 @@ namespace Vulkan { HostMemoryImport &operator=(const HostMemoryImport &) = delete; [[nodiscard]] bool IsValid() const noexcept { - return buffer != VK_NULL_HANDLE; - } - - [[nodiscard]] VkBuffer GetBuffer() const noexcept { - return buffer; + return !windows.empty(); } [[nodiscard]] size_t GetSize() const noexcept { return imported_size; } + [[nodiscard]] VkDeviceSize GetWindowSize() const noexcept { + return window_size; + } + + [[nodiscard]] VkBuffer GetWindowBuffer(size_t index) const noexcept { + return windows[index].buffer; + } + + [[nodiscard]] size_t GetWindowCount() const noexcept { + return windows.size(); + } + private: + struct Window { + vk::DeviceMemory memory; + VkBuffer buffer{}; + }; + const Device &device; - vk::DeviceMemory memory; - VkBuffer buffer{}; + std::vector windows; + VkDeviceSize window_size{}; size_t imported_size{}; };