code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <atomic>
#include <cstdlib>
#include <functional>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <thread>
#include <unordered_map>
#include <vector>

#include <catch2/catch_test_macros.hpp>

#include "common/common_types.h"
#include "common/fiber.h"

namespace Common {

class ThreadIds {
public:
    void Register(u32 id) {
        const auto thread_id = std::this_thread::get_id();
        std::scoped_lock lock{mutex};
        if (ids.contains(thread_id)) {
            throw std::logic_error{"Registering the same thread twice"};
        }
        ids.emplace(thread_id, id);
    }

    [[nodiscard]] u32 Get() const {
        std::scoped_lock lock{mutex};
        return ids.at(std::this_thread::get_id());
    }

private:
    mutable std::mutex mutex;
    std::unordered_map<std::thread::id, u32> ids;
};

class TestControl1 {
public:
    TestControl1() = default;

    void DoWork() {
        const u32 id = thread_ids.Get();
        u32 value = items[id];
        for (u32 i = 0; i < id; i++) {
            value++;
        }
        results[id] = value;
        Fiber::YieldTo(work_fibers[id], *thread_fibers[id]);
    }

    void ExecuteThread(u32 id);

    ThreadIds thread_ids;
    std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
    std::vector<std::shared_ptr<Common::Fiber>> work_fibers;
    std::vector<u32> items;
    std::vector<u32> results;
};

void TestControl1::ExecuteThread(u32 id) {
    thread_ids.Register(id);
    auto thread_fiber = Fiber::ThreadToFiber();
    thread_fibers[id] = thread_fiber;
    work_fibers[id] = std::make_shared<Fiber>([this] { DoWork(); });
    items[id] = rand() % 256;
    Fiber::YieldTo(thread_fibers[id], *work_fibers[id]);
    thread_fibers[id]->Exit();
}

/** This test checks for fiber setup configuration and validates that fibers are
 *  doing all the work required.
 */
TEST_CASE("Fibers::Setup", "[common]") {
    constexpr std::size_t num_threads = 7;
    TestControl1 test_control{};
    test_control.thread_fibers.resize(num_threads);
    test_control.work_fibers.resize(num_threads);
    test_control.items.resize(num_threads, 0);
    test_control.results.resize(num_threads, 0);
    std::vector<std::thread> threads;
    for (u32 i = 0; i < num_threads; i++) {
        threads.emplace_back([&test_control, i] { test_control.ExecuteThread(i); });
    }
    for (u32 i = 0; i < num_threads; i++) {
        threads[i].join();
    }
    for (u32 i = 0; i < num_threads; i++) {
        REQUIRE(test_control.items[i] + i == test_control.results[i]);
    }
}

class TestControl2 {
public:
    TestControl2() = default;

    void DoWork1() {
        trap2 = false;
        while (trap.load())
            ;
        for (u32 i = 0; i < 12000; i++) {
            value1 += i;
        }
        Fiber::YieldTo(fiber1, *fiber3);
        const u32 id = thread_ids.Get();
        assert1 = id == 1;
        value2 += 5000;
        Fiber::YieldTo(fiber1, *thread_fibers[id]);
    }

    void DoWork2() {
        while (trap2.load())
            ;
        value2 = 2000;
        trap = false;
        Fiber::YieldTo(fiber2, *fiber1);
        assert3 = false;
    }

    void DoWork3() {
        const u32 id = thread_ids.Get();
        assert2 = id == 0;
        value1 += 1000;
        Fiber::YieldTo(fiber3, *thread_fibers[id]);
    }

    void ExecuteThread(u32 id);

    void CallFiber1() {
        const u32 id = thread_ids.Get();
        Fiber::YieldTo(thread_fibers[id], *fiber1);
    }

    void CallFiber2() {
        const u32 id = thread_ids.Get();
        Fiber::YieldTo(thread_fibers[id], *fiber2);
    }

    void Exit();

    bool assert1{};
    bool assert2{};
    bool assert3{true};
    u32 value1{};
    u32 value2{};
    std::atomic<bool> trap{true};
    std::atomic<bool> trap2{true};
    ThreadIds thread_ids;
    std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
    std::shared_ptr<Common::Fiber> fiber1;
    std::shared_ptr<Common::Fiber> fiber2;
    std::shared_ptr<Common::Fiber> fiber3;
};

void TestControl2::ExecuteThread(u32 id) {
    thread_ids.Register(id);
    auto thread_fiber = Fiber::ThreadToFiber();
    thread_fibers[id] = thread_fiber;
}

void TestControl2::Exit() {
    const u32 id = thread_ids.Get();
    thread_fibers[id]->Exit();
}

/** This test checks for fiber thread exchange configuration and validates that fibers are
 *  that a fiber has been successfully transferred from one thread to another and that the TLS
 *  region of the thread is kept while changing fibers.
 */
TEST_CASE("Fibers::InterExchange", "[common]") {
    TestControl2 test_control{};
    test_control.thread_fibers.resize(2);
    test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
    test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
    test_control.fiber3 = std::make_shared<Fiber>([&test_control] { test_control.DoWork3(); });
    std::thread thread1{[&test_control] {
        test_control.ExecuteThread(0);
        test_control.CallFiber1();
        test_control.Exit();
    }};
    std::thread thread2{[&test_control] {
        test_control.ExecuteThread(1);
        test_control.CallFiber2();
        test_control.Exit();
    }};
    thread1.join();
    thread2.join();
    REQUIRE(test_control.assert1);
    REQUIRE(test_control.assert2);
    REQUIRE(test_control.assert3);
    REQUIRE(test_control.value2 == 7000);
    u32 cal_value = 0;
    for (u32 i = 0; i < 12000; i++) {
        cal_value += i;
    }
    cal_value += 1000;
    REQUIRE(test_control.value1 == cal_value);
}

class TestControl3 {
public:
    TestControl3() = default;

    void DoWork1() {
        value1 += 1;
        Fiber::YieldTo(fiber1, *fiber2);
        const u32 id = thread_ids.Get();
        value3 += 1;
        Fiber::YieldTo(fiber1, *thread_fibers[id]);
    }

    void DoWork2() {
        value2 += 1;
        const u32 id = thread_ids.Get();
        Fiber::YieldTo(fiber2, *thread_fibers[id]);
    }

    void ExecuteThread(u32 id);

    void CallFiber1() {
        const u32 id = thread_ids.Get();
        Fiber::YieldTo(thread_fibers[id], *fiber1);
    }

    void Exit();

    u32 value1{};
    u32 value2{};
    u32 value3{};
    ThreadIds thread_ids;
    std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
    std::shared_ptr<Common::Fiber> fiber1;
    std::shared_ptr<Common::Fiber> fiber2;
};

void TestControl3::ExecuteThread(u32 id) {
    thread_ids.Register(id);
    auto thread_fiber = Fiber::ThreadToFiber();
    thread_fibers[id] = thread_fiber;
}

void TestControl3::Exit() {
    const u32 id = thread_ids.Get();
    thread_fibers[id]->Exit();
}

/** This test checks for one two threads racing for starting the same fiber.
 *  It checks execution occurred in an ordered manner and by no time there were
 *  two contexts at the same time.
 */
TEST_CASE("Fibers::StartRace", "[common]") {
    TestControl3 test_control{};
    test_control.thread_fibers.resize(2);
    test_control.fiber1 = std::make_shared<Fiber>([&test_control] { test_control.DoWork1(); });
    test_control.fiber2 = std::make_shared<Fiber>([&test_control] { test_control.DoWork2(); });
    const auto race_function{[&test_control](u32 id) {
        test_control.ExecuteThread(id);
        test_control.CallFiber1();
        test_control.Exit();
    }};
    std::thread thread1([&] { race_function(0); });
    std::thread thread2([&] { race_function(1); });
    thread1.join();
    thread2.join();
    REQUIRE(test_control.value1 == 1);
    REQUIRE(test_control.value2 == 1);
    REQUIRE(test_control.value3 == 1);
}

class TestControl4;

class TestControl4 {
public:
    TestControl4() {
        fiber1 = std::make_shared<Fiber>([this] { DoWork(); });
        goal_reached = false;
        rewinded = false;
    }

