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

#pragma once

#include <algorithm>
#include <bit>
#include <deque>
#include <limits>
#include <type_traits>
#include <unordered_set>
#include <utility>

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

namespace VideoCommon {

template <typename DeviceTracker>
class MemoryTrackerBase {
    static constexpr size_t MAX_CPU_PAGE_BITS = 34;
    static constexpr size_t HIGHER_PAGE_BITS = 22;
    static constexpr size_t HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS;
    static constexpr size_t HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL;
    static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS);
    static constexpr size_t MANAGER_POOL_SIZE = 32;
    static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD;
    using Manager = WordManager<DeviceTracker, WORDS_STACK_NEEDED>;

public:
    MemoryTrackerBase(DeviceTracker& device_tracker_) : device_tracker{&device_tracker_} {}
    ~MemoryTrackerBase() = default;

    /// Returns the inclusive CPU modified range in a begin end pair
    [[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
                                                        u64 query_size) noexcept {
        return IteratePairs<true>(
            query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
                return manager->template ModifiedRegion<Type::CPU>(offset, size);
            });
    }

    /// Returns the inclusive GPU modified range in a begin end pair
    [[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
                                                        u64 query_size) noexcept {
        return IteratePairs<false>(
            query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
                return manager->template ModifiedRegion<Type::GPU>(offset, size);
            });
    }

    /// Returns true if a region has been modified from the CPU
    [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
        return IteratePages<true>(
            query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
                return manager->template IsRegionModified<Type::CPU>(offset, size);
            });
    }

    /// Returns true if a region has been modified from the GPU
    [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
        return IteratePages<false>(
            query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
                return manager->template IsRegionModified<Type::GPU>(offset, size);
            });
    }

    /// Returns true if a region has been marked as Preflushable
    [[nodiscard]] bool IsRegionPreflushable(VAddr query_cpu_addr, u64 query_size) noexcept {
        return IteratePages<false>(
            query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
                return manager->template IsRegionModified<Type::Preflushable>(offset, size);
            });
    }

    /// Mark region as CPU modified, notifying the device_tracker about this change
    void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
        IteratePages<true>(dirty_cpu_addr, query_size,
                           [](Manager* manager, u64 offset, size_t size) {
                               manager->template ChangeRegionState<Type::CPU, true>(
                                   manager->GetCpuAddr() + offset, size);
                           });
    }

    /// Unmark region as CPU modified, notifying the device_tracker about this change
    void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
        IteratePages<true>(dirty_cpu_addr, query_size,
                           [](Manager* manager, u64 offset, size_t size) {
                               manager->template ChangeRegionState<Type::CPU, false>(
                                   manager->GetCpuAddr() + offset, size);
                           });
    }

    /// Mark region as modified from the host GPU
    void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
        IteratePages<true>(dirty_cpu_addr, query_size,
                           [](Manager* manager, u64 offset, size_t size) {
                               manager->template ChangeRegionState<Type::GPU, true>(
                                   manager->GetCpuAddr() + offset, size);
                           });
    }

    /// Mark region as modified from the host GPU
    void MarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
        IteratePages<true>(dirty_cpu_addr, query_size,
                           [](Manager* manager, u64 offset, size_t size) {
                               manager->template ChangeRegionState<Type::Preflushable, true>(
                                   manager->GetCpuAddr() + offset, size);
                           });
    }

    /// Unmark region as modified from the host GPU
    void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
        IteratePages<true>(dirty_cpu_addr, query_size,
                           [](Manager* manager, u64 offset, size_t size) {
                               manager->template ChangeRegionState<Type::GPU, false>(
                                   manager->GetCpuAddr() + offset, size);
                           });
    }

    /// Unmark region as modified from the host GPU
    void UnmarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
        IteratePages<true>(dirty_cpu_addr, query_size,
                           [](Manager* manager, u64 offset, size_t size) {
                               manager->template ChangeRegionState<Type::Preflushable, false>(
                                   manager->GetCpuAddr() + offset, size);
                           });
    }

    /// Mark region as modified from the CPU
    /// but don't mark it as modified until FlusHCachedWrites is called.
    void CachedCpuWrite(VAddr dirty_cpu_addr, u64 query_size) {
        IteratePages<true>(
            dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
                const VAddr cpu_address = manager->GetCpuAddr() + offset;
                manager->template ChangeRegionState<Type::CachedCPU, true>(cpu_address, size);
                cached_pages.insert(static_cast<u32>(cpu_address >> HIGHER_PAGE_BITS));
            });
    }

    /// Flushes cached CPU writes, and notify the device_tracker about the deltas
    void FlushCachedWrites(VAddr query_cpu_addr, u64 query_size) noexcept {
        IteratePages<false>(query_cpu_addr, query_size,
                            [](Manager* manager, [[maybe_unused]] u64 offset,
                               [[maybe_unused]] size_t size) { manager->FlushCachedWrites(); });
    }

    void FlushCachedWrites() noexcept {
        for (auto id : cached_pages) {
            top_tier[id]->FlushCachedWrites();
        }
        cached_pages.clear();
    }

    /// Call 'func' for each CPU modified range and unmark those pages as CPU modified
    template <typename Func>
    void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) {
        IteratePages<true>(query_cpu_range, query_size,
                           [&func](Manager* manager, u64 offset, size_t size) {
                               manager->template ForEachModifiedRange<Type::CPU, true>(
                                   manager->GetCpuAddr() + offset, size, func);
                           });
    }

    /// Call 'func' for each GPU modified range and unmark those pages as GPU modified
    template <typename Func>
    void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, bool clear, Func&& func) {
        IteratePages<false>(query_cpu_range, query_size,
                            [&func, clear](Manager* manager, u64 offset, size_t size) {
                                if (clear) {
                                    manager->template ForEachModifiedRange<Type::GPU, true>(
                                        manager->GetCpuAddr() + offset, size, func);
                                } else {
                                    manager->template ForEachModifiedRange<Type::GPU, false>(
                                        manager->GetCpuAddr() + offset, size, func);
                                }
                            });
    }

    template <typename Func>
    void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 query_size, Func&& func) {
        IteratePages<false>(query_cpu_range, query_size,
                            [&func](Manager* manager, u64 offset, size_t size) {
                                manager->template ForEachModifiedRange<Type::GPU, true>(
                                    manager->GetCpuAddr() + offset, size, func);
                            });
    }

private:
    template <bool create_region_on_fail, typename Func>
    bool IteratePages(VAddr cpu_address, size_t size, Func&& func) {
        using FuncReturn = typename std::invoke_result<Func, Manager*, u64, size_t>::type;
        static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
        std::size_t remaining_size{size};
        std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS};
        u64 page_offset{cpu_address & HIGHER_PAGE_MASK};
        while (remaining_size > 0) {
            const std::size_t copy_amount{
                std::min<std::size_t>(HIGHER_PAGE_SIZE - page_offset, remaining_size)};
            auto* manager{top_tier[page_index]};
            if (manager) {
                if constexpr (BOOL_BREAK) {
                    if (func(manager, page_offset, copy_amount)) {
                        return true;
                    }
                } else {
                    func(manager, page_offset, copy_amount);
                }
            } else if constexpr (create_region_on_fail) {
                CreateRegion(page_index);
                manager = top_tier[page_index];
                if constexpr (BOOL_BREAK) {
                    if (func(manager, page_offset, copy_amount)) {
                        return true;
                    }
                } else {
                    func(manager, page_offset, copy_amount);
                }
            }
            page_index++;
            page_offset = 0;
            remaining_size -= copy_amount;
        }
        return false;
    }

    template <bool create_region_on_fail, typename Func>
    std::pair<u64, u64> IteratePairs(VAddr cpu_address, size_t size, Func&& func) {
        std::size_t remaining_size{size};
        std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS};
        u64 page_offset{cpu_address & HIGHER_PAGE_MASK};
        u64 begin = std::numeric_limits<u64>::max();
        u64 end = 0;
        while (remaining_size > 0) {
            const std::size_t copy_amount{
                std::min<std::size_t>(HIGHER_PAGE_SIZE - page_offset, remaining_size)};
            auto* manager{top_tier[page_index]};
            const auto execute = [&] {
                auto [new_begin, new_end] = func(manager, page_offset, copy_amount);
                if (new_begin != 0 || new_end != 0) {
                    const u64 base_address = page_index << HIGHER_PAGE_BITS;
                    begin = std::min(new_begin + base_address, begin);
                    end = std::max(new_end + base_address, end);
                }
            };
            if (manager) {
                execute();
            } else if constexpr (create_region_on_fail) {
                CreateRegion(page_index);
                manager = top_tier[page_index];
                execute();
            }
            page_index++;
            page_offset = 0;
            remaining_size -= copy_amount;
        }
        if (begin < end) {
            return std::make_pair(begin, end);
        } else {
            return std::make_pair(0ULL, 0ULL);
        }
    }

    void CreateRegion(std::size_t page_index) {
        const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS;
        top_tier[page_index] = GetNewManager(base_cpu_addr);
    }

    Manager* GetNewManager(VAddr base_cpu_address) {
        const auto on_return = [&] {
            auto* new_manager = free_managers.front();
            new_manager->SetCpuAddress(base_cpu_address);
            free_managers.pop_front();
            return new_manager;
        };
        if (!free_managers.empty()) {
            return on_return();
        }
        manager_pool.emplace_back();
        auto& last_pool = manager_pool.back();
        for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
            new (&last_pool[i]) Manager(0, *device_tracker, HIGHER_PAGE_SIZE);
            free_managers.push_back(&last_pool[i]);
        }
        return on_return();
    }

    std::deque<std::array<Manager, MANAGER_POOL_SIZE>> manager_pool;
    std::deque<Manager*> free_managers;

    std::array<Manager*, NUM_HIGH_PAGES> top_tier{};

    std::unordered_set<u32> cached_pages;

    DeviceTracker* device_tracker = nullptr;
};

} // namespace VideoCommon


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

#pragma once

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

namespace VideoCommon {

class UsageTracker {
    static constexpr size_t BYTES_PER_BIT_SHIFT = 6;
    static constexpr size_t PAGE_SHIFT = 6 + BYTES_PER_BIT_SHIFT;
    static constexpr size_t PAGE_BYTES = 1 << PAGE_SHIFT;

public:
    explicit UsageTracker(size_t size) {
        const size_t num_pages = (size >> PAGE_SHIFT) + 1;
        pages.resize(num_pages, 0ULL);
    }

    void Reset() noexcept {
        std::ranges::fill(pages, 0ULL);
    }

    void Track(u64 offset, u64 size) noexcept {
        const size_t page = offset >> PAGE_SHIFT;
        const size_t page_end = (offset + size) >> PAGE_SHIFT;
        if (page_end < page || page_end >= pages.size()) {
            return;
        }
        TrackPage(page, offset, size);
        if (page == page_end) {
            return;
        }
        for (size_t i = page + 1; i < page_end; i++) {
            pages[i] = ~u64{0};
        }
        const size_t offset_end = offset + size;
        const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES);
        TrackPage(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned);
    }

