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{};
};