    void Execute() {
        thread_fiber = Fiber::ThreadToFiber();
        Fiber::YieldTo(thread_fiber, *fiber1);
        thread_fiber->Exit();
    }

    void DoWork() {
        fiber1->SetRewindPoint([this] { DoWork(); });
        if (rewinded) {
            goal_reached = true;
            Fiber::YieldTo(fiber1, *thread_fiber);
        }
        rewinded = true;
        fiber1->Rewind();
    }

    std::shared_ptr<Common::Fiber> fiber1;
    std::shared_ptr<Common::Fiber> thread_fiber;
    bool goal_reached;
    bool rewinded;
};

TEST_CASE("Fibers::Rewind", "[common]") {
    TestControl4 test_control{};
    test_control.Execute();
    REQUIRE(test_control.goal_reached);
    REQUIRE(test_control.rewinded);
}

} // namespace Common


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <catch2/catch_test_macros.hpp>

#include "common/host_memory.h"
#include "common/literals.h"

using Common::HostMemory;
using namespace Common::Literals;

static constexpr size_t VIRTUAL_SIZE = 1ULL << 39;
static constexpr size_t BACKING_SIZE = 4_GiB;
static constexpr auto PERMS = Common::MemoryPermission::ReadWrite;
static constexpr auto HEAP = false;

TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
    { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
    { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
}

TEST_CASE("HostMemory: Simple map", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x5000, 0x8000, 0x1000, PERMS, HEAP);

    volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
    data[0] = 50;
    REQUIRE(data[0] == 50);
}

TEST_CASE("HostMemory: Simple mirror map", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);
    mem.Map(0x8000, 0x4000, 0x1000, PERMS, HEAP);

    volatile u8* const mirror_a = mem.VirtualBasePointer() + 0x5000;
    volatile u8* const mirror_b = mem.VirtualBasePointer() + 0x8000;
    mirror_b[0] = 76;
    REQUIRE(mirror_a[0x1000] == 76);
}

TEST_CASE("HostMemory: Simple unmap", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);

    volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
    data[75] = 50;
    REQUIRE(data[75] == 50);

    mem.Unmap(0x5000, 0x2000, HEAP);
}

TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);

    volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
    data[0] = 50;
    REQUIRE(data[0] == 50);

    mem.Unmap(0x5000, 0x2000, HEAP);

    mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP);
    REQUIRE(data[0] == 50);

    mem.Map(0x7000, 0x2000, 0x5000, PERMS, HEAP);
    REQUIRE(data[0x3000] == 50);
}

TEST_CASE("HostMemory: Nieche allocation", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x0000, 0, 0x20000, PERMS, HEAP);
    mem.Unmap(0x0000, 0x4000, HEAP);
    mem.Map(0x1000, 0, 0x2000, PERMS, HEAP);
    mem.Map(0x3000, 0, 0x1000, PERMS, HEAP);
    mem.Map(0, 0, 0x1000, PERMS, HEAP);
}

TEST_CASE("HostMemory: Full unmap", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
    mem.Unmap(0x8000, 0x4000, HEAP);
    mem.Map(0x6000, 0, 0x16000, PERMS, HEAP);
}

TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x0000, 0, 0x4000, PERMS, HEAP);
    mem.Unmap(0x2000, 0x4000, HEAP);
    mem.Map(0x2000, 0x80000, 0x4000, PERMS, HEAP);
}

TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
    mem.Unmap(0x6000, 0x4000, HEAP);
    mem.Map(0x8000, 0, 0x2000, PERMS, HEAP);
}

TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x0000, 0, 0x4000, PERMS, HEAP);
    mem.Map(0x4000, 0, 0x1b000, PERMS, HEAP);
    mem.Unmap(0x3000, 0x1c000, HEAP);
    mem.Map(0x3000, 0, 0x20000, PERMS, HEAP);
}

TEST_CASE("HostMemory: Unmap between placeholders", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x0000, 0, 0x4000, PERMS, HEAP);
    mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
    mem.Unmap(0x2000, 0x4000, HEAP);
    mem.Map(0x2000, 0, 0x4000, PERMS, HEAP);
}

TEST_CASE("HostMemory: Unmap to origin", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
    mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
    mem.Unmap(0x4000, 0x4000, HEAP);
    mem.Map(0, 0, 0x4000, PERMS, HEAP);
    mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
}

TEST_CASE("HostMemory: Unmap to right", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x4000, 0, 0x4000, PERMS, HEAP);
    mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
    mem.Unmap(0x8000, 0x4000, HEAP);
    mem.Map(0x8000, 0, 0x4000, PERMS, HEAP);
}

TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP);

    volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
    ptr[0x1000] = 17;

    mem.Unmap(0x6000, 0x2000, HEAP);

    REQUIRE(ptr[0x1000] == 17);
}

TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP);

    volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
    ptr[0x3000] = 19;
    ptr[0x3fff] = 12;

    mem.Unmap(0x4000, 0x2000, HEAP);

    REQUIRE(ptr[0x3000] == 19);
    REQUIRE(ptr[0x3fff] == 12);
}

TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP);

    volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
    ptr[0x0000] = 19;
    ptr[0x3fff] = 12;

    mem.Unmap(0x1000, 0x2000, HEAP);

    REQUIRE(ptr[0x0000] == 19);
    REQUIRE(ptr[0x3fff] == 12);
}

TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") {
    HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
    mem.Map(0x4000, 0x10000, 0x2000, PERMS, HEAP);
    mem.Map(0x6000, 0x20000, 0x2000, PERMS, HEAP);

    volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
    ptr[0x0000] = 19;
    ptr[0x3fff] = 12;

    mem.Unmap(0x5000, 0x2000, HEAP);

    REQUIRE(ptr[0x0000] == 19);
    REQUIRE(ptr[0x3fff] == 12);
}

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <catch2/catch_test_macros.hpp>
#include <math.h>
#include "common/logging/backend.h"
#include "common/param_package.h"

namespace Common {

TEST_CASE("ParamPackage", "[common]") {
    Common::Log::DisableLoggingInTests();
    ParamPackage original{
        {"abc", "xyz"},
        {"def", "42"},
        {"jkl", "$$:1:$2$,3"},
    };
    original.Set("ghi", 3.14f);
    ParamPackage copy(original.Serialize());
    REQUIRE(copy.Get("abc", "") == "xyz");
    REQUIRE(copy.Get("def", 0) == 42);
    REQUIRE(std::abs(copy.Get("ghi", 0.0f) - 3.14f) < 0.01f);
    REQUIRE(copy.Get("jkl", "") == "$$:1:$2$,3");
    REQUIRE(copy.Get("mno", "uvw") == "uvw");
    REQUIRE(copy.Get("abc", 42) == 42);
}

} // namespace Common


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include <stdexcept>

#include <catch2/catch_test_macros.hpp>

#include "common/range_map.h"

enum class MappedEnum : u32 {
    Invalid = 0,
    Valid_1 = 1,
    Valid_2 = 2,
    Valid_3 = 3,
};

