Browse Source

Adjustments on UMA

temporary-branch
CamilleLaVey 2 days ago
parent
commit
8c8c7c6162
  1. 2
      src/android/app/src/main/res/values/strings.xml
  2. 4
      src/qt_common/config/shared_translation.cpp
  3. 83
      src/video_core/buffer_cache/buffer_cache.h
  4. 2
      src/video_core/buffer_cache/buffer_cache_base.h
  5. 12
      src/video_core/renderer_vulkan/vk_buffer_cache.h
  6. 175
      src/video_core/vulkan_common/vulkan_memory_allocator.cpp
  7. 27
      src/video_core/vulkan_common/vulkan_memory_allocator.h

2
src/android/app/src/main/res/values/strings.xml

@ -525,7 +525,7 @@
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string> <string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string> <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="renderer_unified_memory">Unified memory access (UMA)</string> <string name="renderer_unified_memory">Unified memory access (UMA)</string>
<string name="renderer_unified_memory_description">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.</string>
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string> <string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string> <string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string> <string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>

4
src/qt_common/config/shared_translation.cpp

@ -231,9 +231,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
tr("May reduce shader stutter.")); tr("May reduce shader stutter."));
INSERT(Settings, use_unified_memory, tr("Enable unified memory access (UMA)"), 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"), INSERT(Settings, pipeline_worker_count, tr("Pipeline Worker Threads"),
tr("Number of threads used to build Vulkan pipelines.\n" tr("Number of threads used to build Vulkan pipelines.\n"
"Higher values speed up compilation at the cost of heat and power.")); "Higher values speed up compilation at the cost of heat and power."));

83
src/video_core/buffer_cache/buffer_cache.h

@ -1815,53 +1815,73 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
} }
template <class P> template <class P>
bool BufferCache<P>::TryUnifiedUploadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] std::span<BufferCopy> copies) {
bool BufferCache<P>::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] std::span<BufferCopy> copies) {
if constexpr (USE_UNIFIED_MEMORY) { if constexpr (USE_UNIFIED_MEMORY) {
boost::container::small_vector<BufferCopy, 16> host_copies;
const u8* const physical_base = device_memory.GetPhysicalBase(); const u8* const physical_base = device_memory.GetPhysicalBase();
const u64 unified_size = runtime.UnifiedMemorySize(); const u64 unified_size = runtime.UnifiedMemorySize();
const u64 window_size = runtime.UnifiedMemoryWindowSize();
if (window_size == 0) {
return false;
}
boost::container::small_vector<u64, 4> window_ids;
boost::container::small_vector<boost::container::small_vector<BufferCopy, 16>, 4> groups;
const auto group_for = [&](u64 window) -> boost::container::small_vector<BufferCopy, 16>& {
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) { 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<u8>(page_addr); const u8* const ptr = device_memory.GetPointer<u8>(page_addr);
if (ptr == nullptr) { if (ptr == nullptr) {
return false; return false;
} }
const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK; const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK;
const u64 chunk = (std::min)(copy.size - uploaded,
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
const u64 src_offset = static_cast<u64>(ptr - physical_base);
if (src_offset + chunk > unified_size) {
u64 chunk = (std::min)(copy.size - downloaded,
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
const u64 phys_offset = static_cast<u64>(ptr - physical_base);
if (phys_offset + chunk > unified_size) {
return false; 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; last.size += chunk;
uploaded += chunk;
downloaded += chunk;
continue; 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, .size = chunk,
}); });
uploaded += chunk;
downloaded += chunk;
} }
} }
for (const BufferCopy& copy : copies) { 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<BufferCopy> group_span(groups[i].data(), groups[i].size());
runtime.CopyBuffer(runtime.UnifiedMemoryWindowBuffer(window_ids[i]), buffer,
group_span, true);
} }
const std::span<BufferCopy> 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; return true;
} else { } else {
return false; return false;
@ -1873,11 +1893,6 @@ void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] u64 total_size_bytes, [[maybe_unused]] u64 total_size_bytes,
[[maybe_unused]] std::span<BufferCopy> copies) { [[maybe_unused]] std::span<BufferCopy> copies) {
if constexpr (USE_MEMORY_MAPS) { 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); auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes);
const std::span<u8> staging_pointer = upload_staging.mapped_span; const std::span<u8> staging_pointer = upload_staging.mapped_span;
for (BufferCopy& copy : copies) { for (BufferCopy& copy : copies) {
@ -1976,6 +1991,12 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
} }
if constexpr (USE_MEMORY_MAPS) { 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); auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
const u8* const mapped_memory = download_staging.mapped_span.data(); const u8* const mapped_memory = download_staging.mapped_span.data();
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size()); const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());

