From fff6155bc3a24016496d1290944b8f111a367c9a Mon Sep 17 00:00:00 2001
From: Fernando Sahmkow <fsahmkow27@gmail.com>
Date: Sun, 23 Apr 2023 11:58:43 +0200
Subject: [PATCH] Tests: Add memory tracker tests.

---
 src/tests/CMakeLists.txt                |   2 +-
 src/tests/video_core/buffer_base.cpp    | 549 ------------------------
 src/tests/video_core/memory_tracker.cpp | 547 +++++++++++++++++++++++
 3 files changed, 548 insertions(+), 550 deletions(-)
 delete mode 100644 src/tests/video_core/buffer_base.cpp
 create mode 100644 src/tests/video_core/memory_tracker.cpp

diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 39b774c984..1e158f3759 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -15,7 +15,7 @@ add_executable(tests
     core/core_timing.cpp
     core/internal_network/network.cpp
     precompiled_headers.h
-    video_core/buffer_base.cpp
+    video_core/memory_tracker.cpp
     input_common/calibration_configuration_job.cpp
 )
 
diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp
deleted file mode 100644
index 734dbf4b69..0000000000
--- a/src/tests/video_core/buffer_base.cpp
+++ /dev/null
@@ -1,549 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <stdexcept>
-#include <unordered_map>
-
-#include <catch2/catch_test_macros.hpp>
-
-#include "common/alignment.h"
-#include "common/common_types.h"
-#include "video_core/buffer_cache/buffer_base.h"
-
-namespace {
-using VideoCommon::BufferBase;
-using Range = std::pair<u64, u64>;
-
-constexpr u64 PAGE = 4096;
-constexpr u64 WORD = 4096 * 64;
-
-constexpr VAddr c = 0x1328914000;
-
-class RasterizerInterface {
-public:
-    void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
-        const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS};
-        const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >>
-                           Core::Memory::YUZU_PAGEBITS};
-        for (u64 page = page_start; page < page_end; ++page) {
-            int& value = page_table[page];
-            value += delta;
-            if (value < 0) {
-                throw std::logic_error{"negative page"};
-            }
-            if (value == 0) {
-                page_table.erase(page);
-            }
-        }
-    }
-
-    [[nodiscard]] int Count(VAddr addr) const noexcept {
-        const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS);
-        return it == page_table.end() ? 0 : it->second;
-    }
-
-    [[nodiscard]] unsigned Count() const noexcept {
-        unsigned count = 0;
-        for (const auto& [index, value] : page_table) {
-            count += value;
-        }
-        return count;
-    }
-
-private:
-    std::unordered_map<u64, int> page_table;
-};
-} // Anonymous namespace
-
-TEST_CASE("BufferBase: Small buffer", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    REQUIRE(rasterizer.Count() == 0);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    REQUIRE(rasterizer.Count() == WORD / PAGE);
-    REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{0, 0});
-
-    buffer.MarkRegionAsCpuModified(c + PAGE, 1);
-    REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{PAGE * 1, PAGE * 2});
-}
-
-TEST_CASE("BufferBase: Large buffer", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 32);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 32);
-    buffer.MarkRegionAsCpuModified(c + 4096, WORD * 4);
-    REQUIRE(buffer.ModifiedCpuRegion(c, WORD + PAGE * 2) == Range{PAGE, WORD + PAGE * 2});
-    REQUIRE(buffer.ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) == Range{PAGE * 2, PAGE * 8});
-    REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 4 + PAGE});
-    REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 4, PAGE) == Range{WORD * 4, WORD * 4 + PAGE});
-    REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) ==
-            Range{WORD * 3 + PAGE * 63, WORD * 4});
-
-    buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE);
-    buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
-    REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) ==
-            Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 9});
-
-    buffer.UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
-    REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) ==
-            Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 7});
-
-    buffer.MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63);
-    REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 32});
-
-    buffer.UnmarkRegionAsCpuModified(c + PAGE * 4, PAGE);
-    buffer.UnmarkRegionAsCpuModified(c + PAGE * 6, PAGE);
-
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 32);
-    REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{0, 0});
-}
-
-TEST_CASE("BufferBase: Rasterizer counting", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, PAGE * 2);
-    REQUIRE(rasterizer.Count() == 0);
-    buffer.UnmarkRegionAsCpuModified(c, PAGE);
-    REQUIRE(rasterizer.Count() == 1);
-    buffer.MarkRegionAsCpuModified(c, PAGE * 2);
-    REQUIRE(rasterizer.Count() == 0);
-    buffer.UnmarkRegionAsCpuModified(c, PAGE);
-    buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE);
-    REQUIRE(rasterizer.Count() == 2);
-    buffer.MarkRegionAsCpuModified(c, PAGE * 2);
-    REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Basic range", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    buffer.MarkRegionAsCpuModified(c, PAGE);
-    int num = 0;
-    buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
-        REQUIRE(offset == 0U);
-        REQUIRE(size == PAGE);
-        ++num;
-    });
-    REQUIRE(num == 1U);
-}
-
-TEST_CASE("BufferBase: Border upload", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 2);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
-    buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
-    buffer.ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) {
-        REQUIRE(offset == WORD - PAGE);
-        REQUIRE(size == PAGE * 2);
-    });
-}
-
-TEST_CASE("BufferBase: Border upload range", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 2);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
-    buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
-    buffer.ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) {
-        REQUIRE(offset == WORD - PAGE);
-        REQUIRE(size == PAGE * 2);
-    });
-    buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
-    buffer.ForEachUploadRange(c + WORD - PAGE, PAGE, [](u64 offset, u64 size) {
-        REQUIRE(offset == WORD - PAGE);
-        REQUIRE(size == PAGE);
-    });
-    buffer.ForEachUploadRange(c + WORD, PAGE, [](u64 offset, u64 size) {
-        REQUIRE(offset == WORD);
-        REQUIRE(size == PAGE);
-    });
-}
-
-TEST_CASE("BufferBase: Border upload partial range", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 2);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
-    buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
-    buffer.ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) {
-        REQUIRE(offset == WORD - PAGE);
-        REQUIRE(size == PAGE * 2);
-    });
-    buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
-    buffer.ForEachUploadRange(c + WORD - 1, 1, [](u64 offset, u64 size) {
-        REQUIRE(offset == WORD - PAGE);
-        REQUIRE(size == PAGE);
-    });
-    buffer.ForEachUploadRange(c + WORD + 50, 1, [](u64 offset, u64 size) {
-        REQUIRE(offset == WORD);
-        REQUIRE(size == PAGE);
-    });
-}
-
-TEST_CASE("BufferBase: Partial word uploads", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, 0x9d000);
-    int num = 0;
-    buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
-        REQUIRE(offset == 0U);
-        REQUIRE(size == WORD);
-        ++num;
-    });
-    REQUIRE(num == 1);
-    buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) {
-        REQUIRE(offset == WORD);
-        REQUIRE(size == WORD);
-        ++num;
-    });
-    REQUIRE(num == 2);
-    buffer.ForEachUploadRange(c + 0x79000, 0x24000, [&](u64 offset, u64 size) {
-        REQUIRE(offset == WORD * 2);
-        REQUIRE(size == PAGE * 0x1d);
-        ++num;
-    });
-    REQUIRE(num == 3);
-}
-
-TEST_CASE("BufferBase: Partial page upload", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    int num = 0;
-    buffer.MarkRegionAsCpuModified(c + PAGE * 2, PAGE);
-    buffer.MarkRegionAsCpuModified(c + PAGE * 9, PAGE);
-    buffer.ForEachUploadRange(c, PAGE * 3, [&](u64 offset, u64 size) {
-        REQUIRE(offset == PAGE * 2);
-        REQUIRE(size == PAGE);
-        ++num;
-    });
-    REQUIRE(num == 1);
-    buffer.ForEachUploadRange(c + PAGE * 7, PAGE * 3, [&](u64 offset, u64 size) {
-        REQUIRE(offset == PAGE * 9);
-        REQUIRE(size == PAGE);
-        ++num;
-    });
-    REQUIRE(num == 2);
-}
-
-TEST_CASE("BufferBase: Partial page upload with multiple words on the right") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 8);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
-    buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
-    int num = 0;
-    buffer.ForEachUploadRange(c + PAGE * 10, WORD * 7, [&](u64 offset, u64 size) {
-        REQUIRE(offset == PAGE * 13);
-        REQUIRE(size == WORD * 7 - PAGE * 3);
-        ++num;
-    });
-    REQUIRE(num == 1);
-    buffer.ForEachUploadRange(c + PAGE, WORD * 8, [&](u64 offset, u64 size) {
-        REQUIRE(offset == WORD * 7 + PAGE * 10);
-        REQUIRE(size == PAGE * 3);
-        ++num;
-    });
-    REQUIRE(num == 2);
-}
-
-TEST_CASE("BufferBase: Partial page upload with multiple words on the left", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 8);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
-    buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
-    int num = 0;
-    buffer.ForEachUploadRange(c + PAGE * 16, WORD * 7, [&](u64 offset, u64 size) {
-        REQUIRE(offset == PAGE * 16);
-        REQUIRE(size == WORD * 7 - PAGE * 3);
-        ++num;
-    });
-    REQUIRE(num == 1);
-    buffer.ForEachUploadRange(c + PAGE, WORD, [&](u64 offset, u64 size) {
-        REQUIRE(offset == PAGE * 13);
-        REQUIRE(size == PAGE * 3);
-        ++num;
-    });
-    REQUIRE(num == 2);
-}
-
-TEST_CASE("BufferBase: Partial page upload with multiple words in the middle", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 8);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
-    buffer.MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140);
-    int num = 0;
-    buffer.ForEachUploadRange(c + PAGE * 16, WORD, [&](u64 offset, u64 size) {
-        REQUIRE(offset == PAGE * 16);
-        REQUIRE(size == WORD);
-        ++num;
-    });
-    REQUIRE(num == 1);
-    buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
-        REQUIRE(offset == PAGE * 13);
-        REQUIRE(size == PAGE * 3);
-        ++num;
-    });
-    REQUIRE(num == 2);
-    buffer.ForEachUploadRange(c, WORD * 8, [&](u64 offset, u64 size) {
-        REQUIRE(offset == WORD + PAGE * 16);
-        REQUIRE(size == PAGE * 73);
-        ++num;
-    });
-    REQUIRE(num == 3);
-}
-
-TEST_CASE("BufferBase: Empty right bits", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 2048);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 2048);
-    buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
-    buffer.ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) {
-        REQUIRE(offset == WORD - PAGE);
-        REQUIRE(size == PAGE * 2);
-    });
-}
-
-TEST_CASE("BufferBase: Out of bound ranges 1", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    buffer.MarkRegionAsCpuModified(c, PAGE);
-    int num = 0;
-    buffer.ForEachUploadRange(c - WORD, WORD, [&](u64 offset, u64 size) { ++num; });
-    buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { ++num; });
-    buffer.ForEachUploadRange(c - PAGE, PAGE, [&](u64 offset, u64 size) { ++num; });
-    REQUIRE(num == 0);
-    buffer.ForEachUploadRange(c - PAGE, PAGE * 2, [&](u64 offset, u64 size) { ++num; });
-    REQUIRE(num == 1);
-    buffer.MarkRegionAsCpuModified(c, WORD);
-    REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Out of bound ranges 2", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, 0x22000);
-    REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x22000, PAGE));
-    REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x28000, PAGE));
-    REQUIRE(rasterizer.Count() == 0);
-    REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x21100, PAGE - 0x100));
-    REQUIRE(rasterizer.Count() == 1);
-    REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c - 0x1000, PAGE * 2));
-    buffer.UnmarkRegionAsCpuModified(c - 0x3000, PAGE * 2);
-    buffer.UnmarkRegionAsCpuModified(c - 0x2000, PAGE * 2);
-    REQUIRE(rasterizer.Count() == 2);
-}
-
-TEST_CASE("BufferBase: Out of bound ranges 3", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, 0x310720);
-    buffer.UnmarkRegionAsCpuModified(c, 0x310720);
-    REQUIRE(rasterizer.Count(c) == 1);
-    REQUIRE(rasterizer.Count(c + PAGE) == 1);
-    REQUIRE(rasterizer.Count(c + WORD) == 1);
-    REQUIRE(rasterizer.Count(c + WORD + PAGE) == 1);
-}
-
-TEST_CASE("BufferBase: Sparse regions 1", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    buffer.MarkRegionAsCpuModified(c + PAGE * 1, PAGE);
-    buffer.MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4);
-    buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
-        static constexpr std::array<u64, 2> offsets{PAGE, PAGE * 3};
-        static constexpr std::array<u64, 2> sizes{PAGE, PAGE * 4};
-        REQUIRE(offset == offsets.at(i));
-        REQUIRE(size == sizes.at(i));
-        ++i;
-    });
-}
-
-TEST_CASE("BufferBase: Sparse regions 2", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, 0x22000);
-    buffer.UnmarkRegionAsCpuModified(c, 0x22000);
-    REQUIRE(rasterizer.Count() == 0x22);
-    buffer.MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE);
-    buffer.MarkRegionAsCpuModified(c + PAGE * 0x21, PAGE);
-    buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
-        static constexpr std::array<u64, 2> offsets{PAGE * 0x1B, PAGE * 0x21};
-        static constexpr std::array<u64, 2> sizes{PAGE, PAGE};
-        REQUIRE(offset == offsets.at(i));
-        REQUIRE(size == sizes.at(i));
-        ++i;
-    });
-}
-
-TEST_CASE("BufferBase: Single page modified range", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, PAGE);
-    REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
-    buffer.UnmarkRegionAsCpuModified(c, PAGE);
-    REQUIRE(!buffer.IsRegionCpuModified(c, PAGE));
-}
-
-TEST_CASE("BufferBase: Two page modified range", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, PAGE * 2);
-    REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c, PAGE * 2));
-    buffer.UnmarkRegionAsCpuModified(c, PAGE);
-    REQUIRE(!buffer.IsRegionCpuModified(c, PAGE));
-}
-
-TEST_CASE("BufferBase: Multi word modified ranges", "[video_core]") {
-    for (int offset = 0; offset < 4; ++offset) {
-        const VAddr address = c + WORD * offset;
-        RasterizerInterface rasterizer;
-        BufferBase buffer(rasterizer, address, WORD * 4);
-        REQUIRE(buffer.IsRegionCpuModified(address, PAGE));
-        REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 48, PAGE));
-        REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 56, PAGE));
-
-        buffer.UnmarkRegionAsCpuModified(address + PAGE * 32, PAGE);
-        REQUIRE(buffer.IsRegionCpuModified(address + PAGE, WORD));
-        REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE));
-        REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE));
-        REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 33, PAGE));
-        REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE * 2));
-        REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
-
-        buffer.UnmarkRegionAsCpuModified(address + PAGE * 33, PAGE);
-        REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
-    }
-}
-
-TEST_CASE("BufferBase: Single page in large buffer", "[video_core]") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 16);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 16);
-    REQUIRE(!buffer.IsRegionCpuModified(c, WORD * 16));
-
-    buffer.MarkRegionAsCpuModified(c + WORD * 12 + PAGE * 8, PAGE);
-    REQUIRE(buffer.IsRegionCpuModified(c, WORD * 16));
-    REQUIRE(buffer.IsRegionCpuModified(c + WORD * 10, WORD * 2));
-    REQUIRE(buffer.IsRegionCpuModified(c + WORD * 11, WORD * 2));
-    REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12, WORD * 2));
-    REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 4, PAGE * 8));
-    REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE * 8));
-    REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 7, PAGE * 2));
-    REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 8, PAGE * 2));
-}
-
-TEST_CASE("BufferBase: Out of bounds region query") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 16);
-    REQUIRE(!buffer.IsRegionCpuModified(c - PAGE, PAGE));
-    REQUIRE(!buffer.IsRegionCpuModified(c - PAGE * 2, PAGE));
-    REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + WORD * 16 - PAGE, WORD * 64));
-    REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, WORD * 64));
-}
-
-TEST_CASE("BufferBase: Wrap word regions") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD * 2);
-    buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
-    buffer.MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2);
-    REQUIRE(buffer.IsRegionCpuModified(c, WORD * 2));
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 62, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 64, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 2));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 8));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 60, PAGE * 8));
-
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16));
-    buffer.MarkRegionAsCpuModified(c + PAGE * 127, PAGE);
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, PAGE));
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 126, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 126, PAGE * 2));
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 128, WORD * 16));
-}
-
-TEST_CASE("BufferBase: Unaligned page region query") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    buffer.MarkRegionAsCpuModified(c + 4000, 1000);
-    REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1000));
-    REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1));
-}
-
-TEST_CASE("BufferBase: Cached write") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    buffer.CachedCpuWrite(c + PAGE, PAGE);
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    buffer.FlushCachedWrites();
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    buffer.MarkRegionAsCpuModified(c, WORD);
-    REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Multiple cached write") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    buffer.CachedCpuWrite(c + PAGE, PAGE);
-    buffer.CachedCpuWrite(c + PAGE * 3, PAGE);
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 3, PAGE));
-    buffer.FlushCachedWrites();
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 3, PAGE));
-    buffer.MarkRegionAsCpuModified(c, WORD);
-    REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Cached write unmarked") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    buffer.CachedCpuWrite(c + PAGE, PAGE);
-    buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE);
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    buffer.FlushCachedWrites();
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    buffer.MarkRegionAsCpuModified(c, WORD);
-    REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Cached write iterated") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    buffer.CachedCpuWrite(c + PAGE, PAGE);
-    int num = 0;
-    buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
-    REQUIRE(num == 0);
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    buffer.FlushCachedWrites();
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    buffer.MarkRegionAsCpuModified(c, WORD);
-    REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Cached write downloads") {
-    RasterizerInterface rasterizer;
-    BufferBase buffer(rasterizer, c, WORD);
-    buffer.UnmarkRegionAsCpuModified(c, WORD);
-    REQUIRE(rasterizer.Count() == 64);
-    buffer.CachedCpuWrite(c + PAGE, PAGE);
-    REQUIRE(rasterizer.Count() == 63);
-    buffer.MarkRegionAsGpuModified(c + PAGE, PAGE);
-    int num = 0;
-    buffer.ForEachDownloadRangeAndClear(c, WORD, [&](u64 offset, u64 size) { ++num; });
-    buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
-    REQUIRE(num == 0);
-    REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE));
-    buffer.FlushCachedWrites();
-    REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
-    REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE));
-    buffer.MarkRegionAsCpuModified(c, WORD);
-    REQUIRE(rasterizer.Count() == 0);
-}
diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp
new file mode 100644
index 0000000000..77d391f15e
--- /dev/null
+++ b/src/tests/video_core/memory_tracker.cpp
@@ -0,0 +1,547 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <memory>
+#include <stdexcept>
+#include <unordered_map>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include "common/alignment.h"
+#include "common/common_types.h"
+#include "video_core/buffer_cache/memory_tracker_base.h"
+
+namespace {
+using Range = std::pair<u64, u64>;
+
+constexpr u64 PAGE = 4096;
+constexpr u64 WORD = 4096 * 64;
+constexpr u64 HIGH_PAGE_BITS = 22;
+constexpr u64 HIGH_PAGE_SIZE = 1ULL << HIGH_PAGE_BITS;
+
+constexpr VAddr c = 16 * HIGH_PAGE_SIZE;
+
+class RasterizerInterface {
+public:
+    void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
+        const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS};
+        const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >>
+                           Core::Memory::YUZU_PAGEBITS};
+        for (u64 page = page_start; page < page_end; ++page) {
+            int& value = page_table[page];
+            value += delta;
+            if (value < 0) {
+                throw std::logic_error{"negative page"};
+            }
+            if (value == 0) {
+                page_table.erase(page);
+            }
+        }
+    }
+
+    [[nodiscard]] int Count(VAddr addr) const noexcept {
+        const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS);
+        return it == page_table.end() ? 0 : it->second;
+    }
+
+    [[nodiscard]] unsigned Count() const noexcept {
+        unsigned count = 0;
+        for (const auto& [index, value] : page_table) {
+            count += value;
+        }
+        return count;
+    }
+
+private:
+    std::unordered_map<u64, int> page_table;
+};
+} // Anonymous namespace
+
+using MemoryTracker = VideoCommon::MemoryTrackerBase<RasterizerInterface>;
+
+TEST_CASE("MemoryTracker: Small region", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    REQUIRE(rasterizer.Count() == 0);
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    REQUIRE(rasterizer.Count() == WORD / PAGE);
+    REQUIRE(memory_track->ModifiedCpuRegion(c, WORD) == Range{0, 0});
+
+    memory_track->MarkRegionAsCpuModified(c + PAGE, 1);
+    REQUIRE(memory_track->ModifiedCpuRegion(c, WORD) == Range{c + PAGE * 1, c + PAGE * 2});
+}
+
+TEST_CASE("MemoryTracker: Large region", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 32);
+    memory_track->MarkRegionAsCpuModified(c + 4096, WORD * 4);
+    REQUIRE(memory_track->ModifiedCpuRegion(c, WORD + PAGE * 2) ==
+            Range{c + PAGE, c + WORD + PAGE * 2});
+    REQUIRE(memory_track->ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) ==
+            Range{c + PAGE * 2, c + PAGE * 8});
+    REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{c + PAGE, c + WORD * 4 + PAGE});
+    REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 4, PAGE) ==
+            Range{c + WORD * 4, c + WORD * 4 + PAGE});
+    REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) ==
+            Range{c + WORD * 3 + PAGE * 63, c + WORD * 4});
+
+    memory_track->MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE);
+    memory_track->MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
+    REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) ==
+            Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 9});
+
+    memory_track->UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
+    REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) ==
+            Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 7});
+
+    memory_track->MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63);
+    REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{c + PAGE, c + WORD * 32});
+
+    memory_track->UnmarkRegionAsCpuModified(c + PAGE * 4, PAGE);
+    memory_track->UnmarkRegionAsCpuModified(c + PAGE * 6, PAGE);
+
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 32);
+    REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{0, 0});
+}
+
+TEST_CASE("MemoryTracker: Rasterizer counting", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    REQUIRE(rasterizer.Count() == 0);
+    memory_track->UnmarkRegionAsCpuModified(c, PAGE);
+    REQUIRE(rasterizer.Count() == 1);
+    memory_track->MarkRegionAsCpuModified(c, PAGE * 2);
+    REQUIRE(rasterizer.Count() == 0);
+    memory_track->UnmarkRegionAsCpuModified(c, PAGE);
+    memory_track->UnmarkRegionAsCpuModified(c + PAGE, PAGE);
+    REQUIRE(rasterizer.Count() == 2);
+    memory_track->MarkRegionAsCpuModified(c, PAGE * 2);
+    REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Basic range", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    memory_track->MarkRegionAsCpuModified(c, PAGE);
+    int num = 0;
+    memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c);
+        REQUIRE(size == PAGE);
+        ++num;
+    });
+    REQUIRE(num == 1U);
+}
+
+TEST_CASE("MemoryTracker: Border upload", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
+    memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+    memory_track->ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD - PAGE);
+        REQUIRE(size == PAGE * 2);
+    });
+}
+
+TEST_CASE("MemoryTracker: Border upload range", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
+    memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+    memory_track->ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD - PAGE);
+        REQUIRE(size == PAGE * 2);
+    });
+    memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+    memory_track->ForEachUploadRange(c + WORD - PAGE, PAGE, [](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD - PAGE);
+        REQUIRE(size == PAGE);
+    });
+    memory_track->ForEachUploadRange(c + WORD, PAGE, [](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD);
+        REQUIRE(size == PAGE);
+    });
+}
+
+TEST_CASE("MemoryTracker: Border upload partial range", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
+    memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+    memory_track->ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD - PAGE);
+        REQUIRE(size == PAGE * 2);
+    });
+    memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+    memory_track->ForEachUploadRange(c + WORD - 1, 1, [](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD - PAGE);
+        REQUIRE(size == PAGE);
+    });
+    memory_track->ForEachUploadRange(c + WORD + 50, 1, [](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD);
+        REQUIRE(size == PAGE);
+    });
+}
+
+TEST_CASE("MemoryTracker: Partial word uploads", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    int num = 0;
+    memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c);
+        REQUIRE(size == WORD);
+        ++num;
+    });
+    REQUIRE(num == 1);
+    memory_track->ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD);
+        REQUIRE(size == WORD);
+        ++num;
+    });
+    REQUIRE(num == 2);
+    memory_track->ForEachUploadRange(c + 0x79000, 0x24000, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD * 2);
+        REQUIRE(size == PAGE * 0x1d);
+        ++num;
+    });
+    REQUIRE(num == 3);
+}
+
+TEST_CASE("MemoryTracker: Partial page upload", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    int num = 0;
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 2, PAGE);
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 9, PAGE);
+    memory_track->ForEachUploadRange(c, PAGE * 3, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + PAGE * 2);
+        REQUIRE(size == PAGE);
+        ++num;
+    });
+    REQUIRE(num == 1);
+    memory_track->ForEachUploadRange(c + PAGE * 7, PAGE * 3, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + PAGE * 9);
+        REQUIRE(size == PAGE);
+        ++num;
+    });
+    REQUIRE(num == 2);
+}
+
+TEST_CASE("MemoryTracker: Partial page upload with multiple words on the right") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 9);
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
+    int num = 0;
+    memory_track->ForEachUploadRange(c + PAGE * 10, WORD * 7, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + PAGE * 13);
+        REQUIRE(size == WORD * 7 - PAGE * 3);
+        ++num;
+    });
+    REQUIRE(num == 1);
+    memory_track->ForEachUploadRange(c + PAGE, WORD * 8, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD * 7 + PAGE * 10);
+        REQUIRE(size == PAGE * 3);
+        ++num;
+    });
+    REQUIRE(num == 2);
+}
+
+TEST_CASE("MemoryTracker: Partial page upload with multiple words on the left", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 8);
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
+    int num = 0;
+    memory_track->ForEachUploadRange(c + PAGE * 16, WORD * 7, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + PAGE * 16);
+        REQUIRE(size == WORD * 7 - PAGE * 3);
+        ++num;
+    });
+    REQUIRE(num == 1);
+    memory_track->ForEachUploadRange(c + PAGE, WORD, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + PAGE * 13);
+        REQUIRE(size == PAGE * 3);
+        ++num;
+    });
+    REQUIRE(num == 2);
+}
+
+TEST_CASE("MemoryTracker: Partial page upload with multiple words in the middle", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 8);
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140);
+    int num = 0;
+    memory_track->ForEachUploadRange(c + PAGE * 16, WORD, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + PAGE * 16);
+        REQUIRE(size == WORD);
+        ++num;
+    });
+    REQUIRE(num == 1);
+    memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + PAGE * 13);
+        REQUIRE(size == PAGE * 3);
+        ++num;
+    });
+    REQUIRE(num == 2);
+    memory_track->ForEachUploadRange(c, WORD * 8, [&](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD + PAGE * 16);
+        REQUIRE(size == PAGE * 73);
+        ++num;
+    });
+    REQUIRE(num == 3);
+}
+
+TEST_CASE("MemoryTracker: Empty right bits", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 2048);
+    memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+    memory_track->ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) {
+        REQUIRE(offset == c + WORD - PAGE);
+        REQUIRE(size == PAGE * 2);
+    });
+}
+
+TEST_CASE("MemoryTracker: Out of bound ranges 1", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c - WORD, 3 * WORD);
+    memory_track->MarkRegionAsCpuModified(c, PAGE);
+    REQUIRE(rasterizer.Count() == (3 * WORD - PAGE) / PAGE);
+    int num = 0;
+    memory_track->ForEachUploadRange(c - WORD, WORD, [&](u64 offset, u64 size) { ++num; });
+    memory_track->ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { ++num; });
+    memory_track->ForEachUploadRange(c - PAGE, PAGE, [&](u64 offset, u64 size) { ++num; });
+    REQUIRE(num == 0);
+    memory_track->ForEachUploadRange(c - PAGE, PAGE * 2, [&](u64 offset, u64 size) { ++num; });
+    REQUIRE(num == 1);
+    memory_track->MarkRegionAsCpuModified(c, WORD);
+    REQUIRE(rasterizer.Count() == 2 * WORD / PAGE);
+}
+
+TEST_CASE("MemoryTracker: Out of bound ranges 2", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x22000, PAGE));
+    REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x28000, PAGE));
+    REQUIRE(rasterizer.Count() == 2);
+    REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x21100, PAGE - 0x100));
+    REQUIRE(rasterizer.Count() == 3);
+    REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c - PAGE, PAGE * 2));
+    memory_track->UnmarkRegionAsCpuModified(c - PAGE * 3, PAGE * 2);
+    memory_track->UnmarkRegionAsCpuModified(c - PAGE * 2, PAGE * 2);
+    REQUIRE(rasterizer.Count() == 7);
+}
+
+TEST_CASE("MemoryTracker: Out of bound ranges 3", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, 0x310720);
+    REQUIRE(rasterizer.Count(c) == 1);
+    REQUIRE(rasterizer.Count(c + PAGE) == 1);
+    REQUIRE(rasterizer.Count(c + WORD) == 1);
+    REQUIRE(rasterizer.Count(c + WORD + PAGE) == 1);
+}
+
+TEST_CASE("MemoryTracker: Sparse regions 1", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 1, PAGE);
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4);
+    memory_track->ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
+        static constexpr std::array<u64, 2> offsets{c + PAGE, c + PAGE * 3};
+        static constexpr std::array<u64, 2> sizes{PAGE, PAGE * 4};
+        REQUIRE(offset == offsets.at(i));
+        REQUIRE(size == sizes.at(i));
+        ++i;
+    });
+}
+
+TEST_CASE("MemoryTracker: Sparse regions 2", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, PAGE * 0x23);
+    REQUIRE(rasterizer.Count() == 0x23);
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE);
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 0x21, PAGE);
+    memory_track->ForEachUploadRange(c, PAGE * 0x23, [i = 0](u64 offset, u64 size) mutable {
+        static constexpr std::array<u64, 3> offsets{c + PAGE * 0x1B, c + PAGE * 0x21};
+        static constexpr std::array<u64, 3> sizes{PAGE, PAGE};
+        REQUIRE(offset == offsets.at(i));
+        REQUIRE(size == sizes.at(i));
+        ++i;
+    });
+}
+
+TEST_CASE("MemoryTracker: Single page modified range", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    REQUIRE(memory_track->IsRegionCpuModified(c, PAGE));
+    memory_track->UnmarkRegionAsCpuModified(c, PAGE);
+    REQUIRE(!memory_track->IsRegionCpuModified(c, PAGE));
+}
+
+TEST_CASE("MemoryTracker: Two page modified range", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    REQUIRE(memory_track->IsRegionCpuModified(c, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c, PAGE * 2));
+    memory_track->UnmarkRegionAsCpuModified(c, PAGE);
+    REQUIRE(!memory_track->IsRegionCpuModified(c, PAGE));
+}
+
+TEST_CASE("MemoryTracker: Multi word modified ranges", "[video_core]") {
+    for (int offset = 0; offset < 4; ++offset) {
+        const VAddr address = c + WORD * offset;
+        RasterizerInterface rasterizer;
+        std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+        REQUIRE(memory_track->IsRegionCpuModified(address, PAGE));
+        REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 48, PAGE));
+        REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 56, PAGE));
+
+        memory_track->UnmarkRegionAsCpuModified(address + PAGE * 32, PAGE);
+        REQUIRE(memory_track->IsRegionCpuModified(address + PAGE, WORD));
+        REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 31, PAGE));
+        REQUIRE(!memory_track->IsRegionCpuModified(address + PAGE * 32, PAGE));
+        REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 33, PAGE));
+        REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 31, PAGE * 2));
+        REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
+
+        memory_track->UnmarkRegionAsCpuModified(address + PAGE * 33, PAGE);
+        REQUIRE(!memory_track->IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
+    }
+}
+
+TEST_CASE("MemoryTracker: Single page in large region", "[video_core]") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 16);
+    REQUIRE(!memory_track->IsRegionCpuModified(c, WORD * 16));
+
+    memory_track->MarkRegionAsCpuModified(c + WORD * 12 + PAGE * 8, PAGE);
+    REQUIRE(memory_track->IsRegionCpuModified(c, WORD * 16));
+    REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 10, WORD * 2));
+    REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 11, WORD * 2));
+    REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12, WORD * 2));
+    REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 4, PAGE * 8));
+    REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE * 8));
+    REQUIRE(!memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 7, PAGE * 2));
+    REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 8, PAGE * 2));
+}
+
+TEST_CASE("MemoryTracker: Wrap word regions") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD * 32);
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2);
+    REQUIRE(memory_track->IsRegionCpuModified(c, WORD * 2));
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 62, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 63, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 64, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 63, PAGE * 2));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 63, PAGE * 8));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 60, PAGE * 8));
+
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 127, WORD * 16));
+    memory_track->MarkRegionAsCpuModified(c + PAGE * 127, PAGE);
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 127, WORD * 16));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 127, PAGE));
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 126, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 126, PAGE * 2));
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 128, WORD * 16));
+}
+
+TEST_CASE("MemoryTracker: Unaligned page region query") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    memory_track->MarkRegionAsCpuModified(c + 4000, 1000);
+    REQUIRE(memory_track->IsRegionCpuModified(c, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c + 4000, 1000));
+    REQUIRE(memory_track->IsRegionCpuModified(c + 4000, 1));
+}
+
+TEST_CASE("MemoryTracker: Cached write") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    memory_track->CachedCpuWrite(c + PAGE, c + PAGE);
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    memory_track->FlushCachedWrites();
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    memory_track->MarkRegionAsCpuModified(c, WORD);
+    REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Multiple cached write") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    memory_track->CachedCpuWrite(c + PAGE, PAGE);
+    memory_track->CachedCpuWrite(c + PAGE * 3, PAGE);
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 3, PAGE));
+    memory_track->FlushCachedWrites();
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 3, PAGE));
+    memory_track->MarkRegionAsCpuModified(c, WORD);
+    REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Cached write unmarked") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    memory_track->CachedCpuWrite(c + PAGE, PAGE);
+    memory_track->UnmarkRegionAsCpuModified(c + PAGE, PAGE);
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    memory_track->FlushCachedWrites();
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    memory_track->MarkRegionAsCpuModified(c, WORD);
+    REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Cached write iterated") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    memory_track->CachedCpuWrite(c + PAGE, PAGE);
+    int num = 0;
+    memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
+    REQUIRE(num == 0);
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    memory_track->FlushCachedWrites();
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    memory_track->MarkRegionAsCpuModified(c, WORD);
+    REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Cached write downloads") {
+    RasterizerInterface rasterizer;
+    std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+    memory_track->UnmarkRegionAsCpuModified(c, WORD);
+    REQUIRE(rasterizer.Count() == 64);
+    memory_track->CachedCpuWrite(c + PAGE, PAGE);
+    REQUIRE(rasterizer.Count() == 63);
+    memory_track->MarkRegionAsGpuModified(c + PAGE, PAGE);
+    int num = 0;
+    memory_track->ForEachDownloadRangeAndClear(c, WORD, [&](u64 offset, u64 size) { ++num; });
+    memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
+    REQUIRE(num == 0);
+    REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    REQUIRE(!memory_track->IsRegionGpuModified(c + PAGE, PAGE));
+    memory_track->FlushCachedWrites();
+    REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+    REQUIRE(!memory_track->IsRegionGpuModified(c + PAGE, PAGE));
+    memory_track->MarkRegionAsCpuModified(c, WORD);
+    REQUIRE(rasterizer.Count() == 0);
+}
\ No newline at end of file