    [[nodiscard]] bool IsUsed(u64 offset, u64 size) const noexcept {
        const size_t page = offset >> PAGE_SHIFT;
        const size_t page_end = (offset + size) >> PAGE_SHIFT;
        if (page_end < page || page_end >= pages.size()) {
            return false;
        }
        if (IsPageUsed(page, offset, size)) {
            return true;
        }
        for (size_t i = page + 1; i < page_end; i++) {
            if (pages[i] != 0) {
                return true;
            }
        }
        const size_t offset_end = offset + size;
        const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES);
        return IsPageUsed(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned);
    }

private:
    void TrackPage(u64 page, u64 offset, u64 size) noexcept {
        const size_t offset_in_page = offset % PAGE_BYTES;
        const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
        const size_t num_bits = std::min<size_t>(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
        const size_t mask = ~u64{0} >> (64 - num_bits);
        pages[page] |= (~u64{0} & mask) << first_bit;
    }

    bool IsPageUsed(u64 page, u64 offset, u64 size) const noexcept {
        const size_t offset_in_page = offset % PAGE_BYTES;
        const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
        const size_t num_bits = std::min<size_t>(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
        const size_t mask = ~u64{0} >> (64 - num_bits);
        const size_t mask2 = (~u64{0} & mask) << first_bit;
        return (pages[page] & mask2) != 0;
    }

private:
    std::vector<u64> pages;
};

} // namespace VideoCommon

repo_name: litucks/torzu

license: mit


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 <span>
#include <utility>

#include "common/alignment.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "video_core/host1x/gpu_device_memory_manager.h"

namespace VideoCommon {

constexpr u64 PAGES_PER_WORD = 64;
constexpr u64 BYTES_PER_PAGE = Core::DEVICE_PAGESIZE;
constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;

enum class Type {
    CPU,
    GPU,
    CachedCPU,
    Untracked,
    Preflushable,
};

/// Vector tracking modified pages tightly packed with small vector optimization
template <size_t stack_words = 1>
struct WordsArray {
    /// Returns the pointer to the words state
    [[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
        return is_short ? stack.data() : heap;
    }

    /// Returns the pointer to the words state
    [[nodiscard]] u64* Pointer(bool is_short) noexcept {
        return is_short ? stack.data() : heap;
    }

    std::array<u64, stack_words> stack{}; ///< Small buffers storage
    u64* heap;                            ///< Not-small buffers pointer to the storage
};

template <size_t stack_words = 1>
struct Words {
    explicit Words() = default;
    explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
        num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
        if (IsShort()) {
            cpu.stack.fill(~u64{0});
            gpu.stack.fill(0);
            cached_cpu.stack.fill(0);
            untracked.stack.fill(~u64{0});
            preflushable.stack.fill(0);
        } else {
            // Share allocation between CPU and GPU pages and set their default values
            u64* const alloc = new u64[num_words * 5];
            cpu.heap = alloc;
            gpu.heap = alloc + num_words;
            cached_cpu.heap = alloc + num_words * 2;
            untracked.heap = alloc + num_words * 3;
            preflushable.heap = alloc + num_words * 4;
            std::fill_n(cpu.heap, num_words, ~u64{0});
            std::fill_n(gpu.heap, num_words, 0);
            std::fill_n(cached_cpu.heap, num_words, 0);
            std::fill_n(untracked.heap, num_words, ~u64{0});
            std::fill_n(preflushable.heap, num_words, 0);
        }
        // Clean up tailing bits
        const u64 last_word_size = size_bytes % BYTES_PER_WORD;
        const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
        const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
        const u64 last_word = (~u64{0} << shift) >> shift;
        cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
        untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
    }

    ~Words() {
        Release();
    }

    Words& operator=(Words&& rhs) noexcept {
        Release();
        size_bytes = rhs.size_bytes;
        num_words = rhs.num_words;
        cpu = rhs.cpu;
        gpu = rhs.gpu;
        cached_cpu = rhs.cached_cpu;
        untracked = rhs.untracked;
        preflushable = rhs.preflushable;
        rhs.cpu.heap = nullptr;
        return *this;
    }

    Words(Words&& rhs) noexcept
        : size_bytes{rhs.size_bytes}, num_words{rhs.num_words}, cpu{rhs.cpu}, gpu{rhs.gpu},
          cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked}, preflushable{rhs.preflushable} {
        rhs.cpu.heap = nullptr;
    }

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

    /// Returns true when the buffer fits in the small vector optimization
    [[nodiscard]] bool IsShort() const noexcept {
        return num_words <= stack_words;
    }

    /// Returns the number of words of the buffer
    [[nodiscard]] size_t NumWords() const noexcept {
        return num_words;
    }

    /// Release buffer resources
    void Release() {
        if (!IsShort()) {
            // CPU written words is the base for the heap allocation
            delete[] cpu.heap;
        }
    }

    template <Type type>
    std::span<u64> Span() noexcept {
        if constexpr (type == Type::CPU) {
            return std::span<u64>(cpu.Pointer(IsShort()), num_words);
        } else if constexpr (type == Type::GPU) {
            return std::span<u64>(gpu.Pointer(IsShort()), num_words);
        } else if constexpr (type == Type::CachedCPU) {
            return std::span<u64>(cached_cpu.Pointer(IsShort()), num_words);
        } else if constexpr (type == Type::Untracked) {
            return std::span<u64>(untracked.Pointer(IsShort()), num_words);
        } else if constexpr (type == Type::Preflushable) {
            return std::span<u64>(preflushable.Pointer(IsShort()), num_words);
        }
    }

    template <Type type>
    std::span<const u64> Span() const noexcept {
        if constexpr (type == Type::CPU) {
            return std::span<const u64>(cpu.Pointer(IsShort()), num_words);
        } else if constexpr (type == Type::GPU) {
            return std::span<const u64>(gpu.Pointer(IsShort()), num_words);
        } else if constexpr (type == Type::CachedCPU) {
            return std::span<const u64>(cached_cpu.Pointer(IsShort()), num_words);
        } else if constexpr (type == Type::Untracked) {
            return std::span<const u64>(untracked.Pointer(IsShort()), num_words);
        } else if constexpr (type == Type::Preflushable) {
            return std::span<const u64>(preflushable.Pointer(IsShort()), num_words);
        }
    }

    u64 size_bytes = 0;
    size_t num_words = 0;
    WordsArray<stack_words> cpu;
    WordsArray<stack_words> gpu;
    WordsArray<stack_words> cached_cpu;
    WordsArray<stack_words> untracked;
    WordsArray<stack_words> preflushable;
};

template <class DeviceTracker, size_t stack_words = 1>
class WordManager {
public:
    explicit WordManager(VAddr cpu_addr_, DeviceTracker& tracker_, u64 size_bytes)
        : cpu_addr{cpu_addr_}, tracker{&tracker_}, words{size_bytes} {}

    explicit WordManager() = default;

    void SetCpuAddress(VAddr new_cpu_addr) {
        cpu_addr = new_cpu_addr;
    }

    VAddr GetCpuAddr() const {
        return cpu_addr;
    }