TEST_CASE("Range Map: Setup", "[video_core]") {
    Common::RangeMap<u64, MappedEnum> my_map(MappedEnum::Invalid);
    my_map.Map(3000, 3500, MappedEnum::Valid_1);
    my_map.Unmap(3200, 3600);
    my_map.Map(4000, 4500, MappedEnum::Valid_2);
    my_map.Map(4200, 4400, MappedEnum::Valid_2);
    my_map.Map(4200, 4400, MappedEnum::Valid_1);
    REQUIRE(my_map.GetContinuousSizeFrom(4200) == 200);
    REQUIRE(my_map.GetContinuousSizeFrom(3000) == 200);
    REQUIRE(my_map.GetContinuousSizeFrom(2900) == 0);

    REQUIRE(my_map.GetValueAt(2900) == MappedEnum::Invalid);
    REQUIRE(my_map.GetValueAt(3100) == MappedEnum::Valid_1);
    REQUIRE(my_map.GetValueAt(3000) == MappedEnum::Valid_1);
    REQUIRE(my_map.GetValueAt(3200) == MappedEnum::Invalid);

    REQUIRE(my_map.GetValueAt(4199) == MappedEnum::Valid_2);
    REQUIRE(my_map.GetValueAt(4200) == MappedEnum::Valid_1);
    REQUIRE(my_map.GetValueAt(4400) == MappedEnum::Valid_2);
    REQUIRE(my_map.GetValueAt(4500) == MappedEnum::Invalid);
    REQUIRE(my_map.GetValueAt(4600) == MappedEnum::Invalid);

    my_map.Unmap(0, 6000);
    for (u64 address = 0; address < 10000; address += 1000) {
        REQUIRE(my_map.GetContinuousSizeFrom(address) == 0);
    }

    my_map.Map(1000, 3000, MappedEnum::Valid_1);
    my_map.Map(4000, 5000, MappedEnum::Valid_1);
    my_map.Map(2500, 4100, MappedEnum::Valid_1);
    REQUIRE(my_map.GetContinuousSizeFrom(1000) == 4000);

    my_map.Map(1000, 3000, MappedEnum::Valid_1);
    my_map.Map(4000, 5000, MappedEnum::Valid_2);
    my_map.Map(2500, 4100, MappedEnum::Valid_3);
    REQUIRE(my_map.GetContinuousSizeFrom(1000) == 1500);
    REQUIRE(my_map.GetContinuousSizeFrom(2500) == 1600);
    REQUIRE(my_map.GetContinuousSizeFrom(4100) == 900);
    REQUIRE(my_map.GetValueAt(900) == MappedEnum::Invalid);
    REQUIRE(my_map.GetValueAt(1000) == MappedEnum::Valid_1);
    REQUIRE(my_map.GetValueAt(2500) == MappedEnum::Valid_3);
    REQUIRE(my_map.GetValueAt(4100) == MappedEnum::Valid_2);
    REQUIRE(my_map.GetValueAt(5000) == MappedEnum::Invalid);

    my_map.Map(2000, 6000, MappedEnum::Valid_3);
    REQUIRE(my_map.GetContinuousSizeFrom(1000) == 1000);
    REQUIRE(my_map.GetContinuousSizeFrom(3000) == 3000);
    REQUIRE(my_map.GetValueAt(1000) == MappedEnum::Valid_1);
    REQUIRE(my_map.GetValueAt(1999) == MappedEnum::Valid_1);
    REQUIRE(my_map.GetValueAt(1500) == MappedEnum::Valid_1);
    REQUIRE(my_map.GetValueAt(2001) == MappedEnum::Valid_3);
    REQUIRE(my_map.GetValueAt(5999) == MappedEnum::Valid_3);
    REQUIRE(my_map.GetValueAt(6000) == MappedEnum::Invalid);
}


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <array>
#include <cstddef>
#include <numeric>
#include <thread>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include "common/ring_buffer.h"

namespace Common {

TEST_CASE("RingBuffer: Basic Tests", "[common]") {
    RingBuffer<char, 4> buf;

    // Pushing values into a ring buffer with space should succeed.
    for (std::size_t i = 0; i < 4; i++) {
        const char elem = static_cast<char>(i);
        const std::size_t count = buf.Push(&elem, 1);
        REQUIRE(count == 1U);
    }

    REQUIRE(buf.Size() == 4U);

    // Pushing values into a full ring buffer should fail.
    {
        const char elem = static_cast<char>(42);
        const std::size_t count = buf.Push(&elem, 1);
        REQUIRE(count == 0U);
    }

    REQUIRE(buf.Size() == 4U);

    // Popping multiple values from a ring buffer with values should succeed.
    {
        const std::vector<char> popped = buf.Pop(2);
        REQUIRE(popped.size() == 2U);
        REQUIRE(popped[0] == 0);
        REQUIRE(popped[1] == 1);
    }

    REQUIRE(buf.Size() == 2U);

    // Popping a single value from a ring buffer with values should succeed.
    {
        const std::vector<char> popped = buf.Pop(1);
        REQUIRE(popped.size() == 1U);
        REQUIRE(popped[0] == 2);
    }

    REQUIRE(buf.Size() == 1U);

    // Pushing more values than space available should partially succeed.
    {
        std::vector<char> to_push(6);
        std::iota(to_push.begin(), to_push.end(), static_cast<char>(88));
        const std::size_t count = buf.Push(to_push);
        REQUIRE(count == 3U);
    }

    REQUIRE(buf.Size() == 4U);

    // Doing an unlimited pop should pop all values.
    {
        const std::vector<char> popped = buf.Pop();
        REQUIRE(popped.size() == 4U);
        REQUIRE(popped[0] == 3);
        REQUIRE(popped[1] == 88);
        REQUIRE(popped[2] == 89);
        REQUIRE(popped[3] == 90);
    }

    REQUIRE(buf.Size() == 0U);
}

TEST_CASE("RingBuffer: Threaded Test", "[common]") {
    RingBuffer<char, 8> buf;
    const char seed = 42;
    const std::size_t count = 1000000;
    std::size_t full = 0;
    std::size_t empty = 0;

    const auto next_value = [](std::array<char, 2>& value) {
        value[0] += 1;
        value[1] += 2;
    };

    std::thread producer{[&] {
        std::array<char, 2> value = {seed, seed};
        std::size_t i = 0;
        while (i < count) {
            if (const std::size_t c = buf.Push(&value[0], 2); c > 0) {
                REQUIRE(c == 2U);
                i++;
                next_value(value);
            } else {
                full++;
                std::this_thread::yield();
            }
        }
    }};

    std::thread consumer{[&] {
        std::array<char, 2> value = {seed, seed};
        std::size_t i = 0;
        while (i < count) {
            if (const std::vector<char> v = buf.Pop(2); v.size() > 0) {
                REQUIRE(v.size() == 2U);
                REQUIRE(v[0] == value[0]);
                REQUIRE(v[1] == value[1]);
                i++;
                next_value(value);
            } else {
                empty++;
                std::this_thread::yield();
            }
        }
    }};

    producer.join();
    consumer.join();

    REQUIRE(buf.Size() == 0U);
    printf("RingBuffer: Threaded Test: full: %zu, empty: %zu\n", full, empty);
}

} // namespace Common


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <array>
#include <cstring>
#include <span>
#include <catch2/catch_test_macros.hpp>
#include "common/common_types.h"
#include "common/scratch_buffer.h"

namespace Common {

TEST_CASE("ScratchBuffer: Basic Test", "[common]") {
    ScratchBuffer<u8> buf;

    REQUIRE(buf.size() == 0U);
    REQUIRE(buf.capacity() == 0U);

    std::array<u8, 10> payload;
    payload.fill(66);

    buf.resize(payload.size());
    REQUIRE(buf.size() == payload.size());
    REQUIRE(buf.capacity() == payload.size());

    std::memcpy(buf.data(), payload.data(), payload.size());
    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }
}

