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_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_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_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>

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"),
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."));

83
src/video_core/buffer_cache/buffer_cache.h

@ -1815,53 +1815,73 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
}
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) {
boost::container::small_vector<BufferCopy, 16> 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<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) {
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);
if (ptr == nullptr) {
return false;
}
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;
}
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<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;
} else {
return false;
@ -1873,11 +1893,6 @@ void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] u64 total_size_bytes,
[[maybe_unused]] std::span<BufferCopy> 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<u8> staging_pointer = upload_staging.mapped_span;
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_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<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);
bool TryUnifiedUploadMemory(Buffer& buffer, std::span<BufferCopy> copies);
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
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();
}
[[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();

175
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<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;
}
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<u32> {
const auto find_type = [&](u32 type_mask, VkMemoryPropertyFlags wanted)
-> std::optional<u32> {
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<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;
}
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);
}
}
}

27
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<Window> windows;
VkDeviceSize window_size{};
size_t imported_size{};
};

Loading…
Cancel
Save