    static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) {
        constexpr size_t number_bits = sizeof(u64) * 8;
        const size_t limit_page_end = number_bits - std::min(page_end, number_bits);
        u64 bits = (word >> page_start) << page_start;
        bits = (bits << limit_page_end) >> limit_page_end;
        return bits;
    }

    static std::pair<size_t, size_t> GetWordPage(VAddr address) {
        const size_t converted_address = static_cast<size_t>(address);
        const size_t word_number = converted_address / BYTES_PER_WORD;
        const size_t amount_pages = converted_address % BYTES_PER_WORD;
        return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE);
    }

    template <typename Func>
    void IterateWords(size_t offset, size_t size, Func&& func) const {
        using FuncReturn = std::invoke_result_t<Func, std::size_t, u64>;
        static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
        const size_t start = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset), 0LL));
        const size_t end = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset + size), 0LL));
        if (start >= SizeBytes() || end <= start) {
            return;
        }
        auto [start_word, start_page] = GetWordPage(start);
        auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
        const size_t num_words = NumWords();
        start_word = std::min(start_word, num_words);
        end_word = std::min(end_word, num_words);
        const size_t diff = end_word - start_word;
        end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
        end_word = std::min(end_word, num_words);
        end_page += diff * PAGES_PER_WORD;
        constexpr u64 base_mask{~0ULL};
        for (size_t word_index = start_word; word_index < end_word; word_index++) {
            const u64 mask = ExtractBits(base_mask, start_page, end_page);
            start_page = 0;
            end_page -= PAGES_PER_WORD;
            if constexpr (BOOL_BREAK) {
                if (func(word_index, mask)) {
                    return;
                }
            } else {
                func(word_index, mask);
            }
        }
    }

    template <typename Func>
    void IteratePages(u64 mask, Func&& func) const {
        size_t offset = 0;
        while (mask != 0) {
            const size_t empty_bits = std::countr_zero(mask);
            offset += empty_bits;
            mask = mask >> empty_bits;

            const size_t continuous_bits = std::countr_one(mask);
            func(offset, continuous_bits);
            mask = continuous_bits < PAGES_PER_WORD ? (mask >> continuous_bits) : 0;
            offset += continuous_bits;
        }
    }

    /**
     * Change the state of a range of pages
     *
     * @param dirty_addr    Base address to mark or unmark as modified
     * @param size          Size in bytes to mark or unmark as modified
     */
    template <Type type, bool enable>
    void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
        std::span<u64> state_words = words.template Span<type>();
        [[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
        [[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
        IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
            if constexpr (type == Type::CPU || type == Type::CachedCPU) {
                NotifyRasterizer<!enable>(index, untracked_words[index], mask);
            }
            if constexpr (enable) {
                state_words[index] |= mask;
                if constexpr (type == Type::CPU || type == Type::CachedCPU) {
                    untracked_words[index] |= mask;
                }
                if constexpr (type == Type::CPU) {
                    cached_words[index] &= ~mask;
                }
            } else {
                if constexpr (type == Type::CPU) {
                    const u64 word = state_words[index] & mask;
                    cached_words[index] &= ~word;
                }
                state_words[index] &= ~mask;
                if constexpr (type == Type::CPU || type == Type::CachedCPU) {
                    untracked_words[index] &= ~mask;
                }
            }
        });
    }

    /**
     * Loop over each page in the given range, turn off those bits and notify the tracker if
     * needed. Call the given function on each turned off range.
     *
     * @param query_cpu_range Base CPU address to loop over
     * @param size            Size in bytes of the CPU range to loop over
     * @param func            Function to call for each turned off region
     */
    template <Type type, bool clear, typename Func>
    void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) {
        static_assert(type != Type::Untracked);

        std::span<u64> state_words = words.template Span<type>();
        [[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
        [[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
        const size_t offset = query_cpu_range - cpu_addr;
        bool pending = false;
        size_t pending_offset{};
        size_t pending_pointer{};
        const auto release = [&]() {
            func(cpu_addr + pending_offset * BYTES_PER_PAGE,
                 (pending_pointer - pending_offset) * BYTES_PER_PAGE);
        };
        IterateWords(offset, size, [&](size_t index, u64 mask) {
            if constexpr (type == Type::GPU) {
                mask &= ~untracked_words[index];
            }
            const u64 word = state_words[index] & mask;
            if constexpr (clear) {
                if constexpr (type == Type::CPU || type == Type::CachedCPU) {
                    NotifyRasterizer<true>(index, untracked_words[index], mask);
                }
                state_words[index] &= ~mask;
                if constexpr (type == Type::CPU || type == Type::CachedCPU) {
                    untracked_words[index] &= ~mask;
                }
                if constexpr (type == Type::CPU) {
                    cached_words[index] &= ~word;
                }
            }
            const size_t base_offset = index * PAGES_PER_WORD;
            IteratePages(word, [&](size_t pages_offset, size_t pages_size) {
                const auto reset = [&]() {
                    pending_offset = base_offset + pages_offset;
                    pending_pointer = base_offset + pages_offset + pages_size;
                };
                if (!pending) {
                    reset();
                    pending = true;
                    return;
                }
                if (pending_pointer == base_offset + pages_offset) {
                    pending_pointer += pages_size;
                    return;
                }
                release();
                reset();
            });
        });
        if (pending) {
            release();
        }
    }

    /**
     * Returns true when a region has been modified
     *
     * @param offset Offset in bytes from the start of the buffer
     * @param size   Size in bytes of the region to query for modifications
     */
    template <Type type>
    [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
        static_assert(type != Type::Untracked);

        const std::span<const u64> state_words = words.template Span<type>();
        [[maybe_unused]] const std::span<const u64> untracked_words =
            words.template Span<Type::Untracked>();
        bool result = false;
        IterateWords(offset, size, [&](size_t index, u64 mask) {
            if constexpr (type == Type::GPU) {
                mask &= ~untracked_words[index];
            }
            const u64 word = state_words[index] & mask;
            if (word != 0) {
                result = true;
                return true;
            }
            return false;
        });
        return result;
    }

    /**
     * Returns a begin end pair with the inclusive modified region
     *
     * @param offset Offset in bytes from the start of the buffer
     * @param size   Size in bytes of the region to query for modifications
     */
    template <Type type>
    [[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
        static_assert(type != Type::Untracked);
        const std::span<const u64> state_words = words.template Span<type>();
        [[maybe_unused]] const std::span<const u64> untracked_words =
            words.template Span<Type::Untracked>();
        u64 begin = std::numeric_limits<u64>::max();
        u64 end = 0;
        IterateWords(offset, size, [&](size_t index, u64 mask) {
            if constexpr (type == Type::GPU) {
                mask &= ~untracked_words[index];
            }
            const u64 word = state_words[index] & mask;
            if (word == 0) {
                return;
            }
            const u64 local_page_begin = std::countr_zero(word);
            const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
            const u64 page_index = index * PAGES_PER_WORD;
            begin = std::min(begin, page_index + local_page_begin);
            end = page_index + local_page_end;
        });
        static constexpr std::pair<u64, u64> EMPTY{0, 0};
        return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
    }

    /// Returns the number of words of the manager
    [[nodiscard]] size_t NumWords() const noexcept {
        return words.NumWords();
    }

    /// Returns the size in bytes of the manager
    [[nodiscard]] u64 SizeBytes() const noexcept {
        return words.size_bytes;
    }

    /// Returns true when the buffer fits in the small vector optimization
    [[nodiscard]] bool IsShort() const noexcept {
        return words.IsShort();
    }

    void FlushCachedWrites() noexcept {
        const u64 num_words = NumWords();
        u64* const cached_words = Array<Type::CachedCPU>();
        u64* const untracked_words = Array<Type::Untracked>();
        u64* const cpu_words = Array<Type::CPU>();
        for (u64 word_index = 0; word_index < num_words; ++word_index) {
            const u64 cached_bits = cached_words[word_index];
            NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
            untracked_words[word_index] |= cached_bits;
            cpu_words[word_index] |= cached_bits;
            cached_words[word_index] = 0;
        }
    }

private:
    template <Type type>
    u64* Array() noexcept {
        if constexpr (type == Type::CPU) {
            return words.cpu.Pointer(IsShort());
        } else if constexpr (type == Type::GPU) {
            return words.gpu.Pointer(IsShort());
        } else if constexpr (type == Type::CachedCPU) {
            return words.cached_cpu.Pointer(IsShort());
        } else if constexpr (type == Type::Untracked) {
            return words.untracked.Pointer(IsShort());
        }
    }

    template <Type type>
    const u64* Array() const noexcept {
        if constexpr (type == Type::CPU) {
            return words.cpu.Pointer(IsShort());
        } else if constexpr (type == Type::GPU) {
            return words.gpu.Pointer(IsShort());
        } else if constexpr (type == Type::CachedCPU) {
            return words.cached_cpu.Pointer(IsShort());
        } else if constexpr (type == Type::Untracked) {
            return words.untracked.Pointer(IsShort());
        }
    }

    /**
     * Notify tracker about changes in the CPU tracking state of a word in the buffer
     *
     * @param word_index   Index to the word to notify to the tracker
     * @param current_bits Current state of the word
     * @param new_bits     New state of the word
     *
     * @tparam add_to_tracker True when the tracker should start tracking the new pages
     */
    template <bool add_to_tracker>
    void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
        u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
        VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
        IteratePages(changed_bits, [&](size_t offset, size_t size) {
            tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE,
                                            add_to_tracker ? 1 : -1);
        });
    }

    VAddr cpu_addr = 0;
    DeviceTracker* tracker = nullptr;
    Words<stack_words> words;
};

} // namespace VideoCommon


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

#pragma once

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

namespace VideoCommon {

enum class CacheType : u32 {
    None = 0,
    TextureCache = 1 << 0,
    QueryCache = 1 << 1,
    BufferCache = 1 << 2,
    ShaderCache = 1 << 3,
    NoTextureCache = QueryCache | BufferCache | ShaderCache,
    NoBufferCache = TextureCache | QueryCache | ShaderCache,
    NoQueryCache = TextureCache | BufferCache | ShaderCache,
    All = TextureCache | QueryCache | BufferCache | ShaderCache,
};
DECLARE_ENUM_FLAG_OPERATORS(CacheType)

} // namespace VideoCommon


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

#pragma once

#include "common/alignment.h"
#include "common/bit_util.h"
#include "common/common_types.h"
#include "core/frontend/framebuffer_layout.h"
#include "video_core/surface.h"

namespace VideoCore::Capture {

constexpr u32 BlockHeight = 4;
constexpr u32 BlockDepth = 0;
constexpr u32 BppLog2 = 2;

constexpr auto PixelFormat = Surface::PixelFormat::B8G8R8A8_UNORM;

constexpr auto LinearWidth = Layout::ScreenUndocked::Width;
constexpr auto LinearHeight = Layout::ScreenUndocked::Height;
constexpr auto LinearDepth = 1U;
constexpr auto BytesPerPixel = 4U;

constexpr auto TiledWidth = LinearWidth;
constexpr auto TiledHeight = Common::AlignUpLog2(LinearHeight, BlockHeight + BlockDepth + BppLog2);
constexpr auto TiledSize = TiledWidth * TiledHeight * (1 << BppLog2);

constexpr Layout::FramebufferLayout Layout{
    .width = LinearWidth,
    .height = LinearHeight,
    .screen = {0, 0, LinearWidth, LinearHeight},
    .is_srgb = false,
};

} // namespace VideoCore::Capture


code:
// SPDX-FileCopyrightText: Ryujinx Team and Contributors
// SPDX-License-Identifier: MIT

#include <bit>
#include "video_core/cdma_pusher.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/host1x/control.h"
#include "video_core/host1x/host1x.h"
#include "video_core/host1x/nvdec.h"
#include "video_core/host1x/nvdec_common.h"
#include "video_core/host1x/sync_manager.h"
#include "video_core/host1x/vic.h"
#include "video_core/memory_manager.h"

namespace Tegra {
CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_)
    : host1x{host1x_}, nvdec_processor(std::make_shared<Host1x::Nvdec>(host1x)),
      vic_processor(std::make_unique<Host1x::Vic>(host1x, nvdec_processor)),
      host1x_processor(std::make_unique<Host1x::Control>(host1x)),
      sync_manager(std::make_unique<Host1x::SyncptIncrManager>(host1x)) {}

CDmaPusher::~CDmaPusher() = default;

void CDmaPusher::ProcessEntries(ChCommandHeaderList&& entries) {
    for (const auto& value : entries) {
        if (mask != 0) {
            const auto lbs = static_cast<u32>(std::countr_zero(mask));
            mask &= ~(1U << lbs);
            ExecuteCommand(offset + lbs, value.raw);
            continue;
        } else if (count != 0) {
            --count;
            ExecuteCommand(offset, value.raw);
            if (incrementing) {
                ++offset;
            }
            continue;
        }
        const auto mode = value.submission_mode.Value();
        switch (mode) {
        case ChSubmissionMode::SetClass: {
            mask = value.value & 0x3f;
            offset = value.method_offset;
            current_class = static_cast<ChClassId>((value.value >> 6) & 0x3ff);
            break;
        }
        case ChSubmissionMode::Incrementing:
        case ChSubmissionMode::NonIncrementing:
            count = value.value;
            offset = value.method_offset;
            incrementing = mode == ChSubmissionMode::Incrementing;
            break;
        case ChSubmissionMode::Mask:
            mask = value.value;
            offset = value.method_offset;
            break;
        case ChSubmissionMode::Immediate: {
            const u32 data = value.value & 0xfff;
            offset = value.method_offset;
            ExecuteCommand(offset, data);
            break;
        }
        default:
            UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", static_cast<u32>(mode));
            break;
        }
    }
}

void CDmaPusher::ExecuteCommand(u32 state_offset, u32 data) {
    switch (current_class) {
    case ChClassId::NvDec:
        ThiStateWrite(nvdec_thi_state, offset, data);
        switch (static_cast<ThiMethod>(offset)) {
        case ThiMethod::IncSyncpt: {
            LOG_DEBUG(Service_NVDRV, "NVDEC Class IncSyncpt Method");
            const auto syncpoint_id = static_cast<u32>(data & 0xFF);
            const auto cond = static_cast<u32>((data >> 8) & 0xFF);
            if (cond == 0) {
                sync_manager->Increment(syncpoint_id);
            } else {
                sync_manager->SignalDone(
                    sync_manager->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id));
            }
            break;
        }
        case ThiMethod::SetMethod1:
            LOG_DEBUG(Service_NVDRV, "NVDEC method 0x{:X}",
                      static_cast<u32>(nvdec_thi_state.method_0));
            nvdec_processor->ProcessMethod(nvdec_thi_state.method_0, data);
            break;
        default:
            break;
        }
        break;
    case ChClassId::GraphicsVic:
        ThiStateWrite(vic_thi_state, static_cast<u32>(state_offset), {data});
        switch (static_cast<ThiMethod>(state_offset)) {
        case ThiMethod::IncSyncpt: {
            LOG_DEBUG(Service_NVDRV, "VIC Class IncSyncpt Method");
            const auto syncpoint_id = static_cast<u32>(data & 0xFF);
            const auto cond = static_cast<u32>((data >> 8) & 0xFF);
            if (cond == 0) {
                sync_manager->Increment(syncpoint_id);
            } else {
                sync_manager->SignalDone(
                    sync_manager->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id));
            }
            break;
        }
        case ThiMethod::SetMethod1:
            LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})",
                      static_cast<u32>(vic_thi_state.method_0), data);
            vic_processor->ProcessMethod(static_cast<Host1x::Vic::Method>(vic_thi_state.method_0),
                                         data);
            break;
        default:
            break;
        }
        break;
    case ChClassId::Control:
        // This device is mainly for syncpoint synchronization
        LOG_DEBUG(Service_NVDRV, "Host1X Class Method");
        host1x_processor->ProcessMethod(static_cast<Host1x::Control::Method>(offset), data);
        break;
    default:
        UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class));
        break;
    }
}

