diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp
index bc964ec601..88684b82db 100644
--- a/src/core/hle/service/gsp_gpu.cpp
+++ b/src/core/hle/service/gsp_gpu.cpp
@@ -475,12 +475,11 @@ static void ExecuteCommand(const Command& command, u32 thread_id) {
 
         // TODO: Consider attempting rasterizer-accelerated surface blit if that usage is ever
         // possible/likely
-        Memory::RasterizerFlushRegion(
-            Memory::VirtualToPhysicalAddress(command.dma_request.source_address),
-            command.dma_request.size);
-        Memory::RasterizerFlushAndInvalidateRegion(
-            Memory::VirtualToPhysicalAddress(command.dma_request.dest_address),
-            command.dma_request.size);
+        Memory::RasterizerFlushVirtualRegion(command.dma_request.source_address,
+                                             command.dma_request.size, Memory::FlushMode::Flush);
+        Memory::RasterizerFlushVirtualRegion(command.dma_request.dest_address,
+                                             command.dma_request.size,
+                                             Memory::FlushMode::FlushAndInvalidate);
 
         // TODO(Subv): These memory accesses should not go through the application's memory mapping.
         // They should go through the GSP module's memory mapping.
diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp
index e73971d5fb..57172ddd6d 100644
--- a/src/core/hle/service/y2r_u.cpp
+++ b/src/core/hle/service/y2r_u.cpp
@@ -587,8 +587,8 @@ static void StartConversion(Interface* self) {
     // dst_image_size would seem to be perfect for this, but it doesn't include the gap :(
     u32 total_output_size =
         conversion.input_lines * (conversion.dst.transfer_unit + conversion.dst.gap);
-    Memory::RasterizerFlushAndInvalidateRegion(
-        Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size);
+    Memory::RasterizerFlushVirtualRegion(conversion.dst.address, total_output_size,
+                                         Memory::FlushMode::FlushAndInvalidate);
 
     HW::Y2R::PerformConversion(conversion);
 
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 42ca69e006..38fe9e2317 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -83,19 +83,13 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
     LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE,
               (base + size) * PAGE_SIZE);
 
-    u32 end = base + size;
+    RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
+                                 FlushMode::FlushAndInvalidate);
 
+    u32 end = base + size;
     while (base != end) {
         ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base);
 
-        // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
-        // null here
-        if (current_page_table->attributes[base] == PageType::RasterizerCachedMemory ||
-            current_page_table->attributes[base] == PageType::RasterizerCachedSpecial) {
-            RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(base << PAGE_BITS),
-                                               PAGE_SIZE);
-        }
-
         current_page_table->attributes[base] = type;
         current_page_table->pointers[base] = memory;
         current_page_table->cached_res_count[base] = 0;
@@ -189,7 +183,7 @@ T Read(const VAddr vaddr) {
         ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
         break;
     case PageType::RasterizerCachedMemory: {
-        RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
+        RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
 
         T value;
         std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
@@ -198,8 +192,7 @@ T Read(const VAddr vaddr) {
     case PageType::Special:
         return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr);
     case PageType::RasterizerCachedSpecial: {
-        RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
-
+        RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
         return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr);
     }
     default:
@@ -229,8 +222,7 @@ void Write(const VAddr vaddr, const T data) {
         ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
         break;
     case PageType::RasterizerCachedMemory: {
-        RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
-
+        RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate);
         std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
         break;
     }
@@ -238,8 +230,7 @@ void Write(const VAddr vaddr, const T data) {
         WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data);
         break;
     case PageType::RasterizerCachedSpecial: {
-        RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
-
+        RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate);
         WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data);
         break;
     }
@@ -369,11 +360,48 @@ void RasterizerFlushRegion(PAddr start, u32 size) {
 }
 
 void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size) {
+    // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
+    // null here
     if (VideoCore::g_renderer != nullptr) {
         VideoCore::g_renderer->Rasterizer()->FlushAndInvalidateRegion(start, size);
     }
 }
 
+void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode) {
+    // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
+    // null here
+    if (VideoCore::g_renderer != nullptr) {
+        VAddr end = start + size;
+
+        auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
+            if (start >= region_end || end <= region_start) {
+                // No overlap with region
+                return;
+            }
+
+            VAddr overlap_start = std::max(start, region_start);
+            VAddr overlap_end = std::min(end, region_end);
+
+            PAddr physical_start = TryVirtualToPhysicalAddress(overlap_start).value();
+            u32 overlap_size = overlap_end - overlap_start;
+
+            auto* rasterizer = VideoCore::g_renderer->Rasterizer();
+            switch (mode) {
+            case FlushMode::Flush:
+                rasterizer->FlushRegion(physical_start, overlap_size);
+                break;
+            case FlushMode::FlushAndInvalidate:
+                rasterizer->FlushAndInvalidateRegion(physical_start, overlap_size);
+                break;
+            }
+        };
+
+        CheckRegion(LINEAR_HEAP_VADDR, LINEAR_HEAP_VADDR_END);
+        CheckRegion(NEW_LINEAR_HEAP_VADDR, NEW_LINEAR_HEAP_VADDR_END);
+        CheckRegion(VRAM_VADDR, VRAM_VADDR_END);
+    }
+}
+
 u8 Read8(const VAddr addr) {
     return Read<u8>(addr);
 }
@@ -420,16 +448,13 @@ void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) {
             break;
         }
         case PageType::RasterizerCachedMemory: {
-            RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
-
+            RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
             std::memcpy(dest_buffer, GetPointerFromVMA(current_vaddr), copy_amount);
             break;
         }
         case PageType::RasterizerCachedSpecial: {
             DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
-            RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
-
+            RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
             GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount);
             break;
         }
@@ -490,18 +515,13 @@ void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size
             break;
         }
         case PageType::RasterizerCachedMemory: {
-            RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
-                                               copy_amount);
-
+            RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
             std::memcpy(GetPointerFromVMA(current_vaddr), src_buffer, copy_amount);
             break;
         }
         case PageType::RasterizerCachedSpecial: {
             DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
-            RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
-                                               copy_amount);
-
+            RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
             GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount);
             break;
         }
@@ -547,18 +567,13 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) {
             break;
         }
         case PageType::RasterizerCachedMemory: {
-            RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
-                                               copy_amount);
-
+            RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
             std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount);
             break;
         }
         case PageType::RasterizerCachedSpecial: {
             DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
-            RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
-                                               copy_amount);
-
+            RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
             GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount);
             break;
         }
@@ -603,15 +618,13 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
             break;
         }
         case PageType::RasterizerCachedMemory: {
-            RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
-
+            RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
             WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount);
             break;
         }
         case PageType::RasterizerCachedSpecial: {
             DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
-            RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
+            RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
 
             std::vector<u8> buffer(copy_amount);
             GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size());
diff --git a/src/core/memory.h b/src/core/memory.h
index 96ce9e52e0..c8c56babd9 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -190,6 +190,19 @@ void RasterizerFlushRegion(PAddr start, u32 size);
  */
 void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size);
 
+enum class FlushMode {
+    /// Write back modified surfaces to RAM
+    Flush,
+    /// Write back modified surfaces to RAM, and also remove them from the cache
+    FlushAndInvalidate,
+};
+
+/**
+ * Flushes and invalidates any externally cached rasterizer resources touching the given virtual
+ * address region.
+ */
+void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode);
+
 /**
  * Dynarmic has an optimization to memory accesses when the pointer to the page exists that
  * can be used by setting up the current page table as a callback. This function is used to