TEST_CASE("ScratchBuffer: resize_destructive Grow", "[common]") {
    std::array<u8, 10> payload;
    payload.fill(66);

    ScratchBuffer<u8> buf(payload.size());
    REQUIRE(buf.size() == payload.size());
    REQUIRE(buf.capacity() == payload.size());

    // Increasing the size should reallocate the buffer
    buf.resize_destructive(payload.size() * 2);
    REQUIRE(buf.size() == payload.size() * 2);
    REQUIRE(buf.capacity() == payload.size() * 2);

    // Since the buffer is not value initialized, reading its data will be garbage
}

TEST_CASE("ScratchBuffer: resize_destructive Shrink", "[common]") {
    std::array<u8, 10> payload;
    payload.fill(66);

    ScratchBuffer<u8> buf(payload.size());
    REQUIRE(buf.size() == payload.size());
    REQUIRE(buf.capacity() == payload.size());

    std::memcpy(buf.data(), payload.data(), payload.size());
    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }

    // Decreasing the size should not cause a buffer reallocation
    // This can be tested by ensuring the buffer capacity and data has not changed,
    buf.resize_destructive(1U);
    REQUIRE(buf.size() == 1U);
    REQUIRE(buf.capacity() == payload.size());

    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }
}

TEST_CASE("ScratchBuffer: resize Grow u8", "[common]") {
    std::array<u8, 10> payload;
    payload.fill(66);

    ScratchBuffer<u8> buf(payload.size());
    REQUIRE(buf.size() == payload.size());
    REQUIRE(buf.capacity() == payload.size());

    std::memcpy(buf.data(), payload.data(), payload.size());
    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }

    // Increasing the size should reallocate the buffer
    buf.resize(payload.size() * 2);
    REQUIRE(buf.size() == payload.size() * 2);
    REQUIRE(buf.capacity() == payload.size() * 2);

    // resize() keeps the previous data intact
    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }
}

TEST_CASE("ScratchBuffer: resize Grow u64", "[common]") {
    std::array<u64, 10> payload;
    payload.fill(6666);

    ScratchBuffer<u64> buf(payload.size());
    REQUIRE(buf.size() == payload.size());
    REQUIRE(buf.capacity() == payload.size());

    std::memcpy(buf.data(), payload.data(), payload.size() * sizeof(u64));
    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }

    // Increasing the size should reallocate the buffer
    buf.resize(payload.size() * 2);
    REQUIRE(buf.size() == payload.size() * 2);
    REQUIRE(buf.capacity() == payload.size() * 2);

    // resize() keeps the previous data intact
    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }
}

TEST_CASE("ScratchBuffer: resize Shrink", "[common]") {
    std::array<u8, 10> payload;
    payload.fill(66);

    ScratchBuffer<u8> buf(payload.size());
    REQUIRE(buf.size() == payload.size());
    REQUIRE(buf.capacity() == payload.size());

    std::memcpy(buf.data(), payload.data(), payload.size());
    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }

    // Decreasing the size should not cause a buffer reallocation
    // This can be tested by ensuring the buffer capacity and data has not changed,
    buf.resize(1U);
    REQUIRE(buf.size() == 1U);
    REQUIRE(buf.capacity() == payload.size());

    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }
}

TEST_CASE("ScratchBuffer: Span Size", "[common]") {
    std::array<u8, 10> payload;
    payload.fill(66);

    ScratchBuffer<u8> buf(payload.size());
    REQUIRE(buf.size() == payload.size());
    REQUIRE(buf.capacity() == payload.size());

    std::memcpy(buf.data(), payload.data(), payload.size());
    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }

    buf.resize(3U);
    REQUIRE(buf.size() == 3U);
    REQUIRE(buf.capacity() == payload.size());

    const auto buf_span = std::span<u8>(buf);
    // The span size is the last requested size of the buffer, not its capacity
    REQUIRE(buf_span.size() == buf.size());

    for (size_t i = 0; i < buf_span.size(); ++i) {
        REQUIRE(buf_span[i] == buf[i]);
        REQUIRE(buf_span[i] == payload[i]);
    }
}

TEST_CASE("ScratchBuffer: Span Writes", "[common]") {
    std::array<u8, 10> payload;
    payload.fill(66);

    ScratchBuffer<u8> buf(payload.size());
    REQUIRE(buf.size() == payload.size());
    REQUIRE(buf.capacity() == payload.size());

    std::memcpy(buf.data(), payload.data(), payload.size());
    for (size_t i = 0; i < payload.size(); ++i) {
        REQUIRE(buf[i] == payload[i]);
    }

    buf.resize(3U);
    REQUIRE(buf.size() == 3U);
    REQUIRE(buf.capacity() == payload.size());

    const auto buf_span = std::span<u8>(buf);
    REQUIRE(buf_span.size() == buf.size());

    for (size_t i = 0; i < buf_span.size(); ++i) {
        const auto new_value = static_cast<u8>(i + 1U);
        // Writes to a span of the scratch buffer will propagate to the buffer itself
        buf_span[i] = new_value;
        REQUIRE(buf[i] == new_value);
    }
}

} // namespace Common


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <string>

#include <catch2/catch_test_macros.hpp>

#include "common/unique_function.h"

namespace {
struct Noisy {
    Noisy() : state{"Default constructed"} {}
    Noisy(Noisy&& rhs) noexcept : state{"Move constructed"} {
        rhs.state = "Moved away";
    }
    Noisy& operator=(Noisy&& rhs) noexcept {
        state = "Move assigned";
        rhs.state = "Moved away";
        return *this;
    }
    Noisy(const Noisy&) : state{"Copied constructed"} {}
    Noisy& operator=(const Noisy&) {
        state = "Copied assigned";
        return *this;
    }

    std::string state;
};
} // Anonymous namespace

TEST_CASE("UniqueFunction", "[common]") {
    SECTION("Capture reference") {
        int value = 0;
        Common::UniqueFunction<void> func = [&value] { value = 5; };
        func();
        REQUIRE(value == 5);
    }
    SECTION("Capture pointer") {
        int value = 0;
        int* pointer = &value;
        Common::UniqueFunction<void> func = [pointer] { *pointer = 5; };
        func();
        REQUIRE(value == 5);
    }
    SECTION("Move object") {
        Noisy noisy;
        REQUIRE(noisy.state == "Default constructed");

        Common::UniqueFunction<void> func = [noisy_inner = std::move(noisy)] {
            REQUIRE(noisy_inner.state == "Move constructed");
        };
        REQUIRE(noisy.state == "Moved away");
        func();
    }
    SECTION("Move construct function") {
        int value = 0;
        Common::UniqueFunction<void> func = [&value] { value = 5; };
        Common::UniqueFunction<void> new_func = std::move(func);
        new_func();
        REQUIRE(value == 5);
    }
    SECTION("Move assign function") {
        int value = 0;
        Common::UniqueFunction<void> func = [&value] { value = 5; };
        Common::UniqueFunction<void> new_func;
        new_func = std::move(func);
        new_func();
        REQUIRE(value == 5);
    }
    SECTION("Default construct then assign function") {
        int value = 0;
        Common::UniqueFunction<void> func;
        func = [&value] { value = 5; };
        func();
        REQUIRE(value == 5);
    }
    SECTION("Pass arguments") {
        int result = 0;
        Common::UniqueFunction<void, int, int> func = [&result](int a, int b) { result = a + b; };
        func(5, 4);
        REQUIRE(result == 9);
    }
    SECTION("Pass arguments and return value") {
        Common::UniqueFunction<int, int, int> func = [](int a, int b) { return a + b; };
        REQUIRE(func(5, 4) == 9);
    }
    SECTION("Destructor") {
        int num_destroyed = 0;
        struct Foo {
            Foo(int* num_) : num{num_} {}
            Foo(Foo&& rhs) : num{std::exchange(rhs.num, nullptr)} {}
            Foo(const Foo&) = delete;

            ~Foo() {
                if (num) {
                    ++*num;
                }
            }

            int* num = nullptr;
        };
        Foo object{&num_destroyed};
        {
            Common::UniqueFunction<void> func = [object_inner = std::move(object)] {};
            REQUIRE(num_destroyed == 0);
        }
        REQUIRE(num_destroyed == 1);
    }
}