void CDmaPusher::ThiStateWrite(ThiRegisters& state, u32 state_offset, u32 argument) {
    u8* const offset_ptr = reinterpret_cast<u8*>(&state) + sizeof(u32) * state_offset;
    std::memcpy(offset_ptr, &argument, sizeof(u32));
}

} // namespace Tegra


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

#pragma once

#include <memory>
#include <vector>

#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"

namespace Tegra {

namespace Host1x {
class Control;
class Host1x;
class Nvdec;
class SyncptIncrManager;
class Vic;
} // namespace Host1x

enum class ChSubmissionMode : u32 {
    SetClass = 0,
    Incrementing = 1,
    NonIncrementing = 2,
    Mask = 3,
    Immediate = 4,
    Restart = 5,
    Gather = 6,
};

enum class ChClassId : u32 {
    NoClass = 0x0,
    Control = 0x1,
    VideoEncodeMpeg = 0x20,
    VideoEncodeNvEnc = 0x21,
    VideoStreamingVi = 0x30,
    VideoStreamingIsp = 0x32,
    VideoStreamingIspB = 0x34,
    VideoStreamingViI2c = 0x36,
    GraphicsVic = 0x5d,
    Graphics3D = 0x60,
    GraphicsGpu = 0x61,
    Tsec = 0xe0,
    TsecB = 0xe1,
    NvJpg = 0xc0,
    NvDec = 0xf0
};

union ChCommandHeader {
    u32 raw;
    BitField<0, 16, u32> value;
    BitField<16, 12, u32> method_offset;
    BitField<28, 4, ChSubmissionMode> submission_mode;
};
static_assert(sizeof(ChCommandHeader) == sizeof(u32), "ChCommand header is an invalid size");

struct ChCommand {
    ChClassId class_id{};
    int method_offset{};
    std::vector<u32> arguments;
};

using ChCommandHeaderList = std::vector<ChCommandHeader>;

struct ThiRegisters {
    u32_le increment_syncpt{};
    INSERT_PADDING_WORDS(1);
    u32_le increment_syncpt_error{};
    u32_le ctx_switch_incremement_syncpt{};
    INSERT_PADDING_WORDS(4);
    u32_le ctx_switch{};
    INSERT_PADDING_WORDS(1);
    u32_le ctx_syncpt_eof{};
    INSERT_PADDING_WORDS(5);
    u32_le method_0{};
    u32_le method_1{};
    INSERT_PADDING_WORDS(12);
    u32_le int_status{};
    u32_le int_mask{};
};

enum class ThiMethod : u32 {
    IncSyncpt = offsetof(ThiRegisters, increment_syncpt) / sizeof(u32),
    SetMethod0 = offsetof(ThiRegisters, method_0) / sizeof(u32),
    SetMethod1 = offsetof(ThiRegisters, method_1) / sizeof(u32),
};

class CDmaPusher {
public:
    explicit CDmaPusher(Host1x::Host1x& host1x);
    ~CDmaPusher();

    /// Process the command entry
    void ProcessEntries(ChCommandHeaderList&& entries);

private:
    /// Invoke command class devices to execute the command based on the current state
    void ExecuteCommand(u32 state_offset, u32 data);

    /// Write arguments value to the ThiRegisters member at the specified offset
    void ThiStateWrite(ThiRegisters& state, u32 offset, u32 argument);

    Host1x::Host1x& host1x;
    std::shared_ptr<Tegra::Host1x::Nvdec> nvdec_processor;
    std::unique_ptr<Tegra::Host1x::Vic> vic_processor;
    std::unique_ptr<Tegra::Host1x::Control> host1x_processor;
    std::unique_ptr<Host1x::SyncptIncrManager> sync_manager;
    ChClassId current_class{};
    ThiRegisters vic_thi_state{};
    ThiRegisters nvdec_thi_state{};

    u32 count{};
    u32 offset{};
    u32 mask{};
    bool incrementing{};
};

} // namespace Tegra


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

#include <array>
#include <cstddef>

#include "common/common_types.h"
#include "video_core/compatible_formats.h"
#include "video_core/surface.h"

