mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-12 06:39:26 +00:00
[TEST] Adjustment AHB to tiled-gpu-v2 state
This commit is contained in:
+14
-34
@@ -4,43 +4,34 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <fstream>
|
||||||
#include "common/heap_tracker.h"
|
#include "common/heap_tracker.h"
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/memory_detect.h"
|
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
s64 GetMaxPermissibleResidentMapCount(const Common::HostMemory& buffer) {
|
s64 GetMaxPermissibleResidentMapCount() {
|
||||||
// Default value.
|
// Default value.
|
||||||
constexpr s64 DefaultMaxMapCount = 65530;
|
s64 value = 65530;
|
||||||
|
|
||||||
// Try to read how many mappings we can make.
|
// Try to read how many mappings we can make.
|
||||||
const u64 reported = Common::GetMaxMapCount();
|
std::ifstream s("/proc/sys/vm/max_map_count");
|
||||||
const s64 value = reported != 0 ? static_cast<s64>(reported) : DefaultMaxMapCount;
|
s >> value;
|
||||||
|
|
||||||
// Print, for debug.
|
// Print, for debug.
|
||||||
LOG_INFO(HW_Memory, "Current maximum map count: {}", value);
|
LOG_INFO(HW_Memory, "Current maximum map count: {}", value);
|
||||||
|
|
||||||
// Allow 20000 maps for other code and to account for split inaccuracy.
|
// Allow 20000 maps for other code and to account for split inaccuracy.
|
||||||
constexpr s64 ForeignMapReservation = 20000;
|
return std::max<s64>(value - 20000, 0);
|
||||||
|
|
||||||
const size_t hardware_buffer_windows = buffer.BackingHardwareBuffers().size();
|
|
||||||
const s64 unmergeable_window_boundaries =
|
|
||||||
hardware_buffer_windows != 0 ? static_cast<s64>(hardware_buffer_windows) + 1 : 0;
|
|
||||||
|
|
||||||
return std::max<s64>(value - ForeignMapReservation - unmergeable_window_boundaries, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
HeapTracker::HeapTracker(Common::HostMemory& buffer)
|
HeapTracker::HeapTracker(Common::HostMemory& buffer)
|
||||||
: m_buffer(buffer),
|
: m_buffer(buffer), m_max_resident_map_count(GetMaxPermissibleResidentMapCount()) {}
|
||||||
m_has_hardware_buffer_backing(!buffer.BackingHardwareBuffers().empty()),
|
|
||||||
m_max_resident_map_count(GetMaxPermissibleResidentMapCount(buffer)) {}
|
|
||||||
HeapTracker::~HeapTracker() = default;
|
HeapTracker::~HeapTracker() = default;
|
||||||
|
|
||||||
void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
|
||||||
@@ -94,8 +85,7 @@ void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_hea
|
|||||||
|
|
||||||
// If resident, erase from resident map.
|
// If resident, erase from resident map.
|
||||||
if (item->is_resident) {
|
if (item->is_resident) {
|
||||||
m_resident_map_count -= this->HostMapCount(item->paddr, item->size);
|
ASSERT(--m_resident_map_count >= 0);
|
||||||
ASSERT(m_resident_map_count >= 0);
|
|
||||||
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
|
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +191,7 @@ bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) {
|
|||||||
|
|
||||||
// This map is now resident.
|
// This map is now resident.
|
||||||
it->is_resident = true;
|
it->is_resident = true;
|
||||||
m_resident_map_count += this->HostMapCount(it->paddr, it->size);
|
m_resident_map_count++;
|
||||||
m_resident_mappings.insert(*it);
|
m_resident_mappings.insert(*it);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,17 +213,17 @@ void HeapTracker::RebuildSeparateHeapAddressSpace() {
|
|||||||
// Despite being worse in theory, this has proven to be better in practice than more
|
// Despite being worse in theory, this has proven to be better in practice than more
|
||||||
// regularly dumping a smaller amount, because it significantly reduces average case
|
// regularly dumping a smaller amount, because it significantly reduces average case
|
||||||
// lock contention.
|
// lock contention.
|
||||||
s64 const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2;
|
std::size_t const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2;
|
||||||
|
std::size_t const evict_count = m_resident_map_count - desired_count;
|
||||||
auto it = m_resident_mappings.begin();
|
auto it = m_resident_mappings.begin();
|
||||||
|
|
||||||
while (m_resident_map_count > desired_count && it != m_resident_mappings.end()) {
|
for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) {
|
||||||
// Unmark and unmap.
|
// Unmark and unmap.
|
||||||
it->is_resident = false;
|
it->is_resident = false;
|
||||||
m_buffer.Unmap(it->vaddr, it->size, false);
|
m_buffer.Unmap(it->vaddr, it->size, false);
|
||||||
|
|
||||||
// Advance.
|
// Advance.
|
||||||
m_resident_map_count -= this->HostMapCount(it->paddr, it->size);
|
ASSERT(--m_resident_map_count >= 0);
|
||||||
ASSERT(m_resident_map_count >= 0);
|
|
||||||
it = m_resident_mappings.erase(it);
|
it = m_resident_mappings.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,7 +245,6 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
|
|||||||
// Cache the original values.
|
// Cache the original values.
|
||||||
auto* const left = std::addressof(*it);
|
auto* const left = std::addressof(*it);
|
||||||
const size_t orig_size = left->size;
|
const size_t orig_size = left->size;
|
||||||
const s64 orig_host_map_count = this->HostMapCount(left->paddr, orig_size);
|
|
||||||
|
|
||||||
// Adjust the left map.
|
// Adjust the left map.
|
||||||
const size_t left_size = offset - left->vaddr;
|
const size_t left_size = offset - left->vaddr;
|
||||||
@@ -277,20 +266,11 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
|
|||||||
|
|
||||||
// If resident, also insert into resident map.
|
// If resident, also insert into resident map.
|
||||||
if (right->is_resident) {
|
if (right->is_resident) {
|
||||||
m_resident_map_count += this->HostMapCount(left->paddr, left->size) +
|
m_resident_map_count++;
|
||||||
this->HostMapCount(right->paddr, right->size) -
|
|
||||||
orig_host_map_count;
|
|
||||||
m_resident_mappings.insert(*right);
|
m_resident_mappings.insert(*right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s64 HeapTracker::HostMapCount(PAddr paddr, size_t size) const {
|
|
||||||
if (!m_has_hardware_buffer_backing) {
|
|
||||||
return size != 0 ? 1 : 0;
|
|
||||||
}
|
|
||||||
return static_cast<s64>(m_buffer.BackingMapCount(paddr, size));
|
|
||||||
}
|
|
||||||
|
|
||||||
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
|
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
|
||||||
const SeparateHeapMap key{
|
const SeparateHeapMap key{
|
||||||
.vaddr = offset,
|
.vaddr = offset,
|
||||||
|
|||||||
@@ -85,13 +85,10 @@ private:
|
|||||||
|
|
||||||
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
|
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
|
||||||
|
|
||||||
s64 HostMapCount(PAddr paddr, size_t size) const;
|
|
||||||
|
|
||||||
void RebuildSeparateHeapAddressSpace();
|
void RebuildSeparateHeapAddressSpace();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Common::HostMemory& m_buffer;
|
Common::HostMemory& m_buffer;
|
||||||
const bool m_has_hardware_buffer_backing;
|
|
||||||
const s64 m_max_resident_map_count;
|
const s64 m_max_resident_map_count;
|
||||||
|
|
||||||
std::shared_mutex m_rebuild_lock{};
|
std::shared_mutex m_rebuild_lock{};
|
||||||
|
|||||||
+29
-66
@@ -670,37 +670,44 @@ public:
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ComputeAhbWindowCount(size_t window_size) const {
|
size_t ComputeAhbBudget(size_t window_size) const {
|
||||||
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
|
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
|
||||||
if (total_physical == 0) {
|
if (total_physical == 0) {
|
||||||
LOG_WARNING(HW_Memory, "Host memory size is unknown, not committing hardware buffers");
|
LOG_WARNING(HW_Memory, "Host memory size is unknown, not committing hardware buffers");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
constexpr u64 MinimumTotalPhysical = 5632ULL << 20;
|
constexpr u64 MinimumTotalPhysical = 7ULL << 30;
|
||||||
if (total_physical < MinimumTotalPhysical) {
|
if (total_physical < MinimumTotalPhysical) {
|
||||||
LOG_INFO(HW_Memory,
|
LOG_INFO(HW_Memory,
|
||||||
"Skipping hardware buffer backing, {} MiB of RAM is below the {} MiB minimum",
|
"Skipping hardware buffer backing, {} MiB of RAM is below the {} MiB minimum",
|
||||||
total_physical >> 20, MinimumTotalPhysical >> 20);
|
total_physical >> 20, MinimumTotalPhysical >> 20);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
constexpr u64 MaxWindows = 2;
|
const u64 max_map_count = Common::GetMaxMapCount();
|
||||||
u64 windows = MaxWindows;
|
constexpr u64 ReservedMaps = 24576;
|
||||||
windows = (std::min)(windows, (total_physical / 6) / window_size);
|
if (max_map_count == 0 || max_map_count <= ReservedMaps) {
|
||||||
|
LOG_WARNING(HW_Memory,
|
||||||
|
"Skipping hardware buffer backing, vm.max_map_count is unknown or too low");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
u64 budget = total_physical / 6;
|
||||||
|
budget = (std::min)(budget, (max_map_count - ReservedMaps) * PageAlignment);
|
||||||
const u64 available = Common::GetAvailablePhysicalMemory();
|
const u64 available = Common::GetAvailablePhysicalMemory();
|
||||||
if (available != 0) {
|
if (available != 0) {
|
||||||
constexpr u64 Headroom = 2ULL << 30;
|
constexpr u64 Headroom = 2ULL << 30;
|
||||||
const u64 spare = available > Headroom ? available - Headroom : 0;
|
budget = (std::min)(budget, available > Headroom ? available - Headroom : 0);
|
||||||
windows = (std::min)(windows, spare / window_size);
|
|
||||||
}
|
}
|
||||||
windows = (std::min)(windows, static_cast<u64>(backing_size) / window_size);
|
budget = (std::min)(budget, static_cast<u64>(backing_size));
|
||||||
if (windows == 0) {
|
budget = Common::AlignDown(budget, window_size);
|
||||||
|
constexpr u64 MinimumBudget = 256ULL << 20;
|
||||||
|
if (budget < MinimumBudget) {
|
||||||
LOG_INFO(HW_Memory,
|
LOG_INFO(HW_Memory,
|
||||||
"Skipping hardware buffer backing, no {} MiB window fits on a {} MiB system "
|
"Skipping hardware buffer backing, only {} MiB could be committed on a {} MiB "
|
||||||
"with {} MiB available",
|
"system with {} MiB available and vm.max_map_count {}",
|
||||||
window_size >> 20, total_physical >> 20, available >> 20);
|
budget >> 20, total_physical >> 20, available >> 20, max_map_count);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return static_cast<size_t>(windows);
|
return static_cast<size_t>(budget);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InitAhbBacking() {
|
bool InitAhbBacking() {
|
||||||
@@ -713,38 +720,25 @@ public:
|
|||||||
LOG_WARNING(HW_Memory, "AHardwareBuffer_getNativeHandle is not available");
|
LOG_WARNING(HW_Memory, "AHardwareBuffer_getNativeHandle is not available");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
constexpr size_t WindowCandidates[] = {
|
constexpr size_t window_size = 64ULL << 20;
|
||||||
512ULL << 20,
|
const AHardwareBuffer_Desc window_desc = MakeBlobDesc(window_size);
|
||||||
256ULL << 20,
|
if (AHardwareBuffer_isSupported(&window_desc) == 0) {
|
||||||
128ULL << 20,
|
LOG_WARNING(HW_Memory, "Allocator rejects {} MiB hardware buffer windows",
|
||||||
64ULL << 20,
|
window_size >> 20);
|
||||||
};
|
|
||||||
static_assert(WindowCandidates[0] <= 0xFFFFFFFFull,
|
|
||||||
"AHARDWAREBUFFER_FORMAT_BLOB encodes its size in a u32 width");
|
|
||||||
size_t window_size = 0;
|
|
||||||
for (const size_t candidate : WindowCandidates) {
|
|
||||||
const AHardwareBuffer_Desc candidate_desc = MakeBlobDesc(candidate);
|
|
||||||
if (AHardwareBuffer_isSupported(&candidate_desc) != 0) {
|
|
||||||
window_size = candidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
LOG_INFO(HW_Memory, "Allocator rejects {} MiB hardware buffer windows", candidate >> 20);
|
|
||||||
}
|
|
||||||
if (window_size == 0) {
|
|
||||||
LOG_WARNING(HW_Memory, "No hardware buffer window size is supported");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const size_t num_windows = ComputeAhbWindowCount(window_size);
|
const size_t budget = ComputeAhbBudget(window_size);
|
||||||
if (num_windows == 0) {
|
if (budget == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ProbeAhbBacking(get_native_handle)) {
|
if (!ProbeAhbBacking(get_native_handle)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
|
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
|
||||||
const size_t region_size = num_windows * window_size;
|
const size_t region_size = (std::min)(budget, aligned_backing);
|
||||||
const size_t region_base = Common::AlignDown(
|
const size_t region_base = Common::AlignDown(
|
||||||
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
|
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
|
||||||
|
const size_t num_windows = region_size / window_size;
|
||||||
|
|
||||||
std::vector<AHardwareBuffer*> buffers;
|
std::vector<AHardwareBuffer*> buffers;
|
||||||
std::vector<int> buffer_fds;
|
std::vector<int> buffer_fds;
|
||||||
@@ -852,29 +846,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t BackingMapCount(size_t host_offset, size_t length) const noexcept {
|
|
||||||
if (length == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (ahb_bytes == 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
size_t count = 0;
|
|
||||||
while (length > 0) {
|
|
||||||
size_t chunk = length;
|
|
||||||
if (host_offset < ahb_base) {
|
|
||||||
chunk = (std::min)(chunk, ahb_base - host_offset);
|
|
||||||
} else if (host_offset < ahb_base + ahb_bytes) {
|
|
||||||
const size_t local = (host_offset - ahb_base) % ahb_window_size;
|
|
||||||
chunk = (std::min)(chunk, ahb_window_size - local);
|
|
||||||
}
|
|
||||||
host_offset += chunk;
|
|
||||||
length -= chunk;
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
|
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
|
||||||
return ahb_windows;
|
return ahb_windows;
|
||||||
}
|
}
|
||||||
@@ -1132,14 +1103,6 @@ std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noe
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t HostMemory::BackingMapCount(size_t host_offset, size_t length) const noexcept {
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
return impl ? impl->BackingMapCount(host_offset, length) : (length != 0 ? 1 : 0);
|
|
||||||
#else
|
|
||||||
return length != 0 ? 1 : 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
|
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
return impl ? impl->AhbWindowSize() : 0;
|
return impl ? impl->AhbWindowSize() : 0;
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ public:
|
|||||||
return backing_size;
|
return backing_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] size_t BackingMapCount(size_t host_offset, size_t length) const noexcept;
|
|
||||||
|
|
||||||
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
|
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
|
||||||
|
|
||||||
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
|
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
|
||||||
|
|||||||
@@ -233,10 +233,10 @@ namespace Vulkan {
|
|||||||
size)) {
|
size)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (device.IsTiler()) {
|
if (!hardware_buffers.empty()) {
|
||||||
LOG_INFO(Render_Vulkan,
|
LOG_INFO(Render_Vulkan,
|
||||||
"Unified memory disabled, hardware buffer import is the only path supported "
|
"Unified memory disabled, guest memory is backed by hardware buffers that "
|
||||||
"by tiler drivers");
|
"could not be imported");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ImportHostPointer(base, size)) {
|
if (ImportHostPointer(base, size)) {
|
||||||
@@ -260,8 +260,7 @@ namespace Vulkan {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
using namespace Common::Literals;
|
using namespace Common::Literals;
|
||||||
constexpr VkDeviceSize DesktopWindowSize = 4_GiB;
|
VkDeviceSize candidate_window = 1_GiB;
|
||||||
VkDeviceSize candidate_window = DesktopWindowSize;
|
|
||||||
const u64 max_buffer_size = device.GetMaxBufferSize();
|
const u64 max_buffer_size = device.GetMaxBufferSize();
|
||||||
if (max_buffer_size != 0 && max_buffer_size < candidate_window) {
|
if (max_buffer_size != 0 && max_buffer_size < candidate_window) {
|
||||||
candidate_window = max_buffer_size;
|
candidate_window = max_buffer_size;
|
||||||
@@ -325,11 +324,9 @@ namespace Vulkan {
|
|||||||
logical.DestroyBufferRaw(new_buffer);
|
logical.DestroyBufferRaw(new_buffer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
constexpr VkDeviceSize MaxHeapFractionDenominator = 2;
|
|
||||||
const u32 heap_index = memory_props.memoryTypes[*type_index].heapIndex;
|
const u32 heap_index = memory_props.memoryTypes[*type_index].heapIndex;
|
||||||
const VkDeviceSize heap_size = memory_props.memoryHeaps[heap_index].size;
|
const VkDeviceSize heap_size = memory_props.memoryHeaps[heap_index].size;
|
||||||
const VkDeviceSize heap_import_limit = heap_size / MaxHeapFractionDenominator;
|
if (imported_size + window_len > heap_size / 2) {
|
||||||
if (imported_size + window_len > heap_import_limit) {
|
|
||||||
LOG_INFO(Render_Vulkan,
|
LOG_INFO(Render_Vulkan,
|
||||||
"Stopping guest memory import at {} MiB to leave room on heap {} of {} MiB",
|
"Stopping guest memory import at {} MiB to leave room on heap {} of {} MiB",
|
||||||
imported_size >> 20, heap_index, heap_size >> 20);
|
imported_size >> 20, heap_index, heap_size >> 20);
|
||||||
@@ -382,14 +379,7 @@ namespace Vulkan {
|
|||||||
!device.IsExtExternalMemoryAhbSupported()) {
|
!device.IsExtExternalMemoryAhbSupported()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
using namespace Common::Literals;
|
const u64 max_allocation_size = device.GetMaxMemoryAllocationSize();
|
||||||
u64 max_allocation_size = device.GetMaxMemoryAllocationSize();
|
|
||||||
if (device.IsTiler()) {
|
|
||||||
constexpr u64 TilerAllocationLimit = 1_GiB;
|
|
||||||
max_allocation_size = max_allocation_size != 0
|
|
||||||
? (std::min)(max_allocation_size, TilerAllocationLimit)
|
|
||||||
: TilerAllocationLimit;
|
|
||||||
}
|
|
||||||
if (max_allocation_size != 0 && hardware_buffer_window > max_allocation_size) {
|
if (max_allocation_size != 0 && hardware_buffer_window > max_allocation_size) {
|
||||||
LOG_WARNING(Render_Vulkan,
|
LOG_WARNING(Render_Vulkan,
|
||||||
"Hardware buffer windows of {} MiB exceed the {} MiB allocation limit",
|
"Hardware buffer windows of {} MiB exceed the {} MiB allocation limit",
|
||||||
|
|||||||
Reference in New Issue
Block a user