code:
// SPDX-FileCopyrightText: 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <catch2/catch_test_macros.hpp>

#include <array>
#include <bitset>
#include <chrono>
#include <cstdlib>
#include <memory>
#include <optional>
#include <string>

#include "core/core.h"
#include "core/core_timing.h"

namespace {
// Numbers are chosen randomly to make sure the correct one is given.
constexpr std::array<u64, 5> calls_order{{2, 0, 1, 4, 3}};
std::array<s64, 5> delays{};
std::bitset<5> callbacks_ran_flags;
u64 expected_callback = 0;

template <unsigned int IDX>
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(s64 time,
                                                             std::chrono::nanoseconds ns_late) {
    static_assert(IDX < callbacks_ran_flags.size(), "IDX out of range");
    callbacks_ran_flags.set(IDX);
    delays[IDX] = ns_late.count();
    ++expected_callback;
    return std::nullopt;
}

struct ScopeInit final {
    ScopeInit() {
        core_timing.SetMulticore(true);
        core_timing.Initialize([]() {});
    }

    Core::Timing::CoreTiming core_timing;
};

u64 TestTimerSpeed(Core::Timing::CoreTiming& core_timing) {
    const u64 start = core_timing.GetGlobalTimeNs().count();
    volatile u64 placebo = 0;
    for (std::size_t i = 0; i < 1000; i++) {
        placebo = placebo + core_timing.GetGlobalTimeNs().count();
    }
    const u64 end = core_timing.GetGlobalTimeNs().count();
    return end - start;
}

} // Anonymous namespace

TEST_CASE("CoreTiming[BasicOrder]", "[core]") {
    ScopeInit guard;
    auto& core_timing = guard.core_timing;
    std::vector<std::shared_ptr<Core::Timing::EventType>> events{
        Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>),
        Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>),
        Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>),
        Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>),
        Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>),
    };

    expected_callback = 0;

    core_timing.SyncPause(true);

    const u64 one_micro = 1000U;
    for (std::size_t i = 0; i < events.size(); i++) {
        const u64 order = calls_order[i];
        const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};

        core_timing.ScheduleEvent(future_ns, events[order]);
    }
    /// test pause
    REQUIRE(callbacks_ran_flags.none());

    core_timing.Pause(false); // No need to sync

    while (core_timing.HasPendingEvents())
        ;

    REQUIRE(callbacks_ran_flags.all());

    for (std::size_t i = 0; i < delays.size(); i++) {
        const double delay = static_cast<double>(delays[i]);
        const double micro = delay / 1000.0f;
        const double mili = micro / 1000.0f;
        printf("HostTimer Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
    }
}

TEST_CASE("CoreTiming[BasicOrderNoPausing]", "[core]") {
    ScopeInit guard;
    auto& core_timing = guard.core_timing;
    std::vector<std::shared_ptr<Core::Timing::EventType>> events{
        Core::Timing::CreateEvent("callbackA", HostCallbackTemplate<0>),
        Core::Timing::CreateEvent("callbackB", HostCallbackTemplate<1>),
        Core::Timing::CreateEvent("callbackC", HostCallbackTemplate<2>),
        Core::Timing::CreateEvent("callbackD", HostCallbackTemplate<3>),
        Core::Timing::CreateEvent("callbackE", HostCallbackTemplate<4>),
    };

    core_timing.SyncPause(true);
    core_timing.SyncPause(false);

    expected_callback = 0;

    const u64 start = core_timing.GetGlobalTimeNs().count();
    const u64 one_micro = 1000U;

    for (std::size_t i = 0; i < events.size(); i++) {
        const u64 order = calls_order[i];
        const auto future_ns = std::chrono::nanoseconds{static_cast<s64>(i * one_micro + 100)};
        core_timing.ScheduleEvent(future_ns, events[order]);
    }

    const u64 end = core_timing.GetGlobalTimeNs().count();
    const double scheduling_time = static_cast<double>(end - start);
    const double timer_time = static_cast<double>(TestTimerSpeed(core_timing));

    while (core_timing.HasPendingEvents())
        ;

    REQUIRE(callbacks_ran_flags.all());

    for (std::size_t i = 0; i < delays.size(); i++) {
        const double delay = static_cast<double>(delays[i]);
        const double micro = delay / 1000.0f;
        const double mili = micro / 1000.0f;
        printf("HostTimer No Pausing Delay[%zu]: %.3f %.6f\n", i, micro, mili);
    }

    const double micro = scheduling_time / 1000.0f;
    const double mili = micro / 1000.0f;
    printf("HostTimer No Pausing Scheduling Time: %.3f %.6f\n", micro, mili);
    printf("HostTimer No Pausing Timer Time: %.3f %.6f\n", timer_time / 1000.f,
           timer_time / 1000000.f);
}


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <catch2/catch_test_macros.hpp>

#include "core/internal_network/network.h"
#include "core/internal_network/sockets.h"

TEST_CASE("Network::Errors", "[core]") {
    Network::NetworkInstance network_instance; // initialize network

    Network::Socket socks[2];
    for (Network::Socket& sock : socks) {
        REQUIRE(sock.Initialize(Network::Domain::INET, Network::Type::STREAM,
                                Network::Protocol::TCP) == Network::Errno::SUCCESS);
    }

    Network::SockAddrIn addr{
        Network::Domain::INET,
        {127, 0, 0, 1},
        1, // hopefully nobody running this test has something listening on port 1
    };
    REQUIRE(socks[0].Connect(addr) == Network::Errno::CONNREFUSED);

    std::vector<u8> message{1, 2, 3, 4};
    REQUIRE(socks[1].Recv(0, message).second == Network::Errno::NOTCONN);
}


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <array>
#include <string>
#include <thread>
#include <boost/asio.hpp>
#include <boost/crc.hpp>
#include <catch2/catch_test_macros.hpp>

#include "input_common/drivers/udp_client.h"
#include "input_common/helpers/udp_protocol.h"