namespace VideoCore::Surface {
namespace {
using Table = std::array<std::array<u64, 2>, MaxPixelFormat>;

// Compatibility table taken from Table 3.X.2 in:
// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_view.txt

constexpr std::array VIEW_CLASS_128_BITS{
    PixelFormat::R32G32B32A32_FLOAT,
    PixelFormat::R32G32B32A32_UINT,
    PixelFormat::R32G32B32A32_SINT,
};

constexpr std::array VIEW_CLASS_96_BITS{
    PixelFormat::R32G32B32_FLOAT,
};
// Missing formats:
// PixelFormat::RGB32UI,
// PixelFormat::RGB32I,

constexpr std::array VIEW_CLASS_64_BITS{
    PixelFormat::R32G32_FLOAT,       PixelFormat::R32G32_UINT,
    PixelFormat::R32G32_SINT,        PixelFormat::R16G16B16A16_FLOAT,
    PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM,
    PixelFormat::R16G16B16A16_UINT,  PixelFormat::R16G16B16A16_SINT,
};

// TODO: How should we handle 48 bits?

constexpr std::array VIEW_CLASS_32_BITS{
    PixelFormat::R16G16_FLOAT,      PixelFormat::B10G11R11_FLOAT, PixelFormat::R32_FLOAT,
    PixelFormat::A2B10G10R10_UNORM, PixelFormat::R16G16_UINT,     PixelFormat::R32_UINT,
    PixelFormat::R16G16_SINT,       PixelFormat::R32_SINT,        PixelFormat::A8B8G8R8_UNORM,
    PixelFormat::R16G16_UNORM,      PixelFormat::A8B8G8R8_SNORM,  PixelFormat::R16G16_SNORM,
    PixelFormat::A8B8G8R8_SRGB,     PixelFormat::E5B9G9R9_FLOAT,  PixelFormat::B8G8R8A8_UNORM,
    PixelFormat::B8G8R8A8_SRGB,     PixelFormat::A8B8G8R8_UINT,   PixelFormat::A8B8G8R8_SINT,
    PixelFormat::A2B10G10R10_UINT,
};

constexpr std::array VIEW_CLASS_32_BITS_NO_BGR{
    PixelFormat::R16G16_FLOAT,      PixelFormat::B10G11R11_FLOAT,  PixelFormat::R32_FLOAT,
    PixelFormat::A2B10G10R10_UNORM, PixelFormat::R16G16_UINT,      PixelFormat::R32_UINT,
    PixelFormat::R16G16_SINT,       PixelFormat::R32_SINT,         PixelFormat::A8B8G8R8_UNORM,
    PixelFormat::R16G16_UNORM,      PixelFormat::A8B8G8R8_SNORM,   PixelFormat::R16G16_SNORM,
    PixelFormat::A8B8G8R8_SRGB,     PixelFormat::E5B9G9R9_FLOAT,   PixelFormat::A8B8G8R8_UINT,
    PixelFormat::A8B8G8R8_SINT,     PixelFormat::A2B10G10R10_UINT,
};

// TODO: How should we handle 24 bits?

constexpr std::array VIEW_CLASS_16_BITS{
    PixelFormat::R16_FLOAT,  PixelFormat::R8G8_UINT,  PixelFormat::R16_UINT,
    PixelFormat::R16_SINT,   PixelFormat::R8G8_UNORM, PixelFormat::R16_UNORM,
    PixelFormat::R8G8_SNORM, PixelFormat::R16_SNORM,  PixelFormat::R8G8_SINT,
};

constexpr std::array VIEW_CLASS_8_BITS{
    PixelFormat::R8_UINT,
    PixelFormat::R8_UNORM,
    PixelFormat::R8_SINT,
    PixelFormat::R8_SNORM,
};

constexpr std::array VIEW_CLASS_RGTC1_RED{
    PixelFormat::BC4_UNORM,
    PixelFormat::BC4_SNORM,
};

constexpr std::array VIEW_CLASS_RGTC2_RG{
    PixelFormat::BC5_UNORM,
    PixelFormat::BC5_SNORM,
};

constexpr std::array VIEW_CLASS_BPTC_UNORM{
    PixelFormat::BC7_UNORM,
    PixelFormat::BC7_SRGB,
};

constexpr std::array VIEW_CLASS_BPTC_FLOAT{
    PixelFormat::BC6H_SFLOAT,
    PixelFormat::BC6H_UFLOAT,
};

constexpr std::array VIEW_CLASS_ASTC_4x4_RGBA{
    PixelFormat::ASTC_2D_4X4_UNORM,
    PixelFormat::ASTC_2D_4X4_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_5x4_RGBA{
    PixelFormat::ASTC_2D_5X4_UNORM,
    PixelFormat::ASTC_2D_5X4_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_5x5_RGBA{
    PixelFormat::ASTC_2D_5X5_UNORM,
    PixelFormat::ASTC_2D_5X5_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_6x5_RGBA{
    PixelFormat::ASTC_2D_6X5_UNORM,
    PixelFormat::ASTC_2D_6X5_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_6x6_RGBA{
    PixelFormat::ASTC_2D_6X6_UNORM,
    PixelFormat::ASTC_2D_6X6_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_8x5_RGBA{
    PixelFormat::ASTC_2D_8X5_UNORM,
    PixelFormat::ASTC_2D_8X5_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_8x8_RGBA{
    PixelFormat::ASTC_2D_8X8_UNORM,
    PixelFormat::ASTC_2D_8X8_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_10x5_RGBA{
    PixelFormat::ASTC_2D_10X5_UNORM,
    PixelFormat::ASTC_2D_10X5_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_10x6_RGBA{
    PixelFormat::ASTC_2D_10X6_UNORM,
    PixelFormat::ASTC_2D_10X6_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_10x8_RGBA{
    PixelFormat::ASTC_2D_10X8_UNORM,
    PixelFormat::ASTC_2D_10X8_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_10x10_RGBA{
    PixelFormat::ASTC_2D_10X10_UNORM,
    PixelFormat::ASTC_2D_10X10_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_12x10_RGBA{
    PixelFormat::ASTC_2D_12X10_UNORM,
    PixelFormat::ASTC_2D_12X10_SRGB,
};

constexpr std::array VIEW_CLASS_ASTC_12x12_RGBA{
    PixelFormat::ASTC_2D_12X12_UNORM,
    PixelFormat::ASTC_2D_12X12_SRGB,
};

// Compatibility table taken from Table 4.X.1 in:
// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_copy_image.txt

constexpr std::array COPY_CLASS_128_BITS{
    PixelFormat::R32G32B32A32_UINT, PixelFormat::R32G32B32A32_FLOAT, PixelFormat::R32G32B32A32_SINT,
    PixelFormat::BC2_UNORM,         PixelFormat::BC2_SRGB,           PixelFormat::BC3_UNORM,
    PixelFormat::BC3_SRGB,          PixelFormat::BC5_UNORM,          PixelFormat::BC5_SNORM,
    PixelFormat::BC7_UNORM,         PixelFormat::BC7_SRGB,           PixelFormat::BC6H_SFLOAT,
    PixelFormat::BC6H_UFLOAT,
};
// Missing formats:
// PixelFormat::RGBA32I
// COMPRESSED_RG_RGTC2

constexpr std::array COPY_CLASS_64_BITS{
    PixelFormat::R16G16B16A16_FLOAT, PixelFormat::R16G16B16A16_UINT,
    PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM,
    PixelFormat::R16G16B16A16_SINT,  PixelFormat::R32G32_UINT,
    PixelFormat::R32G32_FLOAT,       PixelFormat::R32G32_SINT,
    PixelFormat::BC1_RGBA_UNORM,     PixelFormat::BC1_RGBA_SRGB,
};
// Missing formats:
// COMPRESSED_RGB_S3TC_DXT1_EXT
// COMPRESSED_SRGB_S3TC_DXT1_EXT
// COMPRESSED_RGBA_S3TC_DXT1_EXT
// COMPRESSED_SIGNED_RED_RGTC1

constexpr void Enable(Table& table, size_t format_a, size_t format_b) {
    table[format_a][format_b / 64] |= u64(1) << (format_b % 64);
    table[format_b][format_a / 64] |= u64(1) << (format_a % 64);
}

constexpr void Enable(Table& table, PixelFormat format_a, PixelFormat format_b) {
    Enable(table, static_cast<size_t>(format_a), static_cast<size_t>(format_b));
}

template <typename Range>
constexpr void EnableRange(Table& table, const Range& range) {
    for (auto it_a = range.begin(); it_a != range.end(); ++it_a) {
        for (auto it_b = it_a; it_b != range.end(); ++it_b) {
            Enable(table, *it_a, *it_b);
        }
    }
}

constexpr bool IsSupported(const Table& table, PixelFormat format_a, PixelFormat format_b) {
    const size_t a = static_cast<size_t>(format_a);
    const size_t b = static_cast<size_t>(format_b);
    return ((table[a][b / 64] >> (b % 64)) & 1) != 0;
}

constexpr Table MakeViewTable() {
    Table view{};
    for (size_t i = 0; i < MaxPixelFormat; ++i) {
        // Identity is allowed
        Enable(view, i, i);
    }
    EnableRange(view, VIEW_CLASS_128_BITS);
    EnableRange(view, VIEW_CLASS_96_BITS);
    EnableRange(view, VIEW_CLASS_64_BITS);
    EnableRange(view, VIEW_CLASS_16_BITS);
    EnableRange(view, VIEW_CLASS_8_BITS);
    EnableRange(view, VIEW_CLASS_RGTC1_RED);
    EnableRange(view, VIEW_CLASS_RGTC2_RG);
    EnableRange(view, VIEW_CLASS_BPTC_UNORM);
    EnableRange(view, VIEW_CLASS_BPTC_FLOAT);
    EnableRange(view, VIEW_CLASS_ASTC_4x4_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_5x4_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_5x5_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_6x5_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_6x6_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_8x5_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_8x8_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_10x5_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_10x6_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_10x8_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_10x10_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_12x10_RGBA);
    EnableRange(view, VIEW_CLASS_ASTC_12x12_RGBA);
    return view;
}

constexpr Table MakeCopyTable() {
    Table copy = MakeViewTable();
    EnableRange(copy, COPY_CLASS_128_BITS);
    EnableRange(copy, COPY_CLASS_64_BITS);
    return copy;
}

constexpr Table MakeNativeBgrViewTable() {
    Table copy = MakeViewTable();
    EnableRange(copy, VIEW_CLASS_32_BITS);
    return copy;
}

constexpr Table MakeNonNativeBgrViewTable() {
    Table copy = MakeViewTable();
    EnableRange(copy, VIEW_CLASS_32_BITS_NO_BGR);
    return copy;
}

constexpr Table MakeNativeBgrCopyTable() {
    Table copy = MakeCopyTable();
    EnableRange(copy, VIEW_CLASS_32_BITS);
    return copy;
}

constexpr Table MakeNonNativeBgrCopyTable() {
    Table copy = MakeCopyTable();
    EnableRange(copy, VIEW_CLASS_32_BITS);
    return copy;
}
} // Anonymous namespace

bool IsViewCompatible(PixelFormat format_a, PixelFormat format_b, bool broken_views,
                      bool native_bgr) {
    if (format_a == format_b) {
        return true;
    }
    if (broken_views) {
        // If format views are broken, only accept formats that are identical.
        return format_a == format_b;
    }
    static constexpr Table BGR_TABLE = MakeNativeBgrViewTable();
    static constexpr Table NO_BGR_TABLE = MakeNonNativeBgrViewTable();
    return IsSupported(native_bgr ? BGR_TABLE : NO_BGR_TABLE, format_a, format_b);
}

bool IsCopyCompatible(PixelFormat format_a, PixelFormat format_b, bool native_bgr) {
    if (format_a == format_b) {
        return true;
    }
    static constexpr Table BGR_TABLE = MakeNativeBgrCopyTable();
    static constexpr Table NO_BGR_TABLE = MakeNonNativeBgrCopyTable();
    return IsSupported(native_bgr ? BGR_TABLE : NO_BGR_TABLE, format_a, format_b);
}

} // namespace VideoCore::Surface


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

#pragma once

#include "video_core/surface.h"

namespace VideoCore::Surface {

bool IsViewCompatible(PixelFormat format_a, PixelFormat format_b, bool broken_views,
                      bool native_bgr);

bool IsCopyCompatible(PixelFormat format_a, PixelFormat format_b, bool native_bgr);

} // namespace VideoCore::Surface


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

#include "common/assert.h"
#include "video_core/control/channel_state.h"
#include "video_core/dma_pusher.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/kepler_memory.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/engines/puller.h"
#include "video_core/memory_manager.h"

namespace Tegra::Control {

ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {}

void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) {
    ASSERT(memory_manager);
    program_id = program_id_;
    dma_pusher = std::make_unique<Tegra::DmaPusher>(system, gpu, *memory_manager, *this);
    maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, *memory_manager);
    fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
    kepler_compute = std::make_unique<Engines::KeplerCompute>(system, *memory_manager);
    maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
    kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
    initialized = true;
}

void ChannelState::BindRasterizer(VideoCore::RasterizerInterface* rasterizer) {
    dma_pusher->BindRasterizer(rasterizer);
    memory_manager->BindRasterizer(rasterizer);
    maxwell_3d->BindRasterizer(rasterizer);
    fermi_2d->BindRasterizer(rasterizer);
    kepler_memory->BindRasterizer(rasterizer);
    kepler_compute->BindRasterizer(rasterizer);
    maxwell_dma->BindRasterizer(rasterizer);
}

} // namespace Tegra::Control


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

#pragma once

#include <memory>

#include "common/common_types.h"

namespace Core {
class System;
}

namespace VideoCore {
class RasterizerInterface;
}

namespace Tegra {

class GPU;

namespace Engines {
class Puller;
class Fermi2D;
class Maxwell3D;
class MaxwellDMA;
class KeplerCompute;
class KeplerMemory;
} // namespace Engines

class MemoryManager;
class DmaPusher;

namespace Control {

struct ChannelState {
    explicit ChannelState(s32 bind_id);
    ChannelState(const ChannelState& state) = delete;
    ChannelState& operator=(const ChannelState&) = delete;
    ChannelState(ChannelState&& other) noexcept = default;
    ChannelState& operator=(ChannelState&& other) noexcept = default;

    void Init(Core::System& system, GPU& gpu, u64 program_id);

    void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);

    s32 bind_id = -1;
    u64 program_id = 0;
    /// 3D engine
    std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
    /// 2D engine
    std::unique_ptr<Engines::Fermi2D> fermi_2d;
    /// Compute engine
    std::unique_ptr<Engines::KeplerCompute> kepler_compute;
    /// DMA engine
    std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
    /// Inline memory engine
    std::unique_ptr<Engines::KeplerMemory> kepler_memory;

    std::shared_ptr<MemoryManager> memory_manager;

    std::unique_ptr<DmaPusher> dma_pusher;

    bool initialized{};
};

} // namespace Control

} // namespace Tegra


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

#include "video_core/control/channel_state_cache.inc"

namespace VideoCommon {

ChannelInfo::ChannelInfo(Tegra::Control::ChannelState& channel_state)
    : maxwell3d{*channel_state.maxwell_3d}, kepler_compute{*channel_state.kepler_compute},
      gpu_memory{*channel_state.memory_manager}, program_id{channel_state.program_id} {}

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

} // namespace VideoCommon


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

#pragma once

#include <deque>
#include <limits>
#include <mutex>
#include <optional>
#include <unordered_map>
#include <vector>

#include "common/common_types.h"

namespace Tegra {

namespace Engines {
class Maxwell3D;
class KeplerCompute;
} // namespace Engines

class MemoryManager;

namespace Control {
struct ChannelState;
}

} // namespace Tegra

namespace VideoCommon {

class ChannelInfo {
public:
    ChannelInfo() = delete;
    explicit ChannelInfo(Tegra::Control::ChannelState& state);
    ChannelInfo(const ChannelInfo& state) = delete;
    ChannelInfo& operator=(const ChannelInfo&) = delete;

    Tegra::Engines::Maxwell3D& maxwell3d;
    Tegra::Engines::KeplerCompute& kepler_compute;
    Tegra::MemoryManager& gpu_memory;
    u64 program_id;
};

template <class P>
class ChannelSetupCaches {
public:
    /// Operations for setting the channel of execution.
    virtual ~ChannelSetupCaches();

    /// Create channel state.
    virtual void CreateChannel(Tegra::Control::ChannelState& channel);

    /// Bind a channel for execution.
    virtual void BindToChannel(s32 id);

    /// Erase channel's state.
    void EraseChannel(s32 id);

    Tegra::MemoryManager* GetFromID(size_t id) const {
        std::unique_lock<std::mutex> lk(config_mutex);
        const auto ref = address_spaces.find(id);
        return ref->second.gpu_memory;
    }

    std::optional<size_t> getStorageID(size_t id) const {
        std::unique_lock<std::mutex> lk(config_mutex);
        const auto ref = address_spaces.find(id);
        if (ref == address_spaces.end()) {
            return std::nullopt;
        }
        return ref->second.storage_id;
    }

protected:
    static constexpr size_t UNSET_CHANNEL{std::numeric_limits<size_t>::max()};

    P* channel_state;
    size_t current_channel_id{UNSET_CHANNEL};
    size_t current_address_space{};
    Tegra::Engines::Maxwell3D* maxwell3d{};
    Tegra::Engines::KeplerCompute* kepler_compute{};
    Tegra::MemoryManager* gpu_memory{};
    u64 program_id{};

    std::deque<P> channel_storage;
    std::deque<size_t> free_channel_ids;
    std::unordered_map<s32, size_t> channel_map;
    std::vector<size_t> active_channel_ids;
    struct AddressSpaceRef {
        size_t ref_count;
        size_t storage_id;
        Tegra::MemoryManager* gpu_memory;
    };
    std::unordered_map<size_t, AddressSpaceRef> address_spaces;
    mutable std::mutex config_mutex;

    virtual void OnGPUASRegister([[maybe_unused]] size_t map_id) {}
};

} // namespace VideoCommon


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

#include <algorithm>

#include "video_core/control/channel_state.h"
#include "video_core/control/channel_state_cache.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"

namespace VideoCommon {

template <class P>
ChannelSetupCaches<P>::~ChannelSetupCaches() = default;

template <class P>
void ChannelSetupCaches<P>::CreateChannel(struct Tegra::Control::ChannelState& channel) {
    std::unique_lock<std::mutex> lk(config_mutex);
    ASSERT(channel_map.find(channel.bind_id) == channel_map.end() && channel.bind_id >= 0);
    auto new_id = [this, &channel]() {
        if (!free_channel_ids.empty()) {
            auto id = free_channel_ids.front();
            free_channel_ids.pop_front();
            new (&channel_storage[id]) P(channel);
            return id;
        }
        channel_storage.emplace_back(channel);
        return channel_storage.size() - 1;
    }();
    channel_map.emplace(channel.bind_id, new_id);
    if (current_channel_id != UNSET_CHANNEL) {
        channel_state = &channel_storage[current_channel_id];
    }
    active_channel_ids.push_back(new_id);
    auto as_it = address_spaces.find(channel.memory_manager->GetID());
    if (as_it != address_spaces.end()) {
        as_it->second.ref_count++;
        return;
    }
    AddressSpaceRef new_gpu_mem_ref{
        .ref_count = 1,
        .storage_id = address_spaces.size(),
        .gpu_memory = channel.memory_manager.get(),
    };
    address_spaces.emplace(channel.memory_manager->GetID(), new_gpu_mem_ref);
    OnGPUASRegister(channel.memory_manager->GetID());
}

/// Bind a channel for execution.
template <class P>
void ChannelSetupCaches<P>::BindToChannel(s32 id) {
    std::unique_lock<std::mutex> lk(config_mutex);
    auto it = channel_map.find(id);
    ASSERT(it != channel_map.end() && id >= 0);
    current_channel_id = it->second;
    channel_state = &channel_storage[current_channel_id];
    maxwell3d = &channel_state->maxwell3d;
    kepler_compute = &channel_state->kepler_compute;
    gpu_memory = &channel_state->gpu_memory;
    program_id = channel_state->program_id;
    current_address_space = gpu_memory->GetID();
}

/// Erase channel's channel_state.
template <class P>
void ChannelSetupCaches<P>::EraseChannel(s32 id) {
    std::unique_lock<std::mutex> lk(config_mutex);
    const auto it = channel_map.find(id);
    ASSERT(it != channel_map.end() && id >= 0);
    const auto this_id = it->second;
    free_channel_ids.push_back(this_id);
    channel_map.erase(it);
    if (this_id == current_channel_id) {
        current_channel_id = UNSET_CHANNEL;
        channel_state = nullptr;
        maxwell3d = nullptr;
        kepler_compute = nullptr;
        gpu_memory = nullptr;
        program_id = 0;
    } else if (current_channel_id != UNSET_CHANNEL) {
        channel_state = &channel_storage[current_channel_id];
    }
    active_channel_ids.erase(
        std::find(active_channel_ids.begin(), active_channel_ids.end(), this_id));
}

} // namespace VideoCommon


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

#include <memory>

#include "common/assert.h"
#include "video_core/control/channel_state.h"
#include "video_core/control/scheduler.h"
#include "video_core/gpu.h"

namespace Tegra::Control {
Scheduler::Scheduler(GPU& gpu_) : gpu{gpu_} {}

Scheduler::~Scheduler() = default;

void Scheduler::Push(s32 channel, CommandList&& entries) {
    std::unique_lock lk(scheduling_guard);
    auto it = channels.find(channel);
    ASSERT(it != channels.end());
    auto channel_state = it->second;
    gpu.BindChannel(channel_state->bind_id);
    channel_state->dma_pusher->Push(std::move(entries));
    channel_state->dma_pusher->DispatchCalls();
}

void Scheduler::DeclareChannel(std::shared_ptr<ChannelState> new_channel) {
    s32 channel = new_channel->bind_id;
    std::unique_lock lk(scheduling_guard);
    channels.emplace(channel, new_channel);
}

} // namespace Tegra::Control


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

#pragma once

#include <memory>
#include <mutex>
#include <unordered_map>

#include "video_core/dma_pusher.h"

namespace Tegra {

class GPU;

namespace Control {

struct ChannelState;

class Scheduler {
public:
    explicit Scheduler(GPU& gpu_);
    ~Scheduler();

    void Push(s32 channel, CommandList&& entries);

    void DeclareChannel(std::shared_ptr<ChannelState> new_channel);

private:
    std::unordered_map<s32, std::shared_ptr<ChannelState>> channels;
    std::mutex scheduling_guard;
    GPU& gpu;
};

} // namespace Control

} // namespace Tegra


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

#pragma once

#include <array>
#include <cstddef>
#include <utility>
#include <vector>

namespace VideoCommon {

/// Container to push objects to be destroyed a few ticks in the future
template <typename T, size_t TICKS_TO_DESTROY>
class DelayedDestructionRing {
public:
    void Tick() {
        index = (index + 1) % TICKS_TO_DESTROY;
        elements[index].clear();
    }

    void Push(T&& object) {
        elements[index].push_back(std::move(object));
    }

private:
    size_t index = 0;
    std::array<std::vector<T>, TICKS_TO_DESTROY> elements;
};

} // namespace VideoCommon


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

#include <array>
#include <cstddef>

#include "common/common_types.h"
#include "video_core/dirty_flags.h"

#define OFF(field_name) MAXWELL3D_REG_INDEX(field_name)
#define NUM(field_name) (sizeof(::Tegra::Engines::Maxwell3D::Regs::field_name) / (sizeof(u32)))

namespace VideoCommon::Dirty {
namespace {
using Tegra::Engines::Maxwell3D;

void SetupDirtyVertexBuffers(Maxwell3D::DirtyState::Tables& tables) {
    static constexpr std::size_t num_array = 3;
    for (std::size_t i = 0; i < Maxwell3D::Regs::NumVertexArrays; ++i) {
        const std::size_t array_offset = OFF(vertex_streams) + i * NUM(vertex_streams[0]);
        const std::size_t limit_offset =
            OFF(vertex_stream_limits) + i * NUM(vertex_stream_limits[0]);

        FillBlock(tables, array_offset, num_array, VertexBuffer0 + i, VertexBuffers);
        FillBlock(tables, limit_offset, NUM(vertex_stream_limits), VertexBuffer0 + i,
                  VertexBuffers);
    }
}

void SetupIndexBuffer(Maxwell3D::DirtyState::Tables& tables) {
    FillBlock(tables[0], OFF(index_buffer), NUM(index_buffer), IndexBuffer);
}

void SetupDirtyDescriptors(Maxwell3D::DirtyState::Tables& tables) {
    FillBlock(tables[0], OFF(tex_header), NUM(tex_header), Descriptors);
    FillBlock(tables[0], OFF(tex_sampler), NUM(tex_sampler), Descriptors);
}

void SetupDirtyRenderTargets(Maxwell3D::DirtyState::Tables& tables) {
    static constexpr std::size_t num_per_rt = NUM(rt[0]);
    static constexpr std::size_t begin = OFF(rt);
    static constexpr std::size_t num = num_per_rt * Maxwell3D::Regs::NumRenderTargets;
    for (std::size_t rt = 0; rt < Maxwell3D::Regs::NumRenderTargets; ++rt) {
        FillBlock(tables[0], begin + rt * num_per_rt, num_per_rt, ColorBuffer0 + rt);
    }
    FillBlock(tables[1], begin, num, RenderTargets);
    FillBlock(tables[0], OFF(surface_clip), NUM(surface_clip), RenderTargets);

    tables[0][OFF(rt_control)] = RenderTargets;
    tables[1][OFF(rt_control)] = RenderTargetControl;

    static constexpr std::array zeta_flags{ZetaBuffer, RenderTargets};
    for (std::size_t i = 0; i < std::size(zeta_flags); ++i) {
        const u8 flag = zeta_flags[i];
        auto& table = tables[i];
        table[OFF(zeta_enable)] = flag;
        table[OFF(zeta_size.width)] = flag;
        table[OFF(zeta_size.height)] = flag;
        FillBlock(table, OFF(zeta), NUM(zeta), flag);
    }
}

void SetupDirtyShaders(Maxwell3D::DirtyState::Tables& tables) {
    FillBlock(tables[0], OFF(pipelines), NUM(pipelines[0]) * Maxwell3D::Regs::MaxShaderProgram,
              Shaders);
}
} // Anonymous namespace

void SetupDirtyFlags(Maxwell3D::DirtyState::Tables& tables) {
    SetupDirtyVertexBuffers(tables);
    SetupIndexBuffer(tables);
    SetupDirtyDescriptors(tables);
    SetupDirtyRenderTargets(tables);
    SetupDirtyShaders(tables);
}

} // namespace VideoCommon::Dirty


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

#pragma once

#include <algorithm>
#include <cstddef>
#include <iterator>

#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"

namespace VideoCommon::Dirty {

enum : u8 {
    NullEntry = 0,

    Descriptors,

    RenderTargets,
    RenderTargetControl,
    ColorBuffer0,
    ColorBuffer1,
    ColorBuffer2,
    ColorBuffer3,
    ColorBuffer4,
    ColorBuffer5,
    ColorBuffer6,
    ColorBuffer7,
    ZetaBuffer,
    RescaleViewports,
    RescaleScissors,

    VertexBuffers,
    VertexBuffer0,
    VertexBuffer31 = VertexBuffer0 + 31,

    IndexBuffer,

    Shaders,

    // Special entries
    DepthBiasGlobal,

    LastCommonEntry,
};

template <typename Integer>
void FillBlock(Tegra::Engines::Maxwell3D::DirtyState::Table& table, std::size_t begin,
               std::size_t num, Integer dirty_index) {
    const auto it = std::begin(table) + begin;
    std::fill(it, it + num, static_cast<u8>(dirty_index));
}

template <typename Integer1, typename Integer2>
void FillBlock(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables, std::size_t begin,
               std::size_t num, Integer1 index_a, Integer2 index_b) {
    FillBlock(tables[0], begin, num, index_a);
    FillBlock(tables[1], begin, num, index_b);
}

void SetupDirtyFlags(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables);

} // namespace VideoCommon::Dirty


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

#include "common/cityhash.h"
#include "common/microprofile.h"
#include "common/settings.h"
#include "core/core.h"
#include "video_core/dma_pusher.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/gpu.h"
#include "video_core/guest_memory.h"
#include "video_core/memory_manager.h"

namespace Tegra {

constexpr u32 MacroRegistersStart = 0xE00;
constexpr u32 ComputeInline = 0x6D;

DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
                     Control::ChannelState& channel_state_)
    : gpu{gpu_}, system{system_}, memory_manager{memory_manager_}, puller{gpu_, memory_manager_,
                                                                          *this, channel_state_} {}

DmaPusher::~DmaPusher() = default;

MICROPROFILE_DEFINE(DispatchCalls, "GPU", "Execute command buffer", MP_RGB(128, 128, 192));

void DmaPusher::DispatchCalls() {
    MICROPROFILE_SCOPE(DispatchCalls);

    dma_pushbuffer_subindex = 0;

    dma_state.is_last_call = true;

    while (system.IsPoweredOn()) {
        if (!Step()) {
            break;
        }
    }
    gpu.FlushCommands();
    gpu.OnCommandListEnd();
}

bool DmaPusher::Step() {
    if (!ib_enable || dma_pushbuffer.empty()) {
        // pushbuffer empty and IB empty or nonexistent - nothing to do
        return false;
    }

    CommandList& command_list{dma_pushbuffer.front()};

    ASSERT_OR_EXECUTE(
        command_list.command_lists.size() || command_list.prefetch_command_list.size(), {
            // Somehow the command_list is empty, in order to avoid a crash
            // We ignore it and assume its size is 0.
            dma_pushbuffer.pop();
            dma_pushbuffer_subindex = 0;
            return true;
        });

    if (command_list.prefetch_command_list.size()) {
        // Prefetched command list from nvdrv, used for things like synchronization
        ProcessCommands(command_list.prefetch_command_list);
        dma_pushbuffer.pop();
    } else {
        const CommandListHeader command_list_header{
            command_list.command_lists[dma_pushbuffer_subindex++]};
        dma_state.dma_get = command_list_header.addr;

        if (dma_pushbuffer_subindex >= command_list.command_lists.size()) {
            // We've gone through the current list, remove it from the queue
            dma_pushbuffer.pop();
            dma_pushbuffer_subindex = 0;
        }

        if (command_list_header.size == 0) {
            return true;
        }

        // Push buffer non-empty, read a word
        if (dma_state.method >= MacroRegistersStart) {
            if (subchannels[dma_state.subchannel]) {
                subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(
                    dma_state.dma_get, command_list_header.size * sizeof(u32));
            }
        }
        const auto safe_process = [&] {
            Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader,
                                          Tegra::Memory::GuestMemoryFlags::SafeRead>
                headers(memory_manager, dma_state.dma_get, command_list_header.size,
                        &command_headers);
            ProcessCommands(headers);
        };
        const auto unsafe_process = [&] {
            Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader,
                                          Tegra::Memory::GuestMemoryFlags::UnsafeRead>
                headers(memory_manager, dma_state.dma_get, command_list_header.size,
                        &command_headers);
            ProcessCommands(headers);
        };
        if (Settings::IsGPULevelHigh()) {
            if (dma_state.method >= MacroRegistersStart) {
                unsafe_process();
                return true;
            }
            if (subchannel_type[dma_state.subchannel] == Engines::EngineTypes::KeplerCompute &&
                dma_state.method == ComputeInline) {
                unsafe_process();
                return true;
            }
            safe_process();
            return true;
        }
        unsafe_process();
    }
    return true;
}

void DmaPusher::ProcessCommands(std::span<const CommandHeader> commands) {
    for (std::size_t index = 0; index < commands.size();) {
        const CommandHeader& command_header = commands[index];

        if (dma_state.method_count) {
            // Data word of methods command
            dma_state.dma_word_offset = static_cast<u32>(index * sizeof(u32));
            if (dma_state.non_incrementing) {
                const u32 max_write = static_cast<u32>(
                    std::min<std::size_t>(index + dma_state.method_count, commands.size()) - index);
                CallMultiMethod(&command_header.argument, max_write);
                dma_state.method_count -= max_write;
                dma_state.is_last_call = true;
                index += max_write;
                continue;
            } else {
                dma_state.is_last_call = dma_state.method_count <= 1;
                CallMethod(command_header.argument);
            }

            if (!dma_state.non_incrementing) {
                dma_state.method++;
            }

            if (dma_increment_once) {
                dma_state.non_incrementing = true;
            }

            dma_state.method_count--;
        } else {
            // No command active - this is the first word of a new one
            switch (command_header.mode) {
            case SubmissionMode::Increasing:
                SetState(command_header);
                dma_state.non_incrementing = false;
                dma_increment_once = false;
                break;
            case SubmissionMode::NonIncreasing:
                SetState(command_header);
                dma_state.non_incrementing = true;
                dma_increment_once = false;
                break;
            case SubmissionMode::Inline:
                dma_state.method = command_header.method;
                dma_state.subchannel = command_header.subchannel;
                dma_state.dma_word_offset = static_cast<u64>(
                    -static_cast<s64>(dma_state.dma_get)); // negate to set address as 0
                CallMethod(command_header.arg_count);
                dma_state.non_incrementing = true;
                dma_increment_once = false;
                break;
            case SubmissionMode::IncreaseOnce:
                SetState(command_header);
                dma_state.non_incrementing = false;
                dma_increment_once = true;
                break;
            default:
                break;
            }
        }
        index++;
    }
}

void DmaPusher::SetState(const CommandHeader& command_header) {
    dma_state.method = command_header.method;
    dma_state.subchannel = command_header.subchannel;
    dma_state.method_count = command_header.method_count;
}

void DmaPusher::CallMethod(u32 argument) const {
    if (dma_state.method < non_puller_methods) {
        puller.CallPullerMethod(Engines::Puller::MethodCall{
            dma_state.method,
            argument,
            dma_state.subchannel,
            dma_state.method_count,
        });
    } else {
        auto subchannel = subchannels[dma_state.subchannel];
        if (!subchannel->execution_mask[dma_state.method]) [[likely]] {
            subchannel->method_sink.emplace_back(dma_state.method, argument);
            return;
        }
        subchannel->ConsumeSink();
        subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset;
        subchannel->CallMethod(dma_state.method, argument, dma_state.is_last_call);
    }
}

void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const {
    if (dma_state.method < non_puller_methods) {
        puller.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods,
                               dma_state.method_count);
    } else {
        auto subchannel = subchannels[dma_state.subchannel];
        subchannel->ConsumeSink();
        subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset;
        subchannel->CallMultiMethod(dma_state.method, base_start, num_methods,
                                    dma_state.method_count);
    }
}

void DmaPusher::BindRasterizer(VideoCore::RasterizerInterface* rasterizer) {
    puller.BindRasterizer(rasterizer);
}

} // namespace Tegra


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

#pragma once

#include <array>
#include <span>
#include <vector>
#include <boost/container/small_vector.hpp>
#include <queue>

#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/scratch_buffer.h"
#include "video_core/engines/engine_interface.h"
#include "video_core/engines/puller.h"

namespace Core {
class System;
}

namespace Tegra {

namespace Control {
struct ChannelState;
}

class GPU;
class MemoryManager;

enum class SubmissionMode : u32 {
    IncreasingOld = 0,
    Increasing = 1,
    NonIncreasingOld = 2,
    NonIncreasing = 3,
    Inline = 4,
    IncreaseOnce = 5
};

// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence
// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4.
// So the values you see in docs might be multiplied by 4.
// Register documentation:
// https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/classes/host/cla26f.h
//
// Register Description (approx):
// https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/manuals/volta/gv100/dev_pbdma.ref.txt
enum class BufferMethods : u32 {
    BindObject = 0x0,
    Illegal = 0x1,
    Nop = 0x2,
    SemaphoreAddressHigh = 0x4,
    SemaphoreAddressLow = 0x5,
    SemaphoreSequencePayload = 0x6,
    SemaphoreOperation = 0x7,
    NonStallInterrupt = 0x8,
    WrcacheFlush = 0x9,
    MemOpA = 0xA,
    MemOpB = 0xB,
    MemOpC = 0xC,
    MemOpD = 0xD,
    RefCnt = 0x14,
    SemaphoreAcquire = 0x1A,
    SemaphoreRelease = 0x1B,
    SyncpointPayload = 0x1C,
    SyncpointOperation = 0x1D,
    WaitForIdle = 0x1E,
    CRCCheck = 0x1F,
    Yield = 0x20,
    NonPullerMethods = 0x40,
};

struct CommandListHeader {
    union {
        u64 raw;
        BitField<0, 40, GPUVAddr> addr;
        BitField<41, 1, u64> is_non_main;
        BitField<42, 21, u64> size;
    };
};
static_assert(sizeof(CommandListHeader) == sizeof(u64), "CommandListHeader is incorrect size");

union CommandHeader {
    u32 argument;
    BitField<0, 13, u32> method;
    BitField<0, 24, u32> method_count_;
    BitField<13, 3, u32> subchannel;
    BitField<16, 13, u32> arg_count;
    BitField<16, 13, u32> method_count;
    BitField<29, 3, SubmissionMode> mode;
};
static_assert(std::is_standard_layout_v<CommandHeader>, "CommandHeader is not standard layout");
static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!");

inline CommandHeader BuildCommandHeader(BufferMethods method, u32 arg_count, SubmissionMode mode) {
    CommandHeader result{};
    result.method.Assign(static_cast<u32>(method));
    result.arg_count.Assign(arg_count);
    result.mode.Assign(mode);
    return result;
}

struct CommandList final {
    CommandList() = default;
    explicit CommandList(std::size_t size) : command_lists(size) {}
    explicit CommandList(
        boost::container::small_vector<CommandHeader, 512>&& prefetch_command_list_)
        : prefetch_command_list{std::move(prefetch_command_list_)} {}

    boost::container::small_vector<CommandListHeader, 512> command_lists;
    boost::container::small_vector<CommandHeader, 512> prefetch_command_list;
};

/**
 * The DmaPusher class implements DMA submission to FIFOs, providing an area of memory that the
 * emulated app fills with commands and tells PFIFO to process. The pushbuffers are then assembled
 * into a "command stream" consisting of 32-bit words that make up "commands".
 * See https://envytools.readthedocs.io/en/latest/hw/fifo/dma-pusher.html#fifo-dma-pusher for
 * details on this implementation.
 */
class DmaPusher final {
public:
    explicit DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
                       Control::ChannelState& channel_state_);
    ~DmaPusher();

    void Push(CommandList&& entries) {
        dma_pushbuffer.push(std::move(entries));
    }

    void DispatchCalls();

    void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id,
                        Engines::EngineTypes engine_type) {
        subchannels[subchannel_id] = engine;
        subchannel_type[subchannel_id] = engine_type;
    }

    void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);

private:
    static constexpr u32 non_puller_methods = 0x40;
    static constexpr u32 max_subchannels = 8;
    bool Step();
    void ProcessCommands(std::span<const CommandHeader> commands);

    void SetState(const CommandHeader& command_header);

    void CallMethod(u32 argument) const;
    void CallMultiMethod(const u32* base_start, u32 num_methods) const;

    Common::ScratchBuffer<CommandHeader>
        command_headers; ///< Buffer for list of commands fetched at once

    std::queue<CommandList> dma_pushbuffer; ///< Queue of command lists to be processed
    std::size_t dma_pushbuffer_subindex{};  ///< Index within a command list within the pushbuffer

    struct DmaState {
        u32 method;            ///< Current method
        u32 subchannel;        ///< Current subchannel
        u32 method_count;      ///< Current method count
        u32 length_pending;    ///< Large NI command length pending
        GPUVAddr dma_get;      ///< Currently read segment
        u64 dma_word_offset;   ///< Current word offset from address
        bool non_incrementing; ///< Current command's NI flag
        bool is_last_call;
    };

    DmaState dma_state{};
    bool dma_increment_once{};

    const bool ib_enable{true}; ///< IB mode enabled

    std::array<Engines::EngineInterface*, max_subchannels> subchannels{};
    std::array<Engines::EngineTypes, max_subchannels> subchannel_type;

    GPU& gpu;
    Core::System& system;
    MemoryManager& memory_manager;
    mutable Engines::Puller puller;
};

} // namespace Tegra


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

#pragma once

#include "common/common_types.h"

namespace Tegra::Engines {

struct ConstBufferInfo {
    GPUVAddr address;
    u32 size;
    bool enabled;
};

} // namespace Tegra::Engines


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

#include "common/settings.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/draw_manager.h"
#include "video_core/rasterizer_interface.h"

namespace Tegra::Engines {
DrawManager::DrawManager(Maxwell3D* maxwell3d_) : maxwell3d(maxwell3d_) {}

void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
    const auto& regs{maxwell3d->regs};
    switch (method) {
    case MAXWELL3D_REG_INDEX(clear_surface):
        return Clear(1);
    case MAXWELL3D_REG_INDEX(draw.begin):
        return DrawBegin();
    case MAXWELL3D_REG_INDEX(draw.end):
        return DrawEnd();
    case MAXWELL3D_REG_INDEX(vertex_buffer.first):
    case MAXWELL3D_REG_INDEX(vertex_buffer.count):
    case MAXWELL3D_REG_INDEX(index_buffer.first):
        break;
    case MAXWELL3D_REG_INDEX(index_buffer.count):
        draw_state.draw_indexed = true;
        break;
    case MAXWELL3D_REG_INDEX(index_buffer32_subsequent):
    case MAXWELL3D_REG_INDEX(index_buffer16_subsequent):
    case MAXWELL3D_REG_INDEX(index_buffer8_subsequent):
        draw_state.instance_count++;
        [[fallthrough]];
    case MAXWELL3D_REG_INDEX(index_buffer32_first):
    case MAXWELL3D_REG_INDEX(index_buffer16_first):
    case MAXWELL3D_REG_INDEX(index_buffer8_first):
        return DrawIndexSmall(argument);
    case MAXWELL3D_REG_INDEX(draw_inline_index):
        SetInlineIndexBuffer(argument);
        break;
    case MAXWELL3D_REG_INDEX(inline_index_2x16.even):
        SetInlineIndexBuffer(regs.inline_index_2x16.even);
        SetInlineIndexBuffer(regs.inline_index_2x16.odd);
        break;
    case MAXWELL3D_REG_INDEX(inline_index_4x8.index0):
        SetInlineIndexBuffer(regs.inline_index_4x8.index0);
        SetInlineIndexBuffer(regs.inline_index_4x8.index1);
        SetInlineIndexBuffer(regs.inline_index_4x8.index2);
        SetInlineIndexBuffer(regs.inline_index_4x8.index3);
        break;
    case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
        DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
                           regs.vertex_array_instance_first.start.Value(),
                           regs.vertex_array_instance_first.count.Value(), false);
        break;
    case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
        DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
                           regs.vertex_array_instance_subsequent.start.Value(),
                           regs.vertex_array_instance_subsequent.count.Value(), true);
        break;
    }
    case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
        DrawTexture();
        break;
    }
    default:
        break;
    }
}

