aboutsummaryrefslogtreecommitdiff
path: root/src/video_core
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-01 00:30:52 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-01 08:48:31 -0500
commite2c512916c6f5042d030fcf1708043233f8df08e (patch)
treef9750541db337ba3415a05c4a0d73ebde979c458 /src/video_core
parenta03e816886f4241309a6e77ba7df1ad922da1fc0 (diff)
texture_cache: mark guest memory textures were uploaded from cold
Once a texture is uploaded the host GPU has its own copy, and the guest rarely reads that memory again, but it stayed in the emulator's working set. Every 600 frames the guest ranges of images uploaded since (64 KiB or more, not GPU-written or sparse) are marked cold through HostMemory::HintCold, backing and CPU view, from a low-priority worker, so the OS pages them out before anything else when it needs RAM. Their contents stay; using them brings them back. Mario Kart World's title screen at ~10 minutes: game memory in RAM 4.8-5.5 -> 3.6 GB, the emulator's real RAM 10.6-11.9 -> 9.4 GB, frames over 100 ms not worse. (cherry picked from commit ea36b2c5ca352f8c8cdcc829a2d3bde9bdc06ece)
Diffstat (limited to 'src/video_core')
-rw-r--r--src/video_core/texture_cache/texture_cache.h14
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h2
2 files changed, 16 insertions, 0 deletions
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 63abffed0f..13637aa265 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -315,6 +315,12 @@ void TextureCache<P>::TickFrame() {
sentenced_image_view.Tick();
TickAsyncDecode();
TickAsyncUnswizzle();
+ if (frame_tick % 600 == 0 && !uploaded_ranges.empty()) {
+ for (const auto& [address, size] : uploaded_ranges) {
+ device_memory.HintCold(address, size);
+ }
+ uploaded_ranges.clear();
+ }
runtime.TickFrame();
++frame_tick;
@@ -1352,6 +1358,14 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image));
UploadImageContents(image, staging);
runtime.InsertUploadMemoryBarrier();
+ // The host GPU has its own copy now and the guest rarely reads this memory again: TickFrame
+ // marks it cold, so the OS pages it out before anything else when it needs RAM (title 1
+ // streams several GB of textures through it).
+ constexpr u64 ColdHintMinBytes = 64_KiB;
+ if (image.guest_size_bytes >= ColdHintMinBytes &&
+ False(image.flags & (ImageFlagBits::GpuModified | ImageFlagBits::Sparse))) {
+ uploaded_ranges.emplace_back(image.cpu_addr, image.guest_size_bytes);
+ }
}
template <class P>
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index 11e95f98b3..26d989f2da 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -558,6 +558,8 @@ private:
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {},
Common::ThreadPlacement::Efficiency};
std::vector<std::unique_ptr<AsyncDecodeContext>> async_decodes;
+ /// Guest memory images were uploaded from since the last cold hint (device address, size).
+ std::vector<std::pair<DAddr, u64>> uploaded_ranges;
std::deque<PendingUnswizzle> unswizzle_queue;
u8 current_unswizzle_frame;