aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/buffer_cache
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/buffer_cache')
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h160
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h26
-rw-r--r--src/video_core/buffer_cache/virtual_range_cache.h173
3 files changed, 331 insertions, 28 deletions
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index e40aad1fb5..4c345dd213 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -113,6 +113,13 @@ void BufferCache<P>::TickFrame() {
}
template <class P>
+void BufferCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size) {
+ if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
+ virtual_ranges.Unmap(as_id, gpu_addr, size);
+ }
+}
+
+template <class P>
void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
ClearDownload(device_addr, size);
@@ -208,8 +215,8 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
BufferId buffer_b;
do {
channel_state->has_deleted_buffers = false;
- buffer_a = FindBuffer(*cpu_src_address, static_cast<u32>(amount));
- buffer_b = FindBuffer(*cpu_dest_address, static_cast<u32>(amount));
+ buffer_a = FindBuffer(*cpu_src_address, static_cast<u32>(amount), false);
+ buffer_b = FindBuffer(*cpu_dest_address, static_cast<u32>(amount), false);
} while (channel_state->has_deleted_buffers);
auto& src_buffer = slot_buffers[buffer_a];
auto& dest_buffer = slot_buffers[buffer_b];
@@ -265,7 +272,7 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
ClearDownload(*cpu_dst_address, size);
gpu_modified_ranges.Subtract(*cpu_dst_address, size);
- const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size));
+ const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size), false);
Buffer& dest_buffer = slot_buffers[buffer];
const u32 offset = dest_buffer.Offset(*cpu_dst_address);
runtime.ClearBuffer(dest_buffer, offset, size, value);
@@ -287,7 +294,7 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
template <class P>
std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
DAddr device_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) {
- const BufferId buffer_id = FindBuffer(device_addr, size);
+ const BufferId buffer_id = FindBuffer(device_addr, size, false);
Buffer& buffer = slot_buffers[buffer_id];
// synchronize op
@@ -999,10 +1006,84 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
}
template <class P>
+void BufferCache<P>::ResolveMultiRangeStorage(Binding& binding, bool is_written,
+ std::vector<MultiRangeSegment>& pool) {
+ binding.segment_first = 0;
+ binding.segment_count = 0;
+ if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
+ if (binding.gpu_addr == 0 || binding.size == 0) {
+ return;
+ }
+ if (is_written && !runtime.PrefersSparseSources()) {
+ return;
+ }
+ const VirtualSegments* found =
+ virtual_ranges.Query(*gpu_memory, binding.gpu_addr, binding.size);
+ if (!found || found->size() < 2) {
+ return;
+ }
+ const VirtualSegments segments = *found;
+ const u32 first = static_cast<u32>(pool.size());
+ const bool prefer_sparse = runtime.PrefersSparseSources();
+ for (const VirtualSegment& segment : segments) {
+ const BufferId buffer_id =
+ FindBuffer(segment.device_addr, segment.size, prefer_sparse);
+ if (!buffer_id) {
+ pool.resize(first);
+ return;
+ }
+ pool.push_back(MultiRangeSegment{
+ .buffer_id = buffer_id,
+ .device_addr = segment.device_addr,
+ .size = segment.size,
+ });
+ }
+ binding.segment_first = first;
+ binding.segment_count = static_cast<u32>(segments.size());
+ }
+}
+
+template <class P>
+bool BufferCache<P>::BindMultiRangeStorage(const Binding& binding, bool is_written,
+ std::span<const MultiRangeSegment> pool) {
+ if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
+ if (binding.segment_count < 2) {
+ return false;
+ }
+ if (binding.segment_first + binding.segment_count > pool.size()) {
+ return false;
+ }
+ const u64 key = (static_cast<u64>(gpu_memory->GetID()) << 48) ^ binding.gpu_addr;
+ runtime.ResetMultiRange();
+ for (u32 index = 0; index < binding.segment_count; ++index) {
+ const MultiRangeSegment& segment = pool[binding.segment_first + index];
+ Buffer& buffer = slot_buffers[segment.buffer_id];
+ TouchBuffer(buffer, segment.buffer_id);
+ if (SynchronizeBuffer(buffer, segment.device_addr, segment.size)) {
+ runtime.InvalidateMultiRange(key);
+ }
+ const u32 offset = buffer.Offset(segment.device_addr);
+ buffer.MarkUsage(offset, segment.size);
+ if (is_written) {
+ MarkWrittenBuffer(segment.buffer_id, segment.device_addr, segment.size);
+ }
+ runtime.PushMultiRangeSource(buffer, offset, segment.size);
+ }
+ return runtime.BindMultiRangeStorageBuffer(key, is_written);
+ } else {
+ return false;
+ }
+}
+
+template <class P>
void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
u32 binding_index = 0;
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
const Binding& binding = channel_state->storage_buffers[stage][index];
+ const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
+ if (BindMultiRangeStorage(binding, is_written, graphics_segments)) {
+ return;
+ }
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size;
@@ -1010,7 +1091,6 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size);
- const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
@@ -1139,6 +1219,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
u32 binding_index = 0;
ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) {
const Binding& binding = channel_state->compute_storage_buffers[index];
+ const bool is_written =
+ ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
+ if (BindMultiRangeStorage(binding, is_written, compute_segments)) {
+ return;
+ }
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size;
@@ -1146,8 +1231,6 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size);
- const bool is_written =
- ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
@@ -1193,6 +1276,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
template <class P>
void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
+ graphics_segments.clear();
BufferOperations([&]() {
if (is_indexed) {
UpdateIndexBuffer();
@@ -1212,6 +1296,7 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
template <class P>
void BufferCache<P>::DoUpdateComputeBuffers() {
+ compute_segments.clear();
BufferOperations([&]() {
UpdateComputeUniformBuffers();
UpdateComputeStorageBuffers();
@@ -1234,11 +1319,11 @@ void BufferCache<P>::UpdateIndexBuffer() {
auto inline_index_size = static_cast<u32>(draw_state.inline_index_draw_indexes.size());
u32 buffer_size = Common::AlignUp(inline_index_size, CACHING_PAGESIZE);
if (inline_buffer_id == NULL_BUFFER_ID) [[unlikely]] {
- inline_buffer_id = CreateBuffer(0, buffer_size);
+ inline_buffer_id = CreateBuffer(0, buffer_size, false);
}
if (slot_buffers[inline_buffer_id].SizeBytes() < buffer_size) [[unlikely]] {
slot_buffers.erase(inline_buffer_id);
- inline_buffer_id = CreateBuffer(0, buffer_size);
+ inline_buffer_id = CreateBuffer(0, buffer_size, false);
}
channel_state->index_buffer = Binding{
.device_addr = 0,
@@ -1261,7 +1346,7 @@ void BufferCache<P>::UpdateIndexBuffer() {
channel_state->index_buffer = Binding{
.device_addr = *device_addr,
.size = size,
- .buffer_id = FindBuffer(*device_addr, size),
+ .buffer_id = FindBuffer(*device_addr, size, false),
};
}
@@ -1298,7 +1383,7 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) {
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
}
- const BufferId buffer_id = FindBuffer(*device_addr, size);
+ const BufferId buffer_id = FindBuffer(*device_addr, size, false);
const Binding binding{
.device_addr = *device_addr,
.size = size,
@@ -1319,7 +1404,7 @@ void BufferCache<P>::UpdateDrawIndirect() {
binding = Binding{
.device_addr = *device_addr,
.size = static_cast<u32>(size),
- .buffer_id = FindBuffer(*device_addr, static_cast<u32>(size)),
+ .buffer_id = FindBuffer(*device_addr, static_cast<u32>(size), false),
};
};
if (current_draw_indirect->include_count) {
@@ -1343,7 +1428,7 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
channel_state->dirty_uniform_buffers[stage] |= 1U << index;
}
// Resolve buffer
- binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
+ binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
});
}
@@ -1352,8 +1437,10 @@ void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
// Resolve buffer
Binding& binding = channel_state->storage_buffers[stage][index];
- const BufferId buffer_id = FindBuffer(binding.device_addr, binding.size);
+ const BufferId buffer_id = FindBuffer(binding.device_addr, binding.size, false);
binding.buffer_id = buffer_id;
+ const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
+ ResolveMultiRangeStorage(binding, is_written, graphics_segments);
});
}
@@ -1361,7 +1448,7 @@ template <class P>
void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
Binding& binding = channel_state->texture_buffers[stage][index];
- binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
+ binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
});
}
@@ -1385,7 +1472,7 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
channel_state->transform_feedback_buffers[index] = NULL_BINDING;
return;
}
- const BufferId buffer_id = FindBuffer(*device_addr, size);
+ const BufferId buffer_id = FindBuffer(*device_addr, size, false);
channel_state->transform_feedback_buffers[index] = Binding{
.device_addr = *device_addr,
.size = size,
@@ -1407,7 +1494,7 @@ void BufferCache<P>::UpdateComputeUniformBuffers() {
binding.size = cbuf.size;
}
}
- binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
+ binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
});
}
@@ -1416,7 +1503,10 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) {
// Resolve buffer
Binding& binding = channel_state->compute_storage_buffers[index];
- binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
+ binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
+ const bool is_written =
+ ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
+ ResolveMultiRangeStorage(binding, is_written, compute_segments);
});
}
@@ -1424,7 +1514,7 @@ template <class P>
void BufferCache<P>::UpdateComputeTextureBuffers() {
ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
Binding& binding = channel_state->compute_texture_buffers[index];
- binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
+ binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
});
}
@@ -1440,7 +1530,7 @@ void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3
}
template <class P>
-BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size) {
+BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible) {
if (device_addr == 0) {
return NULL_BUFFER_ID;
}
@@ -1450,10 +1540,18 @@ BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size) {
Buffer& buffer = slot_buffers[buffer_id];
WaitForGpuFenceIfNeeded(buffer);
if (buffer.IsInBounds(device_addr, size)) {
- return buffer_id;
+ bool usable = true;
+ if constexpr (requires { buffer.IsSparseCompatible(); }) {
+ if (sparse_compatible && !buffer.IsSparseCompatible()) {
+ usable = false;
+ }
+ }
+ if (usable) {
+ return buffer_id;
+ }
}
}
- return CreateBuffer(device_addr, size);
+ return CreateBuffer(device_addr, size, sparse_compatible);
}
template <class P>
@@ -1575,13 +1673,15 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
}
template <class P>
-BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) {
+BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size,
+ bool sparse_compatible) {
DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE);
device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE);
wanted_size = static_cast<u32>(device_addr_end - device_addr);
const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size);
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
- const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size);
+ const BufferId new_buffer_id =
+ slot_buffers.insert(runtime, overlap.begin, size, sparse_compatible);
auto& new_buffer = slot_buffers[new_buffer_id];
const size_t size_bytes = new_buffer.SizeBytes();
runtime.ClearBuffer(new_buffer, 0, size_bytes, 0);
@@ -1745,7 +1845,7 @@ void BufferCache<P>::InlineMemoryImplementation(DAddr dest_address, size_t copy_
ClearDownload(dest_address, copy_size);
gpu_modified_ranges.Subtract(dest_address, copy_size);
- BufferId buffer_id = FindBuffer(dest_address, static_cast<u32>(copy_size));
+ BufferId buffer_id = FindBuffer(dest_address, static_cast<u32>(copy_size), false);
auto& buffer = slot_buffers[buffer_id];
SynchronizeBuffer(buffer, dest_address, static_cast<u32>(copy_size));
@@ -1831,6 +1931,9 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
template <class P>
void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
+ if constexpr (requires { runtime.OnBufferDeleted(slot_buffers[buffer_id]); }) {
+ runtime.OnBufferDeleted(slot_buffers[buffer_id]);
+ }
bool dirty_index{false};
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> dirty_vertex_buffers;
const auto scalar_replace = [buffer_id](Binding& binding) {
@@ -1934,9 +2037,14 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
// The end address used for size calculation does not need to be aligned
const DAddr cpu_end = Common::AlignUp(*device_addr + size, Core::DEVICE_PAGESIZE);
+ u32 binding_size = static_cast<u32>(cpu_end - *aligned_device_addr);
+ if (is_written) {
+ binding_size = aligned_size;
+ }
const Binding binding{
.device_addr = *aligned_device_addr,
- .size = is_written ? aligned_size : static_cast<u32>(cpu_end - *aligned_device_addr),
+ .gpu_addr = aligned_gpu_addr,
+ .size = binding_size,
.buffer_id = BufferId{},
};
return binding;
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index 0a521192c6..0dbb368c47 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -29,6 +29,7 @@
#include "common/settings.h"
#include "common/slot_vector.h"
#include "video_core/buffer_cache/buffer_base.h"
+#include "video_core/buffer_cache/virtual_range_cache.h"
#include "video_core/control/channel_state_cache.h"
#include "video_core/delayed_destruction_ring.h"
#include "video_core/dirty_flags.h"
@@ -81,8 +82,17 @@ static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
struct Binding {
DAddr device_addr{};
+ GPUVAddr gpu_addr{};
u32 size{};
BufferId buffer_id;
+ u32 segment_first{};
+ u32 segment_count{};
+};
+
+struct MultiRangeSegment {
+ BufferId buffer_id;
+ DAddr device_addr{};
+ u32 size{};
};
struct TextureBufferBinding : Binding {
@@ -215,6 +225,14 @@ public:
void TickFrame();
+ bool BindMultiRangeStorage(const Binding& binding, bool is_written,
+ std::span<const MultiRangeSegment> pool);
+
+ void ResolveMultiRangeStorage(Binding& binding, bool is_written,
+ std::vector<MultiRangeSegment>& pool);
+
+ void UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size);
+
void WriteMemory(DAddr device_addr, u64 size);
void CachedWriteMemory(DAddr device_addr, u64 size);
@@ -414,7 +432,7 @@ private:
void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size);
- [[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size);
+ [[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible);
void WaitForGpuFenceIfNeeded(Buffer& buffer);
@@ -422,7 +440,8 @@ private:
void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score);
- [[nodiscard]] BufferId CreateBuffer(DAddr device_addr, u32 wanted_size);
+ [[nodiscard]] BufferId CreateBuffer(DAddr device_addr, u32 wanted_size,
+ bool sparse_compatible);
void Register(BufferId buffer_id);
@@ -513,6 +532,9 @@ private:
using TickType = u64;
};
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
+ VirtualRangeCache virtual_ranges;
+ std::vector<MultiRangeSegment> graphics_segments;
+ std::vector<MultiRangeSegment> compute_segments;
u64 frame_tick = 0;
u64 total_used_memory = 0;
u64 minimum_memory = 0;
diff --git a/src/video_core/buffer_cache/virtual_range_cache.h b/src/video_core/buffer_cache/virtual_range_cache.h
new file mode 100644
index 0000000000..f4284d6abb
--- /dev/null
+++ b/src/video_core/buffer_cache/virtual_range_cache.h
@@ -0,0 +1,173 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <limits>
+#include <mutex>
+#include <optional>
+#include <vector>
+
+#include <boost/container/small_vector.hpp>
+
+#include "common/common_types.h"
+#include "common/container/unordered_map.h"
+#include "video_core/memory_manager.h"
+
+namespace VideoCommon {
+
+struct VirtualSegment {
+ GPUVAddr gpu_addr;
+ DAddr device_addr;
+ u32 size;
+};
+
+using VirtualSegments = boost::container::small_vector<VirtualSegment, 8>;
+
+class VirtualRangeCache {
+public:
+ static constexpr size_t MAX_ENTRIES = 8192;
+ static constexpr size_t MAX_DEFERRED = 4096;
+
+ const VirtualSegments* Query(Tegra::MemoryManager& memory, GPUVAddr gpu_addr, u32 size) {
+ if (has_deferred.load(std::memory_order_acquire)) {
+ ApplyDeferred();
+ }
+ if (entries.size() > MAX_ENTRIES) {
+ entries.clear();
+ }
+ const size_t as_id = memory.GetID();
+ const u64 key = MakeKey(as_id, gpu_addr);
+ const auto it = entries.find(key);
+ if (it != entries.end() && it->second.as_id == as_id &&
+ it->second.gpu_addr == gpu_addr && it->second.size == size) {
+ return &it->second.segments;
+ }
+ Entry entry{};
+ entry.as_id = as_id;
+ entry.gpu_addr = gpu_addr;
+ entry.size = size;
+ const auto ranges = memory.GetSubmappedRange(gpu_addr, size);
+ GPUVAddr expected = gpu_addr;
+ bool contiguous = true;
+ for (const auto& [range_addr, range_size] : ranges) {
+ if (range_addr != expected || range_size == 0) {
+ contiguous = false;
+ break;
+ }
+ const std::optional<DAddr> device_addr = memory.GpuToCpuAddress(range_addr);
+ if (!device_addr || *device_addr == 0) {
+ contiguous = false;
+ break;
+ }
+ if (range_size > static_cast<size_t>((std::numeric_limits<u32>::max)())) {
+ contiguous = false;
+ break;
+ }
+ entry.segments.push_back(VirtualSegment{
+ .gpu_addr = range_addr,
+ .device_addr = *device_addr,
+ .size = static_cast<u32>(range_size),
+ });
+ expected += range_size;
+ }
+ if (!contiguous || expected != gpu_addr + size) {
+ entry.segments.clear();
+ }
+ const auto result = entries.insert_or_assign(key, std::move(entry));
+ return &result.first->second.segments;
+ }
+
+ void Unmap(size_t as_id, GPUVAddr gpu_addr, u64 size) {
+ if (size == 0) {
+ return;
+ }
+ {
+ std::scoped_lock lock{deferred_mutex};
+ if (!deferred.empty()) {
+ DeferredUnmap& last = deferred.back();
+ if (last.as_id == as_id && last.gpu_addr + last.size == gpu_addr) {
+ last.size += size;
+ has_deferred.store(true, std::memory_order_release);
+ return;
+ }
+ }
+ if (deferred.size() >= MAX_DEFERRED) {
+ deferred.clear();
+ deferred_overflow = true;
+ } else {
+ deferred.push_back(DeferredUnmap{
+ .as_id = as_id,
+ .gpu_addr = gpu_addr,
+ .size = size,
+ });
+ }
+ }
+ has_deferred.store(true, std::memory_order_release);
+ }
+
+private:
+ struct Entry {
+ VirtualSegments segments;
+ size_t as_id{};
+ GPUVAddr gpu_addr{};
+ u32 size{};
+ };
+
+ struct DeferredUnmap {
+ size_t as_id;
+ GPUVAddr gpu_addr;
+ u64 size;
+ };
+
+ static u64 MakeKey(size_t as_id, GPUVAddr gpu_addr) {
+ return (static_cast<u64>(as_id) << 48) ^ gpu_addr;
+ }
+
+ void ApplyDeferred() {
+ std::vector<DeferredUnmap> pending;
+ bool overflow = false;
+ {
+ std::scoped_lock lock{deferred_mutex};
+ has_deferred.store(false, std::memory_order_release);
+ pending.swap(deferred);
+ overflow = deferred_overflow;
+ deferred_overflow = false;
+ }
+ if (overflow) {
+ entries.clear();
+ return;
+ }
+ if (pending.empty() || entries.empty()) {
+ return;
+ }
+ for (auto it = entries.begin(); it != entries.end();) {
+ const Entry& entry = it->second;
+ const GPUVAddr entry_end = entry.gpu_addr + entry.size;
+ bool overlaps = false;
+ for (const DeferredUnmap& unmap : pending) {
+ if (unmap.as_id != entry.as_id) {
+ continue;
+ }
+ if (entry.gpu_addr < unmap.gpu_addr + unmap.size && unmap.gpu_addr < entry_end) {
+ overlaps = true;
+ break;
+ }
+ }
+ if (overlaps) {
+ it = entries.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ ::Common::unordered_map<u64, Entry> entries;
+ std::vector<DeferredUnmap> deferred;
+ std::mutex deferred_mutex;
+ std::atomic<bool> has_deferred{false};
+ bool deferred_overflow{};
+};
+
+} // namespace VideoCommon