void DrawManager::Clear(u32 layer_count) {
    if (maxwell3d->ShouldExecute()) {
        maxwell3d->rasterizer->Clear(layer_count);
    }
}

void DrawManager::DrawDeferred() {
    if (draw_state.draw_mode != DrawMode::Instance || draw_state.instance_count == 0) {
        return;
    }
    DrawEnd(draw_state.instance_count + 1, true);
    draw_state.instance_count = 0;
}

void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
                            u32 base_instance, u32 num_instances) {
    draw_state.topology = topology;
    draw_state.vertex_buffer.first = vertex_first;
    draw_state.vertex_buffer.count = vertex_count;
    draw_state.base_instance = base_instance;
    ProcessDraw(false, num_instances);
}

void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
                                     bool subsequent) {
    draw_state.topology = topology;
    draw_state.vertex_buffer.first = vertex_first;
    draw_state.vertex_buffer.count = vertex_count;

    if (!subsequent) {
        draw_state.instance_count = 1;
    }

    draw_state.base_instance = draw_state.instance_count - 1;
    draw_state.draw_mode = DrawMode::Instance;
    draw_state.instance_count++;
    ProcessDraw(false, 1);
}

void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
                            u32 base_index, u32 base_instance, u32 num_instances) {
    const auto& regs{maxwell3d->regs};
    draw_state.topology = topology;
    draw_state.index_buffer = regs.index_buffer;
    draw_state.index_buffer.first = index_first;
    draw_state.index_buffer.count = index_count;
    draw_state.base_index = base_index;
    draw_state.base_instance = base_instance;
    ProcessDraw(true, num_instances);
}

