aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/buffer_cache/buffer_cache.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/buffer_cache/buffer_cache.h')
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h125
1 files changed, 110 insertions, 15 deletions
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index ad2e200438..42d7ba066d 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -17,6 +17,7 @@
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/guest_memory.h"
#include "video_core/host1x/gpu_device_memory_manager.h"
+#include "video_core/nx2_profile.h"
#include "video_core/texture_cache/util.h"
namespace VideoCommon {
@@ -57,21 +58,39 @@ template <class P>
BufferCache<P>::~BufferCache() = default;
template <class P>
-void BufferCache<P>::RunGarbageCollector() {
- const bool aggressive_gc = total_used_memory >= critical_memory;
+void BufferCache<P>::RunGarbageCollector(bool aggressive_gc) {
const u64 ticks_to_destroy = aggressive_gc ? 60 : 120;
int num_iterations = aggressive_gc ? 64 : 32;
- const auto clean_up = [this, &num_iterations](BufferId buffer_id) {
+ // Their GPU-written data is downloaded first, all with one wait for the GPU: one wait per
+ // buffer stalled the GPU thread every frame on title 1 (an 8 GB GPU near its budget, no
+ // per-fence downloads keeping the buffers clean), ~2.5% of its time.
+ boost::container::small_vector<BufferId, 64> evicted;
+ lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, [&](BufferId buffer_id) {
if (num_iterations == 0) {
return true;
}
--num_iterations;
- auto& buffer = slot_buffers[buffer_id];
- DownloadBufferMemory(buffer);
- DeleteBuffer(buffer_id);
+ // Below the critical threshold, buffers holding GPU-written data stay: evicting one
+ // means downloading it first. The thresholds compare the whole device's use (mostly
+ // textures) with fixed headroom, so on an 8 GB GPU the collector ran every frame once a
+ // title 1 course was loaded, and the downloads were ~2.7% of the GPU thread.
+ const Buffer& buffer = slot_buffers[buffer_id];
+ if (!aggressive_gc && gpu_modified_ranges.Intersects(buffer.CpuAddr(), buffer.SizeBytes())) {
+ return false;
+ }
+ evicted.push_back(buffer_id);
return false;
- };
- lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, clean_up);
+ });
+ if constexpr (USE_MEMORY_MAPS) {
+ DownloadBuffersMemory(evicted);
+ } else {
+ for (const BufferId buffer_id : evicted) {
+ DownloadBufferMemory(slot_buffers[buffer_id]);
+ }
+ }
+ for (const BufferId buffer_id : evicted) {
+ DeleteBuffer(buffer_id);
+ }
}
template <class P>
@@ -117,8 +136,17 @@ void BufferCache<P>::TickFrame() {
if (runtime.CanReportMemoryUsage()) {
total_used_memory = runtime.GetDeviceMemoryUsage();
}
- if (total_used_memory >= minimum_memory) {
- RunGarbageCollector();
+ if (gpu_memory != nullptr && gpu_memory->UsesNativeTiling()) {
+ // Switch 2 titles fill the device with textures (their collector watches the budget);
+ // title 1's buffers were ~200 MiB of an 8 GB GPU's ~6.8 GB in use, yet this collector
+ // ran aggressively every frame on the device-wide figure, evicting up to 64 buffers (and
+ // downloading the GPU-written ones, ~5% of the GPU thread) that were recreated later.
+ // Collect when the buffers themselves are large.
+ if (buffer_bytes >= 1_GiB) {
+ RunGarbageCollector(buffer_bytes >= 2_GiB);
+ }
+ } else if (total_used_memory >= minimum_memory) {
+ RunGarbageCollector(total_used_memory >= critical_memory);
}
++frame_tick;
delayed_destruction_ring.Tick();
@@ -251,7 +279,9 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
const DAddr diff = base_address - *cpu_src_address;
const DAddr new_base_address = *cpu_dest_address + diff;
tmp_intervals.push_back({new_base_address, size});
- uncommitted_gpu_modified_ranges.Add(new_base_address, size);
+ if (AsyncBufferDownloads()) {
+ uncommitted_gpu_modified_ranges.Add(new_base_address, size);
+ }
};
gpu_modified_ranges.ForEachInRange(*cpu_src_address, amount, mirror);
// This subtraction in this order is important for overlapping copies.
@@ -666,6 +696,8 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
async_buffers.emplace_back(std::optional<Async_Buffer>{});
return;
}
+ Nx2Profile::Add(Nx2Profile::Counter::DownloadBytes, total_size_bytes);
+ Nx2Profile::Add(Nx2Profile::Counter::DownloadCopies, downloads.size());
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
boost::container::small_vector<BufferCopy, 4> normalized_copies;
runtime.PreCopyBarrier();
@@ -676,6 +708,19 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
Buffer& buffer = slot_buffers[buffer_id];
second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
const DAddr orig_device_addr = static_cast<DAddr>(second_copy.src_offset);
+ if (Nx2Profile::Enabled()) {
+ u64 on_dma_pages = 0;
+ const auto& pages = Nx2Profile::DmaReadPages();
+ for (DAddr page = orig_device_addr >> 12; page <= (orig_device_addr + copy.size - 1) >> 12;
+ ++page) {
+ if (pages.contains(page)) {
+ const DAddr begin = (std::max)(orig_device_addr, page << 12);
+ const DAddr end = (std::min)(orig_device_addr + copy.size, (page + 1) << 12);
+ on_dma_pages += end - begin;
+ }
+ }
+ Nx2Profile::Add(Nx2Profile::Counter::DownloadBytesOnDmaPages, on_dma_pages);
+ }
async_downloads.Add(orig_device_addr, copy.size);
buffer.MarkUsage(copy.src_offset, copy.size);
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
@@ -728,9 +773,9 @@ void BufferCache<P>::PopAsyncBuffers() {
template <class P>
bool BufferCache<P>::IsRegionGpuModified(DAddr addr, size_t size) {
- bool is_dirty = false;
- gpu_modified_ranges.ForEachInRange(addr, size, [&](DAddr, DAddr) { is_dirty = true; });
- return is_dirty;
+ // One lookup instead of visiting every range inside (~4% of the GPU thread for title 1's
+ // inline uploads once GPU-written ranges are no longer cleared by downloads).
+ return gpu_modified_ranges.Intersects(addr, size);
}
template <class P>
@@ -1558,7 +1603,9 @@ void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3
}
memory_tracker.MarkRegionAsGpuModified(device_addr, size);
gpu_modified_ranges.Add(device_addr, size);
- uncommitted_gpu_modified_ranges.Add(device_addr, size);
+ if (AsyncBufferDownloads()) {
+ uncommitted_gpu_modified_ranges.Add(device_addr, size);
+ }
}
template <class P>
@@ -1739,9 +1786,11 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
Buffer& buffer = slot_buffers[buffer_id];
const auto size = buffer.SizeBytes();
if (insert) {
+ buffer_bytes += Common::AlignUp(size, 1024);
total_used_memory += Common::AlignUp(size, 1024);
buffer.setLRUID(lru_cache.Insert(buffer_id, frame_tick));
} else {
+ buffer_bytes -= Common::AlignUp(size, 1024);
total_used_memory -= Common::AlignUp(size, 1024);
lru_cache.Free(buffer.getLRUID());
}
@@ -1932,6 +1981,52 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer) {
}
template <class P>
+void BufferCache<P>::DownloadBuffersMemory(std::span<const BufferId> buffer_ids) {
+ struct Download {
+ BufferId buffer_id;
+ BufferCopy copy;
+ };
+ std::vector<Download> downloads;
+ u64 total_size_bytes = 0;
+ for (const BufferId buffer_id : buffer_ids) {
+ Buffer& buffer = slot_buffers[buffer_id];
+ const DAddr buffer_addr = buffer.CpuAddr();
+ memory_tracker.ForEachDownloadRangeAndClear(
+ buffer_addr, buffer.SizeBytes(), [&](u64 device_addr_out, u64 range_size) {
+ gpu_modified_ranges.ForEachInRange(device_addr_out, range_size,
+ [&](DAddr start, DAddr end) {
+ downloads.push_back({buffer_id, BufferCopy{
+ .src_offset = start - buffer_addr,
+ .dst_offset = total_size_bytes,
+ .size = end - start,
+ }});
+ total_size_bytes += Common::AlignUp(end - start, 64);
+ });
+ ClearDownload(device_addr_out, range_size);
+ gpu_modified_ranges.Subtract(device_addr_out, range_size);
+ });
+ }
+ if (total_size_bytes == 0) {
+ return;
+ }
+ auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
+ for (Download& download : downloads) {
+ download.copy.dst_offset += download_staging.offset;
+ Buffer& buffer = slot_buffers[download.buffer_id];
+ buffer.MarkUsage(download.copy.src_offset, download.copy.size);
+ runtime.CopyBuffer(download_staging.buffer, buffer, std::span{&download.copy, 1}, true);
+ }
+ runtime.Finish();
+ const u8* const mapped_memory = download_staging.mapped_span.data();
+ for (const Download& download : downloads) {
+ const DAddr device_addr = slot_buffers[download.buffer_id].CpuAddr() + download.copy.src_offset;
+ device_memory.WriteBlockUnsafe(
+ device_addr, mapped_memory + (download.copy.dst_offset - download_staging.offset),
+ download.copy.size);
+ }
+}
+
+template <class P>
void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64 size) {
boost::container::small_vector<BufferCopy, 1> copies;
u64 total_size_bytes = 0;