summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/memory.cpp2
-rw-r--r--src/tests/video_core/buffer_base.cpp2
-rw-r--r--src/video_core/CMakeLists.txt1
-rw-r--r--src/video_core/buffer_cache/buffer_base.h14
-rw-r--r--src/video_core/engines/engine_upload.cpp2
-rw-r--r--src/video_core/engines/fermi_2d.cpp6
-rw-r--r--src/video_core/engines/fermi_2d.h1
-rw-r--r--src/video_core/engines/maxwell_3d.cpp7
-rw-r--r--src/video_core/engines/maxwell_dma.cpp21
-rw-r--r--src/video_core/invalidation_accumulator.h79
-rw-r--r--src/video_core/memory_manager.cpp102
-rw-r--r--src/video_core/memory_manager.h18
-rw-r--r--src/video_core/rasterizer_interface.h7
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp23
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h1
15 files changed, 233 insertions, 53 deletions
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 26be74df4..a1e41faff 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -436,7 +436,7 @@ struct Memory::Impl {
436 } 436 }
437 437
438 if (Settings::IsFastmemEnabled()) { 438 if (Settings::IsFastmemEnabled()) {
439 const bool is_read_enable = Settings::IsGPULevelHigh() || !cached; 439 const bool is_read_enable = !Settings::IsGPULevelExtreme() || !cached;
440 system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached); 440 system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached);
441 } 441 }
442 442
diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp
index f7236afab..5cd0628f2 100644
--- a/src/tests/video_core/buffer_base.cpp
+++ b/src/tests/video_core/buffer_base.cpp
@@ -538,7 +538,7 @@ TEST_CASE("BufferBase: Cached write downloads") {
538 int num = 0; 538 int num = 0;
539 buffer.ForEachDownloadRangeAndClear(c, WORD, [&](u64 offset, u64 size) { ++num; }); 539 buffer.ForEachDownloadRangeAndClear(c, WORD, [&](u64 offset, u64 size) { ++num; });
540 buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; }); 540 buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
541 REQUIRE(num == 0); 541 REQUIRE(num == 1);
542 REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE)); 542 REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
543 REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE)); 543 REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE));
544 buffer.FlushCachedWrites(); 544 buffer.FlushCachedWrites();
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index aa271a377..b7095ae13 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -85,6 +85,7 @@ add_library(video_core STATIC
85 gpu.h 85 gpu.h
86 gpu_thread.cpp 86 gpu_thread.cpp
87 gpu_thread.h 87 gpu_thread.h
88 invalidation_accumulator.h
88 memory_manager.cpp 89 memory_manager.cpp
89 memory_manager.h 90 memory_manager.h
90 precompiled_headers.h 91 precompiled_headers.h
diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h
index 92d77eef2..c47b7d866 100644
--- a/src/video_core/buffer_cache/buffer_base.h
+++ b/src/video_core/buffer_cache/buffer_base.h
@@ -430,7 +430,7 @@ private:
430 if (query_begin >= SizeBytes() || size < 0) { 430 if (query_begin >= SizeBytes() || size < 0) {
431 return; 431 return;
432 } 432 }
433 u64* const untracked_words = Array<Type::Untracked>(); 433 [[maybe_unused]] u64* const untracked_words = Array<Type::Untracked>();
434 u64* const state_words = Array<type>(); 434 u64* const state_words = Array<type>();
435 const u64 query_end = query_begin + std::min(static_cast<u64>(size), SizeBytes()); 435 const u64 query_end = query_begin + std::min(static_cast<u64>(size), SizeBytes());
436 u64* const words_begin = state_words + query_begin / BYTES_PER_WORD; 436 u64* const words_begin = state_words + query_begin / BYTES_PER_WORD;
@@ -483,7 +483,7 @@ private:
483 NotifyRasterizer<true>(word_index, current_bits, ~u64{0}); 483 NotifyRasterizer<true>(word_index, current_bits, ~u64{0});
484 } 484 }
485 // Exclude CPU modified pages when visiting GPU pages 485 // Exclude CPU modified pages when visiting GPU pages
486 const u64 word = current_word & ~(type == Type::GPU ? untracked_words[word_index] : 0); 486 const u64 word = current_word;
487 u64 page = page_begin; 487 u64 page = page_begin;
488 page_begin = 0; 488 page_begin = 0;
489 489
@@ -531,7 +531,7 @@ private:
531 [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { 531 [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
532 static_assert(type != Type::Untracked); 532 static_assert(type != Type::Untracked);
533 533
534 const u64* const untracked_words = Array<Type::Untracked>(); 534 [[maybe_unused]] const u64* const untracked_words = Array<Type::Untracked>();
535 const u64* const state_words = Array<type>(); 535 const u64* const state_words = Array<type>();
536 const u64 num_query_words = size / BYTES_PER_WORD + 1; 536 const u64 num_query_words = size / BYTES_PER_WORD + 1;
537 const u64 word_begin = offset / BYTES_PER_WORD; 537 const u64 word_begin = offset / BYTES_PER_WORD;
@@ -539,8 +539,7 @@ private:
539 const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE); 539 const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
540 u64 page_index = (offset / BYTES_PER_PAGE) % PAGES_PER_WORD; 540 u64 page_index = (offset / BYTES_PER_PAGE) % PAGES_PER_WORD;
541 for (u64 word_index = word_begin; word_index < word_end; ++word_index, page_index = 0) { 541 for (u64 word_index = word_begin; word_index < word_end; ++word_index, page_index = 0) {
542 const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0; 542 const u64 word = state_words[word_index];
543 const u64 word = state_words[word_index] & ~off_word;
544 if (word == 0) { 543 if (word == 0) {
545 continue; 544 continue;
546 } 545 }
@@ -564,7 +563,7 @@ private:
564 [[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept { 563 [[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
565 static_assert(type != Type::Untracked); 564 static_assert(type != Type::Untracked);
566 565
567 const u64* const untracked_words = Array<Type::Untracked>(); 566 [[maybe_unused]] const u64* const untracked_words = Array<Type::Untracked>();
568 const u64* const state_words = Array<type>(); 567 const u64* const state_words = Array<type>();
569 const u64 num_query_words = size / BYTES_PER_WORD + 1; 568 const u64 num_query_words = size / BYTES_PER_WORD + 1;
570 const u64 word_begin = offset / BYTES_PER_WORD; 569 const u64 word_begin = offset / BYTES_PER_WORD;
@@ -574,8 +573,7 @@ private:
574 u64 begin = std::numeric_limits<u64>::max(); 573 u64 begin = std::numeric_limits<u64>::max();
575 u64 end = 0; 574 u64 end = 0;
576 for (u64 word_index = word_begin; word_index < word_end; ++word_index) { 575 for (u64 word_index = word_begin; word_index < word_end; ++word_index) {
577 const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0; 576 const u64 word = state_words[word_index];
578 const u64 word = state_words[word_index] & ~off_word;
579 if (word == 0) { 577 if (word == 0) {
580 continue; 578 continue;
581 } 579 }
diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp
index cea1dd8b0..7f5a0c29d 100644
--- a/src/video_core/engines/engine_upload.cpp
+++ b/src/video_core/engines/engine_upload.cpp
@@ -76,7 +76,7 @@ void State::ProcessData(std::span<const u8> read_buffer) {
76 regs.dest.height, regs.dest.depth, x_offset, regs.dest.y, 76 regs.dest.height, regs.dest.depth, x_offset, regs.dest.y,
77 x_elements, regs.line_count, regs.dest.BlockHeight(), 77 x_elements, regs.line_count, regs.dest.BlockHeight(),
78 regs.dest.BlockDepth(), regs.line_length_in); 78 regs.dest.BlockDepth(), regs.line_length_in);
79 memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size); 79 memory_manager.WriteBlockCached(address, tmp_buffer.data(), dst_size);
80 } 80 }
81} 81}
82 82
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index e655e7254..a126c359c 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -6,6 +6,7 @@
6#include "common/microprofile.h" 6#include "common/microprofile.h"
7#include "video_core/engines/fermi_2d.h" 7#include "video_core/engines/fermi_2d.h"
8#include "video_core/engines/sw_blitter/blitter.h" 8#include "video_core/engines/sw_blitter/blitter.h"
9#include "video_core/memory_manager.h"
9#include "video_core/rasterizer_interface.h" 10#include "video_core/rasterizer_interface.h"
10#include "video_core/surface.h" 11#include "video_core/surface.h"
11#include "video_core/textures/decoders.h" 12#include "video_core/textures/decoders.h"
@@ -20,8 +21,8 @@ namespace Tegra::Engines {
20 21
21using namespace Texture; 22using namespace Texture;
22 23
23Fermi2D::Fermi2D(MemoryManager& memory_manager_) { 24Fermi2D::Fermi2D(MemoryManager& memory_manager_) : memory_manager{memory_manager_} {
24 sw_blitter = std::make_unique<Blitter::SoftwareBlitEngine>(memory_manager_); 25 sw_blitter = std::make_unique<Blitter::SoftwareBlitEngine>(memory_manager);
25 // Nvidia's OpenGL driver seems to assume these values 26 // Nvidia's OpenGL driver seems to assume these values
26 regs.src.depth = 1; 27 regs.src.depth = 1;
27 regs.dst.depth = 1; 28 regs.dst.depth = 1;
@@ -104,6 +105,7 @@ void Fermi2D::Blit() {
104 config.src_x0 = 0; 105 config.src_x0 = 0;
105 } 106 }
106 107
108 memory_manager.FlushCaching();
107 if (!rasterizer->AccelerateSurfaceCopy(src, regs.dst, config)) { 109 if (!rasterizer->AccelerateSurfaceCopy(src, regs.dst, config)) {
108 sw_blitter->Blit(src, regs.dst, config); 110 sw_blitter->Blit(src, regs.dst, config);
109 } 111 }
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 523fbdec2..705b323e1 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -305,6 +305,7 @@ public:
305private: 305private:
306 VideoCore::RasterizerInterface* rasterizer = nullptr; 306 VideoCore::RasterizerInterface* rasterizer = nullptr;
307 std::unique_ptr<Blitter::SoftwareBlitEngine> sw_blitter; 307 std::unique_ptr<Blitter::SoftwareBlitEngine> sw_blitter;
308 MemoryManager& memory_manager;
308 309
309 /// Performs the copy from the source surface to the destination surface as configured in the 310 /// Performs the copy from the source surface to the destination surface as configured in the
310 /// registers. 311 /// registers.
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index fbfd1ddd2..97f547789 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -485,11 +485,6 @@ void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
485} 485}
486 486
487void Maxwell3D::ProcessQueryGet() { 487void Maxwell3D::ProcessQueryGet() {
488 // TODO(Subv): Support the other query units.
489 if (regs.report_semaphore.query.location != Regs::ReportSemaphore::Location::All) {
490 LOG_DEBUG(HW_GPU, "Locations other than ALL are unimplemented");
491 }
492
493 switch (regs.report_semaphore.query.operation) { 488 switch (regs.report_semaphore.query.operation) {
494 case Regs::ReportSemaphore::Operation::Release: 489 case Regs::ReportSemaphore::Operation::Release:
495 if (regs.report_semaphore.query.short_query != 0) { 490 if (regs.report_semaphore.query.short_query != 0) {
@@ -649,7 +644,7 @@ void Maxwell3D::ProcessCBMultiData(const u32* start_base, u32 amount) {
649 644
650 const GPUVAddr address{buffer_address + regs.const_buffer.offset}; 645 const GPUVAddr address{buffer_address + regs.const_buffer.offset};
651 const size_t copy_size = amount * sizeof(u32); 646 const size_t copy_size = amount * sizeof(u32);
652 memory_manager.WriteBlock(address, start_base, copy_size); 647 memory_manager.WriteBlockCached(address, start_base, copy_size);
653 648
654 // Increment the current buffer position. 649 // Increment the current buffer position.
655 regs.const_buffer.offset += static_cast<u32>(copy_size); 650 regs.const_buffer.offset += static_cast<u32>(copy_size);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index 01f70ea9e..7762c7d96 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -69,7 +69,7 @@ void MaxwellDMA::Launch() {
69 if (launch.multi_line_enable) { 69 if (launch.multi_line_enable) {
70 const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH; 70 const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH;
71 const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH; 71 const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH;
72 72 memory_manager.FlushCaching();
73 if (!is_src_pitch && !is_dst_pitch) { 73 if (!is_src_pitch && !is_dst_pitch) {
74 // If both the source and the destination are in block layout, assert. 74 // If both the source and the destination are in block layout, assert.
75 CopyBlockLinearToBlockLinear(); 75 CopyBlockLinearToBlockLinear();
@@ -104,6 +104,7 @@ void MaxwellDMA::Launch() {
104 reinterpret_cast<u8*>(tmp_buffer.data()), 104 reinterpret_cast<u8*>(tmp_buffer.data()),
105 regs.line_length_in * sizeof(u32)); 105 regs.line_length_in * sizeof(u32));
106 } else { 106 } else {
107 memory_manager.FlushCaching();
107 const auto convert_linear_2_blocklinear_addr = [](u64 address) { 108 const auto convert_linear_2_blocklinear_addr = [](u64 address) {
108 return (address & ~0x1f0ULL) | ((address & 0x40) >> 2) | ((address & 0x10) << 1) | 109 return (address & ~0x1f0ULL) | ((address & 0x40) >> 2) | ((address & 0x10) << 1) |
109 ((address & 0x180) >> 1) | ((address & 0x20) << 3); 110 ((address & 0x180) >> 1) | ((address & 0x20) << 3);
@@ -121,8 +122,8 @@ void MaxwellDMA::Launch() {
121 memory_manager.ReadBlockUnsafe( 122 memory_manager.ReadBlockUnsafe(
122 convert_linear_2_blocklinear_addr(regs.offset_in + offset), 123 convert_linear_2_blocklinear_addr(regs.offset_in + offset),
123 tmp_buffer.data(), tmp_buffer.size()); 124 tmp_buffer.data(), tmp_buffer.size());
124 memory_manager.WriteBlock(regs.offset_out + offset, tmp_buffer.data(), 125 memory_manager.WriteBlockCached(regs.offset_out + offset, tmp_buffer.data(),
125 tmp_buffer.size()); 126 tmp_buffer.size());
126 } 127 }
127 } else if (is_src_pitch && !is_dst_pitch) { 128 } else if (is_src_pitch && !is_dst_pitch) {
128 UNIMPLEMENTED_IF(regs.line_length_in % 16 != 0); 129 UNIMPLEMENTED_IF(regs.line_length_in % 16 != 0);
@@ -132,7 +133,7 @@ void MaxwellDMA::Launch() {
132 for (u32 offset = 0; offset < regs.line_length_in; offset += 16) { 133 for (u32 offset = 0; offset < regs.line_length_in; offset += 16) {
133 memory_manager.ReadBlockUnsafe(regs.offset_in + offset, tmp_buffer.data(), 134 memory_manager.ReadBlockUnsafe(regs.offset_in + offset, tmp_buffer.data(),
134 tmp_buffer.size()); 135 tmp_buffer.size());
135 memory_manager.WriteBlock( 136 memory_manager.WriteBlockCached(
136 convert_linear_2_blocklinear_addr(regs.offset_out + offset), 137 convert_linear_2_blocklinear_addr(regs.offset_out + offset),
137 tmp_buffer.data(), tmp_buffer.size()); 138 tmp_buffer.data(), tmp_buffer.size());
138 } 139 }
@@ -141,8 +142,8 @@ void MaxwellDMA::Launch() {
141 std::vector<u8> tmp_buffer(regs.line_length_in); 142 std::vector<u8> tmp_buffer(regs.line_length_in);
142 memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), 143 memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(),
143 regs.line_length_in); 144 regs.line_length_in);
144 memory_manager.WriteBlock(regs.offset_out, tmp_buffer.data(), 145 memory_manager.WriteBlockCached(regs.offset_out, tmp_buffer.data(),
145 regs.line_length_in); 146 regs.line_length_in);
146 } 147 }
147 } 148 }
148 } 149 }
@@ -204,7 +205,7 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
204 src_params.origin.y, x_elements, regs.line_count, block_height, block_depth, 205 src_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
205 regs.pitch_out); 206 regs.pitch_out);
206 207
207 memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size); 208 memory_manager.WriteBlockCached(regs.offset_out, write_buffer.data(), dst_size);
208} 209}
209 210
210void MaxwellDMA::CopyPitchToBlockLinear() { 211void MaxwellDMA::CopyPitchToBlockLinear() {
@@ -256,7 +257,7 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
256 dst_params.origin.y, x_elements, regs.line_count, block_height, block_depth, 257 dst_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
257 regs.pitch_in); 258 regs.pitch_in);
258 259
259 memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size); 260 memory_manager.WriteBlockCached(regs.offset_out, write_buffer.data(), dst_size);
260} 261}
261 262
262void MaxwellDMA::FastCopyBlockLinearToPitch() { 263void MaxwellDMA::FastCopyBlockLinearToPitch() {
@@ -287,7 +288,7 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
287 regs.src_params.block_size.height, regs.src_params.block_size.depth, 288 regs.src_params.block_size.height, regs.src_params.block_size.depth,
288 regs.pitch_out); 289 regs.pitch_out);
289 290
290 memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size); 291 memory_manager.WriteBlockCached(regs.offset_out, write_buffer.data(), dst_size);
291} 292}
292 293
293void MaxwellDMA::CopyBlockLinearToBlockLinear() { 294void MaxwellDMA::CopyBlockLinearToBlockLinear() {
@@ -347,7 +348,7 @@ void MaxwellDMA::CopyBlockLinearToBlockLinear() {
347 dst.depth, dst_x_offset, dst.origin.y, x_elements, regs.line_count, 348 dst.depth, dst_x_offset, dst.origin.y, x_elements, regs.line_count,
348 dst.block_size.height, dst.block_size.depth, pitch); 349 dst.block_size.height, dst.block_size.depth, pitch);
349 350
350 memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size); 351 memory_manager.WriteBlockCached(regs.offset_out, write_buffer.data(), dst_size);
351} 352}
352 353
353void MaxwellDMA::ReleaseSemaphore() { 354void MaxwellDMA::ReleaseSemaphore() {
diff --git a/src/video_core/invalidation_accumulator.h b/src/video_core/invalidation_accumulator.h
new file mode 100644
index 000000000..2c2aaf7bb
--- /dev/null
+++ b/src/video_core/invalidation_accumulator.h
@@ -0,0 +1,79 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <utility>
7#include <vector>
8
9#include "common/common_types.h"
10
11namespace VideoCommon {
12
13class InvalidationAccumulator {
14public:
15 InvalidationAccumulator() = default;
16 ~InvalidationAccumulator() = default;
17
18 void Add(GPUVAddr address, size_t size) {
19 const auto reset_values = [&]() {
20 if (has_collected) {
21 buffer.emplace_back(start_address, accumulated_size);
22 }
23 start_address = address;
24 accumulated_size = size;
25 last_collection = start_address + size;
26 };
27 if (address >= start_address && address + size <= last_collection) [[likely]] {
28 return;
29 }
30 size = ((address + size + atomicity_size_mask) & atomicity_mask) - address;
31 address = address & atomicity_mask;
32 if (!has_collected) [[unlikely]] {
33 reset_values();
34 has_collected = true;
35 return;
36 }
37 if (address != last_collection) [[unlikely]] {
38 reset_values();
39 return;
40 }
41 accumulated_size += size;
42 last_collection += size;
43 }
44
45 void Clear() {
46 buffer.clear();
47 start_address = 0;
48 last_collection = 0;
49 has_collected = false;
50 }
51
52 bool AnyAccumulated() const {
53 return has_collected;
54 }
55
56 template <typename Func>
57 void Callback(Func&& func) {
58 if (!has_collected) {
59 return;
60 }
61 buffer.emplace_back(start_address, accumulated_size);
62 for (auto& [address, size] : buffer) {
63 func(address, size);
64 }
65 }
66
67private:
68 static constexpr size_t atomicity_bits = 5;
69 static constexpr size_t atomicity_size = 1ULL << atomicity_bits;
70 static constexpr size_t atomicity_size_mask = atomicity_size - 1;
71 static constexpr size_t atomicity_mask = ~atomicity_size_mask;
72 GPUVAddr start_address{};
73 GPUVAddr last_collection{};
74 size_t accumulated_size{};
75 bool has_collected{};
76 std::vector<std::pair<VAddr, size_t>> buffer;
77};
78
79} // namespace VideoCommon
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 3a5cdeb39..3bcae3503 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -6,11 +6,13 @@
6#include "common/alignment.h" 6#include "common/alignment.h"
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "common/settings.h"
9#include "core/core.h" 10#include "core/core.h"
10#include "core/device_memory.h" 11#include "core/device_memory.h"
11#include "core/hle/kernel/k_page_table.h" 12#include "core/hle/kernel/k_page_table.h"
12#include "core/hle/kernel/k_process.h" 13#include "core/hle/kernel/k_process.h"
13#include "core/memory.h" 14#include "core/memory.h"
15#include "video_core/invalidation_accumulator.h"
14#include "video_core/memory_manager.h" 16#include "video_core/memory_manager.h"
15#include "video_core/rasterizer_interface.h" 17#include "video_core/rasterizer_interface.h"
16#include "video_core/renderer_base.h" 18#include "video_core/renderer_base.h"
@@ -26,7 +28,8 @@ MemoryManager::MemoryManager(Core::System& system_, u64 address_space_bits_, u64
26 entries{}, big_entries{}, page_table{address_space_bits, address_space_bits + page_bits - 38, 28 entries{}, big_entries{}, page_table{address_space_bits, address_space_bits + page_bits - 38,
27 page_bits != big_page_bits ? page_bits : 0}, 29 page_bits != big_page_bits ? page_bits : 0},
28 kind_map{PTEKind::INVALID}, unique_identifier{unique_identifier_generator.fetch_add( 30 kind_map{PTEKind::INVALID}, unique_identifier{unique_identifier_generator.fetch_add(
29 1, std::memory_order_acq_rel)} { 31 1, std::memory_order_acq_rel)},
32 accumulator{std::make_unique<VideoCommon::InvalidationAccumulator>()} {
30 address_space_size = 1ULL << address_space_bits; 33 address_space_size = 1ULL << address_space_bits;
31 page_size = 1ULL << page_bits; 34 page_size = 1ULL << page_bits;
32 page_mask = page_size - 1ULL; 35 page_mask = page_size - 1ULL;
@@ -43,6 +46,11 @@ MemoryManager::MemoryManager(Core::System& system_, u64 address_space_bits_, u64
43 big_page_table_cpu.resize(big_page_table_size); 46 big_page_table_cpu.resize(big_page_table_size);
44 big_page_continous.resize(big_page_table_size / continous_bits, 0); 47 big_page_continous.resize(big_page_table_size / continous_bits, 0);
45 entries.resize(page_table_size / 32, 0); 48 entries.resize(page_table_size / 32, 0);
49 if (!Settings::IsGPULevelExtreme() && Settings::IsFastmemEnabled()) {
50 fastmem_arena = system.DeviceMemory().buffer.VirtualBasePointer();
51 } else {
52 fastmem_arena = nullptr;
53 }
46} 54}
47 55
48MemoryManager::~MemoryManager() = default; 56MemoryManager::~MemoryManager() = default;
@@ -185,15 +193,12 @@ void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) {
185 if (size == 0) { 193 if (size == 0) {
186 return; 194 return;
187 } 195 }
188 const auto submapped_ranges = GetSubmappedRange(gpu_addr, size); 196 GetSubmappedRangeImpl<false>(gpu_addr, size, page_stash);
189
190 for (const auto& [map_addr, map_size] : submapped_ranges) {
191 // Flush and invalidate through the GPU interface, to be asynchronous if possible.
192 const std::optional<VAddr> cpu_addr = GpuToCpuAddress(map_addr);
193 ASSERT(cpu_addr);
194 197
195 rasterizer->UnmapMemory(*cpu_addr, map_size); 198 for (const auto& [map_addr, map_size] : page_stash) {
199 rasterizer->UnmapMemory(map_addr, map_size);
196 } 200 }
201 page_stash.clear();
197 202
198 BigPageTableOp<EntryType::Free>(gpu_addr, 0, size, PTEKind::INVALID); 203 BigPageTableOp<EntryType::Free>(gpu_addr, 0, size, PTEKind::INVALID);
199 PageTableOp<EntryType::Free>(gpu_addr, 0, size, PTEKind::INVALID); 204 PageTableOp<EntryType::Free>(gpu_addr, 0, size, PTEKind::INVALID);
@@ -355,7 +360,7 @@ inline void MemoryManager::MemoryOperation(GPUVAddr gpu_src_addr, std::size_t si
355 } 360 }
356} 361}
357 362
358template <bool is_safe> 363template <bool is_safe, bool use_fastmem>
359void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, 364void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size,
360 [[maybe_unused]] VideoCommon::CacheType which) const { 365 [[maybe_unused]] VideoCommon::CacheType which) const {
361 auto set_to_zero = [&]([[maybe_unused]] std::size_t page_index, 366 auto set_to_zero = [&]([[maybe_unused]] std::size_t page_index,
@@ -369,8 +374,12 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std:
369 if constexpr (is_safe) { 374 if constexpr (is_safe) {
370 rasterizer->FlushRegion(cpu_addr_base, copy_amount, which); 375 rasterizer->FlushRegion(cpu_addr_base, copy_amount, which);
371 } 376 }
372 u8* physical = memory.GetPointer(cpu_addr_base); 377 if constexpr (use_fastmem) {
373 std::memcpy(dest_buffer, physical, copy_amount); 378 std::memcpy(dest_buffer, &fastmem_arena[cpu_addr_base], copy_amount);
379 } else {
380 u8* physical = memory.GetPointer(cpu_addr_base);
381 std::memcpy(dest_buffer, physical, copy_amount);
382 }
374 dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; 383 dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
375 }; 384 };
376 auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { 385 auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) {
@@ -379,11 +388,15 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std:
379 if constexpr (is_safe) { 388 if constexpr (is_safe) {
380 rasterizer->FlushRegion(cpu_addr_base, copy_amount, which); 389 rasterizer->FlushRegion(cpu_addr_base, copy_amount, which);
381 } 390 }
382 if (!IsBigPageContinous(page_index)) [[unlikely]] { 391 if constexpr (use_fastmem) {
383 memory.ReadBlockUnsafe(cpu_addr_base, dest_buffer, copy_amount); 392 std::memcpy(dest_buffer, &fastmem_arena[cpu_addr_base], copy_amount);
384 } else { 393 } else {
385 u8* physical = memory.GetPointer(cpu_addr_base); 394 if (!IsBigPageContinous(page_index)) [[unlikely]] {
386 std::memcpy(dest_buffer, physical, copy_amount); 395 memory.ReadBlockUnsafe(cpu_addr_base, dest_buffer, copy_amount);
396 } else {
397 u8* physical = memory.GetPointer(cpu_addr_base);
398 std::memcpy(dest_buffer, physical, copy_amount);
399 }
387 } 400 }
388 dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; 401 dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
389 }; 402 };
@@ -397,12 +410,20 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std:
397 410
398void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, 411void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size,
399 VideoCommon::CacheType which) const { 412 VideoCommon::CacheType which) const {
400 ReadBlockImpl<true>(gpu_src_addr, dest_buffer, size, which); 413 if (fastmem_arena) [[likely]] {
414 ReadBlockImpl<true, true>(gpu_src_addr, dest_buffer, size, which);
415 return;
416 }
417 ReadBlockImpl<true, false>(gpu_src_addr, dest_buffer, size, which);
401} 418}
402 419
403void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, 420void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer,
404 const std::size_t size) const { 421 const std::size_t size) const {
405 ReadBlockImpl<false>(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None); 422 if (fastmem_arena) [[likely]] {
423 ReadBlockImpl<false, true>(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None);
424 return;
425 }
426 ReadBlockImpl<false, false>(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None);
406} 427}
407 428
408template <bool is_safe> 429template <bool is_safe>
@@ -454,6 +475,12 @@ void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buf
454 WriteBlockImpl<false>(gpu_dest_addr, src_buffer, size, VideoCommon::CacheType::None); 475 WriteBlockImpl<false>(gpu_dest_addr, src_buffer, size, VideoCommon::CacheType::None);
455} 476}
456 477
478void MemoryManager::WriteBlockCached(GPUVAddr gpu_dest_addr, const void* src_buffer,
479 std::size_t size) {
480 WriteBlockImpl<false>(gpu_dest_addr, src_buffer, size, VideoCommon::CacheType::None);
481 accumulator->Add(gpu_dest_addr, size);
482}
483
457void MemoryManager::FlushRegion(GPUVAddr gpu_addr, size_t size, 484void MemoryManager::FlushRegion(GPUVAddr gpu_addr, size_t size,
458 VideoCommon::CacheType which) const { 485 VideoCommon::CacheType which) const {
459 auto do_nothing = [&]([[maybe_unused]] std::size_t page_index, 486 auto do_nothing = [&]([[maybe_unused]] std::size_t page_index,
@@ -663,7 +690,17 @@ bool MemoryManager::IsFullyMappedRange(GPUVAddr gpu_addr, std::size_t size) cons
663std::vector<std::pair<GPUVAddr, std::size_t>> MemoryManager::GetSubmappedRange( 690std::vector<std::pair<GPUVAddr, std::size_t>> MemoryManager::GetSubmappedRange(
664 GPUVAddr gpu_addr, std::size_t size) const { 691 GPUVAddr gpu_addr, std::size_t size) const {
665 std::vector<std::pair<GPUVAddr, std::size_t>> result{}; 692 std::vector<std::pair<GPUVAddr, std::size_t>> result{};
666 std::optional<std::pair<GPUVAddr, std::size_t>> last_segment{}; 693 GetSubmappedRangeImpl<true>(gpu_addr, size, result);
694 return result;
695}
696
697template <bool is_gpu_address>
698void MemoryManager::GetSubmappedRangeImpl(
699 GPUVAddr gpu_addr, std::size_t size,
700 std::vector<std::pair<std::conditional_t<is_gpu_address, GPUVAddr, VAddr>, std::size_t>>&
701 result) const {
702 std::optional<std::pair<std::conditional_t<is_gpu_address, GPUVAddr, VAddr>, std::size_t>>
703 last_segment{};
667 std::optional<VAddr> old_page_addr{}; 704 std::optional<VAddr> old_page_addr{};
668 const auto split = [&last_segment, &result]([[maybe_unused]] std::size_t page_index, 705 const auto split = [&last_segment, &result]([[maybe_unused]] std::size_t page_index,
669 [[maybe_unused]] std::size_t offset, 706 [[maybe_unused]] std::size_t offset,
@@ -685,8 +722,12 @@ std::vector<std::pair<GPUVAddr, std::size_t>> MemoryManager::GetSubmappedRange(
685 } 722 }
686 old_page_addr = {cpu_addr_base + copy_amount}; 723 old_page_addr = {cpu_addr_base + copy_amount};
687 if (!last_segment) { 724 if (!last_segment) {
688 const GPUVAddr new_base_addr = (page_index << big_page_bits) + offset; 725 if constexpr (is_gpu_address) {
689 last_segment = {new_base_addr, copy_amount}; 726 const GPUVAddr new_base_addr = (page_index << big_page_bits) + offset;
727 last_segment = {new_base_addr, copy_amount};
728 } else {
729 last_segment = {cpu_addr_base, copy_amount};
730 }
690 } else { 731 } else {
691 last_segment->second += copy_amount; 732 last_segment->second += copy_amount;
692 } 733 }
@@ -703,8 +744,12 @@ std::vector<std::pair<GPUVAddr, std::size_t>> MemoryManager::GetSubmappedRange(
703 } 744 }
704 old_page_addr = {cpu_addr_base + copy_amount}; 745 old_page_addr = {cpu_addr_base + copy_amount};
705 if (!last_segment) { 746 if (!last_segment) {
706 const GPUVAddr new_base_addr = (page_index << page_bits) + offset; 747 if constexpr (is_gpu_address) {
707 last_segment = {new_base_addr, copy_amount}; 748 const GPUVAddr new_base_addr = (page_index << page_bits) + offset;
749 last_segment = {new_base_addr, copy_amount};
750 } else {
751 last_segment = {cpu_addr_base, copy_amount};
752 }
708 } else { 753 } else {
709 last_segment->second += copy_amount; 754 last_segment->second += copy_amount;
710 } 755 }
@@ -715,7 +760,18 @@ std::vector<std::pair<GPUVAddr, std::size_t>> MemoryManager::GetSubmappedRange(
715 }; 760 };
716 MemoryOperation<true>(gpu_addr, size, extend_size_big, split, do_short_pages); 761 MemoryOperation<true>(gpu_addr, size, extend_size_big, split, do_short_pages);
717 split(0, 0, 0); 762 split(0, 0, 0);
718 return result; 763}
764
765void MemoryManager::FlushCaching() {
766 if (!accumulator->AnyAccumulated()) {
767 return;
768 }
769 accumulator->Callback([this](GPUVAddr addr, size_t size) {
770 GetSubmappedRangeImpl<false>(addr, size, page_stash);
771 });
772 rasterizer->InnerInvalidation(page_stash);
773 page_stash.clear();
774 accumulator->Clear();
719} 775}
720 776
721} // namespace Tegra 777} // namespace Tegra
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index 828e13439..2936364f0 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -19,6 +19,10 @@ namespace VideoCore {
19class RasterizerInterface; 19class RasterizerInterface;
20} 20}
21 21
22namespace VideoCommon {
23class InvalidationAccumulator;
24}
25
22namespace Core { 26namespace Core {
23class DeviceMemory; 27class DeviceMemory;
24namespace Memory { 28namespace Memory {
@@ -80,6 +84,7 @@ public:
80 */ 84 */
81 void ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const; 85 void ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const;
82 void WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size); 86 void WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size);
87 void WriteBlockCached(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size);
83 88
84 /** 89 /**
85 * Checks if a gpu region can be simply read with a pointer. 90 * Checks if a gpu region can be simply read with a pointer.
@@ -129,12 +134,14 @@ public:
129 size_t GetMemoryLayoutSize(GPUVAddr gpu_addr, 134 size_t GetMemoryLayoutSize(GPUVAddr gpu_addr,
130 size_t max_size = std::numeric_limits<size_t>::max()) const; 135 size_t max_size = std::numeric_limits<size_t>::max()) const;
131 136
137 void FlushCaching();
138
132private: 139private:
133 template <bool is_big_pages, typename FuncMapped, typename FuncReserved, typename FuncUnmapped> 140 template <bool is_big_pages, typename FuncMapped, typename FuncReserved, typename FuncUnmapped>
134 inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, FuncMapped&& func_mapped, 141 inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, FuncMapped&& func_mapped,
135 FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const; 142 FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const;
136 143
137 template <bool is_safe> 144 template <bool is_safe, bool use_fastmem>
138 void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, 145 void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size,
139 VideoCommon::CacheType which) const; 146 VideoCommon::CacheType which) const;
140 147
@@ -154,6 +161,12 @@ private:
154 inline bool IsBigPageContinous(size_t big_page_index) const; 161 inline bool IsBigPageContinous(size_t big_page_index) const;
155 inline void SetBigPageContinous(size_t big_page_index, bool value); 162 inline void SetBigPageContinous(size_t big_page_index, bool value);
156 163
164 template <bool is_gpu_address>
165 void GetSubmappedRangeImpl(
166 GPUVAddr gpu_addr, std::size_t size,
167 std::vector<std::pair<std::conditional_t<is_gpu_address, GPUVAddr, VAddr>, std::size_t>>&
168 result) const;
169
157 Core::System& system; 170 Core::System& system;
158 Core::Memory::Memory& memory; 171 Core::Memory::Memory& memory;
159 Core::DeviceMemory& device_memory; 172 Core::DeviceMemory& device_memory;
@@ -201,10 +214,13 @@ private:
201 Common::VirtualBuffer<u32> big_page_table_cpu; 214 Common::VirtualBuffer<u32> big_page_table_cpu;
202 215
203 std::vector<u64> big_page_continous; 216 std::vector<u64> big_page_continous;
217 std::vector<std::pair<VAddr, std::size_t>> page_stash{};
218 u8* fastmem_arena{};
204 219
205 constexpr static size_t continous_bits = 64; 220 constexpr static size_t continous_bits = 64;
206 221
207 const size_t unique_identifier; 222 const size_t unique_identifier;
223 std::unique_ptr<VideoCommon::InvalidationAccumulator> accumulator;
208 224
209 static std::atomic<size_t> unique_identifier_generator; 225 static std::atomic<size_t> unique_identifier_generator;
210}; 226};
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index f44c7df50..1735b6164 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -6,6 +6,7 @@
6#include <functional> 6#include <functional>
7#include <optional> 7#include <optional>
8#include <span> 8#include <span>
9#include <utility>
9#include "common/common_types.h" 10#include "common/common_types.h"
10#include "common/polyfill_thread.h" 11#include "common/polyfill_thread.h"
11#include "video_core/cache_types.h" 12#include "video_core/cache_types.h"
@@ -95,6 +96,12 @@ public:
95 virtual void InvalidateRegion(VAddr addr, u64 size, 96 virtual void InvalidateRegion(VAddr addr, u64 size,
96 VideoCommon::CacheType which = VideoCommon::CacheType::All) = 0; 97 VideoCommon::CacheType which = VideoCommon::CacheType::All) = 0;
97 98
99 virtual void InnerInvalidation(std::span<const std::pair<VAddr, std::size_t>> sequences) {
100 for (const auto& [cpu_addr, size] : sequences) {
101 InvalidateRegion(cpu_addr, size);
102 }
103 }
104
98 /// Notify rasterizer that any caches of the specified region are desync with guest 105 /// Notify rasterizer that any caches of the specified region are desync with guest
99 virtual void OnCPUWrite(VAddr addr, u64 size) = 0; 106 virtual void OnCPUWrite(VAddr addr, u64 size) = 0;
100 107
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 242bf9602..ed4a72166 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -186,6 +186,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
186 186
187 SCOPE_EXIT({ gpu.TickWork(); }); 187 SCOPE_EXIT({ gpu.TickWork(); });
188 FlushWork(); 188 FlushWork();
189 gpu_memory->FlushCaching();
189 190
190 query_cache.UpdateCounters(); 191 query_cache.UpdateCounters();
191 192
@@ -393,6 +394,7 @@ void RasterizerVulkan::Clear(u32 layer_count) {
393 394
394void RasterizerVulkan::DispatchCompute() { 395void RasterizerVulkan::DispatchCompute() {
395 FlushWork(); 396 FlushWork();
397 gpu_memory->FlushCaching();
396 398
397 ComputePipeline* const pipeline{pipeline_cache.CurrentComputePipeline()}; 399 ComputePipeline* const pipeline{pipeline_cache.CurrentComputePipeline()};
398 if (!pipeline) { 400 if (!pipeline) {
@@ -481,6 +483,27 @@ void RasterizerVulkan::InvalidateRegion(VAddr addr, u64 size, VideoCommon::Cache
481 } 483 }
482} 484}
483 485
486void RasterizerVulkan::InnerInvalidation(std::span<const std::pair<VAddr, std::size_t>> sequences) {
487 {
488 std::scoped_lock lock{texture_cache.mutex};
489 for (const auto& [addr, size] : sequences) {
490 texture_cache.WriteMemory(addr, size);
491 }
492 }
493 {
494 std::scoped_lock lock{buffer_cache.mutex};
495 for (const auto& [addr, size] : sequences) {
496 buffer_cache.WriteMemory(addr, size);
497 }
498 }
499 {
500 for (const auto& [addr, size] : sequences) {
501 query_cache.InvalidateRegion(addr, size);
502 pipeline_cache.InvalidateRegion(addr, size);
503 }
504 }
505}
506
484void RasterizerVulkan::OnCPUWrite(VAddr addr, u64 size) { 507void RasterizerVulkan::OnCPUWrite(VAddr addr, u64 size) {
485 if (addr == 0 || size == 0) { 508 if (addr == 0 || size == 0) {
486 return; 509 return;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index c661e5b19..472cc64d9 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -79,6 +79,7 @@ public:
79 VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 79 VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
80 void InvalidateRegion(VAddr addr, u64 size, 80 void InvalidateRegion(VAddr addr, u64 size,
81 VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 81 VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
82 void InnerInvalidation(std::span<const std::pair<VAddr, std::size_t>> sequences) override;
82 void OnCPUWrite(VAddr addr, u64 size) override; 83 void OnCPUWrite(VAddr addr, u64 size) override;
83 void InvalidateGPUCache() override; 84 void InvalidateGPUCache() override;
84 void UnmapMemory(VAddr addr, u64 size) override; 85 void UnmapMemory(VAddr addr, u64 size) override;