mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-18 06:10:35 +00:00
Adjustments on UMA
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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."));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
if (max_buffer_size != 0 && max_buffer_size < candidate_window) {
|
||||
candidate_window = Common::AlignDown(max_buffer_size, alignment);
|
||||
}
|
||||
if (candidate_window == 0) {
|
||||
return;
|
||||
}
|
||||
window_size = candidate_window;
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
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 (!type_index) {
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
if (windows.empty()) {
|
||||
LOG_INFO(Render_Vulkan, "Unified memory disabled, host memory import failed");
|
||||
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;
|
||||
}
|
||||
if (logical.BindBufferMemory(new_buffer, *memory, 0) != VK_SUCCESS) {
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
memory = vk::DeviceMemory{};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user