void DrawManager::DrawArrayIndirect(PrimitiveTopology topology) {
    draw_state.topology = topology;

    ProcessDrawIndirect();
}

void DrawManager::DrawIndexedIndirect(PrimitiveTopology topology, u32 index_first,
                                      u32 index_count) {
    const auto& regs{maxwell3d->regs};
    draw_state.topology = topology;
    draw_state.index_buffer = regs.index_buffer;
    draw_state.index_buffer.first = index_first;
    draw_state.index_buffer.count = index_count;

    ProcessDrawIndirect();
}

void DrawManager::SetInlineIndexBuffer(u32 index) {
    draw_state.inline_index_draw_indexes.push_back(static_cast<u8>(index & 0x000000ff));
    draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x0000ff00) >> 8));
    draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0x00ff0000) >> 16));
    draw_state.inline_index_draw_indexes.push_back(static_cast<u8>((index & 0xff000000) >> 24));
    draw_state.draw_mode = DrawMode::InlineIndex;
}

void DrawManager::DrawBegin() {
    const auto& regs{maxwell3d->regs};
    auto reset_instance_count = regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First;
    auto increment_instance_count =
        regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent;
    if (reset_instance_count) {
        DrawDeferred();
        draw_state.instance_count = 0;
        draw_state.draw_mode = DrawMode::General;
    } else if (increment_instance_count) {
        draw_state.instance_count++;
        draw_state.draw_mode = DrawMode::Instance;
    }

    draw_state.topology = regs.draw.topology;
}