class FakeCemuhookServer {
public:
    FakeCemuhookServer()
        : socket(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)) {}

    ~FakeCemuhookServer() {
        is_running = false;
        boost::system::error_code error_code;
        socket.shutdown(boost::asio::socket_base::shutdown_both, error_code);
        socket.close();
        if (handler.joinable()) {
            handler.join();
        }
    }

    u16 GetPort() {
        return socket.local_endpoint().port();
    }

    std::string GetHost() {
        return socket.local_endpoint().address().to_string();
    }

    void Run(const std::vector<InputCommon::CemuhookUDP::Response::TouchPad> touch_movement_path) {
        constexpr size_t HeaderSize = sizeof(InputCommon::CemuhookUDP::Header);
        constexpr size_t PadDataSize =
            sizeof(InputCommon::CemuhookUDP::Message<InputCommon::CemuhookUDP::Response::PadData>);

        REQUIRE(touch_movement_path.size() > 0);
        is_running = true;
        handler = std::thread([touch_movement_path, this]() {
            auto current_touch_position = touch_movement_path.begin();
            while (is_running) {
                boost::asio::ip::udp::endpoint sender_endpoint;
                boost::system::error_code error_code;
                auto received_size = socket.receive_from(boost::asio::buffer(receive_buffer),
                                                         sender_endpoint, 0, error_code);

                if (received_size < HeaderSize) {
                    continue;
                }

                InputCommon::CemuhookUDP::Header header{};
                std::memcpy(&header, receive_buffer.data(), HeaderSize);
                switch (header.type) {
                case InputCommon::CemuhookUDP::Type::PadData: {
                    InputCommon::CemuhookUDP::Response::PadData pad_data{};
                    pad_data.touch[0] = *current_touch_position;
                    const auto pad_message = InputCommon::CemuhookUDP::CreateMessage(
                        InputCommon::CemuhookUDP::SERVER_MAGIC, pad_data, 0);
                    std::memcpy(send_buffer.data(), &pad_message, PadDataSize);
                    socket.send_to(boost::asio::buffer(send_buffer, PadDataSize), sender_endpoint,
                                   0, error_code);

                    bool can_advance =
                        std::next(current_touch_position) != touch_movement_path.end();
                    if (can_advance) {
                        std::advance(current_touch_position, 1);
                    }
                    break;
                }
                case InputCommon::CemuhookUDP::Type::PortInfo:
                case InputCommon::CemuhookUDP::Type::Version:
                default:
                    break;
                }
            }
        });
    }

private:
    boost::asio::io_service io_service;
    boost::asio::ip::udp::socket socket;
    std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> send_buffer;
    std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> receive_buffer;
    bool is_running = false;
    std::thread handler;
};

TEST_CASE("CalibrationConfigurationJob completed", "[input_common]") {
    Common::Event complete_event;
    FakeCemuhookServer server;
    server.Run({{
                    .is_active = 1,
                    .x = 0,
                    .y = 0,
                },
                {
                    .is_active = 1,
                    .x = 200,
                    .y = 200,
                }});

    InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status{};
    u16 min_x{};
    u16 min_y{};
    u16 max_x{};
    u16 max_y{};
    InputCommon::CemuhookUDP::CalibrationConfigurationJob job(
        server.GetHost(), server.GetPort(),
        [&status,
         &complete_event](InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status_) {
            status = status_;
            if (status ==
                InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed) {
                complete_event.Set();
            }
        },
        [&](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
            min_x = min_x_;
            min_y = min_y_;
            max_x = max_x_;
            max_y = max_y_;
        });

    complete_event.WaitUntil(std::chrono::system_clock::now() + std::chrono::seconds(10));
    REQUIRE(status == InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed);
    REQUIRE(min_x == 0);
    REQUIRE(min_y == 0);
    REQUIRE(max_x == 200);
    REQUIRE(max_y == 200);
}


code:
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/common_precompiled_headers.h"


code:
// 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::DEVICE_PAGEBITS};
        const u64 page_end{(addr + size + Core::DEVICE_PAGESIZE - 1) >> Core::DEVICE_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::DEVICE_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; });
    REQUIRE(num == 0);
    num = 0;
    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);
}


code:
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later

add_subdirectory(host_shaders)