2
src/video_core/buffer_cache/buffer_cache_base.h

@ -451,7 +451,7 @@ private:
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies); void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
bool TryUnifiedUploadMemory(Buffer& buffer, std::span<BufferCopy> copies);
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
void DownloadBufferMemory(Buffer& buffer_id); void DownloadBufferMemory(Buffer& buffer_id);

12
src/video_core/renderer_vulkan/vk_buffer_cache.h

@ -104,14 +104,18 @@ public:
return unified_memory != nullptr && unified_memory->IsValid(); 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 { [[nodiscard]] u64 UnifiedMemorySize() const noexcept {
return unified_memory ? unified_memory->GetSize() : 0; 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 CurrentTick();
u64 KnownGpuTick(); u64 KnownGpuTick();

175
src/video_core/vulkan_common/vulkan_memory_allocator.cpp

@ -83,52 +83,21 @@ namespace Vulkan {
alignment); alignment);
return; return;
} }
using namespace Common::Literals;
VkDeviceSize candidate_window = 1_GiB;
const u64 max_buffer_size = device.GetMaxBufferSize(); const u64 max_buffer_size = device.GetMaxBufferSize();
if (max_buffer_size != 0 && max_buffer_size < size) {
size = static_cast<size_t>(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; return;
} }
window_size = candidate_window;
const auto &logical = device.GetLogical();
const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties; const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
const auto find_type = [&](VkMemoryPropertyFlags wanted) -> std::optional<u32> {
const auto find_type = [&](u32 type_mask, VkMemoryPropertyFlags wanted)
-> std::optional<u32> {
for (u32 i = 0; i < memory_props.memoryTypeCount; ++i) { for (u32 i = 0; i < memory_props.memoryTypeCount; ++i) {
if (((type_mask >> i) & 1u) != 0 && if (((type_mask >> i) & 1u) != 0 &&
(memory_props.memoryTypes[i].propertyFlags & wanted) == wanted) { (memory_props.memoryTypes[i].propertyFlags & wanted) == wanted) {
@ -137,48 +106,100 @@ namespace Vulkan {
} }
return std::nullopt; 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<u8 *>(base) + offset;
const VkDeviceSize window_len =
(std::min)(static_cast<VkDeviceSize>(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<size_t>(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; 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() { 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);
}
} }
} }

27
src/video_core/vulkan_common/vulkan_memory_allocator.h

@ -51,21 +51,34 @@ namespace Vulkan {
HostMemoryImport &operator=(const HostMemoryImport &) = delete; HostMemoryImport &operator=(const HostMemoryImport &) = delete;
[[nodiscard]] bool IsValid() const noexcept { [[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 { [[nodiscard]] size_t GetSize() const noexcept {
return imported_size; 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: private:
struct Window {
vk::DeviceMemory memory;
VkBuffer buffer{};
};
const Device &device; const Device &device;
vk::DeviceMemory memory;
VkBuffer buffer{};
std::vector<Window> windows;
VkDeviceSize window_size{};
size_t imported_size{}; size_t imported_size{};
}; };

Loading…
Cancel
Save