void DrawManager::DrawEnd(u32 instance_count, bool force_draw) {
    const auto& regs{maxwell3d->regs};
    switch (draw_state.draw_mode) {
    case DrawMode::Instance:
        if (!force_draw) {
            break;
        }
        [[fallthrough]];
    case DrawMode::General:
        draw_state.base_instance = regs.global_base_instance_index;
        draw_state.base_index = regs.global_base_vertex_index;
        if (draw_state.draw_indexed) {
            draw_state.index_buffer = regs.index_buffer;
            ProcessDraw(true, instance_count);
        } else {
            draw_state.vertex_buffer = regs.vertex_buffer;
            ProcessDraw(false, instance_count);
        }
        draw_state.draw_indexed = false;
        break;
    case DrawMode::InlineIndex:
        draw_state.base_instance = regs.global_base_instance_index;
        draw_state.base_index = regs.global_base_vertex_index;
        draw_state.index_buffer = regs.index_buffer;
        draw_state.index_buffer.count =
            static_cast<u32>(draw_state.inline_index_draw_indexes.size() / 4);
        draw_state.index_buffer.format = Maxwell3D::Regs::IndexFormat::UnsignedInt;
        maxwell3d->dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
        ProcessDraw(true, instance_count);
        draw_state.inline_index_draw_indexes.clear();
        break;
    }
}

void DrawManager::DrawIndexSmall(u32 argument) {
    const auto& regs{maxwell3d->regs};
    IndexBufferSmall index_small_params{argument};
    draw_state.base_instance = regs.global_base_instance_index;
    draw_state.base_index = regs.global_base_vertex_index;
    draw_state.index_buffer = regs.index_buffer;
    draw_state.index_buffer.first = index_small_params.first;
    draw_state.index_buffer.count = index_small_params.count;
    draw_state.topology = index_small_params.topology;
    maxwell3d->dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
    ProcessDraw(true, 1);
}

void DrawManager::DrawTexture() {
    const auto& regs{maxwell3d->regs};
    draw_texture_state.dst_x0 = static_cast<float>(regs.draw_texture.dst_x0) / 4096.f;
    draw_texture_state.dst_y0 = static_cast<float>(regs.draw_texture.dst_y0) / 4096.f;
    const auto dst_width = static_cast<float>(regs.draw_texture.dst_width) / 4096.f;
    const auto dst_height = static_cast<float>(regs.draw_texture.dst_height) / 4096.f;
    const bool lower_left{regs.window_origin.mode !=
                          Maxwell3D::Regs::WindowOrigin::Mode::UpperLeft};
    if (lower_left) {
        draw_texture_state.dst_y0 =
            static_cast<f32>(regs.surface_clip.height) - draw_texture_state.dst_y0;
    }
    draw_texture_state.dst_x1 = draw_texture_state.dst_x0 + dst_width;
    draw_texture_state.dst_y1 = draw_texture_state.dst_y0 + dst_height;
    draw_texture_state.src_x0 = static_cast<float>(regs.draw_texture.src_x0) / 4096.f;
    draw_texture_state.src_y0 = static_cast<float>(regs.draw_texture.src_y0) / 4096.f;
    draw_texture_state.src_x1 =
        (static_cast<float>(regs.draw_texture.dx_du) / 4294967296.f) * dst_width +
        draw_texture_state.src_x0;
    draw_texture_state.src_y1 =
        (static_cast<float>(regs.draw_texture.dy_dv) / 4294967296.f) * dst_height +
        draw_texture_state.src_y0;
    draw_texture_state.src_sampler = regs.draw_texture.src_sampler;
    draw_texture_state.src_texture = regs.draw_texture.src_texture;
    maxwell3d->rasterizer->DrawTexture();
}

void DrawManager::UpdateTopology() {
    const auto& regs{maxwell3d->regs};
    switch (regs.primitive_topology_control) {
    case PrimitiveTopologyControl::UseInBeginMethods:
        break;
    case PrimitiveTopologyControl::UseSeparateState:
        switch (regs.topology_override) {
        case PrimitiveTopologyOverride::None:
            break;
        case PrimitiveTopologyOverride::Points:
            draw_state.topology = PrimitiveTopology::Points;
            break;
        case PrimitiveTopologyOverride::Lines:
            draw_state.topology = PrimitiveTopology::Lines;
            break;
        case PrimitiveTopologyOverride::LineStrip:
            draw_state.topology = PrimitiveTopology::LineStrip;
            break;
        default:
            draw_state.topology = static_cast<PrimitiveTopology>(regs.topology_override);
            break;
        }
        break;
    }
}

void DrawManager::ProcessDraw(bool draw_indexed, u32 instance_count) {
    LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology,
              draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);

    UpdateTopology();

    if (maxwell3d->ShouldExecute()) {
        maxwell3d->rasterizer->Draw(draw_indexed, instance_count);
    }
}

void DrawManager::ProcessDrawIndirect() {
    LOG_TRACE(
        HW_GPU,
        "called, topology={}, is_indexed={}, includes_count={}, buffer_size={}, max_draw_count={}",
        draw_state.topology, indirect_state.is_indexed, indirect_state.include_count,
        indirect_state.buffer_size, indirect_state.max_draw_counts);

    UpdateTopology();

    if (maxwell3d->ShouldExecute()) {
        maxwell3d->rasterizer->DrawIndirect();
    }
}
} // namespace Tegra::Engines