if(LIBVA_FOUND)
    set_source_files_properties(host1x/ffmpeg/ffmpeg.cpp
        PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
    list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
endif()

add_library(video_core STATIC
    buffer_cache/buffer_base.h
    buffer_cache/buffer_cache_base.h
    buffer_cache/buffer_cache.cpp
    buffer_cache/buffer_cache.h
    buffer_cache/memory_tracker_base.h
    buffer_cache/usage_tracker.h
    buffer_cache/word_manager.h
    cache_types.h
    capture.h
    cdma_pusher.cpp
    cdma_pusher.h
    compatible_formats.cpp
    compatible_formats.h
    control/channel_state.cpp
    control/channel_state.h
    control/channel_state_cache.cpp
    control/channel_state_cache.h
    control/scheduler.cpp
    control/scheduler.h
    delayed_destruction_ring.h
    dirty_flags.cpp
    dirty_flags.h
    dma_pusher.cpp
    dma_pusher.h
    engines/sw_blitter/blitter.cpp
    engines/sw_blitter/blitter.h
    engines/sw_blitter/converter.cpp
    engines/sw_blitter/converter.h
    engines/const_buffer_info.h
    engines/draw_manager.cpp
    engines/draw_manager.h
    engines/engine_interface.h
    engines/engine_upload.cpp
    engines/engine_upload.h
    engines/fermi_2d.cpp
    engines/fermi_2d.h
    engines/kepler_compute.cpp
    engines/kepler_compute.h
    engines/kepler_memory.cpp
    engines/kepler_memory.h
    engines/maxwell_3d.cpp
    engines/maxwell_3d.h
    engines/maxwell_dma.cpp
    engines/maxwell_dma.h
    engines/puller.cpp
    engines/puller.h
    framebuffer_config.cpp
    framebuffer_config.h
    fsr.cpp
    fsr.h
    host1x/codecs/codec.cpp
    host1x/codecs/codec.h
    host1x/codecs/h264.cpp
    host1x/codecs/h264.h
    host1x/codecs/vp8.cpp
    host1x/codecs/vp8.h
    host1x/codecs/vp9.cpp
    host1x/codecs/vp9.h
    host1x/codecs/vp9_types.h
    host1x/ffmpeg/ffmpeg.cpp
    host1x/ffmpeg/ffmpeg.h
    host1x/control.cpp
    host1x/control.h
    host1x/gpu_device_memory_manager.cpp
    host1x/gpu_device_memory_manager.h
    host1x/host1x.cpp
    host1x/host1x.h
    host1x/nvdec.cpp
    host1x/nvdec.h
    host1x/nvdec_common.h
    host1x/sync_manager.cpp
    host1x/sync_manager.h
    host1x/syncpoint_manager.cpp
    host1x/syncpoint_manager.h
    host1x/vic.cpp
    host1x/vic.h
    macro/macro.cpp
    macro/macro.h
    macro/macro_hle.cpp
    macro/macro_hle.h
    macro/macro_interpreter.cpp
    macro/macro_interpreter.h
    fence_manager.h
    gpu.cpp
    gpu.h
    gpu_thread.cpp
    gpu_thread.h
    guest_memory.h
    invalidation_accumulator.h
    memory_manager.cpp
    memory_manager.h
    precompiled_headers.h
    present.h
    pte_kind.h
    query_cache/bank_base.h
    query_cache/query_base.h
    query_cache/query_cache_base.h
    query_cache/query_cache.h
    query_cache/query_stream.h
    query_cache/types.h
    query_cache.h
    rasterizer_interface.h
    renderer_base.cpp
    renderer_base.h
    renderer_null/null_rasterizer.cpp
    renderer_null/null_rasterizer.h
    renderer_null/renderer_null.cpp
    renderer_null/renderer_null.h
    renderer_opengl/present/filters.cpp
    renderer_opengl/present/filters.h
    renderer_opengl/present/fsr.cpp
    renderer_opengl/present/fsr.h
    renderer_opengl/present/fxaa.cpp
    renderer_opengl/present/fxaa.h
    renderer_opengl/present/layer.cpp
    renderer_opengl/present/layer.h
    renderer_opengl/present/present_uniforms.h
    renderer_opengl/present/smaa.cpp
    renderer_opengl/present/smaa.h
    renderer_opengl/present/util.h
    renderer_opengl/present/window_adapt_pass.cpp
    renderer_opengl/present/window_adapt_pass.h
    renderer_opengl/blit_image.cpp
    renderer_opengl/blit_image.h
    renderer_opengl/gl_blit_screen.cpp
    renderer_opengl/gl_blit_screen.h
    renderer_opengl/gl_buffer_cache_base.cpp
    renderer_opengl/gl_buffer_cache.cpp
    renderer_opengl/gl_buffer_cache.h
    renderer_opengl/gl_compute_pipeline.cpp
    renderer_opengl/gl_compute_pipeline.h
    renderer_opengl/gl_device.cpp
    renderer_opengl/gl_device.h
    renderer_opengl/gl_fence_manager.cpp
    renderer_opengl/gl_fence_manager.h
    renderer_opengl/gl_graphics_pipeline.cpp
    renderer_opengl/gl_graphics_pipeline.h
    renderer_opengl/gl_rasterizer.cpp
    renderer_opengl/gl_rasterizer.h
    renderer_opengl/gl_resource_manager.cpp
    renderer_opengl/gl_resource_manager.h
    renderer_opengl/gl_shader_cache.cpp
    renderer_opengl/gl_shader_cache.h
    renderer_opengl/gl_shader_manager.cpp
    renderer_opengl/gl_shader_manager.h
    renderer_opengl/gl_shader_context.h
    renderer_opengl/gl_shader_util.cpp
    renderer_opengl/gl_shader_util.h
    renderer_opengl/gl_state_tracker.cpp
    renderer_opengl/gl_state_tracker.h
    renderer_opengl/gl_staging_buffer_pool.cpp
    renderer_opengl/gl_staging_buffer_pool.h
    renderer_opengl/gl_texture_cache.cpp
    renderer_opengl/gl_texture_cache.h
    renderer_opengl/gl_texture_cache_base.cpp
    renderer_opengl/gl_query_cache.cpp
    renderer_opengl/gl_query_cache.h
    renderer_opengl/maxwell_to_gl.h
    renderer_opengl/renderer_opengl.cpp
    renderer_opengl/renderer_opengl.h
    renderer_opengl/util_shaders.cpp
    renderer_opengl/util_shaders.h
    renderer_vulkan/present/anti_alias_pass.h
    renderer_vulkan/present/filters.cpp
    renderer_vulkan/present/filters.h
    renderer_vulkan/present/fsr.cpp
    renderer_vulkan/present/fsr.h
    renderer_vulkan/present/fxaa.cpp
    renderer_vulkan/present/fxaa.h
    renderer_vulkan/present/layer.cpp
    renderer_vulkan/present/layer.h
    renderer_vulkan/present/present_push_constants.h
    renderer_vulkan/present/smaa.cpp
    renderer_vulkan/present/smaa.h
    renderer_vulkan/present/util.cpp
    renderer_vulkan/present/util.h
    renderer_vulkan/present/window_adapt_pass.cpp
    renderer_vulkan/present/window_adapt_pass.h
    renderer_vulkan/blit_image.cpp
    renderer_vulkan/blit_image.h
    renderer_vulkan/fixed_pipeline_state.cpp
    renderer_vulkan/fixed_pipeline_state.h
    renderer_vulkan/maxwell_to_vk.cpp
    renderer_vulkan/maxwell_to_vk.h
    renderer_vulkan/pipeline_helper.h
    renderer_vulkan/pipeline_statistics.cpp
    renderer_vulkan/pipeline_statistics.h
    renderer_vulkan/renderer_vulkan.h
    renderer_vulkan/renderer_vulkan.cpp
    renderer_vulkan/vk_blit_screen.cpp
    renderer_vulkan/vk_blit_screen.h
    renderer_vulkan/vk_buffer_cache_base.cpp
    renderer_vulkan/vk_buffer_cache.cpp
    renderer_vulkan/vk_buffer_cache.h
    renderer_vulkan/vk_command_pool.cpp
    renderer_vulkan/vk_command_pool.h
    renderer_vulkan/vk_compute_pass.cpp
    renderer_vulkan/vk_compute_pass.h
    renderer_vulkan/vk_compute_pipeline.cpp
    renderer_vulkan/vk_compute_pipeline.h
    renderer_vulkan/vk_descriptor_pool.cpp
    renderer_vulkan/vk_descriptor_pool.h
    renderer_vulkan/vk_fence_manager.cpp
    renderer_vulkan/vk_fence_manager.h
    renderer_vulkan/vk_graphics_pipeline.cpp
    renderer_vulkan/vk_graphics_pipeline.h
    renderer_vulkan/vk_master_semaphore.cpp
    renderer_vulkan/vk_master_semaphore.h
    renderer_vulkan/vk_pipeline_cache.cpp
    renderer_vulkan/vk_pipeline_cache.h
    renderer_vulkan/vk_present_manager.cpp
    renderer_vulkan/vk_present_manager.h
    renderer_vulkan/vk_query_cache.cpp
    renderer_vulkan/vk_query_cache.h
    renderer_vulkan/vk_rasterizer.cpp
    renderer_vulkan/vk_rasterizer.h
    renderer_vulkan/vk_render_pass_cache.cpp
    renderer_vulkan/vk_render_pass_cache.h
    renderer_vulkan/vk_resource_pool.cpp
    renderer_vulkan/vk_resource_pool.h
    renderer_vulkan/vk_scheduler.cpp
    renderer_vulkan/vk_scheduler.h
    renderer_vulkan/vk_shader_util.cpp
    renderer_vulkan/vk_shader_util.h
    renderer_vulkan/vk_staging_buffer_pool.cpp
    renderer_vulkan/vk_staging_buffer_pool.h
    renderer_vulkan/vk_state_tracker.cpp
    renderer_vulkan/vk_state_tracker.h
    renderer_vulkan/vk_swapchain.cpp
    renderer_vulkan/vk_swapchain.h
    renderer_vulkan/vk_texture_cache.cpp
    renderer_vulkan/vk_texture_cache.h
    renderer_vulkan/vk_texture_cache_base.cpp
    renderer_vulkan/vk_turbo_mode.cpp
    renderer_vulkan/vk_turbo_mode.h
    renderer_vulkan/vk_update_descriptor.cpp
    renderer_vulkan/vk_update_descriptor.h
    shader_cache.cpp
    shader_cache.h
    shader_environment.cpp
    shader_environment.h
    shader_notify.cpp
    shader_notify.h
    smaa_area_tex.h
    smaa_search_tex.h
    surface.cpp
    surface.h
    texture_cache/accelerated_swizzle.cpp
    texture_cache/accelerated_swizzle.h
    texture_cache/decode_bc.cpp
    texture_cache/decode_bc.h
    texture_cache/descriptor_table.h
    texture_cache/formatter.cpp
    texture_cache/formatter.h
    texture_cache/format_lookup_table.cpp
    texture_cache/format_lookup_table.h
    texture_cache/image_base.cpp
    texture_cache/image_base.h
    texture_cache/image_info.cpp
    texture_cache/image_info.h
    texture_cache/image_view_base.cpp
    texture_cache/image_view_base.h
    texture_cache/image_view_info.cpp
    texture_cache/image_view_info.h
    texture_cache/render_targets.h
    texture_cache/samples_helper.h
    texture_cache/texture_cache.cpp
    texture_cache/texture_cache.h
    texture_cache/texture_cache_base.h
    texture_cache/types.h
    texture_cache/util.cpp
    texture_cache/util.h
    textures/astc.h
    textures/astc.cpp
    textures/bcn.cpp
    textures/bcn.h
    textures/decoders.cpp
    textures/decoders.h
    textures/texture.cpp
    textures/texture.h
    textures/workers.cpp
    textures/workers.h
    transform_feedback.cpp
    transform_feedback.h
    video_core.cpp
    video_core.h
    vulkan_common/vulkan_debug_callback.cpp
    vulkan_common/vulkan_debug_callback.h
    vulkan_common/vulkan_device.cpp
    vulkan_common/vulkan_device.h
    vulkan_common/vulkan_instance.cpp
    vulkan_common/vulkan_instance.h
    vulkan_common/vulkan_library.cpp
    vulkan_common/vulkan_library.h
    vulkan_common/vulkan_memory_allocator.cpp
    vulkan_common/vulkan_memory_allocator.h
    vulkan_common/vulkan_surface.cpp
    vulkan_common/vulkan_surface.h
    vulkan_common/vulkan_wrapper.cpp
    vulkan_common/vulkan_wrapper.h
    vulkan_common/nsight_aftermath_tracker.cpp
    vulkan_common/nsight_aftermath_tracker.h
    vulkan_common/vma.cpp
    vulkan_common/vma.h
    vulkan_common/vulkan.h
)

target_link_libraries(video_core PUBLIC common core)
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder)

if (YUZU_USE_BUNDLED_FFMPEG AND NOT (WIN32 OR ANDROID))
    add_dependencies(video_core ffmpeg-build)
endif()

target_include_directories(video_core PRIVATE ${FFmpeg_INCLUDE_DIR})
target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})

add_dependencies(video_core host_shaders)
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
target_link_libraries(video_core PRIVATE sirit Vulkan::Headers Vulkan::UtilityHeaders GPUOpen::VulkanMemoryAllocator)

if (ENABLE_NSIGHT_AFTERMATH)
    if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK})
        message(FATAL_ERROR "Environment variable NSIGHT_AFTERMATH_SDK has to be provided")
    endif()
    if (NOT WIN32)
        message(FATAL_ERROR "Nsight Aftermath doesn't support non-Windows platforms")
    endif()
    target_compile_definitions(video_core PRIVATE HAS_NSIGHT_AFTERMATH)
    target_include_directories(video_core PRIVATE "$ENV{NSIGHT_AFTERMATH_SDK}/include")
endif()

if (MSVC)
    target_compile_options(video_core PRIVATE
        /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
        /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data
    )
else()
    if (APPLE)
        # error: declaration shadows a typedef in 'interval_base_set<SubType, DomainT, Compare, Interval, Alloc>'
        # error: implicit conversion loses integer precision: 'int' to 'boost::icl::bound_type' (aka 'unsigned char')
        target_compile_options(video_core PRIVATE -Wno-shadow -Wno-unused-local-typedef)
    else()
        target_compile_options(video_core PRIVATE -Werror=conversion)
    endif()

    target_compile_options(video_core PRIVATE
        -Wno-sign-conversion
    )

    # xbyak
    set_source_files_properties(macro/macro_jit_x64.cpp PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-shadow")

    # VMA
    set_source_files_properties(vulkan_common/vma.cpp PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-unused-variable;-Wno-unused-parameter;-Wno-missing-field-initializers")
endif()

if (ARCHITECTURE_x86_64)
    target_sources(video_core PRIVATE
        macro/macro_jit_x64.cpp
        macro/macro_jit_x64.h
    )
    target_link_libraries(video_core PUBLIC xbyak::xbyak)
endif()

if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
    target_link_libraries(video_core PRIVATE dynarmic::dynarmic)
endif()

if (YUZU_USE_PRECOMPILED_HEADERS)
    target_precompile_headers(video_core PRIVATE precompiled_headers.h)
endif()

if (YUZU_ENABLE_LTO)
  set_property(TARGET video_core PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()

if (ANDROID AND ARCHITECTURE_arm64)
    target_link_libraries(video_core PRIVATE adrenotools)
endif()

create_target_directory_groups(video_core)


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <algorithm>
#include <bit>
#include <limits>
#include <utility>

#include "common/alignment.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "video_core/buffer_cache/word_manager.h"

namespace VideoCommon {

enum class BufferFlagBits {
    Picked = 1 << 0,
    CachedWrites = 1 << 1,
    PreemtiveDownload = 1 << 2,
};
DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits)

/// Tag for creating null buffers with no storage or size
struct NullBufferParams {};

/**
 * Range tracking buffer container.
 *
 * It keeps track of the modified CPU and GPU ranges on a CPU page granularity, notifying the given
 * rasterizer about state changes in the tracking behavior of the buffer.
 *
 * The buffer size and address is forcefully aligned to CPU page boundaries.
 */
class BufferBase {
public:
    static constexpr u64 BASE_PAGE_BITS = 16;
    static constexpr u64 BASE_PAGE_SIZE = 1ULL << BASE_PAGE_BITS;

    explicit BufferBase(VAddr cpu_addr_, u64 size_bytes_)
        : cpu_addr{cpu_addr_}, size_bytes{size_bytes_} {}

    explicit BufferBase(NullBufferParams) {}

    BufferBase& operator=(const BufferBase&) = delete;
    BufferBase(const BufferBase&) = delete;

    BufferBase& operator=(BufferBase&&) = default;
    BufferBase(BufferBase&&) = default;

    /// Mark buffer as picked
    void Pick() noexcept {
        flags |= BufferFlagBits::Picked;
    }

    void MarkPreemtiveDownload() noexcept {
        flags |= BufferFlagBits::PreemtiveDownload;
    }

    /// Unmark buffer as picked
    void Unpick() noexcept {
        flags &= ~BufferFlagBits::Picked;
    }

    /// Increases the likeliness of this being a stream buffer
    void IncreaseStreamScore(int score) noexcept {
        stream_score += score;
    }

    /// Returns the likeliness of this being a stream buffer
    [[nodiscard]] int StreamScore() const noexcept {
        return stream_score;
    }

    /// Returns true when vaddr -> vaddr+size is fully contained in the buffer
    [[nodiscard]] bool IsInBounds(VAddr addr, u64 size) const noexcept {
        return addr >= cpu_addr && addr + size <= cpu_addr + SizeBytes();
    }

    /// Returns true if the buffer has been marked as picked
    [[nodiscard]] bool IsPicked() const noexcept {
        return True(flags & BufferFlagBits::Picked);
    }

    /// Returns true when the buffer has pending cached writes
    [[nodiscard]] bool HasCachedWrites() const noexcept {
        return True(flags & BufferFlagBits::CachedWrites);
    }

    bool IsPreemtiveDownload() const noexcept {
        return True(flags & BufferFlagBits::PreemtiveDownload);
    }

    /// Returns the base CPU address of the buffer
    [[nodiscard]] VAddr CpuAddr() const noexcept {
        return cpu_addr;
    }

    /// Returns the offset relative to the given CPU address
    /// @pre IsInBounds returns true
    [[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept {
        return static_cast<u32>(other_cpu_addr - cpu_addr);
    }

    size_t getLRUID() const noexcept {
        return lru_id;
    }

    void setLRUID(size_t lru_id_) {
        lru_id = lru_id_;
    }

    size_t SizeBytes() const {
        return size_bytes;
    }

private:
    VAddr cpu_addr = 0;
    BufferFlagBits flags{};
    int stream_score = 0;
    size_t lru_id = SIZE_MAX;
    size_t size_bytes = 0;
};

} // namespace VideoCommon


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include "common/microprofile.h"
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/control/channel_state_cache.inc"

namespace VideoCommon {

MICROPROFILE_DEFINE(GPU_PrepareBuffers, "GPU", "Prepare buffers", MP_RGB(224, 128, 128));
MICROPROFILE_DEFINE(GPU_BindUploadBuffers, "GPU", "Bind and upload buffers", MP_RGB(224, 128, 128));
MICROPROFILE_DEFINE(GPU_DownloadMemory, "GPU", "Download buffers", MP_RGB(224, 128, 128));

template class VideoCommon::ChannelSetupCaches<VideoCommon::BufferCacheChannelInfo>;

} // namespace VideoCommon