Parcourir la source

Merge pull request #8467 from FernandoS27/yfc-rel-1

Project yuzu Fried Chicken (Y.F.C.) Part 1
Fernando S il y a 3 ans
Parent
commit
1effa578f1
100 fichiers modifiés avec 4694 ajouts et 2052 suppressions
  1. 1 0
      src/CMakeLists.txt
  2. 4 0
      src/common/CMakeLists.txt
  3. 10 0
      src/common/address_space.cpp
  4. 150 0
      src/common/address_space.h
  5. 366 0
      src/common/address_space.inc
  6. 8 0
      src/common/algorithm.h
  7. 7 0
      src/common/hash.h
  8. 9 0
      src/common/multi_level_page_table.cpp
  9. 78 0
      src/common/multi_level_page_table.h
  10. 84 0
      src/common/multi_level_page_table.inc
  11. 6 4
      src/core/CMakeLists.txt
  12. 8 7
      src/core/core.cpp
  13. 9 10
      src/core/core.h
  14. 0 32
      src/core/hardware_interrupt_manager.cpp
  15. 0 32
      src/core/hardware_interrupt_manager.h
  16. 50 0
      src/core/hle/service/nvdrv/core/container.cpp
  17. 52 0
      src/core/hle/service/nvdrv/core/container.h
  18. 272 0
      src/core/hle/service/nvdrv/core/nvmap.cpp
  19. 175 0
      src/core/hle/service/nvdrv/core/nvmap.h
  20. 121 0
      src/core/hle/service/nvdrv/core/syncpoint_manager.cpp
  21. 134 0
      src/core/hle/service/nvdrv/core/syncpoint_manager.h
  22. 8 0
      src/core/hle/service/nvdrv/devices/nvdevice.h
  23. 13 6
      src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
  24. 12 3
      src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
  25. 332 160
      src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
  26. 111 80
      src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
  27. 270 93
      src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
  28. 91 23
      src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
  29. 23 2
      src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
  30. 13 1
      src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
  31. 86 43
      src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
  32. 38 16
      src/core/hle/service/nvdrv/devices/nvhost_gpu.h
  33. 9 7
      src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
  34. 1 5
      src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
  35. 42 39
      src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
  36. 16 7
      src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
  37. 12 8
      src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
  38. 1 5
      src/core/hle/service/nvdrv/devices/nvhost_vic.h
  39. 109 121
      src/core/hle/service/nvdrv/devices/nvmap.cpp
  40. 23 33
      src/core/hle/service/nvdrv/devices/nvmap.h
  41. 11 6
      src/core/hle/service/nvdrv/nvdata.h
  42. 84 46
      src/core/hle/service/nvdrv/nvdrv.cpp
  43. 39 86
      src/core/hle/service/nvdrv/nvdrv.h
  44. 12 19
      src/core/hle/service/nvdrv/nvdrv_interface.cpp
  45. 0 2
      src/core/hle/service/nvdrv/nvdrv_interface.h
  46. 0 38
      src/core/hle/service/nvdrv/syncpoint_manager.cpp
  47. 0 84
      src/core/hle/service/nvdrv/syncpoint_manager.h
  48. 7 2
      src/core/hle/service/nvflinger/buffer_queue_consumer.cpp
  49. 7 1
      src/core/hle/service/nvflinger/buffer_queue_consumer.h
  50. 7 3
      src/core/hle/service/nvflinger/buffer_queue_producer.cpp
  51. 8 1
      src/core/hle/service/nvflinger/buffer_queue_producer.h
  52. 15 14
      src/core/hle/service/nvflinger/nvflinger.cpp
  53. 1 0
      src/core/hle/service/nvflinger/nvflinger.h
  54. 10 6
      src/core/hle/service/vi/display/vi_display.cpp
  55. 6 1
      src/core/hle/service/vi/display/vi_display.h
  56. 1 0
      src/core/hle/service/vi/vi.cpp
  57. 9 0
      src/core/memory.cpp
  58. 1 0
      src/core/memory.h
  59. 3 3
      src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp
  60. 88 10
      src/shader_recompiler/ir_opt/texture_pass.cpp
  61. 4 0
      src/shader_recompiler/shader_info.h
  62. 32 19
      src/video_core/CMakeLists.txt
  63. 84 82
      src/video_core/buffer_cache/buffer_cache.h
  64. 16 13
      src/video_core/cdma_pusher.cpp
  65. 10 8
      src/video_core/cdma_pusher.h
  66. 0 29
      src/video_core/command_classes/host1x.cpp
  67. 40 0
      src/video_core/control/channel_state.cpp
  68. 68 0
      src/video_core/control/channel_state.h
  69. 14 0
      src/video_core/control/channel_state_cache.cpp
  70. 101 0
      src/video_core/control/channel_state_cache.h
  71. 86 0
      src/video_core/control/channel_state_cache.inc
  72. 32 0
      src/video_core/control/scheduler.cpp
  73. 37 0
      src/video_core/control/scheduler.h
  74. 15 11
      src/video_core/dma_pusher.cpp
  75. 29 10
      src/video_core/dma_pusher.h
  76. 37 9
      src/video_core/engines/engine_upload.cpp
  77. 5 1
      src/video_core/engines/engine_upload.h
  78. 9 4
      src/video_core/engines/kepler_compute.cpp
  79. 9 4
      src/video_core/engines/kepler_memory.cpp
  80. 28 15
      src/video_core/engines/maxwell_3d.cpp
  81. 71 40
      src/video_core/engines/maxwell_dma.cpp
  82. 6 0
      src/video_core/engines/maxwell_dma.h
  83. 306 0
      src/video_core/engines/puller.cpp
  84. 177 0
      src/video_core/engines/puller.h
  85. 46 58
      src/video_core/fence_manager.h
  86. 173 533
      src/video_core/gpu.cpp
  87. 32 61
      src/video_core/gpu.h
  88. 13 9
      src/video_core/gpu_thread.cpp
  89. 10 4
      src/video_core/gpu_thread.h
  90. 22 22
      src/video_core/host1x/codecs/codec.cpp
  91. 12 9
      src/video_core/host1x/codecs/codec.h
  92. 9 8
      src/video_core/host1x/codecs/h264.cpp
  93. 10 6
      src/video_core/host1x/codecs/h264.h
  94. 6 6
      src/video_core/host1x/codecs/vp8.cpp
  95. 10 5
      src/video_core/host1x/codecs/vp8.h
  96. 12 11
      src/video_core/host1x/codecs/vp9.cpp
  97. 14 8
      src/video_core/host1x/codecs/vp9.h
  98. 0 1
      src/video_core/host1x/codecs/vp9_types.h
  99. 33 0
      src/video_core/host1x/control.cpp
  100. 13 7
      src/video_core/host1x/control.h

+ 1 - 0
src/CMakeLists.txt

@@ -121,6 +121,7 @@ else()
 
 
     if (ARCHITECTURE_x86_64)
     if (ARCHITECTURE_x86_64)
         add_compile_options("-mcx16")
         add_compile_options("-mcx16")
+        add_compile_options("-fwrapv")
     endif()
     endif()
 
 
     if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
     if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)

+ 4 - 0
src/common/CMakeLists.txt

@@ -17,6 +17,8 @@ endif ()
 include(GenerateSCMRev)
 include(GenerateSCMRev)
 
 
 add_library(common STATIC
 add_library(common STATIC
+    address_space.cpp
+    address_space.h
     algorithm.h
     algorithm.h
     alignment.h
     alignment.h
     announce_multiplayer_room.h
     announce_multiplayer_room.h
@@ -81,6 +83,8 @@ add_library(common STATIC
     microprofile.cpp
     microprofile.cpp
     microprofile.h
     microprofile.h
     microprofileui.h
     microprofileui.h
+    multi_level_page_table.cpp
+    multi_level_page_table.h
     nvidia_flags.cpp
     nvidia_flags.cpp
     nvidia_flags.h
     nvidia_flags.h
     page_table.cpp
     page_table.cpp

+ 10 - 0
src/common/address_space.cpp

@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/address_space.inc"
+
+namespace Common {
+
+template class Common::FlatAllocator<u32, 0, 32>;
+
+}

+ 150 - 0
src/common/address_space.h

@@ -0,0 +1,150 @@
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <concepts>
+#include <functional>
+#include <mutex>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Common {
+template <typename VaType, size_t AddressSpaceBits>
+concept AddressSpaceValid = std::is_unsigned_v<VaType> && sizeof(VaType) * 8 >= AddressSpaceBits;
+
+struct EmptyStruct {};
+
+/**
+ * @brief FlatAddressSpaceMap provides a generic VA->PA mapping implementation using a sorted vector
+ */
+template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa,
+          bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo = EmptyStruct>
+requires AddressSpaceValid<VaType, AddressSpaceBits>
+class FlatAddressSpaceMap {
+public:
+    /// The maximum VA that this AS can technically reach
+    static constexpr VaType VaMaximum{(1ULL << (AddressSpaceBits - 1)) +
+                                      ((1ULL << (AddressSpaceBits - 1)) - 1)};
+
+    explicit FlatAddressSpaceMap(VaType va_limit,
+                                 std::function<void(VaType, VaType)> unmap_callback = {});
+
+    FlatAddressSpaceMap() = default;
+
+    void Map(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info = {}) {
+        std::scoped_lock lock(block_mutex);
+        MapLocked(virt, phys, size, extra_info);
+    }
+
+    void Unmap(VaType virt, VaType size) {
+        std::scoped_lock lock(block_mutex);
+        UnmapLocked(virt, size);
+    }
+
+    VaType GetVALimit() const {
+        return va_limit;
+    }
+
+protected:
+    /**
+     * @brief Represents a block of memory in the AS, the physical mapping is contiguous until
+     * another block with a different phys address is hit
+     */
+    struct Block {
+        /// VA of the block
+        VaType virt{UnmappedVa};
+        /// PA of the block, will increase 1-1 with VA until a new block is encountered
+        PaType phys{UnmappedPa};
+        [[no_unique_address]] ExtraBlockInfo extra_info;
+
+        Block() = default;
+
+        Block(VaType virt_, PaType phys_, ExtraBlockInfo extra_info_)
+            : virt(virt_), phys(phys_), extra_info(extra_info_) {}
+
+        bool Valid() const {
+            return virt != UnmappedVa;
+        }
+
+        bool Mapped() const {
+            return phys != UnmappedPa;
+        }
+
+        bool Unmapped() const {
+            return phys == UnmappedPa;
+        }
+
+        bool operator<(const VaType& p_virt) const {
+            return virt < p_virt;
+        }
+    };
+
+    /**
+     * @brief Maps a PA range into the given AS region
+     * @note block_mutex MUST be locked when calling this
+     */
+    void MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info);
+
+    /**
+     * @brief Unmaps the given range and merges it with other unmapped regions
+     * @note block_mutex MUST be locked when calling this
+     */
+    void UnmapLocked(VaType virt, VaType size);
+
+    std::mutex block_mutex;
+    std::vector<Block> blocks{Block{}};
+
+    /// a soft limit on the maximum VA of the AS
+    VaType va_limit{VaMaximum};
+
+private:
+    /// Callback called when the mappings in an region have changed
+    std::function<void(VaType, VaType)> unmap_callback{};
+};
+
+/**
+ * @brief FlatMemoryManager specialises FlatAddressSpaceMap to work as an allocator, with an
+ * initial, fast linear pass and a subsequent slower pass that iterates until it finds a free block
+ */
+template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits>
+requires AddressSpaceValid<VaType, AddressSpaceBits>
+class FlatAllocator
+    : public FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits> {
+private:
+    using Base = FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits>;
+
+public:
+    explicit FlatAllocator(VaType virt_start, VaType va_limit = Base::VaMaximum);
+
+    /**
+     * @brief Allocates a region in the AS of the given size and returns its address
+     */
+    VaType Allocate(VaType size);
+
+    /**
+     * @brief Marks the given region in the AS as allocated
+     */
+    void AllocateFixed(VaType virt, VaType size);
+
+    /**
+     * @brief Frees an AS region so it can be used again
+     */
+    void Free(VaType virt, VaType size);
+
+    VaType GetVAStart() const {
+        return virt_start;
+    }
+
+private:
+    /// The base VA of the allocator, no allocations will be below this
+    VaType virt_start;
+
+    /**
+     * The end address for the initial linear allocation pass
+     * Once this reaches the AS limit the slower allocation path will be used
+     */
+    VaType current_linear_alloc_end;
+};
+} // namespace Common

+ 366 - 0
src/common/address_space.inc

@@ -0,0 +1,366 @@
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/address_space.h"
+#include "common/assert.h"
+
+#define MAP_MEMBER(returnType)                                                                     \
+    template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa,              \
+              bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo>                \
+    requires AddressSpaceValid<VaType, AddressSpaceBits> returnType FlatAddressSpaceMap<           \
+        VaType, UnmappedVa, PaType, UnmappedPa, PaContigSplit, AddressSpaceBits, ExtraBlockInfo>
+#define MAP_MEMBER_CONST()                                                                         \
+    template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa,              \
+              bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo>                \
+    requires AddressSpaceValid<VaType, AddressSpaceBits> FlatAddressSpaceMap<                      \
+        VaType, UnmappedVa, PaType, UnmappedPa, PaContigSplit, AddressSpaceBits, ExtraBlockInfo>
+
+#define MM_MEMBER(returnType)                                                                      \
+    template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits>                         \
+    requires AddressSpaceValid<VaType, AddressSpaceBits> returnType                                \
+        FlatMemoryManager<VaType, UnmappedVa, AddressSpaceBits>
+
+#define ALLOC_MEMBER(returnType)                                                                   \
+    template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits>                         \
+    requires AddressSpaceValid<VaType, AddressSpaceBits> returnType                                \
+        FlatAllocator<VaType, UnmappedVa, AddressSpaceBits>
+#define ALLOC_MEMBER_CONST()                                                                       \
+    template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits>                         \
+    requires AddressSpaceValid<VaType, AddressSpaceBits>                                           \
+        FlatAllocator<VaType, UnmappedVa, AddressSpaceBits>
+
+namespace Common {
+MAP_MEMBER_CONST()::FlatAddressSpaceMap(VaType va_limit_,
+                                        std::function<void(VaType, VaType)> unmap_callback_)
+    : va_limit{va_limit_}, unmap_callback{std::move(unmap_callback_)} {
+    if (va_limit > VaMaximum) {
+        ASSERT_MSG(false, "Invalid VA limit!");
+    }
+}
+
+MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info) {
+    VaType virt_end{virt + size};
+
+    if (virt_end > va_limit) {
+        ASSERT_MSG(false,
+                   "Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
+                   virt_end, va_limit);
+    }
+
+    auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
+    if (block_end_successor == blocks.begin()) {
+        ASSERT_MSG(false, "Trying to map a block before the VA start: virt_end: 0x{:X}", virt_end);
+    }
+
+    auto block_end_predecessor{std::prev(block_end_successor)};
+
+    if (block_end_successor != blocks.end()) {
+        // We have blocks in front of us, if one is directly in front then we don't have to add a
+        // tail
+        if (block_end_successor->virt != virt_end) {
+            PaType tailPhys{[&]() -> PaType {
+                if constexpr (!PaContigSplit) {
+                    // Always propagate unmapped regions rather than calculating offset
+                    return block_end_predecessor->phys;
+                } else {
+                    if (block_end_predecessor->Unmapped()) {
+                        // Always propagate unmapped regions rather than calculating offset
+                        return block_end_predecessor->phys;
+                    } else {
+                        return block_end_predecessor->phys + virt_end - block_end_predecessor->virt;
+                    }
+                }
+            }()};
+
+            if (block_end_predecessor->virt >= virt) {
+                // If this block's start would be overlapped by the map then reuse it as a tail
+                // block
+                block_end_predecessor->virt = virt_end;
+                block_end_predecessor->phys = tailPhys;
+                block_end_predecessor->extra_info = block_end_predecessor->extra_info;
+
+                // No longer predecessor anymore
+                block_end_successor = block_end_predecessor--;
+            } else {
+                // Else insert a new one and we're done
+                blocks.insert(block_end_successor,
+                              {Block(virt, phys, extra_info),
+                               Block(virt_end, tailPhys, block_end_predecessor->extra_info)});
+                if (unmap_callback) {
+                    unmap_callback(virt, size);
+                }
+
+                return;
+            }
+        }
+    } else {
+        // block_end_predecessor will always be unmapped as blocks has to be terminated by an
+        // unmapped chunk
+        if (block_end_predecessor != blocks.begin() && block_end_predecessor->virt >= virt) {
+            // Move the unmapped block start backwards
+            block_end_predecessor->virt = virt_end;
+
+            // No longer predecessor anymore
+            block_end_successor = block_end_predecessor--;
+        } else {
+            // Else insert a new one and we're done
+            blocks.insert(block_end_successor,
+                          {Block(virt, phys, extra_info), Block(virt_end, UnmappedPa, {})});
+            if (unmap_callback) {
+                unmap_callback(virt, size);
+            }
+
+            return;
+        }
+    }
+
+    auto block_start_successor{block_end_successor};
+
+    // Walk the block vector to find the start successor as this is more efficient than another
+    // binary search in most scenarios
+    while (std::prev(block_start_successor)->virt >= virt) {
+        block_start_successor--;
+    }
+
+    // Check that the start successor is either the end block or something in between
+    if (block_start_successor->virt > virt_end) {
+        ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
+    } else if (block_start_successor->virt == virt_end) {
+        // We need to create a new block as there are none spare that we would overwrite
+        blocks.insert(block_start_successor, Block(virt, phys, extra_info));
+    } else {
+        // Erase overwritten blocks
+        if (auto eraseStart{std::next(block_start_successor)}; eraseStart != block_end_successor) {
+            blocks.erase(eraseStart, block_end_successor);
+        }
+
+        // Reuse a block that would otherwise be overwritten as a start block
+        block_start_successor->virt = virt;
+        block_start_successor->phys = phys;
+        block_start_successor->extra_info = extra_info;
+    }
+
+    if (unmap_callback) {
+        unmap_callback(virt, size);
+    }
+}
+
+MAP_MEMBER(void)::UnmapLocked(VaType virt, VaType size) {
+    VaType virt_end{virt + size};
+
+    if (virt_end > va_limit) {
+        ASSERT_MSG(false,
+                   "Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
+                   virt_end, va_limit);
+    }
+
+    auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
+    if (block_end_successor == blocks.begin()) {
+        ASSERT_MSG(false, "Trying to unmap a block before the VA start: virt_end: 0x{:X}",
+                   virt_end);
+    }
+
+    auto block_end_predecessor{std::prev(block_end_successor)};
+
+    auto walk_back_to_predecessor{[&](auto iter) {
+        while (iter->virt >= virt) {
+            iter--;
+        }
+
+        return iter;
+    }};
+
+    auto erase_blocks_with_end_unmapped{[&](auto unmappedEnd) {
+        auto block_start_predecessor{walk_back_to_predecessor(unmappedEnd)};
+        auto block_start_successor{std::next(block_start_predecessor)};
+
+        auto eraseEnd{[&]() {
+            if (block_start_predecessor->Unmapped()) {
+                // If the start predecessor is unmapped then we can erase everything in our region
+                // and be done
+                return std::next(unmappedEnd);
+            } else {
+                // Else reuse the end predecessor as the start of our unmapped region then erase all
+                // up to it
+                unmappedEnd->virt = virt;
+                return unmappedEnd;
+            }
+        }()};
+
+        // We can't have two unmapped regions after each other
+        if (eraseEnd != blocks.end() &&
+            (eraseEnd == block_start_successor ||
+             (block_start_predecessor->Unmapped() && eraseEnd->Unmapped()))) {
+            ASSERT_MSG(false, "Multiple contiguous unmapped regions are unsupported!");
+        }
+
+        blocks.erase(block_start_successor, eraseEnd);
+    }};
+
+    // We can avoid any splitting logic if these are the case
+    if (block_end_predecessor->Unmapped()) {
+        if (block_end_predecessor->virt > virt) {
+            erase_blocks_with_end_unmapped(block_end_predecessor);
+        }
+
+        if (unmap_callback) {
+            unmap_callback(virt, size);
+        }
+
+        return; // The region is unmapped, bail out early
+    } else if (block_end_successor->virt == virt_end && block_end_successor->Unmapped()) {
+        erase_blocks_with_end_unmapped(block_end_successor);
+
+        if (unmap_callback) {
+            unmap_callback(virt, size);
+        }
+
+        return; // The region is unmapped here and doesn't need splitting, bail out early
+    } else if (block_end_successor == blocks.end()) {
+        // This should never happen as the end should always follow an unmapped block
+        ASSERT_MSG(false, "Unexpected Memory Manager state!");
+    } else if (block_end_successor->virt != virt_end) {
+        // If one block is directly in front then we don't have to add a tail
+
+        // The previous block is mapped so we will need to add a tail with an offset
+        PaType tailPhys{[&]() {
+            if constexpr (PaContigSplit) {
+                return block_end_predecessor->phys + virt_end - block_end_predecessor->virt;
+            } else {
+                return block_end_predecessor->phys;
+            }
+        }()};
+
+        if (block_end_predecessor->virt >= virt) {
+            // If this block's start would be overlapped by the unmap then reuse it as a tail block
+            block_end_predecessor->virt = virt_end;
+            block_end_predecessor->phys = tailPhys;
+
+            // No longer predecessor anymore
+            block_end_successor = block_end_predecessor--;
+        } else {
+            blocks.insert(block_end_successor,
+                          {Block(virt, UnmappedPa, {}),
+                           Block(virt_end, tailPhys, block_end_predecessor->extra_info)});
+            if (unmap_callback) {
+                unmap_callback(virt, size);
+            }
+
+            // The previous block is mapped and ends before
+            return;
+        }
+    }
+
+    // Walk the block vector to find the start predecessor as this is more efficient than another
+    // binary search in most scenarios
+    auto block_start_predecessor{walk_back_to_predecessor(block_end_successor)};
+    auto block_start_successor{std::next(block_start_predecessor)};
+
+    if (block_start_successor->virt > virt_end) {
+        ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
+    } else if (block_start_successor->virt == virt_end) {
+        // There are no blocks between the start and the end that would let us skip inserting a new
+        // one for head
+
+        // The previous block is may be unmapped, if so we don't need to insert any unmaps after it
+        if (block_start_predecessor->Mapped()) {
+            blocks.insert(block_start_successor, Block(virt, UnmappedPa, {}));
+        }
+    } else if (block_start_predecessor->Unmapped()) {
+        // If the previous block is unmapped
+        blocks.erase(block_start_successor, block_end_predecessor);
+    } else {
+        // Erase overwritten blocks, skipping the first one as we have written the unmapped start
+        // block there
+        if (auto eraseStart{std::next(block_start_successor)}; eraseStart != block_end_successor) {
+            blocks.erase(eraseStart, block_end_successor);
+        }
+
+        // Add in the unmapped block header
+        block_start_successor->virt = virt;
+        block_start_successor->phys = UnmappedPa;
+    }
+
+    if (unmap_callback)
+        unmap_callback(virt, size);
+}
+
+ALLOC_MEMBER_CONST()::FlatAllocator(VaType virt_start_, VaType va_limit_)
+    : Base{va_limit_}, virt_start{virt_start_}, current_linear_alloc_end{virt_start_} {}
+
+ALLOC_MEMBER(VaType)::Allocate(VaType size) {
+    std::scoped_lock lock(this->block_mutex);
+
+    VaType alloc_start{UnmappedVa};
+    VaType alloc_end{current_linear_alloc_end + size};
+
+    // Avoid searching backwards in the address space if possible
+    if (alloc_end >= current_linear_alloc_end && alloc_end <= this->va_limit) {
+        auto alloc_end_successor{
+            std::lower_bound(this->blocks.begin(), this->blocks.end(), alloc_end)};
+        if (alloc_end_successor == this->blocks.begin()) {
+            ASSERT_MSG(false, "First block in AS map is invalid!");
+        }
+
+        auto alloc_end_predecessor{std::prev(alloc_end_successor)};
+        if (alloc_end_predecessor->virt <= current_linear_alloc_end) {
+            alloc_start = current_linear_alloc_end;
+        } else {
+            // Skip over fixed any mappings in front of us
+            while (alloc_end_successor != this->blocks.end()) {
+                if (alloc_end_successor->virt - alloc_end_predecessor->virt < size ||
+                    alloc_end_predecessor->Mapped()) {
+                    alloc_start = alloc_end_predecessor->virt;
+                    break;
+                }
+
+                alloc_end_predecessor = alloc_end_successor++;
+
+                // Use the VA limit to calculate if we can fit in the final block since it has no
+                // successor
+                if (alloc_end_successor == this->blocks.end()) {
+                    alloc_end = alloc_end_predecessor->virt + size;
+
+                    if (alloc_end >= alloc_end_predecessor->virt && alloc_end <= this->va_limit) {
+                        alloc_start = alloc_end_predecessor->virt;
+                    }
+                }
+            }
+        }
+    }
+
+    if (alloc_start != UnmappedVa) {
+        current_linear_alloc_end = alloc_start + size;
+    } else { // If linear allocation overflows the AS then find a gap
+        if (this->blocks.size() <= 2) {
+            ASSERT_MSG(false, "Unexpected allocator state!");
+        }
+
+        auto search_predecessor{this->blocks.begin()};
+        auto search_successor{std::next(search_predecessor)};
+
+        while (search_successor != this->blocks.end() &&
+               (search_successor->virt - search_predecessor->virt < size ||
+                search_predecessor->Mapped())) {
+            search_predecessor = search_successor++;
+        }
+
+        if (search_successor != this->blocks.end()) {
+            alloc_start = search_predecessor->virt;
+        } else {
+            return {}; // AS is full
+        }
+    }
+
+    this->MapLocked(alloc_start, true, size, {});
+    return alloc_start;
+}
+
+ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) {
+    this->Map(virt, true, size);
+}
+
+ALLOC_MEMBER(void)::Free(VaType virt, VaType size) {
+    this->Unmap(virt, size);
+}
+} // namespace Common

+ 8 - 0
src/common/algorithm.h

@@ -24,4 +24,12 @@ template <class ForwardIt, class T, class Compare = std::less<>>
     return first != last && !comp(value, *first) ? first : last;
     return first != last && !comp(value, *first) ? first : last;
 }
 }
 
 
+template <typename T, typename Func, typename... Args>
+T FoldRight(T initial_value, Func&& func, Args&&... args) {
+    T value{initial_value};
+    const auto high_func = [&value, &func]<typename U>(U x) { value = func(value, x); };
+    (std::invoke(high_func, std::forward<Args>(args)), ...);
+    return value;
+}
+
 } // namespace Common
 } // namespace Common

+ 7 - 0
src/common/hash.h

@@ -18,4 +18,11 @@ struct PairHash {
     }
     }
 };
 };
 
 
+template <typename T>
+struct IdentityHash {
+    [[nodiscard]] size_t operator()(T value) const noexcept {
+        return static_cast<size_t>(value);
+    }
+};
+
 } // namespace Common
 } // namespace Common

+ 9 - 0
src/common/multi_level_page_table.cpp

@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/multi_level_page_table.inc"
+
+namespace Common {
+template class Common::MultiLevelPageTable<u64>;
+template class Common::MultiLevelPageTable<u32>;
+} // namespace Common

+ 78 - 0
src/common/multi_level_page_table.h

@@ -0,0 +1,78 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Common {
+
+template <typename BaseAddr>
+class MultiLevelPageTable final {
+public:
+    constexpr MultiLevelPageTable() = default;
+    explicit MultiLevelPageTable(std::size_t address_space_bits, std::size_t first_level_bits,
+                                 std::size_t page_bits);
+
+    ~MultiLevelPageTable() noexcept;
+
+    MultiLevelPageTable(const MultiLevelPageTable&) = delete;
+    MultiLevelPageTable& operator=(const MultiLevelPageTable&) = delete;
+
+    MultiLevelPageTable(MultiLevelPageTable&& other) noexcept
+        : address_space_bits{std::exchange(other.address_space_bits, 0)},
+          first_level_bits{std::exchange(other.first_level_bits, 0)}, page_bits{std::exchange(
+                                                                          other.page_bits, 0)},
+          first_level_shift{std::exchange(other.first_level_shift, 0)},
+          first_level_chunk_size{std::exchange(other.first_level_chunk_size, 0)},
+          first_level_map{std::move(other.first_level_map)}, base_ptr{std::exchange(other.base_ptr,
+                                                                                    nullptr)} {}
+
+    MultiLevelPageTable& operator=(MultiLevelPageTable&& other) noexcept {
+        address_space_bits = std::exchange(other.address_space_bits, 0);
+        first_level_bits = std::exchange(other.first_level_bits, 0);
+        page_bits = std::exchange(other.page_bits, 0);
+        first_level_shift = std::exchange(other.first_level_shift, 0);
+        first_level_chunk_size = std::exchange(other.first_level_chunk_size, 0);
+        alloc_size = std::exchange(other.alloc_size, 0);
+        first_level_map = std::move(other.first_level_map);
+        base_ptr = std::exchange(other.base_ptr, nullptr);
+        return *this;
+    }
+
+    void ReserveRange(u64 start, std::size_t size);
+
+    [[nodiscard]] const BaseAddr& operator[](std::size_t index) const {
+        return base_ptr[index];
+    }
+
+    [[nodiscard]] BaseAddr& operator[](std::size_t index) {
+        return base_ptr[index];
+    }
+
+    [[nodiscard]] BaseAddr* data() {
+        return base_ptr;
+    }
+
+    [[nodiscard]] const BaseAddr* data() const {
+        return base_ptr;
+    }
+
+private:
+    void AllocateLevel(u64 level);
+
+    std::size_t address_space_bits{};
+    std::size_t first_level_bits{};
+    std::size_t page_bits{};
+    std::size_t first_level_shift{};
+    std::size_t first_level_chunk_size{};
+    std::size_t alloc_size{};
+    std::vector<void*> first_level_map{};
+    BaseAddr* base_ptr{};
+};
+
+} // namespace Common

+ 84 - 0
src/common/multi_level_page_table.inc

@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <sys/mman.h>
+#endif
+
+#include "common/assert.h"
+#include "common/multi_level_page_table.h"
+
+namespace Common {
+
+template <typename BaseAddr>
+MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
+                                                   std::size_t first_level_bits_,
+                                                   std::size_t page_bits_)
+    : address_space_bits{address_space_bits_},
+      first_level_bits{first_level_bits_}, page_bits{page_bits_} {
+    if (page_bits == 0) {
+      return;
+    }
+    first_level_shift = address_space_bits - first_level_bits;
+    first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
+    alloc_size = (1ULL << (address_space_bits - page_bits)) * sizeof(BaseAddr);
+    std::size_t first_level_size = 1ULL << first_level_bits;
+    first_level_map.resize(first_level_size, nullptr);
+#ifdef _WIN32
+    void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
+#else
+    void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)};
+
+    if (base == MAP_FAILED) {
+        base = nullptr;
+    }
+#endif
+
+    ASSERT(base);
+    base_ptr = reinterpret_cast<BaseAddr*>(base);
+}
+
+template <typename BaseAddr>
+MultiLevelPageTable<BaseAddr>::~MultiLevelPageTable() noexcept {
+    if (!base_ptr) {
+        return;
+    }
+#ifdef _WIN32
+    ASSERT(VirtualFree(base_ptr, 0, MEM_RELEASE));
+#else
+    ASSERT(munmap(base_ptr, alloc_size) == 0);
+#endif
+}
+
+template <typename BaseAddr>
+void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
+    const u64 new_start = start >> first_level_shift;
+    const u64 new_end = (start + size) >> first_level_shift;
+    for (u64 i = new_start; i <= new_end; i++) {
+        if (!first_level_map[i]) {
+            AllocateLevel(i);
+        }
+    }
+}
+
+template <typename BaseAddr>
+void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
+    void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
+#ifdef _WIN32
+    void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
+#else
+    void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
+                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
+
+    if (base == MAP_FAILED) {
+        base = nullptr;
+    }
+#endif
+    ASSERT(base);
+
+    first_level_map[level] = base;
+}
+
+} // namespace Common

+ 6 - 4
src/core/CMakeLists.txt

@@ -138,8 +138,6 @@ add_library(core STATIC
     frontend/emu_window.h
     frontend/emu_window.h
     frontend/framebuffer_layout.cpp
     frontend/framebuffer_layout.cpp
     frontend/framebuffer_layout.h
     frontend/framebuffer_layout.h
-    hardware_interrupt_manager.cpp
-    hardware_interrupt_manager.h
     hid/emulated_console.cpp
     hid/emulated_console.cpp
     hid/emulated_console.h
     hid/emulated_console.h
     hid/emulated_controller.cpp
     hid/emulated_controller.cpp
@@ -550,6 +548,12 @@ add_library(core STATIC
     hle/service/ns/ns.h
     hle/service/ns/ns.h
     hle/service/ns/pdm_qry.cpp
     hle/service/ns/pdm_qry.cpp
     hle/service/ns/pdm_qry.h
     hle/service/ns/pdm_qry.h
+    hle/service/nvdrv/core/container.cpp
+    hle/service/nvdrv/core/container.h
+    hle/service/nvdrv/core/nvmap.cpp
+    hle/service/nvdrv/core/nvmap.h
+    hle/service/nvdrv/core/syncpoint_manager.cpp
+    hle/service/nvdrv/core/syncpoint_manager.h
     hle/service/nvdrv/devices/nvdevice.h
     hle/service/nvdrv/devices/nvdevice.h
     hle/service/nvdrv/devices/nvdisp_disp0.cpp
     hle/service/nvdrv/devices/nvdisp_disp0.cpp
     hle/service/nvdrv/devices/nvdisp_disp0.h
     hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -578,8 +582,6 @@ add_library(core STATIC
     hle/service/nvdrv/nvdrv_interface.h
     hle/service/nvdrv/nvdrv_interface.h
     hle/service/nvdrv/nvmemp.cpp
     hle/service/nvdrv/nvmemp.cpp
     hle/service/nvdrv/nvmemp.h
     hle/service/nvdrv/nvmemp.h
-    hle/service/nvdrv/syncpoint_manager.cpp
-    hle/service/nvdrv/syncpoint_manager.h
     hle/service/nvflinger/binder.h
     hle/service/nvflinger/binder.h
     hle/service/nvflinger/buffer_item.h
     hle/service/nvflinger/buffer_item.h
     hle/service/nvflinger/buffer_item_consumer.cpp
     hle/service/nvflinger/buffer_item_consumer.cpp

+ 8 - 7
src/core/core.cpp

@@ -27,7 +27,6 @@
 #include "core/file_sys/savedata_factory.h"
 #include "core/file_sys/savedata_factory.h"
 #include "core/file_sys/vfs_concat.h"
 #include "core/file_sys/vfs_concat.h"
 #include "core/file_sys/vfs_real.h"
 #include "core/file_sys/vfs_real.h"
-#include "core/hardware_interrupt_manager.h"
 #include "core/hid/hid_core.h"
 #include "core/hid/hid_core.h"
 #include "core/hle/kernel/k_memory_manager.h"
 #include "core/hle/kernel/k_memory_manager.h"
 #include "core/hle/kernel/k_process.h"
 #include "core/hle/kernel/k_process.h"
@@ -51,6 +50,7 @@
 #include "core/telemetry_session.h"
 #include "core/telemetry_session.h"
 #include "core/tools/freezer.h"
 #include "core/tools/freezer.h"
 #include "network/network.h"
 #include "network/network.h"
+#include "video_core/host1x/host1x.h"
 #include "video_core/renderer_base.h"
 #include "video_core/renderer_base.h"
 #include "video_core/video_core.h"
 #include "video_core/video_core.h"
 
 
@@ -215,6 +215,7 @@ struct System::Impl {
 
 
         telemetry_session = std::make_unique<Core::TelemetrySession>();
         telemetry_session = std::make_unique<Core::TelemetrySession>();
 
 
+        host1x_core = std::make_unique<Tegra::Host1x::Host1x>(system);
         gpu_core = VideoCore::CreateGPU(emu_window, system);
         gpu_core = VideoCore::CreateGPU(emu_window, system);
         if (!gpu_core) {
         if (!gpu_core) {
             return SystemResultStatus::ErrorVideoCore;
             return SystemResultStatus::ErrorVideoCore;
@@ -224,7 +225,6 @@ struct System::Impl {
 
 
         service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
         service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
         services = std::make_unique<Service::Services>(service_manager, system);
         services = std::make_unique<Service::Services>(service_manager, system);
-        interrupt_manager = std::make_unique<Hardware::InterruptManager>(system);
 
 
         // Initialize time manager, which must happen after kernel is created
         // Initialize time manager, which must happen after kernel is created
         time_manager.Initialize();
         time_manager.Initialize();
@@ -373,6 +373,7 @@ struct System::Impl {
         app_loader.reset();
         app_loader.reset();
         audio_core.reset();
         audio_core.reset();
         gpu_core.reset();
         gpu_core.reset();
+        host1x_core.reset();
         perf_stats.reset();
         perf_stats.reset();
         kernel.Shutdown();
         kernel.Shutdown();
         memory.Reset();
         memory.Reset();
@@ -450,7 +451,7 @@ struct System::Impl {
     /// AppLoader used to load the current executing application
     /// AppLoader used to load the current executing application
     std::unique_ptr<Loader::AppLoader> app_loader;
     std::unique_ptr<Loader::AppLoader> app_loader;
     std::unique_ptr<Tegra::GPU> gpu_core;
     std::unique_ptr<Tegra::GPU> gpu_core;
-    std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
+    std::unique_ptr<Tegra::Host1x::Host1x> host1x_core;
     std::unique_ptr<Core::DeviceMemory> device_memory;
     std::unique_ptr<Core::DeviceMemory> device_memory;
     std::unique_ptr<AudioCore::AudioCore> audio_core;
     std::unique_ptr<AudioCore::AudioCore> audio_core;
     Core::Memory::Memory memory;
     Core::Memory::Memory memory;
@@ -668,12 +669,12 @@ const Tegra::GPU& System::GPU() const {
     return *impl->gpu_core;
     return *impl->gpu_core;
 }
 }
 
 
-Core::Hardware::InterruptManager& System::InterruptManager() {
-    return *impl->interrupt_manager;
+Tegra::Host1x::Host1x& System::Host1x() {
+    return *impl->host1x_core;
 }
 }
 
 
-const Core::Hardware::InterruptManager& System::InterruptManager() const {
-    return *impl->interrupt_manager;
+const Tegra::Host1x::Host1x& System::Host1x() const {
+    return *impl->host1x_core;
 }
 }
 
 
 VideoCore::RendererBase& System::Renderer() {
 VideoCore::RendererBase& System::Renderer() {

+ 9 - 10
src/core/core.h

@@ -74,6 +74,9 @@ class TimeManager;
 namespace Tegra {
 namespace Tegra {
 class DebugContext;
 class DebugContext;
 class GPU;
 class GPU;
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
 } // namespace Tegra
 } // namespace Tegra
 
 
 namespace VideoCore {
 namespace VideoCore {
@@ -88,10 +91,6 @@ namespace Core::Timing {
 class CoreTiming;
 class CoreTiming;
 }
 }
 
 
-namespace Core::Hardware {
-class InterruptManager;
-}
-
 namespace Core::HID {
 namespace Core::HID {
 class HIDCore;
 class HIDCore;
 }
 }
@@ -260,6 +259,12 @@ public:
     /// Gets an immutable reference to the GPU interface.
     /// Gets an immutable reference to the GPU interface.
     [[nodiscard]] const Tegra::GPU& GPU() const;
     [[nodiscard]] const Tegra::GPU& GPU() const;
 
 
+    /// Gets a mutable reference to the Host1x interface
+    [[nodiscard]] Tegra::Host1x::Host1x& Host1x();
+
+    /// Gets an immutable reference to the Host1x interface.
+    [[nodiscard]] const Tegra::Host1x::Host1x& Host1x() const;
+
     /// Gets a mutable reference to the renderer.
     /// Gets a mutable reference to the renderer.
     [[nodiscard]] VideoCore::RendererBase& Renderer();
     [[nodiscard]] VideoCore::RendererBase& Renderer();
 
 
@@ -296,12 +301,6 @@ public:
     /// Provides a constant reference to the core timing instance.
     /// Provides a constant reference to the core timing instance.
     [[nodiscard]] const Timing::CoreTiming& CoreTiming() const;
     [[nodiscard]] const Timing::CoreTiming& CoreTiming() const;
 
 
-    /// Provides a reference to the interrupt manager instance.
-    [[nodiscard]] Core::Hardware::InterruptManager& InterruptManager();
-
-    /// Provides a constant reference to the interrupt manager instance.
-    [[nodiscard]] const Core::Hardware::InterruptManager& InterruptManager() const;
-
     /// Provides a reference to the kernel instance.
     /// Provides a reference to the kernel instance.
     [[nodiscard]] Kernel::KernelCore& Kernel();
     [[nodiscard]] Kernel::KernelCore& Kernel();
 
 

+ 0 - 32
src/core/hardware_interrupt_manager.cpp

@@ -1,32 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/hardware_interrupt_manager.h"
-#include "core/hle/service/nvdrv/nvdrv_interface.h"
-#include "core/hle/service/sm/sm.h"
-
-namespace Core::Hardware {
-
-InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) {
-    gpu_interrupt_event = Core::Timing::CreateEvent(
-        "GPUInterrupt",
-        [this](std::uintptr_t message, u64 time,
-               std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
-            auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
-            const u32 syncpt = static_cast<u32>(message >> 32);
-            const u32 value = static_cast<u32>(message);
-            nvdrv->SignalGPUInterruptSyncpt(syncpt, value);
-            return std::nullopt;
-        });
-}
-
-InterruptManager::~InterruptManager() = default;
-
-void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
-    const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value;
-    system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{10}, gpu_interrupt_event, msg);
-}
-
-} // namespace Core::Hardware

+ 0 - 32
src/core/hardware_interrupt_manager.h

@@ -1,32 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <memory>
-
-#include "common/common_types.h"
-
-namespace Core {
-class System;
-}
-
-namespace Core::Timing {
-struct EventType;
-}
-
-namespace Core::Hardware {
-
-class InterruptManager {
-public:
-    explicit InterruptManager(Core::System& system);
-    ~InterruptManager();
-
-    void GPUInterruptSyncpt(u32 syncpoint_id, u32 value);
-
-private:
-    Core::System& system;
-    std::shared_ptr<Core::Timing::EventType> gpu_interrupt_event;
-};
-
-} // namespace Core::Hardware

+ 50 - 0
src/core/hle/service/nvdrv/core/container.cpp

@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
+#include "video_core/host1x/host1x.h"
+
+namespace Service::Nvidia::NvCore {
+
+struct ContainerImpl {
+    explicit ContainerImpl(Tegra::Host1x::Host1x& host1x_)
+        : file{host1x_}, manager{host1x_}, device_file_data{} {}
+    NvMap file;
+    SyncpointManager manager;
+    Container::Host1xDeviceFileData device_file_data;
+};
+
+Container::Container(Tegra::Host1x::Host1x& host1x_) {
+    impl = std::make_unique<ContainerImpl>(host1x_);
+}
+
+Container::~Container() = default;
+
+NvMap& Container::GetNvMapFile() {
+    return impl->file;
+}
+
+const NvMap& Container::GetNvMapFile() const {
+    return impl->file;
+}
+
+Container::Host1xDeviceFileData& Container::Host1xDeviceFile() {
+    return impl->device_file_data;
+}
+
+const Container::Host1xDeviceFileData& Container::Host1xDeviceFile() const {
+    return impl->device_file_data;
+}
+
+SyncpointManager& Container::GetSyncpointManager() {
+    return impl->manager;
+}
+
+const SyncpointManager& Container::GetSyncpointManager() const {
+    return impl->manager;
+}
+
+} // namespace Service::Nvidia::NvCore

+ 52 - 0
src/core/hle/service/nvdrv/core/container.h

@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <deque>
+#include <memory>
+#include <unordered_map>
+
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra::Host1x {
+class Host1x;
+} // namespace Tegra::Host1x
+
+namespace Service::Nvidia::NvCore {
+
+class NvMap;
+class SyncpointManager;
+
+struct ContainerImpl;
+
+class Container {
+public:
+    explicit Container(Tegra::Host1x::Host1x& host1x);
+    ~Container();
+
+    NvMap& GetNvMapFile();
+
+    const NvMap& GetNvMapFile() const;
+
+    SyncpointManager& GetSyncpointManager();
+
+    const SyncpointManager& GetSyncpointManager() const;
+
+    struct Host1xDeviceFileData {
+        std::unordered_map<DeviceFD, u32> fd_to_id{};
+        std::deque<u32> syncpts_accumulated{};
+        u32 nvdec_next_id{};
+        u32 vic_next_id{};
+    };
+
+    Host1xDeviceFileData& Host1xDeviceFile();
+
+    const Host1xDeviceFileData& Host1xDeviceFile() const;
+
+private:
+    std::unique_ptr<ContainerImpl> impl;
+};
+
+} // namespace Service::Nvidia::NvCore

+ 272 - 0
src/core/hle/service/nvdrv/core/nvmap.cpp

@@ -0,0 +1,272 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/memory.h"
+#include "video_core/host1x/host1x.h"
+
+using Core::Memory::YUZU_PAGESIZE;
+
+namespace Service::Nvidia::NvCore {
+NvMap::Handle::Handle(u64 size_, Id id_)
+    : size(size_), aligned_size(size), orig_size(size), id(id_) {
+    flags.raw = 0;
+}
+
+NvResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress) {
+    std::scoped_lock lock(mutex);
+
+    // Handles cannot be allocated twice
+    if (allocated) {
+        return NvResult::AccessDenied;
+    }
+
+    flags = pFlags;
+    kind = pKind;
+    align = pAlign < YUZU_PAGESIZE ? YUZU_PAGESIZE : pAlign;
+
+    // This flag is only applicable for handles with an address passed
+    if (pAddress) {
+        flags.keep_uncached_after_free.Assign(0);
+    } else {
+        LOG_CRITICAL(Service_NVDRV,
+                     "Mapping nvmap handles without a CPU side address is unimplemented!");
+    }
+
+    size = Common::AlignUp(size, YUZU_PAGESIZE);
+    aligned_size = Common::AlignUp(size, align);
+    address = pAddress;
+    allocated = true;
+
+    return NvResult::Success;
+}
+
+NvResult NvMap::Handle::Duplicate(bool internal_session) {
+    std::scoped_lock lock(mutex);
+    // Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS)
+    if (!allocated) [[unlikely]] {
+        return NvResult::BadValue;
+    }
+
+    // If we internally use FromId the duplication tracking of handles won't work accurately due to
+    // us not implementing per-process handle refs.
+    if (internal_session) {
+        internal_dupes++;
+    } else {
+        dupes++;
+    }
+
+    return NvResult::Success;
+}
+
+NvMap::NvMap(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {}
+
+void NvMap::AddHandle(std::shared_ptr<Handle> handle_description) {
+    std::scoped_lock lock(handles_lock);
+
+    handles.emplace(handle_description->id, std::move(handle_description));
+}
+
+void NvMap::UnmapHandle(Handle& handle_description) {
+    // Remove pending unmap queue entry if needed
+    if (handle_description.unmap_queue_entry) {
+        unmap_queue.erase(*handle_description.unmap_queue_entry);
+        handle_description.unmap_queue_entry.reset();
+    }
+
+    // Free and unmap the handle from the SMMU
+    host1x.MemoryManager().Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
+                                 handle_description.aligned_size);
+    host1x.Allocator().Free(handle_description.pin_virt_address,
+                            static_cast<u32>(handle_description.aligned_size));
+    handle_description.pin_virt_address = 0;
+}
+
+bool NvMap::TryRemoveHandle(const Handle& handle_description) {
+    // No dupes left, we can remove from handle map
+    if (handle_description.dupes == 0 && handle_description.internal_dupes == 0) {
+        std::scoped_lock lock(handles_lock);
+
+        auto it{handles.find(handle_description.id)};
+        if (it != handles.end()) {
+            handles.erase(it);
+        }
+
+        return true;
+    } else {
+        return false;
+    }
+}
+
+NvResult NvMap::CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out) {
+    if (!size) [[unlikely]] {
+        return NvResult::BadValue;
+    }
+
+    u32 id{next_handle_id.fetch_add(HandleIdIncrement, std::memory_order_relaxed)};
+    auto handle_description{std::make_shared<Handle>(size, id)};
+    AddHandle(handle_description);
+
+    result_out = handle_description;
+    return NvResult::Success;
+}
+
+std::shared_ptr<NvMap::Handle> NvMap::GetHandle(Handle::Id handle) {
+    std::scoped_lock lock(handles_lock);
+    try {
+        return handles.at(handle);
+    } catch (std::out_of_range&) {
+        return nullptr;
+    }
+}
+
+VAddr NvMap::GetHandleAddress(Handle::Id handle) {
+    std::scoped_lock lock(handles_lock);
+    try {
+        return handles.at(handle)->address;
+    } catch (std::out_of_range&) {
+        return 0;
+    }
+}
+
+u32 NvMap::PinHandle(NvMap::Handle::Id handle) {
+    auto handle_description{GetHandle(handle)};
+    if (!handle_description) [[unlikely]] {
+        return 0;
+    }
+
+    std::scoped_lock lock(handle_description->mutex);
+    if (!handle_description->pins) {
+        // If we're in the unmap queue we can just remove ourselves and return since we're already
+        // mapped
+        {
+            // Lock now to prevent our queue entry from being removed for allocation in-between the
+            // following check and erase
+            std::scoped_lock queueLock(unmap_queue_lock);
+            if (handle_description->unmap_queue_entry) {
+                unmap_queue.erase(*handle_description->unmap_queue_entry);
+                handle_description->unmap_queue_entry.reset();
+
+                handle_description->pins++;
+                return handle_description->pin_virt_address;
+            }
+        }
+
+        // If not then allocate some space and map it
+        u32 address{};
+        auto& smmu_allocator = host1x.Allocator();
+        auto& smmu_memory_manager = host1x.MemoryManager();
+        while (!(address =
+                     smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) {
+            // Free handles until the allocation succeeds
+            std::scoped_lock queueLock(unmap_queue_lock);
+            if (auto freeHandleDesc{unmap_queue.front()}) {
+                // Handles in the unmap queue are guaranteed not to be pinned so don't bother
+                // checking if they are before unmapping
+                std::scoped_lock freeLock(freeHandleDesc->mutex);
+                if (handle_description->pin_virt_address)
+                    UnmapHandle(*freeHandleDesc);
+            } else {
+                LOG_CRITICAL(Service_NVDRV, "Ran out of SMMU address space!");
+            }
+        }
+
+        smmu_memory_manager.Map(static_cast<GPUVAddr>(address), handle_description->address,
+                                handle_description->aligned_size);
+        handle_description->pin_virt_address = address;
+    }
+
+    handle_description->pins++;
+    return handle_description->pin_virt_address;
+}
+
+void NvMap::UnpinHandle(Handle::Id handle) {
+    auto handle_description{GetHandle(handle)};
+    if (!handle_description) {
+        return;
+    }
+
+    std::scoped_lock lock(handle_description->mutex);
+    if (--handle_description->pins < 0) {
+        LOG_WARNING(Service_NVDRV, "Pin count imbalance detected!");
+    } else if (!handle_description->pins) {
+        std::scoped_lock queueLock(unmap_queue_lock);
+
+        // Add to the unmap queue allowing this handle's memory to be freed if needed
+        unmap_queue.push_back(handle_description);
+        handle_description->unmap_queue_entry = std::prev(unmap_queue.end());
+    }
+}
+
+void NvMap::DuplicateHandle(Handle::Id handle, bool internal_session) {
+    auto handle_description{GetHandle(handle)};
+    if (!handle_description) {
+        LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
+        return;
+    }
+
+    auto result = handle_description->Duplicate(internal_session);
+    if (result != NvResult::Success) {
+        LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
+    }
+}
+
+std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool internal_session) {
+    std::weak_ptr<Handle> hWeak{GetHandle(handle)};
+    FreeInfo freeInfo;
+
+    // We use a weak ptr here so we can tell when the handle has been freed and report that back to
+    // guest
+    if (auto handle_description = hWeak.lock()) {
+        std::scoped_lock lock(handle_description->mutex);
+
+        if (internal_session) {
+            if (--handle_description->internal_dupes < 0)
+                LOG_WARNING(Service_NVDRV, "Internal duplicate count imbalance detected!");
+        } else {
+            if (--handle_description->dupes < 0) {
+                LOG_WARNING(Service_NVDRV, "User duplicate count imbalance detected!");
+            } else if (handle_description->dupes == 0) {
+                // Force unmap the handle
+                if (handle_description->pin_virt_address) {
+                    std::scoped_lock queueLock(unmap_queue_lock);
+                    UnmapHandle(*handle_description);
+                }
+
+                handle_description->pins = 0;
+            }
+        }
+
+        // Try to remove the shared ptr to the handle from the map, if nothing else is using the
+        // handle then it will now be freed when `handle_description` goes out of scope
+        if (TryRemoveHandle(*handle_description)) {
+            LOG_DEBUG(Service_NVDRV, "Removed nvmap handle: {}", handle);
+        } else {
+            LOG_DEBUG(Service_NVDRV,
+                      "Tried to free nvmap handle: {} but didn't as it still has duplicates",
+                      handle);
+        }
+
+        freeInfo = {
+            .address = handle_description->address,
+            .size = handle_description->size,
+            .was_uncached = handle_description->flags.map_uncached.Value() != 0,
+        };
+    } else {
+        return std::nullopt;
+    }
+
+    // Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed
+    if (!hWeak.expired()) {
+        LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
+        freeInfo.address = 0;
+    }
+
+    return freeInfo;
+}
+
+} // namespace Service::Nvidia::NvCore

+ 175 - 0
src/core/hle/service/nvdrv/core/nvmap.h

@@ -0,0 +1,175 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <assert.h>
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra {
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
+} // namespace Tegra
+
+namespace Service::Nvidia::NvCore {
+/**
+ * @brief The nvmap core class holds the global state for nvmap and provides methods to manage
+ * handles
+ */
+class NvMap {
+public:
+    /**
+     * @brief A handle to a contiguous block of memory in an application's address space
+     */
+    struct Handle {
+        std::mutex mutex;
+
+        u64 align{};      //!< The alignment to use when pinning the handle onto the SMMU
+        u64 size;         //!< Page-aligned size of the memory the handle refers to
+        u64 aligned_size; //!< `align`-aligned size of the memory the handle refers to
+        u64 orig_size;    //!< Original unaligned size of the memory this handle refers to
+
+        s32 dupes{1};          //!< How many guest references there are to this handle
+        s32 internal_dupes{0}; //!< How many emulator-internal references there are to this handle
+
+        using Id = u32;
+        Id id; //!< A globally unique identifier for this handle
+
+        s32 pins{};
+        u32 pin_virt_address{};
+        std::optional<typename std::list<std::shared_ptr<Handle>>::iterator> unmap_queue_entry{};
+
+        union Flags {
+            u32 raw;
+            BitField<0, 1, u32> map_uncached; //!< If the handle should be mapped as uncached
+            BitField<2, 1, u32> keep_uncached_after_free; //!< Only applicable when the handle was
+                                                          //!< allocated with a fixed address
+            BitField<4, 1, u32> _unk0_;                   //!< Passed to IOVMM for pins
+        } flags{};
+        static_assert(sizeof(Flags) == sizeof(u32));
+
+        u64 address{}; //!< The memory location in the guest's AS that this handle corresponds to,
+                       //!< this can also be in the nvdrv tmem
+        bool is_shared_mem_mapped{}; //!< If this nvmap has been mapped with the MapSharedMem IPC
+                                     //!< call
+
+        u8 kind{};        //!< Used for memory compression
+        bool allocated{}; //!< If the handle has been allocated with `Alloc`
+
+        u64 dma_map_addr{}; //! remove me after implementing pinning.
+
+        Handle(u64 size, Id id);
+
+        /**
+         * @brief Sets up the handle with the given memory config, can allocate memory from the tmem
+         * if a 0 address is passed
+         */
+        [[nodiscard]] NvResult Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress);
+
+        /**
+         * @brief Increases the dupe counter of the handle for the given session
+         */
+        [[nodiscard]] NvResult Duplicate(bool internal_session);
+
+        /**
+         * @brief Obtains a pointer to the handle's memory and marks the handle it as having been
+         * mapped
+         */
+        u8* GetPointer() {
+            if (!address) {
+                return nullptr;
+            }
+
+            is_shared_mem_mapped = true;
+            return reinterpret_cast<u8*>(address);
+        }
+    };
+
+    /**
+     * @brief Encapsulates the result of a FreeHandle operation
+     */
+    struct FreeInfo {
+        u64 address;       //!< Address the handle referred to before deletion
+        u64 size;          //!< Page-aligned handle size
+        bool was_uncached; //!< If the handle was allocated as uncached
+    };
+
+    explicit NvMap(Tegra::Host1x::Host1x& host1x);
+
+    /**
+     * @brief Creates an unallocated handle of the given size
+     */
+    [[nodiscard]] NvResult CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out);
+
+    std::shared_ptr<Handle> GetHandle(Handle::Id handle);
+
+    VAddr GetHandleAddress(Handle::Id handle);
+
+    /**
+     * @brief Maps a handle into the SMMU address space
+     * @note This operation is refcounted, the number of calls to this must eventually match the
+     * number of calls to `UnpinHandle`
+     * @return The SMMU virtual address that the handle has been mapped to
+     */
+    u32 PinHandle(Handle::Id handle);
+
+    /**
+     * @brief When this has been called an equal number of times to `PinHandle` for the supplied
+     * handle it will be added to a list of handles to be freed when necessary
+     */
+    void UnpinHandle(Handle::Id handle);
+
+    /**
+     * @brief Tries to duplicate a handle
+     */
+    void DuplicateHandle(Handle::Id handle, bool internal_session = false);
+
+    /**
+     * @brief Tries to free a handle and remove a single dupe
+     * @note If a handle has no dupes left and has no other users a FreeInfo struct will be returned
+     * describing the prior state of the handle
+     */
+    std::optional<FreeInfo> FreeHandle(Handle::Id handle, bool internal_session);
+
+private:
+    std::list<std::shared_ptr<Handle>> unmap_queue{};
+    std::mutex unmap_queue_lock{}; //!< Protects access to `unmap_queue`
+
+    std::unordered_map<Handle::Id, std::shared_ptr<Handle>>
+        handles{};           //!< Main owning map of handles
+    std::mutex handles_lock; //!< Protects access to `handles`
+
+    static constexpr u32 HandleIdIncrement{
+        4}; //!< Each new handle ID is an increment of 4 from the previous
+    std::atomic<u32> next_handle_id{HandleIdIncrement};
+    Tegra::Host1x::Host1x& host1x;
+
+    void AddHandle(std::shared_ptr<Handle> handle);
+
+    /**
+     * @brief Unmaps and frees the SMMU memory region a handle is mapped to
+     * @note Both `unmap_queue_lock` and `handle_description.mutex` MUST be locked when calling this
+     */
+    void UnmapHandle(Handle& handle_description);
+
+    /**
+     * @brief Removes a handle from the map taking its dupes into account
+     * @note handle_description.mutex MUST be locked when calling this
+     * @return If the handle was removed from the map
+     */
+    bool TryRemoveHandle(const Handle& handle_description);
+};
+} // namespace Service::Nvidia::NvCore

+ 121 - 0
src/core/hle/service/nvdrv/core/syncpoint_manager.cpp

@@ -0,0 +1,121 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/assert.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
+#include "video_core/host1x/host1x.h"
+
+namespace Service::Nvidia::NvCore {
+
+SyncpointManager::SyncpointManager(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {
+    constexpr u32 VBlank0SyncpointId{26};
+    constexpr u32 VBlank1SyncpointId{27};
+
+    // Reserve both vblank syncpoints as client managed as they use Continuous Mode
+    // Refer to section 14.3.5.3 of the TRM for more information on Continuous Mode
+    // https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/drm/dc.c#L660
+    ReserveSyncpoint(VBlank0SyncpointId, true);
+    ReserveSyncpoint(VBlank1SyncpointId, true);
+
+    for (u32 syncpoint_id : channel_syncpoints) {
+        if (syncpoint_id) {
+            ReserveSyncpoint(syncpoint_id, false);
+        }
+    }
+}
+
+SyncpointManager::~SyncpointManager() = default;
+
+u32 SyncpointManager::ReserveSyncpoint(u32 id, bool client_managed) {
+    if (syncpoints.at(id).reserved) {
+        ASSERT_MSG(false, "Requested syncpoint is in use");
+        return 0;
+    }
+
+    syncpoints.at(id).reserved = true;
+    syncpoints.at(id).interface_managed = client_managed;
+
+    return id;
+}
+
+u32 SyncpointManager::FindFreeSyncpoint() {
+    for (u32 i{1}; i < syncpoints.size(); i++) {
+        if (!syncpoints[i].reserved) {
+            return i;
+        }
+    }
+    ASSERT_MSG(false, "Failed to find a free syncpoint!");
+    return 0;
+}
+
+u32 SyncpointManager::AllocateSyncpoint(bool client_managed) {
+    std::lock_guard lock(reservation_lock);
+    return ReserveSyncpoint(FindFreeSyncpoint(), client_managed);
+}
+
+void SyncpointManager::FreeSyncpoint(u32 id) {
+    std::lock_guard lock(reservation_lock);
+    ASSERT(syncpoints.at(id).reserved);
+    syncpoints.at(id).reserved = false;
+}
+
+bool SyncpointManager::IsSyncpointAllocated(u32 id) {
+    return (id <= SyncpointCount) && syncpoints[id].reserved;
+}
+
+bool SyncpointManager::HasSyncpointExpired(u32 id, u32 threshold) const {
+    const SyncpointInfo& syncpoint{syncpoints.at(id)};
+
+    if (!syncpoint.reserved) {
+        ASSERT(false);
+        return 0;
+    }
+
+    // If the interface manages counters then we don't keep track of the maximum value as it handles
+    // sanity checking the values then
+    if (syncpoint.interface_managed) {
+        return static_cast<s32>(syncpoint.counter_min - threshold) >= 0;
+    } else {
+        return (syncpoint.counter_max - threshold) >= (syncpoint.counter_min - threshold);
+    }
+}
+
+u32 SyncpointManager::IncrementSyncpointMaxExt(u32 id, u32 amount) {
+    if (!syncpoints.at(id).reserved) {
+        ASSERT(false);
+        return 0;
+    }
+
+    return syncpoints.at(id).counter_max += amount;
+}
+
+u32 SyncpointManager::ReadSyncpointMinValue(u32 id) {
+    if (!syncpoints.at(id).reserved) {
+        ASSERT(false);
+        return 0;
+    }
+
+    return syncpoints.at(id).counter_min;
+}
+
+u32 SyncpointManager::UpdateMin(u32 id) {
+    if (!syncpoints.at(id).reserved) {
+        ASSERT(false);
+        return 0;
+    }
+
+    syncpoints.at(id).counter_min = host1x.GetSyncpointManager().GetHostSyncpointValue(id);
+    return syncpoints.at(id).counter_min;
+}
+
+NvFence SyncpointManager::GetSyncpointFence(u32 id) {
+    if (!syncpoints.at(id).reserved) {
+        ASSERT(false);
+        return NvFence{};
+    }
+
+    return {.id = static_cast<s32>(id), .value = syncpoints.at(id).counter_max};
+}
+
+} // namespace Service::Nvidia::NvCore

+ 134 - 0
src/core/hle/service/nvdrv/core/syncpoint_manager.h

@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra::Host1x {
+class Host1x;
+} // namespace Tegra::Host1x
+
+namespace Service::Nvidia::NvCore {
+
+enum class ChannelType : u32 {
+    MsEnc = 0,
+    VIC = 1,
+    GPU = 2,
+    NvDec = 3,
+    Display = 4,
+    NvJpg = 5,
+    TSec = 6,
+    Max = 7
+};
+
+/**
+ * @brief SyncpointManager handles allocating and accessing host1x syncpoints, these are cached
+ * versions of the HW syncpoints which are intermittently synced
+ * @note Refer to Chapter 14 of the Tegra X1 TRM for an exhaustive overview of them
+ * @url https://http.download.nvidia.com/tegra-public-appnotes/host1x.html
+ * @url
+ * https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/jetson-tx1/drivers/video/tegra/host/nvhost_syncpt.c
+ */
+class SyncpointManager final {
+public:
+    explicit SyncpointManager(Tegra::Host1x::Host1x& host1x);
+    ~SyncpointManager();
+
+    /**
+     * @brief Checks if the given syncpoint is both allocated and below the number of HW syncpoints
+     */
+    bool IsSyncpointAllocated(u32 id);
+
+    /**
+     * @brief Finds a free syncpoint and reserves it
+     * @return The ID of the reserved syncpoint
+     */
+    u32 AllocateSyncpoint(bool client_managed);
+
+    /**
+     * @url
+     * https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/syncpt.c#L259
+     */
+    bool HasSyncpointExpired(u32 id, u32 threshold) const;
+
+    bool IsFenceSignalled(NvFence fence) const {
+        return HasSyncpointExpired(fence.id, fence.value);
+    }
+
+    /**
+     * @brief Atomically increments the maximum value of a syncpoint by the given amount
+     * @return The new max value of the syncpoint
+     */
+    u32 IncrementSyncpointMaxExt(u32 id, u32 amount);
+
+    /**
+     * @return The minimum value of the syncpoint
+     */
+    u32 ReadSyncpointMinValue(u32 id);
+
+    /**
+     * @brief Synchronises the minimum value of the syncpoint to with the GPU
+     * @return The new minimum value of the syncpoint
+     */
+    u32 UpdateMin(u32 id);
+
+    /**
+     * @brief Frees the usage of a syncpoint.
+     */
+    void FreeSyncpoint(u32 id);
+
+    /**
+     * @return A fence that will be signalled once this syncpoint hits its maximum value
+     */
+    NvFence GetSyncpointFence(u32 id);
+
+    static constexpr std::array<u32, static_cast<u32>(ChannelType::Max)> channel_syncpoints{
+        0x0,  // `MsEnc` is unimplemented
+        0xC,  // `VIC`
+        0x0,  // `GPU` syncpoints are allocated per-channel instead
+        0x36, // `NvDec`
+        0x0,  // `Display` is unimplemented
+        0x37, // `NvJpg`
+        0x0,  // `TSec` is unimplemented
+    };        //!< Maps each channel ID to a constant syncpoint
+
+private:
+    /**
+     * @note reservation_lock should be locked when calling this
+     */
+    u32 ReserveSyncpoint(u32 id, bool client_managed);
+
+    /**
+     * @return The ID of the first free syncpoint
+     */
+    u32 FindFreeSyncpoint();
+
+    struct SyncpointInfo {
+        std::atomic<u32> counter_min; //!< The least value the syncpoint can be (The value it was
+                                      //!< when it was last synchronized with host1x)
+        std::atomic<u32> counter_max; //!< The maximum value the syncpoint can reach according to
+                                      //!< the current usage
+        bool interface_managed; //!< If the syncpoint is managed by a host1x client interface, a
+                                //!< client interface is a HW block that can handle host1x
+                                //!< transactions on behalf of a host1x client (Which would
+                                //!< otherwise need to be manually synced using PIO which is
+                                //!< synchronous and requires direct cooperation of the CPU)
+        bool reserved; //!< If the syncpoint is reserved or not, not to be confused with a reserved
+                       //!< value
+    };
+
+    constexpr static std::size_t SyncpointCount{192};
+    std::array<SyncpointInfo, SyncpointCount> syncpoints{};
+    std::mutex reservation_lock;
+
+    Tegra::Host1x::Host1x& host1x;
+};
+
+} // namespace Service::Nvidia::NvCore

+ 8 - 0
src/core/hle/service/nvdrv/devices/nvdevice.h

@@ -11,6 +11,10 @@ namespace Core {
 class System;
 class System;
 }
 }
 
 
+namespace Kernel {
+class KEvent;
+}
+
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
 /// Represents an abstract nvidia device node. It is to be subclassed by concrete device nodes to
 /// Represents an abstract nvidia device node. It is to be subclassed by concrete device nodes to
@@ -64,6 +68,10 @@ public:
      */
      */
     virtual void OnClose(DeviceFD fd) = 0;
     virtual void OnClose(DeviceFD fd) = 0;
 
 
+    virtual Kernel::KEvent* QueryEvent(u32 event_id) {
+        return nullptr;
+    }
+
 protected:
 protected:
     Core::System& system;
     Core::System& system;
 };
 };

+ 13 - 6
src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp

@@ -5,15 +5,16 @@
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/core_timing.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
 #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
 #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
-#include "core/hle/service/nvdrv/devices/nvmap.h"
 #include "core/perf_stats.h"
 #include "core/perf_stats.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu.h"
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
-nvdisp_disp0::nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_)
-    : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {}
+nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core)
+    : nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {}
 nvdisp_disp0::~nvdisp_disp0() = default;
 nvdisp_disp0::~nvdisp_disp0() = default;
 
 
 NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
 NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -39,8 +40,9 @@ void nvdisp_disp0::OnClose(DeviceFD fd) {}
 
 
 void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width,
 void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width,
                         u32 height, u32 stride, android::BufferTransformFlags transform,
                         u32 height, u32 stride, android::BufferTransformFlags transform,
-                        const Common::Rectangle<int>& crop_rect) {
-    const VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
+                        const Common::Rectangle<int>& crop_rect,
+                        std::array<Service::Nvidia::NvFence, 4>& fences, u32 num_fences) {
+    const VAddr addr = nvmap.GetHandleAddress(buffer_handle);
     LOG_TRACE(Service,
     LOG_TRACE(Service,
               "Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
               "Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
               addr, offset, width, height, stride, format);
               addr, offset, width, height, stride, format);
@@ -48,10 +50,15 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat form
     const Tegra::FramebufferConfig framebuffer{addr,   offset, width,     height,
     const Tegra::FramebufferConfig framebuffer{addr,   offset, width,     height,
                                                stride, format, transform, crop_rect};
                                                stride, format, transform, crop_rect};
 
 
+    system.GPU().RequestSwapBuffers(&framebuffer, fences, num_fences);
     system.GetPerfStats().EndSystemFrame();
     system.GetPerfStats().EndSystemFrame();
-    system.GPU().SwapBuffers(&framebuffer);
     system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
     system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
     system.GetPerfStats().BeginSystemFrame();
     system.GetPerfStats().BeginSystemFrame();
 }
 }
 
 
+Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
+    LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
+    return nullptr;
+}
+
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 12 - 3
src/core/hle/service/nvdrv/devices/nvdisp_disp0.h

@@ -11,13 +11,18 @@
 #include "core/hle/service/nvflinger/buffer_transform_flags.h"
 #include "core/hle/service/nvflinger/buffer_transform_flags.h"
 #include "core/hle/service/nvflinger/pixel_format.h"
 #include "core/hle/service/nvflinger/pixel_format.h"
 
 
+namespace Service::Nvidia::NvCore {
+class Container;
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
 class nvmap;
 class nvmap;
 
 
 class nvdisp_disp0 final : public nvdevice {
 class nvdisp_disp0 final : public nvdevice {
 public:
 public:
-    explicit nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_);
+    explicit nvdisp_disp0(Core::System& system_, NvCore::Container& core);
     ~nvdisp_disp0() override;
     ~nvdisp_disp0() override;
 
 
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -33,10 +38,14 @@ public:
     /// Performs a screen flip, drawing the buffer pointed to by the handle.
     /// Performs a screen flip, drawing the buffer pointed to by the handle.
     void flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width, u32 height,
     void flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width, u32 height,
               u32 stride, android::BufferTransformFlags transform,
               u32 stride, android::BufferTransformFlags transform,
-              const Common::Rectangle<int>& crop_rect);
+              const Common::Rectangle<int>& crop_rect,
+              std::array<Service::Nvidia::NvFence, 4>& fences, u32 num_fences);
+
+    Kernel::KEvent* QueryEvent(u32 event_id) override;
 
 
 private:
 private:
-    std::shared_ptr<nvmap> nvmap_dev;
+    NvCore::Container& container;
+    NvCore::NvMap& nvmap;
 };
 };
 
 
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 332 - 160
src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp

@@ -1,21 +1,30 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
 
 
 #include <cstring>
 #include <cstring>
 #include <utility>
 #include <utility>
 
 
+#include "common/alignment.h"
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
 #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
 #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
-#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/gpu.h"
 #include "video_core/memory_manager.h"
 #include "video_core/memory_manager.h"
 #include "video_core/rasterizer_interface.h"
 #include "video_core/rasterizer_interface.h"
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
-nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_)
-    : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {}
+nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, Module& module_, NvCore::Container& core)
+    : nvdevice{system_}, module{module_}, container{core}, nvmap{core.GetNvMapFile()}, vm{},
+      gmmu{} {}
+
 nvhost_as_gpu::~nvhost_as_gpu() = default;
 nvhost_as_gpu::~nvhost_as_gpu() = default;
 
 
 NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
 NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -82,12 +91,52 @@ NvResult nvhost_as_gpu::AllocAsEx(const std::vector<u8>& input, std::vector<u8>&
     IoctlAllocAsEx params{};
     IoctlAllocAsEx params{};
     std::memcpy(&params, input.data(), input.size());
     std::memcpy(&params, input.data(), input.size());
 
 
-    LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size);
-    if (params.big_page_size == 0) {
-        params.big_page_size = DEFAULT_BIG_PAGE_SIZE;
+    LOG_DEBUG(Service_NVDRV, "called, big_page_size=0x{:X}", params.big_page_size);
+
+    std::scoped_lock lock(mutex);
+
+    if (vm.initialised) {
+        ASSERT_MSG(false, "Cannot initialise an address space twice!");
+        return NvResult::InvalidState;
+    }
+
+    if (params.big_page_size) {
+        if (!std::has_single_bit(params.big_page_size)) {
+            LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: 0x{:X}!", params.big_page_size);
+            return NvResult::BadValue;
+        }
+
+        if ((params.big_page_size & VM::SUPPORTED_BIG_PAGE_SIZES) == 0) {
+            LOG_ERROR(Service_NVDRV, "Unsupported big page size: 0x{:X}!", params.big_page_size);
+            return NvResult::BadValue;
+        }
+
+        vm.big_page_size = params.big_page_size;
+        vm.big_page_size_bits = static_cast<u32>(std::countr_zero(params.big_page_size));
+
+        vm.va_range_start = params.big_page_size << VM::VA_START_SHIFT;
+    }
+
+    // If this is unspecified then default values should be used
+    if (params.va_range_start) {
+        vm.va_range_start = params.va_range_start;
+        vm.va_range_split = params.va_range_split;
+        vm.va_range_end = params.va_range_end;
     }
     }
 
 
-    big_page_size = params.big_page_size;
+    const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)};
+    const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)};
+    vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
+
+    const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
+    const auto end_big_pages{
+        static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
+    vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
+
+    gmmu = std::make_shared<Tegra::MemoryManager>(system, 40, vm.big_page_size_bits,
+                                                  VM::PAGE_SIZE_BITS);
+    system.GPU().InitAddressSpace(*gmmu);
+    vm.initialised = true;
 
 
     return NvResult::Success;
     return NvResult::Success;
 }
 }
@@ -99,21 +148,76 @@ NvResult nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<
     LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
     LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
               params.page_size, params.flags);
               params.page_size, params.flags);
 
 
-    const auto size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
-    if ((params.flags & AddressSpaceFlags::FixedOffset) != AddressSpaceFlags::None) {
-        params.offset = *system.GPU().MemoryManager().AllocateFixed(params.offset, size);
+    std::scoped_lock lock(mutex);
+
+    if (!vm.initialised) {
+        return NvResult::BadValue;
+    }
+
+    if (params.page_size != VM::YUZU_PAGESIZE && params.page_size != vm.big_page_size) {
+        return NvResult::BadValue;
+    }
+
+    if (params.page_size != vm.big_page_size &&
+        ((params.flags & MappingFlags::Sparse) != MappingFlags::None)) {
+        UNIMPLEMENTED_MSG("Sparse small pages are not implemented!");
+        return NvResult::NotImplemented;
+    }
+
+    const u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
+                                                                   : vm.big_page_size_bits};
+
+    auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
+                                                          : *vm.big_page_allocator};
+
+    if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
+        allocator.AllocateFixed(static_cast<u32>(params.offset >> page_size_bits), params.pages);
     } else {
     } else {
-        params.offset = system.GPU().MemoryManager().Allocate(size, params.align);
+        params.offset = static_cast<u64>(allocator.Allocate(params.pages)) << page_size_bits;
+        if (!params.offset) {
+            ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
+            return NvResult::InsufficientMemory;
+        }
     }
     }
 
 
-    auto result = NvResult::Success;
-    if (!params.offset) {
-        LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size);
-        result = NvResult::InsufficientMemory;
+    u64 size{static_cast<u64>(params.pages) * params.page_size};
+
+    if ((params.flags & MappingFlags::Sparse) != MappingFlags::None) {
+        gmmu->MapSparse(params.offset, size);
     }
     }
 
 
+    allocation_map[params.offset] = {
+        .size = size,
+        .mappings{},
+        .page_size = params.page_size,
+        .sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
+        .big_pages = params.page_size != VM::YUZU_PAGESIZE,
+    };
+
     std::memcpy(output.data(), &params, output.size());
     std::memcpy(output.data(), &params, output.size());
-    return result;
+    return NvResult::Success;
+}
+
+void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
+    auto mapping{mapping_map.at(offset)};
+
+    if (!mapping->fixed) {
+        auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
+        u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+
+        allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
+                       static_cast<u32>(mapping->size >> page_size_bits));
+    }
+
+    // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
+    // Only FreeSpace can unmap them fully
+    if (mapping->sparse_alloc) {
+        gmmu->MapSparse(offset, mapping->size, mapping->big_page);
+    } else {
+        gmmu->Unmap(offset, mapping->size);
+    }
+
+    mapping_map.erase(offset);
 }
 }
 
 
 NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -123,8 +227,40 @@ NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>&
     LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
     LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
               params.pages, params.page_size);
               params.pages, params.page_size);
 
 
-    system.GPU().MemoryManager().Unmap(params.offset,
-                                       static_cast<std::size_t>(params.pages) * params.page_size);
+    std::scoped_lock lock(mutex);
+
+    if (!vm.initialised) {
+        return NvResult::BadValue;
+    }
+
+    try {
+        auto allocation{allocation_map[params.offset]};
+
+        if (allocation.page_size != params.page_size ||
+            allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
+            return NvResult::BadValue;
+        }
+
+        for (const auto& mapping : allocation.mappings) {
+            FreeMappingLocked(mapping->offset);
+        }
+
+        // Unset sparse flag if required
+        if (allocation.sparse) {
+            gmmu->Unmap(params.offset, allocation.size);
+        }
+
+        auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
+                                                              : *vm.big_page_allocator};
+        u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
+                                                                 : vm.big_page_size_bits};
+
+        allocator.Free(static_cast<u32>(params.offset >> page_size_bits),
+                       static_cast<u32>(allocation.size >> page_size_bits));
+        allocation_map.erase(params.offset);
+    } catch (const std::out_of_range&) {
+        return NvResult::BadValue;
+    }
 
 
     std::memcpy(output.data(), &params, output.size());
     std::memcpy(output.data(), &params, output.size());
     return NvResult::Success;
     return NvResult::Success;
@@ -135,35 +271,52 @@ NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& out
 
 
     LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
     LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
 
 
-    auto result = NvResult::Success;
     std::vector<IoctlRemapEntry> entries(num_entries);
     std::vector<IoctlRemapEntry> entries(num_entries);
     std::memcpy(entries.data(), input.data(), input.size());
     std::memcpy(entries.data(), input.data(), input.size());
 
 
+    std::scoped_lock lock(mutex);
+
+    if (!vm.initialised) {
+        return NvResult::BadValue;
+    }
+
     for (const auto& entry : entries) {
     for (const auto& entry : entries) {
-        LOG_DEBUG(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
-                  entry.offset, entry.nvmap_handle, entry.pages);
+        GPUVAddr virtual_address{static_cast<u64>(entry.as_offset_big_pages)
+                                 << vm.big_page_size_bits};
+        u64 size{static_cast<u64>(entry.big_pages) << vm.big_page_size_bits};
 
 
-        const auto object{nvmap_dev->GetObject(entry.nvmap_handle)};
-        if (!object) {
-            LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle);
-            result = NvResult::InvalidState;
-            break;
+        auto alloc{allocation_map.upper_bound(virtual_address)};
+
+        if (alloc-- == allocation_map.begin() ||
+            (virtual_address - alloc->first) + size > alloc->second.size) {
+            LOG_WARNING(Service_NVDRV, "Cannot remap into an unallocated region!");
+            return NvResult::BadValue;
         }
         }
 
 
-        const auto offset{static_cast<GPUVAddr>(entry.offset) << 0x10};
-        const auto size{static_cast<u64>(entry.pages) << 0x10};
-        const auto map_offset{static_cast<u64>(entry.map_offset) << 0x10};
-        const auto addr{system.GPU().MemoryManager().Map(object->addr + map_offset, offset, size)};
+        if (!alloc->second.sparse) {
+            LOG_WARNING(Service_NVDRV, "Cannot remap a non-sparse mapping!");
+            return NvResult::BadValue;
+        }
 
 
-        if (!addr) {
-            LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!");
-            result = NvResult::InvalidState;
-            break;
+        const bool use_big_pages = alloc->second.big_pages;
+        if (!entry.handle) {
+            gmmu->MapSparse(virtual_address, size, use_big_pages);
+        } else {
+            auto handle{nvmap.GetHandle(entry.handle)};
+            if (!handle) {
+                return NvResult::BadValue;
+            }
+
+            VAddr cpu_address{static_cast<VAddr>(
+                handle->address +
+                (static_cast<u64>(entry.handle_offset_big_pages) << vm.big_page_size_bits))};
+
+            gmmu->Map(virtual_address, cpu_address, size, use_big_pages);
         }
         }
     }
     }
 
 
     std::memcpy(output.data(), entries.data(), output.size());
     std::memcpy(output.data(), entries.data(), output.size());
-    return result;
+    return NvResult::Success;
 }
 }
 
 
 NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -173,79 +326,98 @@ NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8
     LOG_DEBUG(Service_NVDRV,
     LOG_DEBUG(Service_NVDRV,
               "called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
               "called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
               ", offset={}",
               ", offset={}",
-              params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
+              params.flags, params.handle, params.buffer_offset, params.mapping_size,
               params.offset);
               params.offset);
 
 
-    const auto object{nvmap_dev->GetObject(params.nvmap_handle)};
-    if (!object) {
-        LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle);
-        std::memcpy(output.data(), &params, output.size());
-        return NvResult::InvalidState;
-    }
-
-    // The real nvservices doesn't make a distinction between handles and ids, and
-    // object can only have one handle and it will be the same as its id. Assert that this is the
-    // case to prevent unexpected behavior.
-    ASSERT(object->id == params.nvmap_handle);
-    auto& gpu = system.GPU();
+    std::scoped_lock lock(mutex);
 
 
-    u64 page_size{params.page_size};
-    if (!page_size) {
-        page_size = object->align;
+    if (!vm.initialised) {
+        return NvResult::BadValue;
     }
     }
 
 
-    if ((params.flags & AddressSpaceFlags::Remap) != AddressSpaceFlags::None) {
-        if (const auto buffer_map{FindBufferMap(params.offset)}; buffer_map) {
-            const auto cpu_addr{static_cast<VAddr>(buffer_map->CpuAddr() + params.buffer_offset)};
-            const auto gpu_addr{static_cast<GPUVAddr>(params.offset + params.buffer_offset)};
+    // Remaps a subregion of an existing mapping to a different PA
+    if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
+        try {
+            auto mapping{mapping_map.at(params.offset)};
 
 
-            if (!gpu.MemoryManager().Map(cpu_addr, gpu_addr, params.mapping_size)) {
-                LOG_CRITICAL(Service_NVDRV,
-                             "remap failed, flags={:X}, nvmap_handle={:X}, buffer_offset={}, "
-                             "mapping_size = {}, offset={}",
-                             params.flags, params.nvmap_handle, params.buffer_offset,
-                             params.mapping_size, params.offset);
-
-                std::memcpy(output.data(), &params, output.size());
-                return NvResult::InvalidState;
+            if (mapping->size < params.mapping_size) {
+                LOG_WARNING(Service_NVDRV,
+                            "Cannot remap a partially mapped GPU address space region: 0x{:X}",
+                            params.offset);
+                return NvResult::BadValue;
             }
             }
 
 
-            std::memcpy(output.data(), &params, output.size());
-            return NvResult::Success;
-        } else {
-            LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset);
+            u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
+            VAddr cpu_address{mapping->ptr + params.buffer_offset};
+
+            gmmu->Map(gpu_address, cpu_address, params.mapping_size, mapping->big_page);
 
 
-            std::memcpy(output.data(), &params, output.size());
-            return NvResult::InvalidState;
+            return NvResult::Success;
+        } catch (const std::out_of_range&) {
+            LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: 0x{:X}",
+                        params.offset);
+            return NvResult::BadValue;
         }
         }
     }
     }
 
 
-    // We can only map objects that have already been assigned a CPU address.
-    ASSERT(object->status == nvmap::Object::Status::Allocated);
-
-    const auto physical_address{object->addr + params.buffer_offset};
-    u64 size{params.mapping_size};
-    if (!size) {
-        size = object->size;
+    auto handle{nvmap.GetHandle(params.handle)};
+    if (!handle) {
+        return NvResult::BadValue;
     }
     }
 
 
-    const bool is_alloc{(params.flags & AddressSpaceFlags::FixedOffset) == AddressSpaceFlags::None};
-    if (is_alloc) {
-        params.offset = gpu.MemoryManager().MapAllocate(physical_address, size, page_size);
-    } else {
-        params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size);
-    }
+    VAddr cpu_address{static_cast<VAddr>(handle->address + params.buffer_offset)};
+    u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
+
+    bool big_page{[&]() {
+        if (Common::IsAligned(handle->align, vm.big_page_size)) {
+            return true;
+        } else if (Common::IsAligned(handle->align, VM::YUZU_PAGESIZE)) {
+            return false;
+        } else {
+            ASSERT(false);
+            return false;
+        }
+    }()};
+
+    if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
+        auto alloc{allocation_map.upper_bound(params.offset)};
 
 
-    auto result = NvResult::Success;
-    if (!params.offset) {
-        LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size);
-        result = NvResult::InvalidState;
+        if (alloc-- == allocation_map.begin() ||
+            (params.offset - alloc->first) + size > alloc->second.size) {
+            ASSERT_MSG(false, "Cannot perform a fixed mapping into an unallocated region!");
+            return NvResult::BadValue;
+        }
+
+        const bool use_big_pages = alloc->second.big_pages && big_page;
+        gmmu->Map(params.offset, cpu_address, size, use_big_pages);
+
+        auto mapping{std::make_shared<Mapping>(cpu_address, params.offset, size, true,
+                                               use_big_pages, alloc->second.sparse)};
+        alloc->second.mappings.push_back(mapping);
+        mapping_map[params.offset] = mapping;
     } else {
     } else {
-        AddBufferMap(params.offset, size, physical_address, is_alloc);
+
+        auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
+        u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
+        u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+
+        params.offset = static_cast<u64>(allocator.Allocate(
+                            static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
+                        << page_size_bits;
+        if (!params.offset) {
+            ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
+            return NvResult::InsufficientMemory;
+        }
+
+        gmmu->Map(params.offset, cpu_address, Common::AlignUp(size, page_size), big_page);
+
+        auto mapping{
+            std::make_shared<Mapping>(cpu_address, params.offset, size, false, big_page, false)};
+        mapping_map[params.offset] = mapping;
     }
     }
 
 
     std::memcpy(output.data(), &params, output.size());
     std::memcpy(output.data(), &params, output.size());
-    return result;
+    return NvResult::Success;
 }
 }
 
 
 NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -254,47 +426,82 @@ NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8
 
 
     LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
     LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
 
 
-    if (const auto size{RemoveBufferMap(params.offset)}; size) {
-        system.GPU().MemoryManager().Unmap(params.offset, *size);
-    } else {
-        LOG_ERROR(Service_NVDRV, "invalid offset=0x{:X}", params.offset);
+    std::scoped_lock lock(mutex);
+
+    if (!vm.initialised) {
+        return NvResult::BadValue;
+    }
+
+    try {
+        auto mapping{mapping_map.at(params.offset)};
+
+        if (!mapping->fixed) {
+            auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
+            u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+
+            allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
+                           static_cast<u32>(mapping->size >> page_size_bits));
+        }
+
+        // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
+        // Only FreeSpace can unmap them fully
+        if (mapping->sparse_alloc) {
+            gmmu->MapSparse(params.offset, mapping->size, mapping->big_page);
+        } else {
+            gmmu->Unmap(params.offset, mapping->size);
+        }
+
+        mapping_map.erase(params.offset);
+    } catch (const std::out_of_range&) {
+        LOG_WARNING(Service_NVDRV, "Couldn't find region to unmap at 0x{:X}", params.offset);
     }
     }
 
 
-    std::memcpy(output.data(), &params, output.size());
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
 NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
     IoctlBindChannel params{};
     IoctlBindChannel params{};
     std::memcpy(&params, input.data(), input.size());
     std::memcpy(&params, input.data(), input.size());
-    LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}", params.fd);
+    LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
 
 
-    channel = params.fd;
+    auto gpu_channel_device = module.GetDevice<nvhost_gpu>(params.fd);
+    gpu_channel_device->channel_state->memory_manager = gmmu;
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
+void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
+    params.buf_size = 2 * sizeof(VaRegion);
+
+    params.regions = std::array<VaRegion, 2>{
+        VaRegion{
+            .offset = vm.small_page_allocator->GetVAStart() << VM::PAGE_SIZE_BITS,
+            .page_size = VM::YUZU_PAGESIZE,
+            ._pad0_{},
+            .pages = vm.small_page_allocator->GetVALimit() - vm.small_page_allocator->GetVAStart(),
+        },
+        VaRegion{
+            .offset = vm.big_page_allocator->GetVAStart() << vm.big_page_size_bits,
+            .page_size = vm.big_page_size,
+            ._pad0_{},
+            .pages = vm.big_page_allocator->GetVALimit() - vm.big_page_allocator->GetVAStart(),
+        },
+    };
+}
+
 NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
     IoctlGetVaRegions params{};
     IoctlGetVaRegions params{};
     std::memcpy(&params, input.data(), input.size());
     std::memcpy(&params, input.data(), input.size());
 
 
-    LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
-                params.buf_size);
-
-    params.buf_size = 0x30;
+    LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
+              params.buf_size);
 
 
-    params.small = IoctlVaRegion{
-        .offset = 0x04000000,
-        .page_size = DEFAULT_SMALL_PAGE_SIZE,
-        .pages = 0x3fbfff,
-    };
+    std::scoped_lock lock(mutex);
 
 
-    params.big = IoctlVaRegion{
-        .offset = 0x04000000,
-        .page_size = big_page_size,
-        .pages = 0x1bffff,
-    };
+    if (!vm.initialised) {
+        return NvResult::BadValue;
+    }
 
 
-    // TODO(ogniK): This probably can stay stubbed but should add support way way later
+    GetVARegionsImpl(params);
 
 
     std::memcpy(output.data(), &params, output.size());
     std::memcpy(output.data(), &params, output.size());
     return NvResult::Success;
     return NvResult::Success;
@@ -305,62 +512,27 @@ NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u
     IoctlGetVaRegions params{};
     IoctlGetVaRegions params{};
     std::memcpy(&params, input.data(), input.size());
     std::memcpy(&params, input.data(), input.size());
 
 
-    LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
-                params.buf_size);
-
-    params.buf_size = 0x30;
+    LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
+              params.buf_size);
 
 
-    params.small = IoctlVaRegion{
-        .offset = 0x04000000,
-        .page_size = 0x1000,
-        .pages = 0x3fbfff,
-    };
+    std::scoped_lock lock(mutex);
 
 
-    params.big = IoctlVaRegion{
-        .offset = 0x04000000,
-        .page_size = big_page_size,
-        .pages = 0x1bffff,
-    };
+    if (!vm.initialised) {
+        return NvResult::BadValue;
+    }
 
 
-    // TODO(ogniK): This probably can stay stubbed but should add support way way later
+    GetVARegionsImpl(params);
 
 
     std::memcpy(output.data(), &params, output.size());
     std::memcpy(output.data(), &params, output.size());
-    std::memcpy(inline_output.data(), &params.small, sizeof(IoctlVaRegion));
-    std::memcpy(inline_output.data() + sizeof(IoctlVaRegion), &params.big, sizeof(IoctlVaRegion));
+    std::memcpy(inline_output.data(), &params.regions[0], sizeof(VaRegion));
+    std::memcpy(inline_output.data() + sizeof(VaRegion), &params.regions[1], sizeof(VaRegion));
 
 
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
-std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const {
-    const auto end{buffer_mappings.upper_bound(gpu_addr)};
-    for (auto iter{buffer_mappings.begin()}; iter != end; ++iter) {
-        if (gpu_addr >= iter->second.StartAddr() && gpu_addr < iter->second.EndAddr()) {
-            return iter->second;
-        }
-    }
-
-    return std::nullopt;
-}
-
-void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
-                                 bool is_allocated) {
-    buffer_mappings[gpu_addr] = {gpu_addr, size, cpu_addr, is_allocated};
-}
-
-std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) {
-    if (const auto iter{buffer_mappings.find(gpu_addr)}; iter != buffer_mappings.end()) {
-        std::size_t size{};
-
-        if (iter->second.IsAllocated()) {
-            size = iter->second.Size();
-        }
-
-        buffer_mappings.erase(iter);
-
-        return size;
-    }
-
-    return std::nullopt;
+Kernel::KEvent* nvhost_as_gpu::QueryEvent(u32 event_id) {
+    LOG_CRITICAL(Service_NVDRV, "Unknown AS GPU Event {}", event_id);
+    return nullptr;
 }
 }
 
 
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 111 - 80
src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h

@@ -1,35 +1,50 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
 
 
 #pragma once
 #pragma once
 
 
+#include <bit>
+#include <list>
 #include <map>
 #include <map>
 #include <memory>
 #include <memory>
+#include <mutex>
 #include <optional>
 #include <optional>
 #include <vector>
 #include <vector>
 
 
+#include "common/address_space.h"
 #include "common/common_funcs.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "common/swap.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 
 
-namespace Service::Nvidia::Devices {
+namespace Tegra {
+class MemoryManager;
+} // namespace Tegra
+
+namespace Service::Nvidia {
+class Module;
+}
 
 
-constexpr u32 DEFAULT_BIG_PAGE_SIZE = 1 << 16;
-constexpr u32 DEFAULT_SMALL_PAGE_SIZE = 1 << 12;
+namespace Service::Nvidia::NvCore {
+class Container;
+class NvMap;
+} // namespace Service::Nvidia::NvCore
 
 
-class nvmap;
+namespace Service::Nvidia::Devices {
 
 
-enum class AddressSpaceFlags : u32 {
-    None = 0x0,
-    FixedOffset = 0x1,
-    Remap = 0x100,
+enum class MappingFlags : u32 {
+    None = 0,
+    Fixed = 1 << 0,
+    Sparse = 1 << 1,
+    Remap = 1 << 8,
 };
 };
-DECLARE_ENUM_FLAG_OPERATORS(AddressSpaceFlags);
+DECLARE_ENUM_FLAG_OPERATORS(MappingFlags);
 
 
 class nvhost_as_gpu final : public nvdevice {
 class nvhost_as_gpu final : public nvdevice {
 public:
 public:
-    explicit nvhost_as_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_);
+    explicit nvhost_as_gpu(Core::System& system_, Module& module, NvCore::Container& core);
     ~nvhost_as_gpu() override;
     ~nvhost_as_gpu() override;
 
 
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -42,46 +57,17 @@ public:
     void OnOpen(DeviceFD fd) override;
     void OnOpen(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
 
 
-private:
-    class BufferMap final {
-    public:
-        constexpr BufferMap() = default;
-
-        constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_)
-            : start_addr{start_addr_}, end_addr{start_addr_ + size_} {}
-
-        constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_, VAddr cpu_addr_,
-                            bool is_allocated_)
-            : start_addr{start_addr_}, end_addr{start_addr_ + size_}, cpu_addr{cpu_addr_},
-              is_allocated{is_allocated_} {}
-
-        constexpr VAddr StartAddr() const {
-            return start_addr;
-        }
-
-        constexpr VAddr EndAddr() const {
-            return end_addr;
-        }
-
-        constexpr std::size_t Size() const {
-            return end_addr - start_addr;
-        }
-
-        constexpr VAddr CpuAddr() const {
-            return cpu_addr;
-        }
-
-        constexpr bool IsAllocated() const {
-            return is_allocated;
-        }
-
-    private:
-        GPUVAddr start_addr{};
-        GPUVAddr end_addr{};
-        VAddr cpu_addr{};
-        bool is_allocated{};
+    Kernel::KEvent* QueryEvent(u32 event_id) override;
+
+    struct VaRegion {
+        u64 offset;
+        u32 page_size;
+        u32 _pad0_;
+        u64 pages;
     };
     };
+    static_assert(sizeof(VaRegion) == 0x18);
 
 
+private:
     struct IoctlAllocAsEx {
     struct IoctlAllocAsEx {
         u32_le flags{}; // usually passes 1
         u32_le flags{}; // usually passes 1
         s32_le as_fd{}; // ignored; passes 0
         s32_le as_fd{}; // ignored; passes 0
@@ -96,7 +82,7 @@ private:
     struct IoctlAllocSpace {
     struct IoctlAllocSpace {
         u32_le pages{};
         u32_le pages{};
         u32_le page_size{};
         u32_le page_size{};
-        AddressSpaceFlags flags{};
+        MappingFlags flags{};
         INSERT_PADDING_WORDS(1);
         INSERT_PADDING_WORDS(1);
         union {
         union {
             u64_le offset;
             u64_le offset;
@@ -113,19 +99,19 @@ private:
     static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size");
     static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size");
 
 
     struct IoctlRemapEntry {
     struct IoctlRemapEntry {
-        u16_le flags{};
-        u16_le kind{};
-        u32_le nvmap_handle{};
-        u32_le map_offset{};
-        u32_le offset{};
-        u32_le pages{};
+        u16 flags;
+        u16 kind;
+        NvCore::NvMap::Handle::Id handle;
+        u32 handle_offset_big_pages;
+        u32 as_offset_big_pages;
+        u32 big_pages;
     };
     };
     static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
     static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
 
 
     struct IoctlMapBufferEx {
     struct IoctlMapBufferEx {
-        AddressSpaceFlags flags{}; // bit0: fixed_offset, bit2: cacheable
-        u32_le kind{};             // -1 is default
-        u32_le nvmap_handle{};
+        MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable
+        u32_le kind{};        // -1 is default
+        NvCore::NvMap::Handle::Id handle;
         u32_le page_size{}; // 0 means don't care
         u32_le page_size{}; // 0 means don't care
         s64_le buffer_offset{};
         s64_le buffer_offset{};
         u64_le mapping_size{};
         u64_le mapping_size{};
@@ -143,27 +129,15 @@ private:
     };
     };
     static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
     static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
 
 
-    struct IoctlVaRegion {
-        u64_le offset{};
-        u32_le page_size{};
-        INSERT_PADDING_WORDS(1);
-        u64_le pages{};
-    };
-    static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size");
-
     struct IoctlGetVaRegions {
     struct IoctlGetVaRegions {
         u64_le buf_addr{}; // (contained output user ptr on linux, ignored)
         u64_le buf_addr{}; // (contained output user ptr on linux, ignored)
         u32_le buf_size{}; // forced to 2*sizeof(struct va_region)
         u32_le buf_size{}; // forced to 2*sizeof(struct va_region)
         u32_le reserved{};
         u32_le reserved{};
-        IoctlVaRegion small{};
-        IoctlVaRegion big{};
+        std::array<VaRegion, 2> regions{};
     };
     };
-    static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
+    static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(VaRegion) * 2,
                   "IoctlGetVaRegions is incorrect size");
                   "IoctlGetVaRegions is incorrect size");
 
 
-    s32 channel{};
-    u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
-
     NvResult AllocAsEx(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult AllocAsEx(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output);
@@ -172,18 +146,75 @@ private:
     NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
 
 
+    void GetVARegionsImpl(IoctlGetVaRegions& params);
     NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
     NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
                           std::vector<u8>& inline_output);
                           std::vector<u8>& inline_output);
 
 
-    std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
-    void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
-    std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
+    void FreeMappingLocked(u64 offset);
+
+    Module& module;
+
+    NvCore::Container& container;
+    NvCore::NvMap& nvmap;
 
 
-    std::shared_ptr<nvmap> nvmap_dev;
+    struct Mapping {
+        VAddr ptr;
+        u64 offset;
+        u64 size;
+        bool fixed;
+        bool big_page; // Only valid if fixed == false
+        bool sparse_alloc;
+
+        Mapping(VAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_)
+            : ptr(ptr_), offset(offset_), size(size_), fixed(fixed_), big_page(big_page_),
+              sparse_alloc(sparse_alloc_) {}
+    };
+
+    struct Allocation {
+        u64 size;
+        std::list<std::shared_ptr<Mapping>> mappings;
+        u32 page_size;
+        bool sparse;
+        bool big_pages;
+    };
 
 
-    // This is expected to be ordered, therefore we must use a map, not unordered_map
-    std::map<GPUVAddr, BufferMap> buffer_mappings;
+    std::map<u64, std::shared_ptr<Mapping>>
+        mapping_map; //!< This maps the base addresses of mapped buffers to their total sizes and
+                     //!< mapping type, this is needed as what was originally a single buffer may
+                     //!< have been split into multiple GPU side buffers with the remap flag.
+    std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
+                                              //!< which fixed buffers can be mapped into
+    std::mutex mutex;                         //!< Locks all AS operations
+
+    struct VM {
+        static constexpr u32 YUZU_PAGESIZE{0x1000};
+        static constexpr u32 PAGE_SIZE_BITS{std::countr_zero(YUZU_PAGESIZE)};
+
+        static constexpr u32 SUPPORTED_BIG_PAGE_SIZES{0x30000};
+        static constexpr u32 DEFAULT_BIG_PAGE_SIZE{0x20000};
+        u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
+        u32 big_page_size_bits{std::countr_zero(DEFAULT_BIG_PAGE_SIZE)};
+
+        static constexpr u32 VA_START_SHIFT{10};
+        static constexpr u64 DEFAULT_VA_SPLIT{1ULL << 34};
+        static constexpr u64 DEFAULT_VA_RANGE{1ULL << 37};
+        u64 va_range_start{DEFAULT_BIG_PAGE_SIZE << VA_START_SHIFT};
+        u64 va_range_split{DEFAULT_VA_SPLIT};
+        u64 va_range_end{DEFAULT_VA_RANGE};
+
+        using Allocator = Common::FlatAllocator<u32, 0, 32>;
+
+        std::unique_ptr<Allocator> big_page_allocator;
+        std::shared_ptr<Allocator>
+            small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
+
+        bool initialised{};
+    } vm;
+    std::shared_ptr<Tegra::MemoryManager> gmmu;
+
+    // s32 channel{};
+    // u32 big_page_size{VM::DEFAULT_BIG_PAGE_SIZE};
 };
 };
 
 
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 270 - 93
src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp

@@ -1,24 +1,39 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
 
 
+#include <bit>
 #include <cstdlib>
 #include <cstdlib>
 #include <cstring>
 #include <cstring>
 
 
+#include <fmt/format.h>
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
+#include "common/scope_exit.h"
 #include "core/core.h"
 #include "core/core.h"
 #include "core/hle/kernel/k_event.h"
 #include "core/hle/kernel/k_event.h"
 #include "core/hle/kernel/k_writable_event.h"
 #include "core/hle/kernel/k_writable_event.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
 #include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
 #include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
 nvhost_ctrl::nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
 nvhost_ctrl::nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
-                         SyncpointManager& syncpoint_manager_)
-    : nvdevice{system_}, events_interface{events_interface_}, syncpoint_manager{
-                                                                  syncpoint_manager_} {}
-nvhost_ctrl::~nvhost_ctrl() = default;
+                         NvCore::Container& core_)
+    : nvdevice{system_}, events_interface{events_interface_}, core{core_},
+      syncpoint_manager{core_.GetSyncpointManager()} {}
+
+nvhost_ctrl::~nvhost_ctrl() {
+    for (auto& event : events) {
+        if (!event.registered) {
+            continue;
+        }
+        events_interface.FreeEvent(event.kevent);
+    }
+}
 
 
 NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
 NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
                              std::vector<u8>& output) {
                              std::vector<u8>& output) {
@@ -30,13 +45,15 @@ NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>&
         case 0x1c:
         case 0x1c:
             return IocCtrlClearEventWait(input, output);
             return IocCtrlClearEventWait(input, output);
         case 0x1d:
         case 0x1d:
-            return IocCtrlEventWait(input, output, false);
-        case 0x1e:
             return IocCtrlEventWait(input, output, true);
             return IocCtrlEventWait(input, output, true);
+        case 0x1e:
+            return IocCtrlEventWait(input, output, false);
         case 0x1f:
         case 0x1f:
             return IocCtrlEventRegister(input, output);
             return IocCtrlEventRegister(input, output);
         case 0x20:
         case 0x20:
             return IocCtrlEventUnregister(input, output);
             return IocCtrlEventUnregister(input, output);
+        case 0x21:
+            return IocCtrlEventUnregisterBatch(input, output);
         }
         }
         break;
         break;
     default:
     default:
@@ -60,6 +77,7 @@ NvResult nvhost_ctrl::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>&
 }
 }
 
 
 void nvhost_ctrl::OnOpen(DeviceFD fd) {}
 void nvhost_ctrl::OnOpen(DeviceFD fd) {}
+
 void nvhost_ctrl::OnClose(DeviceFD fd) {}
 void nvhost_ctrl::OnClose(DeviceFD fd) {}
 
 
 NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -71,116 +89,167 @@ NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector
 }
 }
 
 
 NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
 NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
-                                       bool is_async) {
+                                       bool is_allocation) {
     IocCtrlEventWaitParams params{};
     IocCtrlEventWaitParams params{};
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
-    LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}",
-              params.syncpt_id, params.threshold, params.timeout, is_async);
+    LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_allocation={}",
+              params.fence.id, params.fence.value, params.timeout, is_allocation);
 
 
-    if (params.syncpt_id >= MaxSyncPoints) {
-        return NvResult::BadParameter;
-    }
+    bool must_unmark_fail = !is_allocation;
+    const u32 event_id = params.value.raw;
+    SCOPE_EXIT({
+        std::memcpy(output.data(), &params, sizeof(params));
+        if (must_unmark_fail) {
+            events[event_id].fails = 0;
+        }
+    });
 
 
-    u32 event_id = params.value & 0x00FF;
+    const u32 fence_id = static_cast<u32>(params.fence.id);
 
 
-    if (event_id >= MaxNvEvents) {
-        std::memcpy(output.data(), &params, sizeof(params));
+    if (fence_id >= MaxSyncPoints) {
         return NvResult::BadParameter;
         return NvResult::BadParameter;
     }
     }
 
 
-    if (syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
-        params.value = syncpoint_manager.GetSyncpointMin(params.syncpt_id);
-        std::memcpy(output.data(), &params, sizeof(params));
-        events_interface.failed[event_id] = false;
+    if (params.fence.value == 0) {
+        if (!syncpoint_manager.IsSyncpointAllocated(params.fence.id)) {
+            LOG_WARNING(Service_NVDRV,
+                        "Unallocated syncpt_id={}, threshold={}, timeout={}, is_allocation={}",
+                        params.fence.id, params.fence.value, params.timeout, is_allocation);
+        } else {
+            params.value.raw = syncpoint_manager.ReadSyncpointMinValue(fence_id);
+        }
         return NvResult::Success;
         return NvResult::Success;
     }
     }
 
 
-    if (const auto new_value = syncpoint_manager.RefreshSyncpoint(params.syncpt_id);
-        syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
-        params.value = new_value;
-        std::memcpy(output.data(), &params, sizeof(params));
-        events_interface.failed[event_id] = false;
+    if (syncpoint_manager.IsFenceSignalled(params.fence)) {
+        params.value.raw = syncpoint_manager.ReadSyncpointMinValue(fence_id);
         return NvResult::Success;
         return NvResult::Success;
     }
     }
 
 
-    auto& event = events_interface.events[event_id];
-    auto& gpu = system.GPU();
-
-    // This is mostly to take into account unimplemented features. As synced
-    // gpu is always synced.
-    if (!gpu.IsAsync()) {
-        event.event->GetWritableEvent().Signal();
-        return NvResult::Success;
-    }
-    const u32 current_syncpoint_value = event.fence.value;
-    const s32 diff = current_syncpoint_value - params.threshold;
-    if (diff >= 0) {
-        event.event->GetWritableEvent().Signal();
-        params.value = current_syncpoint_value;
-        std::memcpy(output.data(), &params, sizeof(params));
-        events_interface.failed[event_id] = false;
+    if (const auto new_value = syncpoint_manager.UpdateMin(fence_id);
+        syncpoint_manager.IsFenceSignalled(params.fence)) {
+        params.value.raw = new_value;
         return NvResult::Success;
         return NvResult::Success;
     }
     }
-    const u32 target_value = current_syncpoint_value - diff;
 
 
-    if (!is_async) {
-        params.value = 0;
+    auto& host1x_syncpoint_manager = system.Host1x().GetSyncpointManager();
+    const u32 target_value = params.fence.value;
+
+    auto lock = NvEventsLock();
+
+    u32 slot = [&]() {
+        if (is_allocation) {
+            params.value.raw = 0;
+            return FindFreeNvEvent(fence_id);
+        } else {
+            return params.value.raw;
+        }
+    }();
+
+    must_unmark_fail = false;
+
+    const auto check_failing = [&]() {
+        if (events[slot].fails > 2) {
+            {
+                auto lk = system.StallProcesses();
+                host1x_syncpoint_manager.WaitHost(fence_id, target_value);
+                system.UnstallProcesses();
+            }
+            params.value.raw = target_value;
+            return true;
+        }
+        return false;
+    };
+
+    if (slot >= MaxNvEvents) {
+        return NvResult::BadParameter;
     }
     }
 
 
     if (params.timeout == 0) {
     if (params.timeout == 0) {
-        std::memcpy(output.data(), &params, sizeof(params));
+        if (check_failing()) {
+            events[slot].fails = 0;
+            return NvResult::Success;
+        }
         return NvResult::Timeout;
         return NvResult::Timeout;
     }
     }
 
 
-    EventState status = events_interface.status[event_id];
-    const bool bad_parameter = status == EventState::Busy;
-    if (bad_parameter) {
-        std::memcpy(output.data(), &params, sizeof(params));
+    auto& event = events[slot];
+
+    if (!event.registered) {
         return NvResult::BadParameter;
         return NvResult::BadParameter;
     }
     }
-    events_interface.SetEventStatus(event_id, EventState::Waiting);
-    events_interface.assigned_syncpt[event_id] = params.syncpt_id;
-    events_interface.assigned_value[event_id] = target_value;
-    if (is_async) {
-        params.value = params.syncpt_id << 4;
-    } else {
-        params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000;
-    }
-    params.value |= event_id;
-    event.event->GetWritableEvent().Clear();
-    if (events_interface.failed[event_id]) {
-        {
-            auto lk = system.StallProcesses();
-            gpu.WaitFence(params.syncpt_id, target_value);
-            system.UnstallProcesses();
-        }
-        std::memcpy(output.data(), &params, sizeof(params));
-        events_interface.failed[event_id] = false;
+
+    if (event.IsBeingUsed()) {
+        return NvResult::BadParameter;
+    }
+
+    if (check_failing()) {
+        event.fails = 0;
         return NvResult::Success;
         return NvResult::Success;
     }
     }
-    gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
-    std::memcpy(output.data(), &params, sizeof(params));
+
+    params.value.raw = 0;
+
+    event.status.store(EventState::Waiting, std::memory_order_release);
+    event.assigned_syncpt = fence_id;
+    event.assigned_value = target_value;
+    if (is_allocation) {
+        params.value.syncpoint_id_for_allocation.Assign(static_cast<u16>(fence_id));
+        params.value.event_allocated.Assign(1);
+    } else {
+        params.value.syncpoint_id.Assign(fence_id);
+    }
+    params.value.raw |= slot;
+
+    event.wait_handle =
+        host1x_syncpoint_manager.RegisterHostAction(fence_id, target_value, [this, slot]() {
+            auto& event_ = events[slot];
+            if (event_.status.exchange(EventState::Signalling, std::memory_order_acq_rel) ==
+                EventState::Waiting) {
+                event_.kevent->GetWritableEvent().Signal();
+            }
+            event_.status.store(EventState::Signalled, std::memory_order_release);
+        });
     return NvResult::Timeout;
     return NvResult::Timeout;
 }
 }
 
 
+NvResult nvhost_ctrl::FreeEvent(u32 slot) {
+    if (slot >= MaxNvEvents) {
+        return NvResult::BadParameter;
+    }
+
+    auto& event = events[slot];
+
+    if (!event.registered) {
+        return NvResult::Success;
+    }
+
+    if (event.IsBeingUsed()) {
+        return NvResult::Busy;
+    }
+
+    FreeNvEvent(slot);
+    return NvResult::Success;
+}
+
 NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
     IocCtrlEventRegisterParams params{};
     IocCtrlEventRegisterParams params{};
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
-    const u32 event_id = params.user_event_id & 0x00FF;
+    const u32 event_id = params.user_event_id;
     LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
     LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
     if (event_id >= MaxNvEvents) {
     if (event_id >= MaxNvEvents) {
         return NvResult::BadParameter;
         return NvResult::BadParameter;
     }
     }
-    if (events_interface.registered[event_id]) {
-        const auto event_state = events_interface.status[event_id];
-        if (event_state != EventState::Free) {
-            LOG_WARNING(Service_NVDRV, "Event already registered! Unregistering previous event");
-            events_interface.UnregisterEvent(event_id);
-        } else {
-            return NvResult::BadParameter;
+
+    auto lock = NvEventsLock();
+
+    if (events[event_id].registered) {
+        const auto result = FreeEvent(event_id);
+        if (result != NvResult::Success) {
+            return result;
         }
         }
     }
     }
-    events_interface.RegisterEvent(event_id);
+    CreateNvEvent(event_id);
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
@@ -190,34 +259,142 @@ NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input,
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
     const u32 event_id = params.user_event_id & 0x00FF;
     const u32 event_id = params.user_event_id & 0x00FF;
     LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
     LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
-    if (event_id >= MaxNvEvents) {
-        return NvResult::BadParameter;
-    }
-    if (!events_interface.registered[event_id]) {
-        return NvResult::BadParameter;
+
+    auto lock = NvEventsLock();
+    return FreeEvent(event_id);
+}
+
+NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(const std::vector<u8>& input,
+                                                  std::vector<u8>& output) {
+    IocCtrlEventUnregisterBatchParams params{};
+    std::memcpy(&params, input.data(), sizeof(params));
+    u64 event_mask = params.user_events;
+    LOG_DEBUG(Service_NVDRV, " called, event_mask: {:X}", event_mask);
+
+    auto lock = NvEventsLock();
+    while (event_mask != 0) {
+        const u64 event_id = std::countr_zero(event_mask);
+        event_mask &= ~(1ULL << event_id);
+        const auto result = FreeEvent(static_cast<u32>(event_id));
+        if (result != NvResult::Success) {
+            return result;
+        }
     }
     }
-    events_interface.UnregisterEvent(event_id);
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
 NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
-    IocCtrlEventSignalParams params{};
+    IocCtrlEventClearParams params{};
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
 
 
-    u32 event_id = params.event_id & 0x00FF;
-    LOG_WARNING(Service_NVDRV, "cleared event wait on, event_id: {:X}", event_id);
+    u32 event_id = params.event_id.slot;
+    LOG_DEBUG(Service_NVDRV, "called, event_id: {:X}", event_id);
 
 
     if (event_id >= MaxNvEvents) {
     if (event_id >= MaxNvEvents) {
         return NvResult::BadParameter;
         return NvResult::BadParameter;
     }
     }
-    if (events_interface.status[event_id] == EventState::Waiting) {
-        events_interface.LiberateEvent(event_id);
-    }
-    events_interface.failed[event_id] = true;
 
 
-    syncpoint_manager.RefreshSyncpoint(events_interface.events[event_id].fence.id);
+    auto lock = NvEventsLock();
+
+    auto& event = events[event_id];
+    if (event.status.exchange(EventState::Cancelling, std::memory_order_acq_rel) ==
+        EventState::Waiting) {
+        auto& host1x_syncpoint_manager = system.Host1x().GetSyncpointManager();
+        host1x_syncpoint_manager.DeregisterHostAction(event.assigned_syncpt, event.wait_handle);
+        syncpoint_manager.UpdateMin(event.assigned_syncpt);
+        event.wait_handle = {};
+    }
+    event.fails++;
+    event.status.store(EventState::Cancelled, std::memory_order_release);
+    event.kevent->GetWritableEvent().Clear();
 
 
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
+Kernel::KEvent* nvhost_ctrl::QueryEvent(u32 event_id) {
+    const auto desired_event = SyncpointEventValue{.raw = event_id};
+
+    const bool allocated = desired_event.event_allocated.Value() != 0;
+    const u32 slot{allocated ? desired_event.partial_slot.Value()
+                             : static_cast<u32>(desired_event.slot)};
+    if (slot >= MaxNvEvents) {
+        ASSERT(false);
+        return nullptr;
+    }
+
+    const u32 syncpoint_id{allocated ? desired_event.syncpoint_id_for_allocation.Value()
+                                     : desired_event.syncpoint_id.Value()};
+
+    auto lock = NvEventsLock();
+
+    auto& event = events[slot];
+    if (event.registered && event.assigned_syncpt == syncpoint_id) {
+        ASSERT(event.kevent);
+        return event.kevent;
+    }
+    // Is this possible in hardware?
+    ASSERT_MSG(false, "Slot:{}, SyncpointID:{}, requested", slot, syncpoint_id);
+    return nullptr;
+}
+
+std::unique_lock<std::mutex> nvhost_ctrl::NvEventsLock() {
+    return std::unique_lock<std::mutex>(events_mutex);
+}
+
+void nvhost_ctrl::CreateNvEvent(u32 event_id) {
+    auto& event = events[event_id];
+    ASSERT(!event.kevent);
+    ASSERT(!event.registered);
+    ASSERT(!event.IsBeingUsed());
+    event.kevent = events_interface.CreateEvent(fmt::format("NVCTRL::NvEvent_{}", event_id));
+    event.status = EventState::Available;
+    event.registered = true;
+    const u64 mask = 1ULL << event_id;
+    event.fails = 0;
+    events_mask |= mask;
+    event.assigned_syncpt = 0;
+}
+
+void nvhost_ctrl::FreeNvEvent(u32 event_id) {
+    auto& event = events[event_id];
+    ASSERT(event.kevent);
+    ASSERT(event.registered);
+    ASSERT(!event.IsBeingUsed());
+    events_interface.FreeEvent(event.kevent);
+    event.kevent = nullptr;
+    event.status = EventState::Available;
+    event.registered = false;
+    const u64 mask = ~(1ULL << event_id);
+    events_mask &= mask;
+}
+
+u32 nvhost_ctrl::FindFreeNvEvent(u32 syncpoint_id) {
+    u32 slot{MaxNvEvents};
+    u32 free_slot{MaxNvEvents};
+    for (u32 i = 0; i < MaxNvEvents; i++) {
+        auto& event = events[i];
+        if (event.registered) {
+            if (!event.IsBeingUsed()) {
+                slot = i;
+                if (event.assigned_syncpt == syncpoint_id) {
+                    return slot;
+                }
+            }
+        } else if (free_slot == MaxNvEvents) {
+            free_slot = i;
+        }
+    }
+    if (free_slot < MaxNvEvents) {
+        CreateNvEvent(free_slot);
+        return free_slot;
+    }
+
+    if (slot < MaxNvEvents) {
+        return slot;
+    }
+
+    LOG_CRITICAL(Service_NVDRV, "Failed to allocate an event");
+    return 0;
+}
+
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 91 - 23
src/core/hle/service/nvdrv/devices/nvhost_ctrl.h

@@ -1,20 +1,28 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
 
 
 #pragma once
 #pragma once
 
 
 #include <array>
 #include <array>
 #include <vector>
 #include <vector>
+#include "common/bit_field.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 #include "core/hle/service/nvdrv/nvdrv.h"
 #include "core/hle/service/nvdrv/nvdrv.h"
+#include "video_core/host1x/syncpoint_manager.h"
+
+namespace Service::Nvidia::NvCore {
+class Container;
+class SyncpointManager;
+} // namespace Service::Nvidia::NvCore
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
 class nvhost_ctrl final : public nvdevice {
 class nvhost_ctrl final : public nvdevice {
 public:
 public:
     explicit nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
     explicit nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
-                         SyncpointManager& syncpoint_manager_);
+                         NvCore::Container& core);
     ~nvhost_ctrl() override;
     ~nvhost_ctrl() override;
 
 
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -27,7 +35,70 @@ public:
     void OnOpen(DeviceFD fd) override;
     void OnOpen(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
 
 
+    Kernel::KEvent* QueryEvent(u32 event_id) override;
+
+    union SyncpointEventValue {
+        u32 raw;
+
+        union {
+            BitField<0, 4, u32> partial_slot;
+            BitField<4, 28, u32> syncpoint_id;
+        };
+
+        struct {
+            u16 slot;
+            union {
+                BitField<0, 12, u16> syncpoint_id_for_allocation;
+                BitField<12, 1, u16> event_allocated;
+            };
+        };
+    };
+    static_assert(sizeof(SyncpointEventValue) == sizeof(u32));
+
 private:
 private:
+    struct InternalEvent {
+        // Mask representing registered events
+
+        // Each kernel event associated to an NV event
+        Kernel::KEvent* kevent{};
+        // The status of the current NVEvent
+        std::atomic<EventState> status{};
+
+        // Tells the NVEvent that it has failed.
+        u32 fails{};
+        // When an NVEvent is waiting on GPU interrupt, this is the sync_point
+        // associated with it.
+        u32 assigned_syncpt{};
+        // This is the value of the GPU interrupt for which the NVEvent is waiting
+        // for.
+        u32 assigned_value{};
+
+        // Tells if an NVEvent is registered or not
+        bool registered{};
+
+        // Used for waiting on a syncpoint & canceling it.
+        Tegra::Host1x::SyncpointManager::ActionHandle wait_handle{};
+
+        bool IsBeingUsed() const {
+            const auto current_status = status.load(std::memory_order_acquire);
+            return current_status == EventState::Waiting ||
+                   current_status == EventState::Cancelling ||
+                   current_status == EventState::Signalling;
+        }
+    };
+
+    std::unique_lock<std::mutex> NvEventsLock();
+
+    void CreateNvEvent(u32 event_id);
+
+    void FreeNvEvent(u32 event_id);
+
+    u32 FindFreeNvEvent(u32 syncpoint_id);
+
+    std::array<InternalEvent, MaxNvEvents> events{};
+    std::mutex events_mutex;
+    u64 events_mask{};
+
     struct IocSyncptReadParams {
     struct IocSyncptReadParams {
         u32_le id{};
         u32_le id{};
         u32_le value{};
         u32_le value{};
@@ -83,27 +154,18 @@ private:
     };
     };
     static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
     static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
 
 
-    struct IocCtrlEventSignalParams {
-        u32_le event_id{};
+    struct IocCtrlEventClearParams {
+        SyncpointEventValue event_id{};
     };
     };
-    static_assert(sizeof(IocCtrlEventSignalParams) == 4,
-                  "IocCtrlEventSignalParams is incorrect size");
+    static_assert(sizeof(IocCtrlEventClearParams) == 4,
+                  "IocCtrlEventClearParams is incorrect size");
 
 
     struct IocCtrlEventWaitParams {
     struct IocCtrlEventWaitParams {
-        u32_le syncpt_id{};
-        u32_le threshold{};
-        s32_le timeout{};
-        u32_le value{};
-    };
-    static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size");
-
-    struct IocCtrlEventWaitAsyncParams {
-        u32_le syncpt_id{};
-        u32_le threshold{};
+        NvFence fence{};
         u32_le timeout{};
         u32_le timeout{};
-        u32_le value{};
+        SyncpointEventValue value{};
     };
     };
-    static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16,
+    static_assert(sizeof(IocCtrlEventWaitParams) == 16,
                   "IocCtrlEventWaitAsyncParams is incorrect size");
                   "IocCtrlEventWaitAsyncParams is incorrect size");
 
 
     struct IocCtrlEventRegisterParams {
     struct IocCtrlEventRegisterParams {
@@ -118,19 +180,25 @@ private:
     static_assert(sizeof(IocCtrlEventUnregisterParams) == 4,
     static_assert(sizeof(IocCtrlEventUnregisterParams) == 4,
                   "IocCtrlEventUnregisterParams is incorrect size");
                   "IocCtrlEventUnregisterParams is incorrect size");
 
 
-    struct IocCtrlEventKill {
+    struct IocCtrlEventUnregisterBatchParams {
         u64_le user_events{};
         u64_le user_events{};
     };
     };
-    static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size");
+    static_assert(sizeof(IocCtrlEventUnregisterBatchParams) == 8,
+                  "IocCtrlEventKill is incorrect size");
 
 
     NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
-    NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
+    NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
+                              bool is_allocation);
     NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
+    NvResult IocCtrlEventUnregisterBatch(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output);
 
 
+    NvResult FreeEvent(u32 slot);
+
     EventInterface& events_interface;
     EventInterface& events_interface;
-    SyncpointManager& syncpoint_manager;
+    NvCore::Container& core;
+    NvCore::SyncpointManager& syncpoint_manager;
 };
 };
 
 
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 23 - 2
src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp

@@ -7,11 +7,19 @@
 #include "core/core.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/core_timing.h"
 #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
 #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
-nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_) : nvdevice{system_} {}
-nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
+nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_)
+    : nvdevice{system_}, events_interface{events_interface_} {
+    error_notifier_event = events_interface.CreateEvent("CtrlGpuErrorNotifier");
+    unknown_event = events_interface.CreateEvent("CtrlGpuUknownEvent");
+}
+nvhost_ctrl_gpu::~nvhost_ctrl_gpu() {
+    events_interface.FreeEvent(error_notifier_event);
+    events_interface.FreeEvent(unknown_event);
+}
 
 
 NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
 NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
                                  std::vector<u8>& output) {
                                  std::vector<u8>& output) {
@@ -286,4 +294,17 @@ NvResult nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
+Kernel::KEvent* nvhost_ctrl_gpu::QueryEvent(u32 event_id) {
+    switch (event_id) {
+    case 1:
+        return error_notifier_event;
+    case 2:
+        return unknown_event;
+    default: {
+        LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
+    }
+    }
+    return nullptr;
+}
+
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 13 - 1
src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h

@@ -10,11 +10,15 @@
 #include "common/swap.h"
 #include "common/swap.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 
 
+namespace Service::Nvidia {
+class EventInterface;
+}
+
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
 class nvhost_ctrl_gpu final : public nvdevice {
 class nvhost_ctrl_gpu final : public nvdevice {
 public:
 public:
-    explicit nvhost_ctrl_gpu(Core::System& system_);
+    explicit nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_);
     ~nvhost_ctrl_gpu() override;
     ~nvhost_ctrl_gpu() override;
 
 
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -27,6 +31,8 @@ public:
     void OnOpen(DeviceFD fd) override;
     void OnOpen(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
 
 
+    Kernel::KEvent* QueryEvent(u32 event_id) override;
+
 private:
 private:
     struct IoctlGpuCharacteristics {
     struct IoctlGpuCharacteristics {
         u32_le arch;                       // 0x120 (NVGPU_GPU_ARCH_GM200)
         u32_le arch;                       // 0x120 (NVGPU_GPU_ARCH_GM200)
@@ -160,6 +166,12 @@ private:
     NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
+
+    EventInterface& events_interface;
+
+    // Events
+    Kernel::KEvent* error_notifier_event;
+    Kernel::KEvent* unknown_event;
 };
 };
 
 
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 86 - 43
src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp

@@ -5,29 +5,46 @@
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
 #include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
 #include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
 #include "core/memory.h"
 #include "core/memory.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/engines/puller.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 namespace {
 namespace {
-Tegra::CommandHeader BuildFenceAction(Tegra::GPU::FenceOperation op, u32 syncpoint_id) {
-    Tegra::GPU::FenceAction result{};
+Tegra::CommandHeader BuildFenceAction(Tegra::Engines::Puller::FenceOperation op, u32 syncpoint_id) {
+    Tegra::Engines::Puller::FenceAction result{};
     result.op.Assign(op);
     result.op.Assign(op);
     result.syncpoint_id.Assign(syncpoint_id);
     result.syncpoint_id.Assign(syncpoint_id);
     return {result.raw};
     return {result.raw};
 }
 }
 } // namespace
 } // namespace
 
 
-nvhost_gpu::nvhost_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
-                       SyncpointManager& syncpoint_manager_)
-    : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)}, syncpoint_manager{syncpoint_manager_} {
-    channel_fence.id = syncpoint_manager_.AllocateSyncpoint();
-    channel_fence.value = system_.GPU().GetSyncpointValue(channel_fence.id);
+nvhost_gpu::nvhost_gpu(Core::System& system_, EventInterface& events_interface_,
+                       NvCore::Container& core_)
+    : nvdevice{system_}, events_interface{events_interface_}, core{core_},
+      syncpoint_manager{core_.GetSyncpointManager()}, nvmap{core.GetNvMapFile()},
+      channel_state{system.GPU().AllocateChannel()} {
+    channel_syncpoint = syncpoint_manager.AllocateSyncpoint(false);
+    sm_exception_breakpoint_int_report_event =
+        events_interface.CreateEvent("GpuChannelSMExceptionBreakpointInt");
+    sm_exception_breakpoint_pause_report_event =
+        events_interface.CreateEvent("GpuChannelSMExceptionBreakpointPause");
+    error_notifier_event = events_interface.CreateEvent("GpuChannelErrorNotifier");
 }
 }
 
 
-nvhost_gpu::~nvhost_gpu() = default;
+nvhost_gpu::~nvhost_gpu() {
+    events_interface.FreeEvent(sm_exception_breakpoint_int_report_event);
+    events_interface.FreeEvent(sm_exception_breakpoint_pause_report_event);
+    events_interface.FreeEvent(error_notifier_event);
+    syncpoint_manager.FreeSyncpoint(channel_syncpoint);
+}
 
 
 NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
 NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
                             std::vector<u8>& output) {
                             std::vector<u8>& output) {
@@ -167,9 +184,14 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8
                 params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
                 params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
                 params.unk3);
                 params.unk3);
 
 
-    channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id);
+    if (channel_state->initialized) {
+        LOG_CRITICAL(Service_NVDRV, "Already allocated!");
+        return NvResult::AlreadyAllocated;
+    }
+
+    system.GPU().InitChannel(*channel_state);
 
 
-    params.fence_out = channel_fence;
+    params.fence_out = syncpoint_manager.GetSyncpointFence(channel_syncpoint);
 
 
     std::memcpy(output.data(), &params, output.size());
     std::memcpy(output.data(), &params, output.size());
     return NvResult::Success;
     return NvResult::Success;
@@ -188,39 +210,37 @@ NvResult nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::ve
 
 
 static std::vector<Tegra::CommandHeader> BuildWaitCommandList(NvFence fence) {
 static std::vector<Tegra::CommandHeader> BuildWaitCommandList(NvFence fence) {
     return {
     return {
-        Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
+        Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
                                   Tegra::SubmissionMode::Increasing),
                                   Tegra::SubmissionMode::Increasing),
         {fence.value},
         {fence.value},
-        Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+        Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1,
                                   Tegra::SubmissionMode::Increasing),
                                   Tegra::SubmissionMode::Increasing),
-        BuildFenceAction(Tegra::GPU::FenceOperation::Acquire, fence.id),
+        BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Acquire, fence.id),
     };
     };
 }
 }
 
 
-static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(NvFence fence,
-                                                                   u32 add_increment) {
+static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(NvFence fence) {
     std::vector<Tegra::CommandHeader> result{
     std::vector<Tegra::CommandHeader> result{
-        Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
+        Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
                                   Tegra::SubmissionMode::Increasing),
                                   Tegra::SubmissionMode::Increasing),
         {}};
         {}};
 
 
-    for (u32 count = 0; count < add_increment; ++count) {
-        result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+    for (u32 count = 0; count < 2; ++count) {
+        result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1,
                                                       Tegra::SubmissionMode::Increasing));
                                                       Tegra::SubmissionMode::Increasing));
-        result.emplace_back(BuildFenceAction(Tegra::GPU::FenceOperation::Increment, fence.id));
+        result.emplace_back(
+            BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Increment, fence.id));
     }
     }
 
 
     return result;
     return result;
 }
 }
 
 
-static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(NvFence fence,
-                                                                          u32 add_increment) {
+static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(NvFence fence) {
     std::vector<Tegra::CommandHeader> result{
     std::vector<Tegra::CommandHeader> result{
-        Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1,
+        Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForIdle, 1,
                                   Tegra::SubmissionMode::Increasing),
                                   Tegra::SubmissionMode::Increasing),
         {}};
         {}};
-    const std::vector<Tegra::CommandHeader> increment{
-        BuildIncrementCommandList(fence, add_increment)};
+    const std::vector<Tegra::CommandHeader> increment{BuildIncrementCommandList(fence)};
 
 
     result.insert(result.end(), increment.begin(), increment.end());
     result.insert(result.end(), increment.begin(), increment.end());
 
 
@@ -234,33 +254,41 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>
 
 
     auto& gpu = system.GPU();
     auto& gpu = system.GPU();
 
 
-    params.fence_out.id = channel_fence.id;
+    std::scoped_lock lock(channel_mutex);
 
 
-    if (params.flags.add_wait.Value() &&
-        !syncpoint_manager.IsSyncpointExpired(params.fence_out.id, params.fence_out.value)) {
-        gpu.PushGPUEntries(Tegra::CommandList{BuildWaitCommandList(params.fence_out)});
-    }
+    const auto bind_id = channel_state->bind_id;
 
 
-    if (params.flags.add_increment.Value() || params.flags.increment.Value()) {
-        const u32 increment_value = params.flags.increment.Value() ? params.fence_out.value : 0;
-        params.fence_out.value = syncpoint_manager.IncreaseSyncpoint(
-            params.fence_out.id, params.AddIncrementValue() + increment_value);
-    } else {
-        params.fence_out.value = syncpoint_manager.GetSyncpointMax(params.fence_out.id);
+    auto& flags = params.flags;
+
+    if (flags.fence_wait.Value()) {
+        if (flags.increment_value.Value()) {
+            return NvResult::BadParameter;
+        }
+
+        if (!syncpoint_manager.IsFenceSignalled(params.fence)) {
+            gpu.PushGPUEntries(bind_id, Tegra::CommandList{BuildWaitCommandList(params.fence)});
+        }
     }
     }
 
 
-    gpu.PushGPUEntries(std::move(entries));
+    params.fence.id = channel_syncpoint;
+
+    u32 increment{(flags.fence_increment.Value() != 0 ? 2 : 0) +
+                  (flags.increment_value.Value() != 0 ? params.fence.value : 0)};
+    params.fence.value = syncpoint_manager.IncrementSyncpointMaxExt(channel_syncpoint, increment);
+    gpu.PushGPUEntries(bind_id, std::move(entries));
 
 
-    if (params.flags.add_increment.Value()) {
-        if (params.flags.suppress_wfi) {
-            gpu.PushGPUEntries(Tegra::CommandList{
-                BuildIncrementCommandList(params.fence_out, params.AddIncrementValue())});
+    if (flags.fence_increment.Value()) {
+        if (flags.suppress_wfi.Value()) {
+            gpu.PushGPUEntries(bind_id,
+                               Tegra::CommandList{BuildIncrementCommandList(params.fence)});
         } else {
         } else {
-            gpu.PushGPUEntries(Tegra::CommandList{
-                BuildIncrementWithWfiCommandList(params.fence_out, params.AddIncrementValue())});
+            gpu.PushGPUEntries(bind_id,
+                               Tegra::CommandList{BuildIncrementWithWfiCommandList(params.fence)});
         }
         }
     }
     }
 
 
+    flags.raw = 0;
+
     std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
     std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
     return NvResult::Success;
     return NvResult::Success;
 }
 }
@@ -328,4 +356,19 @@ NvResult nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vect
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
+Kernel::KEvent* nvhost_gpu::QueryEvent(u32 event_id) {
+    switch (event_id) {
+    case 1:
+        return sm_exception_breakpoint_int_report_event;
+    case 2:
+        return sm_exception_breakpoint_pause_report_event;
+    case 3:
+        return error_notifier_event;
+    default: {
+        LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
+    }
+    }
+    return nullptr;
+}
+
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 38 - 16
src/core/hle/service/nvdrv/devices/nvhost_gpu.h

@@ -13,17 +13,31 @@
 #include "core/hle/service/nvdrv/nvdata.h"
 #include "core/hle/service/nvdrv/nvdata.h"
 #include "video_core/dma_pusher.h"
 #include "video_core/dma_pusher.h"
 
 
+namespace Tegra {
+namespace Control {
+struct ChannelState;
+}
+} // namespace Tegra
+
 namespace Service::Nvidia {
 namespace Service::Nvidia {
+
+namespace NvCore {
+class Container;
+class NvMap;
 class SyncpointManager;
 class SyncpointManager;
-}
+} // namespace NvCore
+
+class EventInterface;
+} // namespace Service::Nvidia
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
+class nvhost_as_gpu;
 class nvmap;
 class nvmap;
 class nvhost_gpu final : public nvdevice {
 class nvhost_gpu final : public nvdevice {
 public:
 public:
-    explicit nvhost_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
-                        SyncpointManager& syncpoint_manager_);
+    explicit nvhost_gpu(Core::System& system_, EventInterface& events_interface_,
+                        NvCore::Container& core);
     ~nvhost_gpu() override;
     ~nvhost_gpu() override;
 
 
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -36,7 +50,10 @@ public:
     void OnOpen(DeviceFD fd) override;
     void OnOpen(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
 
 
+    Kernel::KEvent* QueryEvent(u32 event_id) override;
+
 private:
 private:
+    friend class nvhost_as_gpu;
     enum class CtxObjects : u32_le {
     enum class CtxObjects : u32_le {
         Ctx2D = 0x902D,
         Ctx2D = 0x902D,
         Ctx3D = 0xB197,
         Ctx3D = 0xB197,
@@ -146,17 +163,13 @@ private:
         u32_le num_entries{}; // number of fence objects being submitted
         u32_le num_entries{}; // number of fence objects being submitted
         union {
         union {
             u32_le raw;
             u32_le raw;
-            BitField<0, 1, u32_le> add_wait;      // append a wait sync_point to the list
-            BitField<1, 1, u32_le> add_increment; // append an increment to the list
-            BitField<2, 1, u32_le> new_hw_format; // mostly ignored
-            BitField<4, 1, u32_le> suppress_wfi;  // suppress wait for interrupt
-            BitField<8, 1, u32_le> increment;     // increment the returned fence
+            BitField<0, 1, u32_le> fence_wait;      // append a wait sync_point to the list
+            BitField<1, 1, u32_le> fence_increment; // append an increment to the list
+            BitField<2, 1, u32_le> new_hw_format;   // mostly ignored
+            BitField<4, 1, u32_le> suppress_wfi;    // suppress wait for interrupt
+            BitField<8, 1, u32_le> increment_value; // increment the returned fence
         } flags;
         } flags;
-        NvFence fence_out{}; // returned new fence object for others to wait on
-
-        u32 AddIncrementValue() const {
-            return flags.add_increment.Value() << 1;
-        }
+        NvFence fence{}; // returned new fence object for others to wait on
     };
     };
     static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(NvFence),
     static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(NvFence),
                   "IoctlSubmitGpfifo is incorrect size");
                   "IoctlSubmitGpfifo is incorrect size");
@@ -191,9 +204,18 @@ private:
     NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
 
 
-    std::shared_ptr<nvmap> nvmap_dev;
-    SyncpointManager& syncpoint_manager;
-    NvFence channel_fence;
+    EventInterface& events_interface;
+    NvCore::Container& core;
+    NvCore::SyncpointManager& syncpoint_manager;
+    NvCore::NvMap& nvmap;
+    std::shared_ptr<Tegra::Control::ChannelState> channel_state;
+    u32 channel_syncpoint;
+    std::mutex channel_mutex;
+
+    // Events
+    Kernel::KEvent* sm_exception_breakpoint_int_report_event;
+    Kernel::KEvent* sm_exception_breakpoint_pause_report_event;
+    Kernel::KEvent* error_notifier_event;
 };
 };
 
 
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 9 - 7
src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp

@@ -5,14 +5,14 @@
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
 #include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
 #include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
 #include "video_core/renderer_base.h"
 #include "video_core/renderer_base.h"
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
-nvhost_nvdec::nvhost_nvdec(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
-                           SyncpointManager& syncpoint_manager_)
-    : nvhost_nvdec_common{system_, std::move(nvmap_dev_), syncpoint_manager_} {}
+nvhost_nvdec::nvhost_nvdec(Core::System& system_, NvCore::Container& core_)
+    : nvhost_nvdec_common{system_, core_, NvCore::ChannelType::NvDec} {}
 nvhost_nvdec::~nvhost_nvdec() = default;
 nvhost_nvdec::~nvhost_nvdec() = default;
 
 
 NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
 NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -21,8 +21,9 @@ NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>&
     case 0x0:
     case 0x0:
         switch (command.cmd) {
         switch (command.cmd) {
         case 0x1: {
         case 0x1: {
-            if (!fd_to_id.contains(fd)) {
-                fd_to_id[fd] = next_id++;
+            auto& host1x_file = core.Host1xDeviceFile();
+            if (!host1x_file.fd_to_id.contains(fd)) {
+                host1x_file.fd_to_id[fd] = host1x_file.nvdec_next_id++;
             }
             }
             return Submit(fd, input, output);
             return Submit(fd, input, output);
         }
         }
@@ -73,8 +74,9 @@ void nvhost_nvdec::OnOpen(DeviceFD fd) {
 
 
 void nvhost_nvdec::OnClose(DeviceFD fd) {
 void nvhost_nvdec::OnClose(DeviceFD fd) {
     LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
     LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
-    const auto iter = fd_to_id.find(fd);
-    if (iter != fd_to_id.end()) {
+    auto& host1x_file = core.Host1xDeviceFile();
+    const auto iter = host1x_file.fd_to_id.find(fd);
+    if (iter != host1x_file.fd_to_id.end()) {
         system.GPU().ClearCdmaInstance(iter->second);
         system.GPU().ClearCdmaInstance(iter->second);
     }
     }
     system.AudioCore().SetNVDECActive(false);
     system.AudioCore().SetNVDECActive(false);

+ 1 - 5
src/core/hle/service/nvdrv/devices/nvhost_nvdec.h

@@ -10,8 +10,7 @@ namespace Service::Nvidia::Devices {
 
 
 class nvhost_nvdec final : public nvhost_nvdec_common {
 class nvhost_nvdec final : public nvhost_nvdec_common {
 public:
 public:
-    explicit nvhost_nvdec(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
-                          SyncpointManager& syncpoint_manager_);
+    explicit nvhost_nvdec(Core::System& system_, NvCore::Container& core);
     ~nvhost_nvdec() override;
     ~nvhost_nvdec() override;
 
 
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -23,9 +22,6 @@ public:
 
 
     void OnOpen(DeviceFD fd) override;
     void OnOpen(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
-
-private:
-    u32 next_id{};
 };
 };
 
 
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 42 - 39
src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp

@@ -8,10 +8,12 @@
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
 #include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
 #include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
-#include "core/hle/service/nvdrv/devices/nvmap.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
 #include "core/memory.h"
 #include "core/memory.h"
+#include "video_core/host1x/host1x.h"
 #include "video_core/memory_manager.h"
 #include "video_core/memory_manager.h"
 #include "video_core/renderer_base.h"
 #include "video_core/renderer_base.h"
 
 
@@ -44,10 +46,22 @@ std::size_t WriteVectors(std::vector<u8>& dst, const std::vector<T>& src, std::s
 }
 }
 } // Anonymous namespace
 } // Anonymous namespace
 
 
-nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
-                                         SyncpointManager& syncpoint_manager_)
-    : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)}, syncpoint_manager{syncpoint_manager_} {}
-nvhost_nvdec_common::~nvhost_nvdec_common() = default;
+nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system_, NvCore::Container& core_,
+                                         NvCore::ChannelType channel_type_)
+    : nvdevice{system_}, core{core_}, syncpoint_manager{core.GetSyncpointManager()},
+      nvmap{core.GetNvMapFile()}, channel_type{channel_type_} {
+    auto& syncpts_accumulated = core.Host1xDeviceFile().syncpts_accumulated;
+    if (syncpts_accumulated.empty()) {
+        channel_syncpoint = syncpoint_manager.AllocateSyncpoint(false);
+    } else {
+        channel_syncpoint = syncpts_accumulated.front();
+        syncpts_accumulated.pop_front();
+    }
+}
+
+nvhost_nvdec_common::~nvhost_nvdec_common() {
+    core.Host1xDeviceFile().syncpts_accumulated.push_back(channel_syncpoint);
+}
 
 
 NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) {
 NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) {
     IoctlSetNvmapFD params{};
     IoctlSetNvmapFD params{};
@@ -84,16 +98,16 @@ NvResult nvhost_nvdec_common::Submit(DeviceFD fd, const std::vector<u8>& input,
         for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
         for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
             const SyncptIncr& syncpt_incr = syncpt_increments[i];
             const SyncptIncr& syncpt_incr = syncpt_increments[i];
             fence_thresholds[i] =
             fence_thresholds[i] =
-                syncpoint_manager.IncreaseSyncpoint(syncpt_incr.id, syncpt_incr.increments);
+                syncpoint_manager.IncrementSyncpointMaxExt(syncpt_incr.id, syncpt_incr.increments);
         }
         }
     }
     }
     for (const auto& cmd_buffer : command_buffers) {
     for (const auto& cmd_buffer : command_buffers) {
-        const auto object = nvmap_dev->GetObject(cmd_buffer.memory_id);
+        const auto object = nvmap.GetHandle(cmd_buffer.memory_id);
         ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
         ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
         Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
         Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
-        system.Memory().ReadBlock(object->addr + cmd_buffer.offset, cmdlist.data(),
+        system.Memory().ReadBlock(object->address + cmd_buffer.offset, cmdlist.data(),
                                   cmdlist.size() * sizeof(u32));
                                   cmdlist.size() * sizeof(u32));
-        gpu.PushCommandBuffer(fd_to_id[fd], cmdlist);
+        gpu.PushCommandBuffer(core.Host1xDeviceFile().fd_to_id[fd], cmdlist);
     }
     }
     std::memcpy(output.data(), &params, sizeof(IoctlSubmit));
     std::memcpy(output.data(), &params, sizeof(IoctlSubmit));
     // Some games expect command_buffers to be written back
     // Some games expect command_buffers to be written back
@@ -112,10 +126,8 @@ NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::ve
     std::memcpy(&params, input.data(), sizeof(IoctlGetSyncpoint));
     std::memcpy(&params, input.data(), sizeof(IoctlGetSyncpoint));
     LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
     LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
 
 
-    if (device_syncpoints[params.param] == 0 && system.GPU().UseNvdec()) {
-        device_syncpoints[params.param] = syncpoint_manager.AllocateSyncpoint();
-    }
-    params.value = device_syncpoints[params.param];
+    // const u32 id{NvCore::SyncpointManager::channel_syncpoints[static_cast<u32>(channel_type)]};
+    params.value = channel_syncpoint;
     std::memcpy(output.data(), &params, sizeof(IoctlGetSyncpoint));
     std::memcpy(output.data(), &params, sizeof(IoctlGetSyncpoint));
 
 
     return NvResult::Success;
     return NvResult::Success;
@@ -123,6 +135,7 @@ NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::ve
 
 
 NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
     IoctlGetWaitbase params{};
     IoctlGetWaitbase params{};
+    LOG_CRITICAL(Service_NVDRV, "called WAITBASE");
     std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
     std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
     params.value = 0; // Seems to be hard coded at 0
     params.value = 0; // Seems to be hard coded at 0
     std::memcpy(output.data(), &params, sizeof(IoctlGetWaitbase));
     std::memcpy(output.data(), &params, sizeof(IoctlGetWaitbase));
@@ -136,28 +149,8 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
 
 
     SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
     SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
 
 
-    auto& gpu = system.GPU();
-
     for (auto& cmd_buffer : cmd_buffer_handles) {
     for (auto& cmd_buffer : cmd_buffer_handles) {
-        auto object{nvmap_dev->GetObject(cmd_buffer.map_handle)};
-        if (!object) {
-            LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmd_buffer.map_handle);
-            std::memcpy(output.data(), &params, output.size());
-            return NvResult::InvalidState;
-        }
-        if (object->dma_map_addr == 0) {
-            // NVDEC and VIC memory is in the 32-bit address space
-            // MapAllocate32 will attempt to map a lower 32-bit value in the shared gpu memory space
-            const GPUVAddr low_addr = gpu.MemoryManager().MapAllocate32(object->addr, object->size);
-            object->dma_map_addr = static_cast<u32>(low_addr);
-            // Ensure that the dma_map_addr is indeed in the lower 32-bit address space.
-            ASSERT(object->dma_map_addr == low_addr);
-        }
-        if (!object->dma_map_addr) {
-            LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size);
-        } else {
-            cmd_buffer.map_address = object->dma_map_addr;
-        }
+        cmd_buffer.map_address = nvmap.PinHandle(cmd_buffer.map_handle);
     }
     }
     std::memcpy(output.data(), &params, sizeof(IoctlMapBuffer));
     std::memcpy(output.data(), &params, sizeof(IoctlMapBuffer));
     std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(),
     std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(),
@@ -167,11 +160,16 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
 }
 }
 
 
 NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
-    // This is intntionally stubbed.
-    // Skip unmapping buffers here, as to not break the continuity of the VP9 reference frame
-    // addresses, and risk invalidating data before the async GPU thread is done with it
+    IoctlMapBuffer params{};
+    std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer));
+    std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
+
+    SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
+    for (auto& cmd_buffer : cmd_buffer_handles) {
+        nvmap.UnpinHandle(cmd_buffer.map_handle);
+    }
+
     std::memset(output.data(), 0, output.size());
     std::memset(output.data(), 0, output.size());
-    LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
@@ -182,4 +180,9 @@ NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input,
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
+Kernel::KEvent* nvhost_nvdec_common::QueryEvent(u32 event_id) {
+    LOG_CRITICAL(Service_NVDRV, "Unknown HOSTX1 Event {}", event_id);
+    return nullptr;
+}
+
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 16 - 7
src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h

@@ -3,21 +3,26 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <deque>
 #include <vector>
 #include <vector>
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "common/swap.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 
 
 namespace Service::Nvidia {
 namespace Service::Nvidia {
-class SyncpointManager;
+
+namespace NvCore {
+class Container;
+class NvMap;
+} // namespace NvCore
 
 
 namespace Devices {
 namespace Devices {
-class nvmap;
 
 
 class nvhost_nvdec_common : public nvdevice {
 class nvhost_nvdec_common : public nvdevice {
 public:
 public:
-    explicit nvhost_nvdec_common(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
-                                 SyncpointManager& syncpoint_manager_);
+    explicit nvhost_nvdec_common(Core::System& system_, NvCore::Container& core,
+                                 NvCore::ChannelType channel_type);
     ~nvhost_nvdec_common() override;
     ~nvhost_nvdec_common() override;
 
 
 protected:
 protected:
@@ -110,11 +115,15 @@ protected:
     NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
 
 
-    std::unordered_map<DeviceFD, u32> fd_to_id{};
+    Kernel::KEvent* QueryEvent(u32 event_id) override;
+
+    u32 channel_syncpoint;
     s32_le nvmap_fd{};
     s32_le nvmap_fd{};
     u32_le submit_timeout{};
     u32_le submit_timeout{};
-    std::shared_ptr<nvmap> nvmap_dev;
-    SyncpointManager& syncpoint_manager;
+    NvCore::Container& core;
+    NvCore::SyncpointManager& syncpoint_manager;
+    NvCore::NvMap& nvmap;
+    NvCore::ChannelType channel_type;
     std::array<u32, MaxSyncPoints> device_syncpoints{};
     std::array<u32, MaxSyncPoints> device_syncpoints{};
 };
 };
 }; // namespace Devices
 }; // namespace Devices

+ 12 - 8
src/core/hle/service/nvdrv/devices/nvhost_vic.cpp

@@ -4,13 +4,14 @@
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/core.h"
+#include "core/hle/service/nvdrv/core/container.h"
 #include "core/hle/service/nvdrv/devices/nvhost_vic.h"
 #include "core/hle/service/nvdrv/devices/nvhost_vic.h"
 #include "video_core/renderer_base.h"
 #include "video_core/renderer_base.h"
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
-nvhost_vic::nvhost_vic(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
-                       SyncpointManager& syncpoint_manager_)
-    : nvhost_nvdec_common{system_, std::move(nvmap_dev_), syncpoint_manager_} {}
+
+nvhost_vic::nvhost_vic(Core::System& system_, NvCore::Container& core_)
+    : nvhost_nvdec_common{system_, core_, NvCore::ChannelType::VIC} {}
 
 
 nvhost_vic::~nvhost_vic() = default;
 nvhost_vic::~nvhost_vic() = default;
 
 
@@ -19,11 +20,13 @@ NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& i
     switch (command.group) {
     switch (command.group) {
     case 0x0:
     case 0x0:
         switch (command.cmd) {
         switch (command.cmd) {
-        case 0x1:
-            if (!fd_to_id.contains(fd)) {
-                fd_to_id[fd] = next_id++;
+        case 0x1: {
+            auto& host1x_file = core.Host1xDeviceFile();
+            if (!host1x_file.fd_to_id.contains(fd)) {
+                host1x_file.fd_to_id[fd] = host1x_file.vic_next_id++;
             }
             }
             return Submit(fd, input, output);
             return Submit(fd, input, output);
+        }
         case 0x2:
         case 0x2:
             return GetSyncpoint(input, output);
             return GetSyncpoint(input, output);
         case 0x3:
         case 0x3:
@@ -67,8 +70,9 @@ NvResult nvhost_vic::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& i
 void nvhost_vic::OnOpen(DeviceFD fd) {}
 void nvhost_vic::OnOpen(DeviceFD fd) {}
 
 
 void nvhost_vic::OnClose(DeviceFD fd) {
 void nvhost_vic::OnClose(DeviceFD fd) {
-    const auto iter = fd_to_id.find(fd);
-    if (iter != fd_to_id.end()) {
+    auto& host1x_file = core.Host1xDeviceFile();
+    const auto iter = host1x_file.fd_to_id.find(fd);
+    if (iter != host1x_file.fd_to_id.end()) {
         system.GPU().ClearCdmaInstance(iter->second);
         system.GPU().ClearCdmaInstance(iter->second);
     }
     }
 }
 }

+ 1 - 5
src/core/hle/service/nvdrv/devices/nvhost_vic.h

@@ -9,8 +9,7 @@ namespace Service::Nvidia::Devices {
 
 
 class nvhost_vic final : public nvhost_nvdec_common {
 class nvhost_vic final : public nvhost_nvdec_common {
 public:
 public:
-    explicit nvhost_vic(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
-                        SyncpointManager& syncpoint_manager_);
+    explicit nvhost_vic(Core::System& system_, NvCore::Container& core);
     ~nvhost_vic();
     ~nvhost_vic();
 
 
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -22,8 +21,5 @@ public:
 
 
     void OnOpen(DeviceFD fd) override;
     void OnOpen(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
-
-private:
-    u32 next_id{};
 };
 };
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 109 - 121
src/core/hle/service/nvdrv/devices/nvmap.cpp

@@ -2,19 +2,26 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 
 #include <algorithm>
 #include <algorithm>
+#include <bit>
 #include <cstring>
 #include <cstring>
 
 
+#include "common/alignment.h"
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
 #include "core/hle/service/nvdrv/devices/nvmap.h"
 #include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/memory.h"
+
+using Core::Memory::YUZU_PAGESIZE;
 
 
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
-nvmap::nvmap(Core::System& system_) : nvdevice{system_} {
-    // Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to
-    // represent this.
-    CreateObject(0);
-}
+nvmap::nvmap(Core::System& system_, NvCore::Container& container_)
+    : nvdevice{system_}, container{container_}, file{container.GetNvMapFile()} {}
 
 
 nvmap::~nvmap() = default;
 nvmap::~nvmap() = default;
 
 
@@ -62,39 +69,21 @@ NvResult nvmap::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
 void nvmap::OnOpen(DeviceFD fd) {}
 void nvmap::OnOpen(DeviceFD fd) {}
 void nvmap::OnClose(DeviceFD fd) {}
 void nvmap::OnClose(DeviceFD fd) {}
 
 
-VAddr nvmap::GetObjectAddress(u32 handle) const {
-    auto object = GetObject(handle);
-    ASSERT(object);
-    ASSERT(object->status == Object::Status::Allocated);
-    return object->addr;
-}
-
-u32 nvmap::CreateObject(u32 size) {
-    // Create a new nvmap object and obtain a handle to it.
-    auto object = std::make_shared<Object>();
-    object->id = next_id++;
-    object->size = size;
-    object->status = Object::Status::Created;
-    object->refcount = 1;
-
-    const u32 handle = next_handle++;
-
-    handles.insert_or_assign(handle, std::move(object));
-
-    return handle;
-}
-
 NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
     IocCreateParams params;
     IocCreateParams params;
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
-    LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
-
-    if (!params.size) {
-        LOG_ERROR(Service_NVDRV, "Size is 0");
-        return NvResult::BadValue;
+    LOG_DEBUG(Service_NVDRV, "called, size=0x{:08X}", params.size);
+
+    std::shared_ptr<NvCore::NvMap::Handle> handle_description{};
+    auto result =
+        file.CreateHandle(Common::AlignUp(params.size, YUZU_PAGESIZE), handle_description);
+    if (result != NvResult::Success) {
+        LOG_CRITICAL(Service_NVDRV, "Failed to create Object");
+        return result;
     }
     }
-
-    params.handle = CreateObject(params.size);
+    handle_description->orig_size = params.size; // Orig size is the unaligned size
+    params.handle = handle_description->id;
+    LOG_DEBUG(Service_NVDRV, "handle: {}, size: 0x{:X}", handle_description->id, params.size);
 
 
     std::memcpy(output.data(), &params, sizeof(params));
     std::memcpy(output.data(), &params, sizeof(params));
     return NvResult::Success;
     return NvResult::Success;
@@ -103,63 +92,68 @@ NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output)
 NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
     IocAllocParams params;
     IocAllocParams params;
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
-    LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
+    LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.address);
 
 
     if (!params.handle) {
     if (!params.handle) {
-        LOG_ERROR(Service_NVDRV, "Handle is 0");
+        LOG_CRITICAL(Service_NVDRV, "Handle is 0");
         return NvResult::BadValue;
         return NvResult::BadValue;
     }
     }
 
 
     if ((params.align - 1) & params.align) {
     if ((params.align - 1) & params.align) {
-        LOG_ERROR(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
+        LOG_CRITICAL(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
         return NvResult::BadValue;
         return NvResult::BadValue;
     }
     }
 
 
-    const u32 min_alignment = 0x1000;
-    if (params.align < min_alignment) {
-        params.align = min_alignment;
+    // Force page size alignment at a minimum
+    if (params.align < YUZU_PAGESIZE) {
+        params.align = YUZU_PAGESIZE;
     }
     }
 
 
-    auto object = GetObject(params.handle);
-    if (!object) {
-        LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
+    auto handle_description{file.GetHandle(params.handle)};
+    if (!handle_description) {
+        LOG_CRITICAL(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
         return NvResult::BadValue;
         return NvResult::BadValue;
     }
     }
 
 
-    if (object->status == Object::Status::Allocated) {
-        LOG_ERROR(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
+    if (handle_description->allocated) {
+        LOG_CRITICAL(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
         return NvResult::InsufficientMemory;
         return NvResult::InsufficientMemory;
     }
     }
 
 
-    object->flags = params.flags;
-    object->align = params.align;
-    object->kind = params.kind;
-    object->addr = params.addr;
-    object->status = Object::Status::Allocated;
-
+    const auto result =
+        handle_description->Alloc(params.flags, params.align, params.kind, params.address);
+    if (result != NvResult::Success) {
+        LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
+        return result;
+    }
+    ASSERT(system.CurrentProcess()
+               ->PageTable()
+               .LockForDeviceAddressSpace(handle_description->address, handle_description->size)
+               .IsSuccess());
     std::memcpy(output.data(), &params, sizeof(params));
     std::memcpy(output.data(), &params, sizeof(params));
-    return NvResult::Success;
+    return result;
 }
 }
 
 
 NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
     IocGetIdParams params;
     IocGetIdParams params;
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
 
 
-    LOG_WARNING(Service_NVDRV, "called");
+    LOG_DEBUG(Service_NVDRV, "called");
 
 
+    // See the comment in FromId for extra info on this function
     if (!params.handle) {
     if (!params.handle) {
-        LOG_ERROR(Service_NVDRV, "Handle is zero");
+        LOG_CRITICAL(Service_NVDRV, "Error!");
         return NvResult::BadValue;
         return NvResult::BadValue;
     }
     }
 
 
-    auto object = GetObject(params.handle);
-    if (!object) {
-        LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
-        return NvResult::BadValue;
+    auto handle_description{file.GetHandle(params.handle)};
+    if (!handle_description) {
+        LOG_CRITICAL(Service_NVDRV, "Error!");
+        return NvResult::AccessDenied; // This will always return EPERM irrespective of if the
+                                       // handle exists or not
     }
     }
 
 
-    params.id = object->id;
-
+    params.id = handle_description->id;
     std::memcpy(output.data(), &params, sizeof(params));
     std::memcpy(output.data(), &params, sizeof(params));
     return NvResult::Success;
     return NvResult::Success;
 }
 }
@@ -168,26 +162,29 @@ NvResult nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output)
     IocFromIdParams params;
     IocFromIdParams params;
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
 
 
-    LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+    LOG_DEBUG(Service_NVDRV, "called, id:{}", params.id);
 
 
-    auto itr = std::find_if(handles.begin(), handles.end(),
-                            [&](const auto& entry) { return entry.second->id == params.id; });
-    if (itr == handles.end()) {
-        LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
+    // Handles and IDs are always the same value in nvmap however IDs can be used globally given the
+    // right permissions.
+    // Since we don't plan on ever supporting multiprocess we can skip implementing handle refs and
+    // so this function just does simple validation and passes through the handle id.
+    if (!params.id) {
+        LOG_CRITICAL(Service_NVDRV, "Zero Id is invalid!");
         return NvResult::BadValue;
         return NvResult::BadValue;
     }
     }
 
 
-    auto& object = itr->second;
-    if (object->status != Object::Status::Allocated) {
-        LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
+    auto handle_description{file.GetHandle(params.id)};
+    if (!handle_description) {
+        LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
         return NvResult::BadValue;
         return NvResult::BadValue;
     }
     }
 
 
-    itr->second->refcount++;
-
-    // Return the existing handle instead of creating a new one.
-    params.handle = itr->first;
-
+    auto result = handle_description->Duplicate(false);
+    if (result != NvResult::Success) {
+        LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
+        return result;
+    }
+    params.handle = handle_description->id;
     std::memcpy(output.data(), &params, sizeof(params));
     std::memcpy(output.data(), &params, sizeof(params));
     return NvResult::Success;
     return NvResult::Success;
 }
 }
@@ -198,35 +195,43 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output)
     IocParamParams params;
     IocParamParams params;
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
 
 
-    LOG_DEBUG(Service_NVDRV, "(STUBBED) called type={}", params.param);
+    LOG_DEBUG(Service_NVDRV, "called type={}", params.param);
 
 
-    auto object = GetObject(params.handle);
-    if (!object) {
-        LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
+    if (!params.handle) {
+        LOG_CRITICAL(Service_NVDRV, "Invalid handle!");
         return NvResult::BadValue;
         return NvResult::BadValue;
     }
     }
 
 
-    if (object->status != Object::Status::Allocated) {
-        LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
+    auto handle_description{file.GetHandle(params.handle)};
+    if (!handle_description) {
+        LOG_CRITICAL(Service_NVDRV, "Not registered handle!");
         return NvResult::BadValue;
         return NvResult::BadValue;
     }
     }
 
 
-    switch (static_cast<ParamTypes>(params.param)) {
-    case ParamTypes::Size:
-        params.result = object->size;
+    switch (params.param) {
+    case HandleParameterType::Size:
+        params.result = static_cast<u32_le>(handle_description->orig_size);
+        break;
+    case HandleParameterType::Alignment:
+        params.result = static_cast<u32_le>(handle_description->align);
         break;
         break;
-    case ParamTypes::Alignment:
-        params.result = object->align;
+    case HandleParameterType::Base:
+        params.result = static_cast<u32_le>(-22); // posix EINVAL
         break;
         break;
-    case ParamTypes::Heap:
-        // TODO(Subv): Seems to be a hardcoded value?
-        params.result = 0x40000000;
+    case HandleParameterType::Heap:
+        if (handle_description->allocated)
+            params.result = 0x40000000;
+        else
+            params.result = 0;
         break;
         break;
-    case ParamTypes::Kind:
-        params.result = object->kind;
+    case HandleParameterType::Kind:
+        params.result = handle_description->kind;
+        break;
+    case HandleParameterType::IsSharedMemMapped:
+        params.result = handle_description->is_shared_mem_mapped;
         break;
         break;
     default:
     default:
-        UNIMPLEMENTED();
+        return NvResult::BadValue;
     }
     }
 
 
     std::memcpy(output.data(), &params, sizeof(params));
     std::memcpy(output.data(), &params, sizeof(params));
@@ -234,46 +239,29 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output)
 }
 }
 
 
 NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
 NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
-    // TODO(Subv): These flags are unconfirmed.
-    enum FreeFlags {
-        Freed = 0,
-        NotFreedYet = 1,
-    };
-
     IocFreeParams params;
     IocFreeParams params;
     std::memcpy(&params, input.data(), sizeof(params));
     std::memcpy(&params, input.data(), sizeof(params));
 
 
-    LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
+    LOG_DEBUG(Service_NVDRV, "called");
 
 
-    auto itr = handles.find(params.handle);
-    if (itr == handles.end()) {
-        LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
-        return NvResult::BadValue;
-    }
-    if (!itr->second->refcount) {
-        LOG_ERROR(
-            Service_NVDRV,
-            "There is no references to this object. The object is already freed. handle={:08X}",
-            params.handle);
-        return NvResult::BadValue;
+    if (!params.handle) {
+        LOG_CRITICAL(Service_NVDRV, "Handle null freed?");
+        return NvResult::Success;
     }
     }
 
 
-    itr->second->refcount--;
-
-    params.size = itr->second->size;
-
-    if (itr->second->refcount == 0) {
-        params.flags = Freed;
-        // The address of the nvmap is written to the output if we're finally freeing it, otherwise
-        // 0 is written.
-        params.address = itr->second->addr;
+    if (auto freeInfo{file.FreeHandle(params.handle, false)}) {
+        ASSERT(system.CurrentProcess()
+                   ->PageTable()
+                   .UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size)
+                   .IsSuccess());
+        params.address = freeInfo->address;
+        params.size = static_cast<u32>(freeInfo->size);
+        params.flags.raw = 0;
+        params.flags.map_uncached.Assign(freeInfo->was_uncached);
     } else {
     } else {
-        params.flags = NotFreedYet;
-        params.address = 0;
+        // This is possible when there's internel dups or other duplicates.
     }
     }
 
 
-    handles.erase(params.handle);
-
     std::memcpy(output.data(), &params, sizeof(params));
     std::memcpy(output.data(), &params, sizeof(params));
     return NvResult::Success;
     return NvResult::Success;
 }
 }

+ 23 - 33
src/core/hle/service/nvdrv/devices/nvmap.h

@@ -9,15 +9,23 @@
 #include "common/common_funcs.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "common/swap.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 
 
+namespace Service::Nvidia::NvCore {
+class Container;
+} // namespace Service::Nvidia::NvCore
+
 namespace Service::Nvidia::Devices {
 namespace Service::Nvidia::Devices {
 
 
 class nvmap final : public nvdevice {
 class nvmap final : public nvdevice {
 public:
 public:
-    explicit nvmap(Core::System& system_);
+    explicit nvmap(Core::System& system_, NvCore::Container& container);
     ~nvmap() override;
     ~nvmap() override;
 
 
+    nvmap(const nvmap&) = delete;
+    nvmap& operator=(const nvmap&) = delete;
+
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
     NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
                     std::vector<u8>& output) override;
                     std::vector<u8>& output) override;
     NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
     NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -28,31 +36,15 @@ public:
     void OnOpen(DeviceFD fd) override;
     void OnOpen(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
     void OnClose(DeviceFD fd) override;
 
 
-    /// Returns the allocated address of an nvmap object given its handle.
-    VAddr GetObjectAddress(u32 handle) const;
-
-    /// Represents an nvmap object.
-    struct Object {
-        enum class Status { Created, Allocated };
-        u32 id;
-        u32 size;
-        u32 flags;
-        u32 align;
-        u8 kind;
-        VAddr addr;
-        Status status;
-        u32 refcount;
-        u32 dma_map_addr;
+    enum class HandleParameterType : u32_le {
+        Size = 1,
+        Alignment = 2,
+        Base = 3,
+        Heap = 4,
+        Kind = 5,
+        IsSharedMemMapped = 6
     };
     };
 
 
-    std::shared_ptr<Object> GetObject(u32 handle) const {
-        auto itr = handles.find(handle);
-        if (itr != handles.end()) {
-            return itr->second;
-        }
-        return {};
-    }
-
 private:
 private:
     /// Id to use for the next handle that is created.
     /// Id to use for the next handle that is created.
     u32 next_handle = 0;
     u32 next_handle = 0;
@@ -60,9 +52,6 @@ private:
     /// Id to use for the next object that is created.
     /// Id to use for the next object that is created.
     u32 next_id = 0;
     u32 next_id = 0;
 
 
-    /// Mapping of currently allocated handles to the objects they represent.
-    std::unordered_map<u32, std::shared_ptr<Object>> handles;
-
     struct IocCreateParams {
     struct IocCreateParams {
         // Input
         // Input
         u32_le size{};
         u32_le size{};
@@ -83,11 +72,11 @@ private:
         // Input
         // Input
         u32_le handle{};
         u32_le handle{};
         u32_le heap_mask{};
         u32_le heap_mask{};
-        u32_le flags{};
+        NvCore::NvMap::Handle::Flags flags{};
         u32_le align{};
         u32_le align{};
         u8 kind{};
         u8 kind{};
         INSERT_PADDING_BYTES(7);
         INSERT_PADDING_BYTES(7);
-        u64_le addr{};
+        u64_le address{};
     };
     };
     static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");
     static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");
 
 
@@ -96,14 +85,14 @@ private:
         INSERT_PADDING_BYTES(4);
         INSERT_PADDING_BYTES(4);
         u64_le address{};
         u64_le address{};
         u32_le size{};
         u32_le size{};
-        u32_le flags{};
+        NvCore::NvMap::Handle::Flags flags{};
     };
     };
     static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");
     static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");
 
 
     struct IocParamParams {
     struct IocParamParams {
         // Input
         // Input
         u32_le handle{};
         u32_le handle{};
-        u32_le param{};
+        HandleParameterType param{};
         // Output
         // Output
         u32_le result{};
         u32_le result{};
     };
     };
@@ -117,14 +106,15 @@ private:
     };
     };
     static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
     static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
 
 
-    u32 CreateObject(u32 size);
-
     NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output);
     NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output);
+
+    NvCore::Container& container;
+    NvCore::NvMap& file;
 };
 };
 
 
 } // namespace Service::Nvidia::Devices
 } // namespace Service::Nvidia::Devices

+ 11 - 6
src/core/hle/service/nvdrv/nvdata.h

@@ -1,5 +1,6 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
 
 
 #pragma once
 #pragma once
 
 
@@ -78,11 +79,15 @@ enum class NvResult : u32 {
     ModuleNotPresent = 0xA000E,
     ModuleNotPresent = 0xA000E,
 };
 };
 
 
+// obtained from
+// https://github.com/skyline-emu/skyline/blob/nvdec-dev/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h#L47
 enum class EventState {
 enum class EventState {
-    Free = 0,
-    Registered = 1,
-    Waiting = 2,
-    Busy = 3,
+    Available = 0,
+    Waiting = 1,
+    Cancelling = 2,
+    Signalling = 3,
+    Signalled = 4,
+    Cancelled = 5,
 };
 };
 
 
 union Ioctl {
 union Ioctl {

+ 84 - 46
src/core/hle/service/nvdrv/nvdrv.cpp

@@ -1,5 +1,6 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
 
 
 #include <utility>
 #include <utility>
 
 
@@ -8,6 +9,7 @@
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/k_event.h"
 #include "core/hle/kernel/k_event.h"
 #include "core/hle/kernel/k_writable_event.h"
 #include "core/hle/kernel/k_writable_event.h"
+#include "core/hle/service/nvdrv/core/container.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 #include "core/hle/service/nvdrv/devices/nvdevice.h"
 #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
 #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
 #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
 #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
@@ -15,17 +17,31 @@
 #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
 #include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
 #include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
 #include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
 #include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
 #include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
 #include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h"
 #include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h"
 #include "core/hle/service/nvdrv/devices/nvhost_vic.h"
 #include "core/hle/service/nvdrv/devices/nvhost_vic.h"
 #include "core/hle/service/nvdrv/devices/nvmap.h"
 #include "core/hle/service/nvdrv/devices/nvmap.h"
 #include "core/hle/service/nvdrv/nvdrv.h"
 #include "core/hle/service/nvdrv/nvdrv.h"
 #include "core/hle/service/nvdrv/nvdrv_interface.h"
 #include "core/hle/service/nvdrv/nvdrv_interface.h"
 #include "core/hle/service/nvdrv/nvmemp.h"
 #include "core/hle/service/nvdrv/nvmemp.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
 #include "core/hle/service/nvflinger/nvflinger.h"
 #include "core/hle/service/nvflinger/nvflinger.h"
+#include "video_core/gpu.h"
 
 
 namespace Service::Nvidia {
 namespace Service::Nvidia {
 
 
+EventInterface::EventInterface(Module& module_) : module{module_}, guard{}, on_signal{} {}
+
+EventInterface::~EventInterface() = default;
+
+Kernel::KEvent* EventInterface::CreateEvent(std::string name) {
+    Kernel::KEvent* new_event = module.service_context.CreateEvent(std::move(name));
+    return new_event;
+}
+
+void EventInterface::FreeEvent(Kernel::KEvent* event) {
+    module.service_context.CloseEvent(event);
+}
+
 void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
 void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
                        Core::System& system) {
                        Core::System& system) {
     auto module_ = std::make_shared<Module>(system);
     auto module_ = std::make_shared<Module>(system);
@@ -38,34 +54,54 @@ void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger
 }
 }
 
 
 Module::Module(Core::System& system)
 Module::Module(Core::System& system)
-    : syncpoint_manager{system.GPU()}, service_context{system, "nvdrv"} {
-    for (u32 i = 0; i < MaxNvEvents; i++) {
-        events_interface.events[i].event =
-            service_context.CreateEvent(fmt::format("NVDRV::NvEvent_{}", i));
-        events_interface.status[i] = EventState::Free;
-        events_interface.registered[i] = false;
-    }
-    auto nvmap_dev = std::make_shared<Devices::nvmap>(system);
-    devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev);
-    devices["/dev/nvhost-gpu"] =
-        std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev, syncpoint_manager);
-    devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system);
-    devices["/dev/nvmap"] = nvmap_dev;
-    devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev);
-    devices["/dev/nvhost-ctrl"] =
-        std::make_shared<Devices::nvhost_ctrl>(system, events_interface, syncpoint_manager);
-    devices["/dev/nvhost-nvdec"] =
-        std::make_shared<Devices::nvhost_nvdec>(system, nvmap_dev, syncpoint_manager);
-    devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system);
-    devices["/dev/nvhost-vic"] =
-        std::make_shared<Devices::nvhost_vic>(system, nvmap_dev, syncpoint_manager);
+    : service_context{system, "nvdrv"}, events_interface{*this}, container{system.Host1x()} {
+    builders["/dev/nvhost-as-gpu"] = [this, &system](DeviceFD fd) {
+        std::shared_ptr<Devices::nvdevice> device =
+            std::make_shared<Devices::nvhost_as_gpu>(system, *this, container);
+        return open_files.emplace(fd, device).first;
+    };
+    builders["/dev/nvhost-gpu"] = [this, &system](DeviceFD fd) {
+        std::shared_ptr<Devices::nvdevice> device =
+            std::make_shared<Devices::nvhost_gpu>(system, events_interface, container);
+        return open_files.emplace(fd, device).first;
+    };
+    builders["/dev/nvhost-ctrl-gpu"] = [this, &system](DeviceFD fd) {
+        std::shared_ptr<Devices::nvdevice> device =
+            std::make_shared<Devices::nvhost_ctrl_gpu>(system, events_interface);
+        return open_files.emplace(fd, device).first;
+    };
+    builders["/dev/nvmap"] = [this, &system](DeviceFD fd) {
+        std::shared_ptr<Devices::nvdevice> device =
+            std::make_shared<Devices::nvmap>(system, container);
+        return open_files.emplace(fd, device).first;
+    };
+    builders["/dev/nvdisp_disp0"] = [this, &system](DeviceFD fd) {
+        std::shared_ptr<Devices::nvdevice> device =
+            std::make_shared<Devices::nvdisp_disp0>(system, container);
+        return open_files.emplace(fd, device).first;
+    };
+    builders["/dev/nvhost-ctrl"] = [this, &system](DeviceFD fd) {
+        std::shared_ptr<Devices::nvdevice> device =
+            std::make_shared<Devices::nvhost_ctrl>(system, events_interface, container);
+        return open_files.emplace(fd, device).first;
+    };
+    builders["/dev/nvhost-nvdec"] = [this, &system](DeviceFD fd) {
+        std::shared_ptr<Devices::nvdevice> device =
+            std::make_shared<Devices::nvhost_nvdec>(system, container);
+        return open_files.emplace(fd, device).first;
+    };
+    builders["/dev/nvhost-nvjpg"] = [this, &system](DeviceFD fd) {
+        std::shared_ptr<Devices::nvdevice> device = std::make_shared<Devices::nvhost_nvjpg>(system);
+        return open_files.emplace(fd, device).first;
+    };
+    builders["/dev/nvhost-vic"] = [this, &system](DeviceFD fd) {
+        std::shared_ptr<Devices::nvdevice> device =
+            std::make_shared<Devices::nvhost_vic>(system, container);
+        return open_files.emplace(fd, device).first;
+    };
 }
 }
 
 
-Module::~Module() {
-    for (u32 i = 0; i < MaxNvEvents; i++) {
-        service_context.CloseEvent(events_interface.events[i].event);
-    }
-}
+Module::~Module() {}
 
 
 NvResult Module::VerifyFD(DeviceFD fd) const {
 NvResult Module::VerifyFD(DeviceFD fd) const {
     if (fd < 0) {
     if (fd < 0) {
@@ -82,18 +118,18 @@ NvResult Module::VerifyFD(DeviceFD fd) const {
 }
 }
 
 
 DeviceFD Module::Open(const std::string& device_name) {
 DeviceFD Module::Open(const std::string& device_name) {
-    if (devices.find(device_name) == devices.end()) {
+    auto it = builders.find(device_name);
+    if (it == builders.end()) {
         LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name);
         LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name);
         return INVALID_NVDRV_FD;
         return INVALID_NVDRV_FD;
     }
     }
 
 
-    auto device = devices[device_name];
     const DeviceFD fd = next_fd++;
     const DeviceFD fd = next_fd++;
+    auto& builder = it->second;
+    auto device = builder(fd)->second;
 
 
     device->OnOpen(fd);
     device->OnOpen(fd);
 
 
-    open_files[fd] = std::move(device);
-
     return fd;
     return fd;
 }
 }
 
 
@@ -168,22 +204,24 @@ NvResult Module::Close(DeviceFD fd) {
     return NvResult::Success;
     return NvResult::Success;
 }
 }
 
 
-void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
-    for (u32 i = 0; i < MaxNvEvents; i++) {
-        if (events_interface.assigned_syncpt[i] == syncpoint_id &&
-            events_interface.assigned_value[i] == value) {
-            events_interface.LiberateEvent(i);
-            events_interface.events[i].event->GetWritableEvent().Signal();
-        }
+NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
+    if (fd < 0) {
+        LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+        return NvResult::InvalidState;
     }
     }
-}
 
 
-Kernel::KReadableEvent& Module::GetEvent(const u32 event_id) {
-    return events_interface.events[event_id].event->GetReadableEvent();
-}
+    const auto itr = open_files.find(fd);
 
 
-Kernel::KWritableEvent& Module::GetEventWriteable(const u32 event_id) {
-    return events_interface.events[event_id].event->GetWritableEvent();
+    if (itr == open_files.end()) {
+        LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+        return NvResult::NotImplemented;
+    }
+
+    event = itr->second->QueryEvent(event_id);
+    if (!event) {
+        return NvResult::BadParameter;
+    }
+    return NvResult::Success;
 }
 }
 
 
 } // namespace Service::Nvidia
 } // namespace Service::Nvidia

+ 39 - 86
src/core/hle/service/nvdrv/nvdrv.h

@@ -1,16 +1,20 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
 
 
 #pragma once
 #pragma once
 
 
+#include <functional>
+#include <list>
 #include <memory>
 #include <memory>
+#include <string>
 #include <unordered_map>
 #include <unordered_map>
 #include <vector>
 #include <vector>
 
 
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "core/hle/service/kernel_helpers.h"
 #include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvdrv/core/container.h"
 #include "core/hle/service/nvdrv/nvdata.h"
 #include "core/hle/service/nvdrv/nvdata.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
 #include "core/hle/service/nvflinger/ui/fence.h"
 #include "core/hle/service/nvflinger/ui/fence.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/service.h"
 
 
@@ -28,81 +32,31 @@ class NVFlinger;
 
 
 namespace Service::Nvidia {
 namespace Service::Nvidia {
 
 
+namespace NvCore {
+class Container;
 class SyncpointManager;
 class SyncpointManager;
+} // namespace NvCore
 
 
 namespace Devices {
 namespace Devices {
 class nvdevice;
 class nvdevice;
-}
+class nvhost_ctrl;
+} // namespace Devices
 
 
-/// Represents an Nvidia event
-struct NvEvent {
-    Kernel::KEvent* event{};
-    NvFence fence{};
-};
+class Module;
 
 
-struct EventInterface {
-    // Mask representing currently busy events
-    u64 events_mask{};
-    // Each kernel event associated to an NV event
-    std::array<NvEvent, MaxNvEvents> events;
-    // The status of the current NVEvent
-    std::array<EventState, MaxNvEvents> status{};
-    // Tells if an NVEvent is registered or not
-    std::array<bool, MaxNvEvents> registered{};
-    // Tells the NVEvent that it has failed.
-    std::array<bool, MaxNvEvents> failed{};
-    // When an NVEvent is waiting on GPU interrupt, this is the sync_point
-    // associated with it.
-    std::array<u32, MaxNvEvents> assigned_syncpt{};
-    // This is the value of the GPU interrupt for which the NVEvent is waiting
-    // for.
-    std::array<u32, MaxNvEvents> assigned_value{};
-    // Constant to denote an unasigned syncpoint.
-    static constexpr u32 unassigned_syncpt = 0xFFFFFFFF;
-    std::optional<u32> GetFreeEvent() const {
-        u64 mask = events_mask;
-        for (u32 i = 0; i < MaxNvEvents; i++) {
-            const bool is_free = (mask & 0x1) == 0;
-            if (is_free) {
-                if (status[i] == EventState::Registered || status[i] == EventState::Free) {
-                    return {i};
-                }
-            }
-            mask = mask >> 1;
-        }
-        return std::nullopt;
-    }
-    void SetEventStatus(const u32 event_id, EventState new_status) {
-        EventState old_status = status[event_id];
-        if (old_status == new_status) {
-            return;
-        }
-        status[event_id] = new_status;
-        if (new_status == EventState::Registered) {
-            registered[event_id] = true;
-        }
-        if (new_status == EventState::Waiting || new_status == EventState::Busy) {
-            events_mask |= (1ULL << event_id);
-        }
-    }
-    void RegisterEvent(const u32 event_id) {
-        registered[event_id] = true;
-        if (status[event_id] == EventState::Free) {
-            status[event_id] = EventState::Registered;
-        }
-    }
-    void UnregisterEvent(const u32 event_id) {
-        registered[event_id] = false;
-        if (status[event_id] == EventState::Registered) {
-            status[event_id] = EventState::Free;
-        }
-    }
-    void LiberateEvent(const u32 event_id) {
-        status[event_id] = registered[event_id] ? EventState::Registered : EventState::Free;
-        events_mask &= ~(1ULL << event_id);
-        assigned_syncpt[event_id] = unassigned_syncpt;
-        assigned_value[event_id] = 0;
-    }
+class EventInterface {
+public:
+    explicit EventInterface(Module& module_);
+    ~EventInterface();
+
+    Kernel::KEvent* CreateEvent(std::string name);
+
+    void FreeEvent(Kernel::KEvent* event);
+
+private:
+    Module& module;
+    std::mutex guard;
+    std::list<Devices::nvhost_ctrl*> on_signal;
 };
 };
 
 
 class Module final {
 class Module final {
@@ -112,9 +66,9 @@ public:
 
 
     /// Returns a pointer to one of the available devices, identified by its name.
     /// Returns a pointer to one of the available devices, identified by its name.
     template <typename T>
     template <typename T>
-    std::shared_ptr<T> GetDevice(const std::string& name) {
-        auto itr = devices.find(name);
-        if (itr == devices.end())
+    std::shared_ptr<T> GetDevice(DeviceFD fd) {
+        auto itr = open_files.find(fd);
+        if (itr == open_files.end())
             return nullptr;
             return nullptr;
         return std::static_pointer_cast<T>(itr->second);
         return std::static_pointer_cast<T>(itr->second);
     }
     }
@@ -137,28 +91,27 @@ public:
     /// Closes a device file descriptor and returns operation success.
     /// Closes a device file descriptor and returns operation success.
     NvResult Close(DeviceFD fd);
     NvResult Close(DeviceFD fd);
 
 
-    void SignalSyncpt(const u32 syncpoint_id, const u32 value);
-
-    Kernel::KReadableEvent& GetEvent(u32 event_id);
-
-    Kernel::KWritableEvent& GetEventWriteable(u32 event_id);
+    NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event);
 
 
 private:
 private:
-    /// Manages syncpoints on the host
-    SyncpointManager syncpoint_manager;
+    friend class EventInterface;
+    friend class Service::NVFlinger::NVFlinger;
 
 
     /// Id to use for the next open file descriptor.
     /// Id to use for the next open file descriptor.
     DeviceFD next_fd = 1;
     DeviceFD next_fd = 1;
 
 
+    using FilesContainerType = std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
     /// Mapping of file descriptors to the devices they reference.
     /// Mapping of file descriptors to the devices they reference.
-    std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>> open_files;
+    FilesContainerType open_files;
 
 
-    /// Mapping of device node names to their implementation.
-    std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices;
+    KernelHelpers::ServiceContext service_context;
 
 
     EventInterface events_interface;
     EventInterface events_interface;
 
 
-    KernelHelpers::ServiceContext service_context;
+    /// Manages syncpoints on the host
+    NvCore::Container container;
+
+    std::unordered_map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
 };
 };
 
 
 /// Registers all NVDRV services with the specified service manager.
 /// Registers all NVDRV services with the specified service manager.

+ 12 - 19
src/core/hle/service/nvdrv/nvdrv_interface.cpp

@@ -1,10 +1,12 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
 
 
 #include <cinttypes>
 #include <cinttypes>
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
 #include "core/hle/kernel/k_readable_event.h"
 #include "core/hle/kernel/k_readable_event.h"
 #include "core/hle/service/nvdrv/nvdata.h"
 #include "core/hle/service/nvdrv/nvdata.h"
 #include "core/hle/service/nvdrv/nvdrv.h"
 #include "core/hle/service/nvdrv/nvdrv.h"
@@ -12,10 +14,6 @@
 
 
 namespace Service::Nvidia {
 namespace Service::Nvidia {
 
 
-void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
-    nvdrv->SignalSyncpt(syncpoint_id, value);
-}
-
 void NVDRV::Open(Kernel::HLERequestContext& ctx) {
 void NVDRV::Open(Kernel::HLERequestContext& ctx) {
     LOG_DEBUG(Service_NVDRV, "called");
     LOG_DEBUG(Service_NVDRV, "called");
     IPC::ResponseBuilder rb{ctx, 4};
     IPC::ResponseBuilder rb{ctx, 4};
@@ -164,8 +162,7 @@ void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
 void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
 void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     IPC::RequestParser rp{ctx};
     const auto fd = rp.Pop<DeviceFD>();
     const auto fd = rp.Pop<DeviceFD>();
-    const auto event_id = rp.Pop<u32>() & 0x00FF;
-    LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id);
+    const auto event_id = rp.Pop<u32>();
 
 
     if (!is_initialized) {
     if (!is_initialized) {
         ServiceError(ctx, NvResult::NotInitialized);
         ServiceError(ctx, NvResult::NotInitialized);
@@ -173,24 +170,20 @@ void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
         return;
         return;
     }
     }
 
 
-    const auto nv_result = nvdrv->VerifyFD(fd);
-    if (nv_result != NvResult::Success) {
-        LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd);
-        ServiceError(ctx, nv_result);
-        return;
-    }
+    Kernel::KEvent* event = nullptr;
+    NvResult result = nvdrv->QueryEvent(fd, event_id, event);
 
 
-    if (event_id < MaxNvEvents) {
+    if (result == NvResult::Success) {
         IPC::ResponseBuilder rb{ctx, 3, 1};
         IPC::ResponseBuilder rb{ctx, 3, 1};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
-        auto& event = nvdrv->GetEvent(event_id);
-        event.Clear();
-        rb.PushCopyObjects(event);
+        auto& readable_event = event->GetReadableEvent();
+        rb.PushCopyObjects(readable_event);
         rb.PushEnum(NvResult::Success);
         rb.PushEnum(NvResult::Success);
     } else {
     } else {
+        LOG_ERROR(Service_NVDRV, "Invalid event request!");
         IPC::ResponseBuilder rb{ctx, 3};
         IPC::ResponseBuilder rb{ctx, 3};
         rb.Push(ResultSuccess);
         rb.Push(ResultSuccess);
-        rb.PushEnum(NvResult::BadParameter);
+        rb.PushEnum(result);
     }
     }
 }
 }
 
 

+ 0 - 2
src/core/hle/service/nvdrv/nvdrv_interface.h

@@ -18,8 +18,6 @@ public:
     explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name);
     explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name);
     ~NVDRV() override;
     ~NVDRV() override;
 
 
-    void SignalGPUInterruptSyncpt(u32 syncpoint_id, u32 value);
-
 private:
 private:
     void Open(Kernel::HLERequestContext& ctx);
     void Open(Kernel::HLERequestContext& ctx);
     void Ioctl1(Kernel::HLERequestContext& ctx);
     void Ioctl1(Kernel::HLERequestContext& ctx);

+ 0 - 38
src/core/hle/service/nvdrv/syncpoint_manager.cpp

@@ -1,38 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/assert.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
-#include "video_core/gpu.h"
-
-namespace Service::Nvidia {
-
-SyncpointManager::SyncpointManager(Tegra::GPU& gpu_) : gpu{gpu_} {}
-
-SyncpointManager::~SyncpointManager() = default;
-
-u32 SyncpointManager::RefreshSyncpoint(u32 syncpoint_id) {
-    syncpoints[syncpoint_id].min = gpu.GetSyncpointValue(syncpoint_id);
-    return GetSyncpointMin(syncpoint_id);
-}
-
-u32 SyncpointManager::AllocateSyncpoint() {
-    for (u32 syncpoint_id = 1; syncpoint_id < MaxSyncPoints; syncpoint_id++) {
-        if (!syncpoints[syncpoint_id].is_allocated) {
-            syncpoints[syncpoint_id].is_allocated = true;
-            return syncpoint_id;
-        }
-    }
-    ASSERT_MSG(false, "No more available syncpoints!");
-    return {};
-}
-
-u32 SyncpointManager::IncreaseSyncpoint(u32 syncpoint_id, u32 value) {
-    for (u32 index = 0; index < value; ++index) {
-        syncpoints[syncpoint_id].max.fetch_add(1, std::memory_order_relaxed);
-    }
-
-    return GetSyncpointMax(syncpoint_id);
-}
-
-} // namespace Service::Nvidia

+ 0 - 84
src/core/hle/service/nvdrv/syncpoint_manager.h

@@ -1,84 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <atomic>
-
-#include "common/common_types.h"
-#include "core/hle/service/nvdrv/nvdata.h"
-
-namespace Tegra {
-class GPU;
-}
-
-namespace Service::Nvidia {
-
-class SyncpointManager final {
-public:
-    explicit SyncpointManager(Tegra::GPU& gpu_);
-    ~SyncpointManager();
-
-    /**
-     * Returns true if the specified syncpoint is expired for the given value.
-     * @param syncpoint_id Syncpoint ID to check.
-     * @param value Value to check against the specified syncpoint.
-     * @returns True if the specified syncpoint is expired for the given value, otherwise False.
-     */
-    bool IsSyncpointExpired(u32 syncpoint_id, u32 value) const {
-        return (GetSyncpointMax(syncpoint_id) - value) >= (GetSyncpointMin(syncpoint_id) - value);
-    }
-
-    /**
-     * Gets the lower bound for the specified syncpoint.
-     * @param syncpoint_id Syncpoint ID to get the lower bound for.
-     * @returns The lower bound for the specified syncpoint.
-     */
-    u32 GetSyncpointMin(u32 syncpoint_id) const {
-        return syncpoints.at(syncpoint_id).min.load(std::memory_order_relaxed);
-    }
-
-    /**
-     * Gets the uper bound for the specified syncpoint.
-     * @param syncpoint_id Syncpoint ID to get the upper bound for.
-     * @returns The upper bound for the specified syncpoint.
-     */
-    u32 GetSyncpointMax(u32 syncpoint_id) const {
-        return syncpoints.at(syncpoint_id).max.load(std::memory_order_relaxed);
-    }
-
-    /**
-     * Refreshes the minimum value for the specified syncpoint.
-     * @param syncpoint_id Syncpoint ID to be refreshed.
-     * @returns The new syncpoint minimum value.
-     */
-    u32 RefreshSyncpoint(u32 syncpoint_id);
-
-    /**
-     * Allocates a new syncoint.
-     * @returns The syncpoint ID for the newly allocated syncpoint.
-     */
-    u32 AllocateSyncpoint();
-
-    /**
-     * Increases the maximum value for the specified syncpoint.
-     * @param syncpoint_id Syncpoint ID to be increased.
-     * @param value Value to increase the specified syncpoint by.
-     * @returns The new syncpoint maximum value.
-     */
-    u32 IncreaseSyncpoint(u32 syncpoint_id, u32 value);
-
-private:
-    struct Syncpoint {
-        std::atomic<u32> min;
-        std::atomic<u32> max;
-        std::atomic<bool> is_allocated;
-    };
-
-    std::array<Syncpoint, MaxSyncPoints> syncpoints{};
-
-    Tegra::GPU& gpu;
-};
-
-} // namespace Service::Nvidia

+ 7 - 2
src/core/hle/service/nvflinger/buffer_queue_consumer.cpp

@@ -5,15 +5,18 @@
 // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
 // https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
 
 
 #include "common/logging/log.h"
 #include "common/logging/log.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
 #include "core/hle/service/nvflinger/buffer_item.h"
 #include "core/hle/service/nvflinger/buffer_item.h"
 #include "core/hle/service/nvflinger/buffer_queue_consumer.h"
 #include "core/hle/service/nvflinger/buffer_queue_consumer.h"
 #include "core/hle/service/nvflinger/buffer_queue_core.h"
 #include "core/hle/service/nvflinger/buffer_queue_core.h"
 #include "core/hle/service/nvflinger/producer_listener.h"
 #include "core/hle/service/nvflinger/producer_listener.h"
+#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
 
 
 namespace Service::android {
 namespace Service::android {
 
 
-BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_)
-    : core{std::move(core_)}, slots{core->slots} {}
+BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
+                                         Service::Nvidia::NvCore::NvMap& nvmap_)
+    : core{std::move(core_)}, slots{core->slots}, nvmap(nvmap_) {}
 
 
 BufferQueueConsumer::~BufferQueueConsumer() = default;
 BufferQueueConsumer::~BufferQueueConsumer() = default;
 
 
@@ -133,6 +136,8 @@ Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fenc
 
 
         slots[slot].buffer_state = BufferState::Free;
         slots[slot].buffer_state = BufferState::Free;
 
 
+        nvmap.FreeHandle(slots[slot].graphic_buffer->BufferId(), true);
+
         listener = core->connected_producer_listener;
         listener = core->connected_producer_listener;
 
 
         LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot);
         LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot);

+ 7 - 1
src/core/hle/service/nvflinger/buffer_queue_consumer.h

@@ -13,6 +13,10 @@
 #include "core/hle/service/nvflinger/buffer_queue_defs.h"
 #include "core/hle/service/nvflinger/buffer_queue_defs.h"
 #include "core/hle/service/nvflinger/status.h"
 #include "core/hle/service/nvflinger/status.h"
 
 
+namespace Service::Nvidia::NvCore {
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
 namespace Service::android {
 namespace Service::android {
 
 
 class BufferItem;
 class BufferItem;
@@ -21,7 +25,8 @@ class IConsumerListener;
 
 
 class BufferQueueConsumer final {
 class BufferQueueConsumer final {
 public:
 public:
-    explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_);
+    explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
+                                 Service::Nvidia::NvCore::NvMap& nvmap_);
     ~BufferQueueConsumer();
     ~BufferQueueConsumer();
 
 
     Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
     Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
@@ -32,6 +37,7 @@ public:
 private:
 private:
     std::shared_ptr<BufferQueueCore> core;
     std::shared_ptr<BufferQueueCore> core;
     BufferQueueDefs::SlotsType& slots;
     BufferQueueDefs::SlotsType& slots;
+    Service::Nvidia::NvCore::NvMap& nvmap;
 };
 };
 
 
 } // namespace Service::android
 } // namespace Service::android

+ 7 - 3
src/core/hle/service/nvflinger/buffer_queue_producer.cpp

@@ -14,7 +14,7 @@
 #include "core/hle/kernel/k_writable_event.h"
 #include "core/hle/kernel/k_writable_event.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/service/kernel_helpers.h"
 #include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
 #include "core/hle/service/nvflinger/buffer_queue_core.h"
 #include "core/hle/service/nvflinger/buffer_queue_core.h"
 #include "core/hle/service/nvflinger/buffer_queue_producer.h"
 #include "core/hle/service/nvflinger/buffer_queue_producer.h"
 #include "core/hle/service/nvflinger/consumer_listener.h"
 #include "core/hle/service/nvflinger/consumer_listener.h"
@@ -26,8 +26,10 @@
 namespace Service::android {
 namespace Service::android {
 
 
 BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
 BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
-                                         std::shared_ptr<BufferQueueCore> buffer_queue_core_)
-    : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots) {
+                                         std::shared_ptr<BufferQueueCore> buffer_queue_core_,
+                                         Service::Nvidia::NvCore::NvMap& nvmap_)
+    : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
+      nvmap(nvmap_) {
     buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
     buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
 }
 }
 
 
@@ -530,6 +532,8 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
         item.is_droppable = core->dequeue_buffer_cannot_block || async;
         item.is_droppable = core->dequeue_buffer_cannot_block || async;
         item.swap_interval = swap_interval;
         item.swap_interval = swap_interval;
 
 
+        nvmap.DuplicateHandle(item.graphic_buffer->BufferId(), true);
+
         sticky_transform = sticky_transform_;
         sticky_transform = sticky_transform_;
 
 
         if (core->queue.empty()) {
         if (core->queue.empty()) {

+ 8 - 1
src/core/hle/service/nvflinger/buffer_queue_producer.h

@@ -31,6 +31,10 @@ namespace Service::KernelHelpers {
 class ServiceContext;
 class ServiceContext;
 } // namespace Service::KernelHelpers
 } // namespace Service::KernelHelpers
 
 
+namespace Service::Nvidia::NvCore {
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
 namespace Service::android {
 namespace Service::android {
 
 
 class BufferQueueCore;
 class BufferQueueCore;
@@ -39,7 +43,8 @@ class IProducerListener;
 class BufferQueueProducer final : public IBinder {
 class BufferQueueProducer final : public IBinder {
 public:
 public:
     explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
     explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
-                                 std::shared_ptr<BufferQueueCore> buffer_queue_core_);
+                                 std::shared_ptr<BufferQueueCore> buffer_queue_core_,
+                                 Service::Nvidia::NvCore::NvMap& nvmap_);
     ~BufferQueueProducer();
     ~BufferQueueProducer();
 
 
     void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override;
     void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override;
@@ -78,6 +83,8 @@ private:
     s32 next_callback_ticket{};
     s32 next_callback_ticket{};
     s32 current_callback_ticket{};
     s32 current_callback_ticket{};
     std::condition_variable_any callback_condition;
     std::condition_variable_any callback_condition;
+
+    Service::Nvidia::NvCore::NvMap& nvmap;
 };
 };
 
 
 } // namespace Service::android
 } // namespace Service::android

+ 15 - 14
src/core/hle/service/nvflinger/nvflinger.cpp

@@ -24,6 +24,8 @@
 #include "core/hle/service/vi/layer/vi_layer.h"
 #include "core/hle/service/vi/layer/vi_layer.h"
 #include "core/hle/service/vi/vi_results.h"
 #include "core/hle/service/vi/vi_results.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/syncpoint_manager.h"
 
 
 namespace Service::NVFlinger {
 namespace Service::NVFlinger {
 
 
@@ -105,10 +107,15 @@ NVFlinger::~NVFlinger() {
             display.GetLayer(layer).Core().NotifyShutdown();
             display.GetLayer(layer).Core().NotifyShutdown();
         }
         }
     }
     }
+
+    if (nvdrv) {
+        nvdrv->Close(disp_fd);
+    }
 }
 }
 
 
 void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
 void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
     nvdrv = std::move(instance);
     nvdrv = std::move(instance);
+    disp_fd = nvdrv->Open("/dev/nvdisp_disp0");
 }
 }
 
 
 std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
 std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
@@ -142,7 +149,7 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
 
 
 void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
 void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
     const auto buffer_id = next_buffer_queue_id++;
     const auto buffer_id = next_buffer_queue_id++;
-    display.CreateLayer(layer_id, buffer_id);
+    display.CreateLayer(layer_id, buffer_id, nvdrv->container);
 }
 }
 
 
 void NVFlinger::CloseLayer(u64 layer_id) {
 void NVFlinger::CloseLayer(u64 layer_id) {
@@ -262,30 +269,24 @@ void NVFlinger::Compose() {
             return; // We are likely shutting down
             return; // We are likely shutting down
         }
         }
 
 
-        auto& gpu = system.GPU();
-        const auto& multi_fence = buffer.fence;
-        guard->unlock();
-        for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) {
-            const auto& fence = multi_fence.fences[fence_id];
-            gpu.WaitFence(fence.id, fence.value);
-        }
-        guard->lock();
-
-        MicroProfileFlip();
-
         // Now send the buffer to the GPU for drawing.
         // Now send the buffer to the GPU for drawing.
         // TODO(Subv): Support more than just disp0. The display device selection is probably based
         // TODO(Subv): Support more than just disp0. The display device selection is probably based
         // on which display we're drawing (Default, Internal, External, etc)
         // on which display we're drawing (Default, Internal, External, etc)
-        auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");
+        auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
         ASSERT(nvdisp);
         ASSERT(nvdisp);
 
 
+        guard->unlock();
         Common::Rectangle<int> crop_rect{
         Common::Rectangle<int> crop_rect{
             static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
             static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
             static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
             static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
 
 
         nvdisp->flip(igbp_buffer.BufferId(), igbp_buffer.Offset(), igbp_buffer.ExternalFormat(),
         nvdisp->flip(igbp_buffer.BufferId(), igbp_buffer.Offset(), igbp_buffer.ExternalFormat(),
                      igbp_buffer.Width(), igbp_buffer.Height(), igbp_buffer.Stride(),
                      igbp_buffer.Width(), igbp_buffer.Height(), igbp_buffer.Stride(),
-                     static_cast<android::BufferTransformFlags>(buffer.transform), crop_rect);
+                     static_cast<android::BufferTransformFlags>(buffer.transform), crop_rect,
+                     buffer.fence.fences, buffer.fence.num_fences);
+
+        MicroProfileFlip();
+        guard->lock();
 
 
         swap_interval = buffer.swap_interval;
         swap_interval = buffer.swap_interval;
 
 

+ 1 - 0
src/core/hle/service/nvflinger/nvflinger.h

@@ -116,6 +116,7 @@ private:
     void SplitVSync(std::stop_token stop_token);
     void SplitVSync(std::stop_token stop_token);
 
 
     std::shared_ptr<Nvidia::Module> nvdrv;
     std::shared_ptr<Nvidia::Module> nvdrv;
+    s32 disp_fd;
 
 
     std::list<VI::Display> displays;
     std::list<VI::Display> displays;
 
 

+ 10 - 6
src/core/hle/service/vi/display/vi_display.cpp

@@ -12,6 +12,7 @@
 #include "core/hle/kernel/k_readable_event.h"
 #include "core/hle/kernel/k_readable_event.h"
 #include "core/hle/kernel/k_writable_event.h"
 #include "core/hle/kernel/k_writable_event.h"
 #include "core/hle/service/kernel_helpers.h"
 #include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvdrv/core/container.h"
 #include "core/hle/service/nvflinger/buffer_item_consumer.h"
 #include "core/hle/service/nvflinger/buffer_item_consumer.h"
 #include "core/hle/service/nvflinger/buffer_queue_consumer.h"
 #include "core/hle/service/nvflinger/buffer_queue_consumer.h"
 #include "core/hle/service/nvflinger/buffer_queue_core.h"
 #include "core/hle/service/nvflinger/buffer_queue_core.h"
@@ -29,11 +30,13 @@ struct BufferQueue {
     std::unique_ptr<android::BufferQueueConsumer> consumer;
     std::unique_ptr<android::BufferQueueConsumer> consumer;
 };
 };
 
 
-static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_context) {
+static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_context,
+                                     Service::Nvidia::NvCore::NvMap& nvmap) {
     auto buffer_queue_core = std::make_shared<android::BufferQueueCore>();
     auto buffer_queue_core = std::make_shared<android::BufferQueueCore>();
-    return {buffer_queue_core,
-            std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core),
-            std::make_unique<android::BufferQueueConsumer>(buffer_queue_core)};
+    return {
+        buffer_queue_core,
+        std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core, nvmap),
+        std::make_unique<android::BufferQueueConsumer>(buffer_queue_core, nvmap)};
 }
 }
 
 
 Display::Display(u64 id, std::string name_,
 Display::Display(u64 id, std::string name_,
@@ -74,10 +77,11 @@ void Display::SignalVSyncEvent() {
     vsync_event->GetWritableEvent().Signal();
     vsync_event->GetWritableEvent().Signal();
 }
 }
 
 
-void Display::CreateLayer(u64 layer_id, u32 binder_id) {
+void Display::CreateLayer(u64 layer_id, u32 binder_id,
+                          Service::Nvidia::NvCore::Container& nv_core) {
     ASSERT_MSG(layers.empty(), "Only one layer is supported per display at the moment");
     ASSERT_MSG(layers.empty(), "Only one layer is supported per display at the moment");
 
 
-    auto [core, producer, consumer] = CreateBufferQueue(service_context);
+    auto [core, producer, consumer] = CreateBufferQueue(service_context, nv_core.GetNvMapFile());
 
 
     auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(consumer));
     auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(consumer));
     buffer_item_consumer->Connect(false);
     buffer_item_consumer->Connect(false);

+ 6 - 1
src/core/hle/service/vi/display/vi_display.h

@@ -27,6 +27,11 @@ namespace Service::NVFlinger {
 class HosBinderDriverServer;
 class HosBinderDriverServer;
 }
 }
 
 
+namespace Service::Nvidia::NvCore {
+class Container;
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
 namespace Service::VI {
 namespace Service::VI {
 
 
 class Layer;
 class Layer;
@@ -93,7 +98,7 @@ public:
     /// @param layer_id The ID to assign to the created layer.
     /// @param layer_id The ID to assign to the created layer.
     /// @param binder_id The ID assigned to the buffer queue.
     /// @param binder_id The ID assigned to the buffer queue.
     ///
     ///
-    void CreateLayer(u64 layer_id, u32 binder_id);
+    void CreateLayer(u64 layer_id, u32 binder_id, Service::Nvidia::NvCore::Container& core);
 
 
     /// Closes and removes a layer from this display with the given ID.
     /// Closes and removes a layer from this display with the given ID.
     ///
     ///

+ 1 - 0
src/core/hle/service/vi/vi.cpp

@@ -58,6 +58,7 @@ static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size");
 class NativeWindow final {
 class NativeWindow final {
 public:
 public:
     constexpr explicit NativeWindow(u32 id_) : id{id_} {}
     constexpr explicit NativeWindow(u32 id_) : id{id_} {}
+    constexpr explicit NativeWindow(const NativeWindow& other) = default;
 
 
 private:
 private:
     const u32 magic = 2;
     const u32 magic = 2;

+ 9 - 0
src/core/memory.cpp

@@ -551,6 +551,11 @@ struct Memory::Impl {
             []() {});
             []() {});
     }
     }
 
 
+    [[nodiscard]] u8* GetPointerSilent(const VAddr vaddr) const {
+        return GetPointerImpl(
+            vaddr, []() {}, []() {});
+    }
+
     /**
     /**
      * Reads a particular data type out of memory at the given virtual address.
      * Reads a particular data type out of memory at the given virtual address.
      *
      *
@@ -686,6 +691,10 @@ u8* Memory::GetPointer(VAddr vaddr) {
     return impl->GetPointer(vaddr);
     return impl->GetPointer(vaddr);
 }
 }
 
 
+u8* Memory::GetPointerSilent(VAddr vaddr) {
+    return impl->GetPointerSilent(vaddr);
+}
+
 const u8* Memory::GetPointer(VAddr vaddr) const {
 const u8* Memory::GetPointer(VAddr vaddr) const {
     return impl->GetPointer(vaddr);
     return impl->GetPointer(vaddr);
 }
 }

+ 1 - 0
src/core/memory.h

@@ -114,6 +114,7 @@ public:
      *          If the address is not valid, nullptr will be returned.
      *          If the address is not valid, nullptr will be returned.
      */
      */
     u8* GetPointer(VAddr vaddr);
     u8* GetPointer(VAddr vaddr);
+    u8* GetPointerSilent(VAddr vaddr);
 
 
     template <typename T>
     template <typename T>
     T* GetPointer(VAddr vaddr) {
     T* GetPointer(VAddr vaddr) {

+ 3 - 3
src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp

@@ -964,9 +964,9 @@ private:
         demote_endif_node.type = Type::EndIf;
         demote_endif_node.type = Type::EndIf;
         demote_endif_node.data.end_if.merge = return_block_it->data.block;
         demote_endif_node.data.end_if.merge = return_block_it->data.block;
 
 
-        asl.insert(return_block_it, demote_endif_node);
-        asl.insert(return_block_it, demote_node);
-        asl.insert(return_block_it, demote_if_node);
+        const auto next_it_1 = asl.insert(return_block_it, demote_endif_node);
+        const auto next_it_2 = asl.insert(next_it_1, demote_node);
+        asl.insert(next_it_2, demote_if_node);
     }
     }
 
 
     ObjectPool<Statement>& stmt_pool;
     ObjectPool<Statement>& stmt_pool;

+ 88 - 10
src/shader_recompiler/ir_opt/texture_pass.cpp

@@ -19,8 +19,10 @@ namespace {
 struct ConstBufferAddr {
 struct ConstBufferAddr {
     u32 index;
     u32 index;
     u32 offset;
     u32 offset;
+    u32 shift_left;
     u32 secondary_index;
     u32 secondary_index;
     u32 secondary_offset;
     u32 secondary_offset;
+    u32 secondary_shift_left;
     IR::U32 dynamic_offset;
     IR::U32 dynamic_offset;
     u32 count;
     u32 count;
     bool has_secondary;
     bool has_secondary;
@@ -172,19 +174,41 @@ bool IsTextureInstruction(const IR::Inst& inst) {
     return IndexedInstruction(inst) != IR::Opcode::Void;
     return IndexedInstruction(inst) != IR::Opcode::Void;
 }
 }
 
 
-std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst);
+std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env);
 
 
-std::optional<ConstBufferAddr> Track(const IR::Value& value) {
-    return IR::BreadthFirstSearch(value, TryGetConstBuffer);
+std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env) {
+    return IR::BreadthFirstSearch(
+        value, [&env](const IR::Inst* inst) { return TryGetConstBuffer(inst, env); });
 }
 }
 
 
-std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
+std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) {
+    const IR::Inst* inst = value.InstRecursive();
+    if (inst->GetOpcode() != IR::Opcode::GetCbufU32) {
+        return std::nullopt;
+    }
+    const IR::Value index{inst->Arg(0)};
+    const IR::Value offset{inst->Arg(1)};
+    if (!index.IsImmediate()) {
+        return std::nullopt;
+    }
+    if (!offset.IsImmediate()) {
+        return std::nullopt;
+    }
+    const auto index_number = index.U32();
+    if (index_number != 1) {
+        return std::nullopt;
+    }
+    const auto offset_number = offset.U32();
+    return env.ReadCbufValue(index_number, offset_number);
+}
+
+std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env) {
     switch (inst->GetOpcode()) {
     switch (inst->GetOpcode()) {
     default:
     default:
         return std::nullopt;
         return std::nullopt;
     case IR::Opcode::BitwiseOr32: {
     case IR::Opcode::BitwiseOr32: {
-        std::optional lhs{Track(inst->Arg(0))};
-        std::optional rhs{Track(inst->Arg(1))};
+        std::optional lhs{Track(inst->Arg(0), env)};
+        std::optional rhs{Track(inst->Arg(1), env)};
         if (!lhs || !rhs) {
         if (!lhs || !rhs) {
             return std::nullopt;
             return std::nullopt;
         }
         }
@@ -194,19 +218,62 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
         if (lhs->count > 1 || rhs->count > 1) {
         if (lhs->count > 1 || rhs->count > 1) {
             return std::nullopt;
             return std::nullopt;
         }
         }
-        if (lhs->index > rhs->index || lhs->offset > rhs->offset) {
+        if (lhs->shift_left > 0 || lhs->index > rhs->index || lhs->offset > rhs->offset) {
             std::swap(lhs, rhs);
             std::swap(lhs, rhs);
         }
         }
         return ConstBufferAddr{
         return ConstBufferAddr{
             .index = lhs->index,
             .index = lhs->index,
             .offset = lhs->offset,
             .offset = lhs->offset,
+            .shift_left = lhs->shift_left,
             .secondary_index = rhs->index,
             .secondary_index = rhs->index,
             .secondary_offset = rhs->offset,
             .secondary_offset = rhs->offset,
+            .secondary_shift_left = rhs->shift_left,
             .dynamic_offset = {},
             .dynamic_offset = {},
             .count = 1,
             .count = 1,
             .has_secondary = true,
             .has_secondary = true,
         };
         };
     }
     }
+    case IR::Opcode::ShiftLeftLogical32: {
+        const IR::Value shift{inst->Arg(1)};
+        if (!shift.IsImmediate()) {
+            return std::nullopt;
+        }
+        std::optional lhs{Track(inst->Arg(0), env)};
+        if (lhs) {
+            lhs->shift_left = shift.U32();
+        }
+        return lhs;
+        break;
+    }
+    case IR::Opcode::BitwiseAnd32: {
+        IR::Value op1{inst->Arg(0)};
+        IR::Value op2{inst->Arg(1)};
+        if (op1.IsImmediate()) {
+            std::swap(op1, op2);
+        }
+        if (!op2.IsImmediate() && !op1.IsImmediate()) {
+            do {
+                auto try_index = TryGetConstant(op1, env);
+                if (try_index) {
+                    op1 = op2;
+                    op2 = IR::Value{*try_index};
+                    break;
+                }
+                auto try_index_2 = TryGetConstant(op2, env);
+                if (try_index_2) {
+                    op2 = IR::Value{*try_index_2};
+                    break;
+                }
+                return std::nullopt;
+            } while (false);
+        }
+        std::optional lhs{Track(op1, env)};
+        if (lhs) {
+            lhs->shift_left = static_cast<u32>(std::countr_zero(op2.U32()));
+        }
+        return lhs;
+        break;
+    }
     case IR::Opcode::GetCbufU32x2:
     case IR::Opcode::GetCbufU32x2:
     case IR::Opcode::GetCbufU32:
     case IR::Opcode::GetCbufU32:
         break;
         break;
@@ -222,8 +289,10 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
         return ConstBufferAddr{
         return ConstBufferAddr{
             .index = index.U32(),
             .index = index.U32(),
             .offset = offset.U32(),
             .offset = offset.U32(),
+            .shift_left = 0,
             .secondary_index = 0,
             .secondary_index = 0,
             .secondary_offset = 0,
             .secondary_offset = 0,
+            .secondary_shift_left = 0,
             .dynamic_offset = {},
             .dynamic_offset = {},
             .count = 1,
             .count = 1,
             .has_secondary = false,
             .has_secondary = false,
@@ -247,8 +316,10 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
     return ConstBufferAddr{
     return ConstBufferAddr{
         .index = index.U32(),
         .index = index.U32(),
         .offset = base_offset,
         .offset = base_offset,
+        .shift_left = 0,
         .secondary_index = 0,
         .secondary_index = 0,
         .secondary_offset = 0,
         .secondary_offset = 0,
+        .secondary_shift_left = 0,
         .dynamic_offset = dynamic_offset,
         .dynamic_offset = dynamic_offset,
         .count = 8,
         .count = 8,
         .has_secondary = false,
         .has_secondary = false,
@@ -258,7 +329,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
 TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
 TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
     ConstBufferAddr addr;
     ConstBufferAddr addr;
     if (IsBindless(inst)) {
     if (IsBindless(inst)) {
-        const std::optional<ConstBufferAddr> track_addr{Track(inst.Arg(0))};
+        const std::optional<ConstBufferAddr> track_addr{Track(inst.Arg(0), env)};
         if (!track_addr) {
         if (!track_addr) {
             throw NotImplementedException("Failed to track bindless texture constant buffer");
             throw NotImplementedException("Failed to track bindless texture constant buffer");
         }
         }
@@ -267,8 +338,10 @@ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
         addr = ConstBufferAddr{
         addr = ConstBufferAddr{
             .index = env.TextureBoundBuffer(),
             .index = env.TextureBoundBuffer(),
             .offset = inst.Arg(0).U32(),
             .offset = inst.Arg(0).U32(),
+            .shift_left = 0,
             .secondary_index = 0,
             .secondary_index = 0,
             .secondary_offset = 0,
             .secondary_offset = 0,
+            .secondary_shift_left = 0,
             .dynamic_offset = {},
             .dynamic_offset = {},
             .count = 1,
             .count = 1,
             .has_secondary = false,
             .has_secondary = false,
@@ -284,8 +357,9 @@ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
 TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
 TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
     const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
     const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
     const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
     const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
-    const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset)};
-    const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)};
+    const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset) << cbuf.shift_left};
+    const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)
+                      << cbuf.secondary_shift_left};
     return env.ReadTextureType(lhs_raw | rhs_raw);
     return env.ReadTextureType(lhs_raw | rhs_raw);
 }
 }
 
 
@@ -487,8 +561,10 @@ void TexturePass(Environment& env, IR::Program& program) {
                     .has_secondary = cbuf.has_secondary,
                     .has_secondary = cbuf.has_secondary,
                     .cbuf_index = cbuf.index,
                     .cbuf_index = cbuf.index,
                     .cbuf_offset = cbuf.offset,
                     .cbuf_offset = cbuf.offset,
+                    .shift_left = cbuf.shift_left,
                     .secondary_cbuf_index = cbuf.secondary_index,
                     .secondary_cbuf_index = cbuf.secondary_index,
                     .secondary_cbuf_offset = cbuf.secondary_offset,
                     .secondary_cbuf_offset = cbuf.secondary_offset,
+                    .secondary_shift_left = cbuf.secondary_shift_left,
                     .count = cbuf.count,
                     .count = cbuf.count,
                     .size_shift = DESCRIPTOR_SIZE_SHIFT,
                     .size_shift = DESCRIPTOR_SIZE_SHIFT,
                 });
                 });
@@ -499,8 +575,10 @@ void TexturePass(Environment& env, IR::Program& program) {
                     .has_secondary = cbuf.has_secondary,
                     .has_secondary = cbuf.has_secondary,
                     .cbuf_index = cbuf.index,
                     .cbuf_index = cbuf.index,
                     .cbuf_offset = cbuf.offset,
                     .cbuf_offset = cbuf.offset,
+                    .shift_left = cbuf.shift_left,
                     .secondary_cbuf_index = cbuf.secondary_index,
                     .secondary_cbuf_index = cbuf.secondary_index,
                     .secondary_cbuf_offset = cbuf.secondary_offset,
                     .secondary_cbuf_offset = cbuf.secondary_offset,
+                    .secondary_shift_left = cbuf.secondary_shift_left,
                     .count = cbuf.count,
                     .count = cbuf.count,
                     .size_shift = DESCRIPTOR_SIZE_SHIFT,
                     .size_shift = DESCRIPTOR_SIZE_SHIFT,
                 });
                 });

+ 4 - 0
src/shader_recompiler/shader_info.h

@@ -61,8 +61,10 @@ struct TextureBufferDescriptor {
     bool has_secondary;
     bool has_secondary;
     u32 cbuf_index;
     u32 cbuf_index;
     u32 cbuf_offset;
     u32 cbuf_offset;
+    u32 shift_left;
     u32 secondary_cbuf_index;
     u32 secondary_cbuf_index;
     u32 secondary_cbuf_offset;
     u32 secondary_cbuf_offset;
+    u32 secondary_shift_left;
     u32 count;
     u32 count;
     u32 size_shift;
     u32 size_shift;
 };
 };
@@ -85,8 +87,10 @@ struct TextureDescriptor {
     bool has_secondary;
     bool has_secondary;
     u32 cbuf_index;
     u32 cbuf_index;
     u32 cbuf_offset;
     u32 cbuf_offset;
+    u32 shift_left;
     u32 secondary_cbuf_index;
     u32 secondary_cbuf_index;
     u32 secondary_cbuf_offset;
     u32 secondary_cbuf_offset;
+    u32 secondary_shift_left;
     u32 count;
     u32 count;
     u32 size_shift;
     u32 size_shift;
 };
 };

+ 32 - 19
src/video_core/CMakeLists.txt

@@ -4,7 +4,7 @@
 add_subdirectory(host_shaders)
 add_subdirectory(host_shaders)
 
 
 if(LIBVA_FOUND)
 if(LIBVA_FOUND)
-    set_source_files_properties(command_classes/codecs/codec.cpp
+    set_source_files_properties(host1x/codecs/codec.cpp
         PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
         PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
     list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
     list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
 endif()
 endif()
@@ -15,26 +15,14 @@ add_library(video_core STATIC
     buffer_cache/buffer_cache.h
     buffer_cache/buffer_cache.h
     cdma_pusher.cpp
     cdma_pusher.cpp
     cdma_pusher.h
     cdma_pusher.h
-    command_classes/codecs/codec.cpp
-    command_classes/codecs/codec.h
-    command_classes/codecs/h264.cpp
-    command_classes/codecs/h264.h
-    command_classes/codecs/vp8.cpp
-    command_classes/codecs/vp8.h
-    command_classes/codecs/vp9.cpp
-    command_classes/codecs/vp9.h
-    command_classes/codecs/vp9_types.h
-    command_classes/host1x.cpp
-    command_classes/host1x.h
-    command_classes/nvdec.cpp
-    command_classes/nvdec.h
-    command_classes/nvdec_common.h
-    command_classes/sync_manager.cpp
-    command_classes/sync_manager.h
-    command_classes/vic.cpp
-    command_classes/vic.h
     compatible_formats.cpp
     compatible_formats.cpp
     compatible_formats.h
     compatible_formats.h
+    control/channel_state.cpp
+    control/channel_state.h
+    control/channel_state_cache.cpp
+    control/channel_state_cache.h
+    control/scheduler.cpp
+    control/scheduler.h
     delayed_destruction_ring.h
     delayed_destruction_ring.h
     dirty_flags.cpp
     dirty_flags.cpp
     dirty_flags.h
     dirty_flags.h
@@ -54,7 +42,31 @@ add_library(video_core STATIC
     engines/maxwell_3d.h
     engines/maxwell_3d.h
     engines/maxwell_dma.cpp
     engines/maxwell_dma.cpp
     engines/maxwell_dma.h
     engines/maxwell_dma.h
+    engines/puller.cpp
+    engines/puller.h
     framebuffer_config.h
     framebuffer_config.h
+    host1x/codecs/codec.cpp
+    host1x/codecs/codec.h
+    host1x/codecs/h264.cpp
+    host1x/codecs/h264.h
+    host1x/codecs/vp8.cpp
+    host1x/codecs/vp8.h
+    host1x/codecs/vp9.cpp
+    host1x/codecs/vp9.h
+    host1x/codecs/vp9_types.h
+    host1x/control.cpp
+    host1x/control.h
+    host1x/host1x.cpp
+    host1x/host1x.h
+    host1x/nvdec.cpp
+    host1x/nvdec.h
+    host1x/nvdec_common.h
+    host1x/sync_manager.cpp
+    host1x/sync_manager.h
+    host1x/syncpoint_manager.cpp
+    host1x/syncpoint_manager.h
+    host1x/vic.cpp
+    host1x/vic.h
     macro/macro.cpp
     macro/macro.cpp
     macro/macro.h
     macro/macro.h
     macro/macro_hle.cpp
     macro/macro_hle.cpp
@@ -195,6 +207,7 @@ add_library(video_core STATIC
     texture_cache/render_targets.h
     texture_cache/render_targets.h
     texture_cache/samples_helper.h
     texture_cache/samples_helper.h
     texture_cache/slot_vector.h
     texture_cache/slot_vector.h
+    texture_cache/texture_cache.cpp
     texture_cache/texture_cache.h
     texture_cache/texture_cache.h
     texture_cache/texture_cache_base.h
     texture_cache/texture_cache_base.h
     texture_cache/types.h
     texture_cache/types.h

+ 84 - 82
src/video_core/buffer_cache/buffer_cache.h

@@ -5,7 +5,6 @@
 
 
 #include <algorithm>
 #include <algorithm>
 #include <array>
 #include <array>
-#include <deque>
 #include <memory>
 #include <memory>
 #include <mutex>
 #include <mutex>
 #include <numeric>
 #include <numeric>
@@ -23,6 +22,7 @@
 #include "common/settings.h"
 #include "common/settings.h"
 #include "core/memory.h"
 #include "core/memory.h"
 #include "video_core/buffer_cache/buffer_base.h"
 #include "video_core/buffer_cache/buffer_base.h"
+#include "video_core/control/channel_state_cache.h"
 #include "video_core/delayed_destruction_ring.h"
 #include "video_core/delayed_destruction_ring.h"
 #include "video_core/dirty_flags.h"
 #include "video_core/dirty_flags.h"
 #include "video_core/engines/kepler_compute.h"
 #include "video_core/engines/kepler_compute.h"
@@ -56,7 +56,7 @@ using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFE
 using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>;
 using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>;
 
 
 template <typename P>
 template <typename P>
-class BufferCache {
+class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
 
 
     // Page size for caching purposes.
     // Page size for caching purposes.
     // This is unrelated to the CPU page size and it can be changed as it seems optimal.
     // This is unrelated to the CPU page size and it can be changed as it seems optimal.
@@ -116,10 +116,7 @@ public:
     static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
     static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
 
 
     explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
     explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
-                         Tegra::Engines::Maxwell3D& maxwell3d_,
-                         Tegra::Engines::KeplerCompute& kepler_compute_,
-                         Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
-                         Runtime& runtime_);
+                         Core::Memory::Memory& cpu_memory_, Runtime& runtime_);
 
 
     void TickFrame();
     void TickFrame();
 
 
@@ -129,7 +126,7 @@ public:
 
 
     void DownloadMemory(VAddr cpu_addr, u64 size);
     void DownloadMemory(VAddr cpu_addr, u64 size);
 
 
-    bool InlineMemory(VAddr dest_address, size_t copy_size, std::span<u8> inlined_buffer);
+    bool InlineMemory(VAddr dest_address, size_t copy_size, std::span<const u8> inlined_buffer);
 
 
     void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size);
     void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size);
 
 
@@ -353,7 +350,7 @@ private:
 
 
     void NotifyBufferDeletion();
     void NotifyBufferDeletion();
 
 
-    [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr) const;
+    [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, bool is_written = false) const;
 
 
     [[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
     [[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
                                                                PixelFormat format);
                                                                PixelFormat format);
@@ -367,9 +364,6 @@ private:
     void ClearDownload(IntervalType subtract_interval);
     void ClearDownload(IntervalType subtract_interval);
 
 
     VideoCore::RasterizerInterface& rasterizer;
     VideoCore::RasterizerInterface& rasterizer;
-    Tegra::Engines::Maxwell3D& maxwell3d;
-    Tegra::Engines::KeplerCompute& kepler_compute;
-    Tegra::MemoryManager& gpu_memory;
     Core::Memory::Memory& cpu_memory;
     Core::Memory::Memory& cpu_memory;
 
 
     SlotVector<Buffer> slot_buffers;
     SlotVector<Buffer> slot_buffers;
@@ -444,12 +438,8 @@ private:
 
 
 template <class P>
 template <class P>
 BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,
 BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,
-                            Tegra::Engines::Maxwell3D& maxwell3d_,
-                            Tegra::Engines::KeplerCompute& kepler_compute_,
-                            Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
-                            Runtime& runtime_)
-    : runtime{runtime_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
-      kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_} {
+                            Core::Memory::Memory& cpu_memory_, Runtime& runtime_)
+    : runtime{runtime_}, rasterizer{rasterizer_}, cpu_memory{cpu_memory_} {
     // Ensure the first slot is used for the null buffer
     // Ensure the first slot is used for the null buffer
     void(slot_buffers.insert(runtime, NullBufferParams{}));
     void(slot_buffers.insert(runtime, NullBufferParams{}));
     common_ranges.clear();
     common_ranges.clear();
@@ -552,8 +542,8 @@ void BufferCache<P>::ClearDownload(IntervalType subtract_interval) {
 
 
 template <class P>
 template <class P>
 bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) {
 bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) {
-    const std::optional<VAddr> cpu_src_address = gpu_memory.GpuToCpuAddress(src_address);
-    const std::optional<VAddr> cpu_dest_address = gpu_memory.GpuToCpuAddress(dest_address);
+    const std::optional<VAddr> cpu_src_address = gpu_memory->GpuToCpuAddress(src_address);
+    const std::optional<VAddr> cpu_dest_address = gpu_memory->GpuToCpuAddress(dest_address);
     if (!cpu_src_address || !cpu_dest_address) {
     if (!cpu_src_address || !cpu_dest_address) {
         return false;
         return false;
     }
     }
@@ -611,7 +601,7 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
 
 
 template <class P>
 template <class P>
 bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
 bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
-    const std::optional<VAddr> cpu_dst_address = gpu_memory.GpuToCpuAddress(dst_address);
+    const std::optional<VAddr> cpu_dst_address = gpu_memory->GpuToCpuAddress(dst_address);
     if (!cpu_dst_address) {
     if (!cpu_dst_address) {
         return false;
         return false;
     }
     }
@@ -635,7 +625,7 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
 template <class P>
 template <class P>
 void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
 void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
                                                u32 size) {
                                                u32 size) {
-    const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+    const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
     const Binding binding{
     const Binding binding{
         .cpu_addr = *cpu_addr,
         .cpu_addr = *cpu_addr,
         .size = size,
         .size = size,
@@ -673,7 +663,7 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
     if (is_indexed) {
     if (is_indexed) {
         BindHostIndexBuffer();
         BindHostIndexBuffer();
     } else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
     } else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
-        const auto& regs = maxwell3d.regs;
+        const auto& regs = maxwell3d->regs;
         if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
         if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
             runtime.BindQuadArrayIndexBuffer(regs.vertex_buffer.first, regs.vertex_buffer.count);
             runtime.BindQuadArrayIndexBuffer(regs.vertex_buffer.first, regs.vertex_buffer.count);
         }
         }
@@ -733,9 +723,9 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
     enabled_storage_buffers[stage] |= 1U << ssbo_index;
     enabled_storage_buffers[stage] |= 1U << ssbo_index;
     written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index;
     written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index;
 
 
-    const auto& cbufs = maxwell3d.state.shader_stages[stage];
+    const auto& cbufs = maxwell3d->state.shader_stages[stage];
     const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
     const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
-    storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr);
+    storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr, is_written);
 }
 }
 
 
 template <class P>
 template <class P>
@@ -770,12 +760,12 @@ void BufferCache<P>::BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index,
     enabled_compute_storage_buffers |= 1U << ssbo_index;
     enabled_compute_storage_buffers |= 1U << ssbo_index;
     written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index;
     written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index;
 
 
-    const auto& launch_desc = kepler_compute.launch_description;
+    const auto& launch_desc = kepler_compute->launch_description;
     ASSERT(((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) != 0);
     ASSERT(((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) != 0);
 
 
     const auto& cbufs = launch_desc.const_buffer_config;
     const auto& cbufs = launch_desc.const_buffer_config;
     const GPUVAddr ssbo_addr = cbufs[cbuf_index].Address() + cbuf_offset;
     const GPUVAddr ssbo_addr = cbufs[cbuf_index].Address() + cbuf_offset;
-    compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr);
+    compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr, is_written);
 }
 }
 
 
 template <class P>
 template <class P>
@@ -836,6 +826,19 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
     const bool is_accuracy_normal =
     const bool is_accuracy_normal =
         Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::Normal;
         Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::Normal;
 
 
+    auto it = committed_ranges.begin();
+    while (it != committed_ranges.end()) {
+        auto& current_intervals = *it;
+        auto next_it = std::next(it);
+        while (next_it != committed_ranges.end()) {
+            for (auto& interval : *next_it) {
+                current_intervals.subtract(interval);
+            }
+            next_it++;
+        }
+        it++;
+    }
+
     boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
     boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
     u64 total_size_bytes = 0;
     u64 total_size_bytes = 0;
     u64 largest_copy = 0;
     u64 largest_copy = 0;
@@ -991,19 +994,19 @@ void BufferCache<P>::BindHostIndexBuffer() {
     const u32 size = index_buffer.size;
     const u32 size = index_buffer.size;
     SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
     SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
     if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
     if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
-        const u32 new_offset = offset + maxwell3d.regs.index_array.first *
-                                            maxwell3d.regs.index_array.FormatSizeInBytes();
+        const u32 new_offset = offset + maxwell3d->regs.index_array.first *
+                                            maxwell3d->regs.index_array.FormatSizeInBytes();
         runtime.BindIndexBuffer(buffer, new_offset, size);
         runtime.BindIndexBuffer(buffer, new_offset, size);
     } else {
     } else {
-        runtime.BindIndexBuffer(maxwell3d.regs.draw.topology, maxwell3d.regs.index_array.format,
-                                maxwell3d.regs.index_array.first, maxwell3d.regs.index_array.count,
-                                buffer, offset, size);
+        runtime.BindIndexBuffer(maxwell3d->regs.draw.topology, maxwell3d->regs.index_array.format,
+                                maxwell3d->regs.index_array.first,
+                                maxwell3d->regs.index_array.count, buffer, offset, size);
     }
     }
 }
 }
 
 
 template <class P>
 template <class P>
 void BufferCache<P>::BindHostVertexBuffers() {
 void BufferCache<P>::BindHostVertexBuffers() {
-    auto& flags = maxwell3d.dirty.flags;
+    auto& flags = maxwell3d->dirty.flags;
     for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
     for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
         const Binding& binding = vertex_buffers[index];
         const Binding& binding = vertex_buffers[index];
         Buffer& buffer = slot_buffers[binding.buffer_id];
         Buffer& buffer = slot_buffers[binding.buffer_id];
@@ -1014,7 +1017,7 @@ void BufferCache<P>::BindHostVertexBuffers() {
         }
         }
         flags[Dirty::VertexBuffer0 + index] = false;
         flags[Dirty::VertexBuffer0 + index] = false;
 
 
-        const u32 stride = maxwell3d.regs.vertex_array[index].stride;
+        const u32 stride = maxwell3d->regs.vertex_array[index].stride;
         const u32 offset = buffer.Offset(binding.cpu_addr);
         const u32 offset = buffer.Offset(binding.cpu_addr);
         runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride);
         runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride);
     }
     }
@@ -1154,7 +1157,7 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
 
 
 template <class P>
 template <class P>
 void BufferCache<P>::BindHostTransformFeedbackBuffers() {
 void BufferCache<P>::BindHostTransformFeedbackBuffers() {
-    if (maxwell3d.regs.tfb_enabled == 0) {
+    if (maxwell3d->regs.tfb_enabled == 0) {
         return;
         return;
     }
     }
     for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
     for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
@@ -1239,16 +1242,19 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
 
 
 template <class P>
 template <class P>
 void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
 void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
-    if (is_indexed) {
-        UpdateIndexBuffer();
-    }
-    UpdateVertexBuffers();
-    UpdateTransformFeedbackBuffers();
-    for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
-        UpdateUniformBuffers(stage);
-        UpdateStorageBuffers(stage);
-        UpdateTextureBuffers(stage);
-    }
+    do {
+        has_deleted_buffers = false;
+        if (is_indexed) {
+            UpdateIndexBuffer();
+        }
+        UpdateVertexBuffers();
+        UpdateTransformFeedbackBuffers();
+        for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
+            UpdateUniformBuffers(stage);
+            UpdateStorageBuffers(stage);
+            UpdateTextureBuffers(stage);
+        }
+    } while (has_deleted_buffers);
 }
 }
 
 
 template <class P>
 template <class P>
@@ -1262,8 +1268,8 @@ template <class P>
 void BufferCache<P>::UpdateIndexBuffer() {
 void BufferCache<P>::UpdateIndexBuffer() {
     // We have to check for the dirty flags and index count
     // We have to check for the dirty flags and index count
     // The index count is currently changed without updating the dirty flags
     // The index count is currently changed without updating the dirty flags
-    const auto& index_array = maxwell3d.regs.index_array;
-    auto& flags = maxwell3d.dirty.flags;
+    const auto& index_array = maxwell3d->regs.index_array;
+    auto& flags = maxwell3d->dirty.flags;
     if (!flags[Dirty::IndexBuffer] && last_index_count == index_array.count) {
     if (!flags[Dirty::IndexBuffer] && last_index_count == index_array.count) {
         return;
         return;
     }
     }
@@ -1272,7 +1278,7 @@ void BufferCache<P>::UpdateIndexBuffer() {
 
 
     const GPUVAddr gpu_addr_begin = index_array.StartAddress();
     const GPUVAddr gpu_addr_begin = index_array.StartAddress();
     const GPUVAddr gpu_addr_end = index_array.EndAddress();
     const GPUVAddr gpu_addr_end = index_array.EndAddress();
-    const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin);
+    const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
     const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
     const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
     const u32 draw_size = (index_array.count + index_array.first) * index_array.FormatSizeInBytes();
     const u32 draw_size = (index_array.count + index_array.first) * index_array.FormatSizeInBytes();
     const u32 size = std::min(address_size, draw_size);
     const u32 size = std::min(address_size, draw_size);
@@ -1289,8 +1295,8 @@ void BufferCache<P>::UpdateIndexBuffer() {
 
 
 template <class P>
 template <class P>
 void BufferCache<P>::UpdateVertexBuffers() {
 void BufferCache<P>::UpdateVertexBuffers() {
-    auto& flags = maxwell3d.dirty.flags;
-    if (!maxwell3d.dirty.flags[Dirty::VertexBuffers]) {
+    auto& flags = maxwell3d->dirty.flags;
+    if (!maxwell3d->dirty.flags[Dirty::VertexBuffers]) {
         return;
         return;
     }
     }
     flags[Dirty::VertexBuffers] = false;
     flags[Dirty::VertexBuffers] = false;
@@ -1302,33 +1308,25 @@ void BufferCache<P>::UpdateVertexBuffers() {
 
 
 template <class P>
 template <class P>
 void BufferCache<P>::UpdateVertexBuffer(u32 index) {
 void BufferCache<P>::UpdateVertexBuffer(u32 index) {
-    if (!maxwell3d.dirty.flags[Dirty::VertexBuffer0 + index]) {
+    if (!maxwell3d->dirty.flags[Dirty::VertexBuffer0 + index]) {
         return;
         return;
     }
     }
-    const auto& array = maxwell3d.regs.vertex_array[index];
-    const auto& limit = maxwell3d.regs.vertex_array_limit[index];
+    const auto& array = maxwell3d->regs.vertex_array[index];
+    const auto& limit = maxwell3d->regs.vertex_array_limit[index];
     const GPUVAddr gpu_addr_begin = array.StartAddress();
     const GPUVAddr gpu_addr_begin = array.StartAddress();
     const GPUVAddr gpu_addr_end = limit.LimitAddress() + 1;
     const GPUVAddr gpu_addr_end = limit.LimitAddress() + 1;
-    const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin);
-    u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
-    if (address_size >= 64_MiB) {
-        // Reported vertex buffer size is very large, cap to mapped buffer size
-        GPUVAddr submapped_addr_end = gpu_addr_begin;
-
-        const auto ranges{gpu_memory.GetSubmappedRange(gpu_addr_begin, address_size)};
-        if (ranges.size() > 0) {
-            const auto& [addr, size] = *ranges.begin();
-            submapped_addr_end = addr + size;
-        }
-
-        address_size =
-            std::min(address_size, static_cast<u32>(submapped_addr_end - gpu_addr_begin));
-    }
-    const u32 size = address_size; // TODO: Analyze stride and number of vertices
-    if (array.enable == 0 || size == 0 || !cpu_addr) {
+    const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
+    u32 address_size = static_cast<u32>(
+        std::min(gpu_addr_end - gpu_addr_begin, static_cast<u64>(std::numeric_limits<u32>::max())));
+    if (array.enable == 0 || address_size == 0 || !cpu_addr) {
         vertex_buffers[index] = NULL_BINDING;
         vertex_buffers[index] = NULL_BINDING;
         return;
         return;
     }
     }
+    if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) {
+        address_size =
+            static_cast<u32>(gpu_memory->MaxContinousRange(gpu_addr_begin, address_size));
+    }
+    const u32 size = address_size; // TODO: Analyze stride and number of vertices
     vertex_buffers[index] = Binding{
     vertex_buffers[index] = Binding{
         .cpu_addr = *cpu_addr,
         .cpu_addr = *cpu_addr,
         .size = size,
         .size = size,
@@ -1382,7 +1380,7 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
 
 
 template <class P>
 template <class P>
 void BufferCache<P>::UpdateTransformFeedbackBuffers() {
 void BufferCache<P>::UpdateTransformFeedbackBuffers() {
-    if (maxwell3d.regs.tfb_enabled == 0) {
+    if (maxwell3d->regs.tfb_enabled == 0) {
         return;
         return;
     }
     }
     for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
     for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
@@ -1392,10 +1390,10 @@ void BufferCache<P>::UpdateTransformFeedbackBuffers() {
 
 
 template <class P>
 template <class P>
 void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
 void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
-    const auto& binding = maxwell3d.regs.tfb_bindings[index];
+    const auto& binding = maxwell3d->regs.tfb_bindings[index];
     const GPUVAddr gpu_addr = binding.Address() + binding.buffer_offset;
     const GPUVAddr gpu_addr = binding.Address() + binding.buffer_offset;
     const u32 size = binding.buffer_size;
     const u32 size = binding.buffer_size;
-    const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+    const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
     if (binding.buffer_enable == 0 || size == 0 || !cpu_addr) {
     if (binding.buffer_enable == 0 || size == 0 || !cpu_addr) {
         transform_feedback_buffers[index] = NULL_BINDING;
         transform_feedback_buffers[index] = NULL_BINDING;
         return;
         return;
@@ -1414,10 +1412,10 @@ void BufferCache<P>::UpdateComputeUniformBuffers() {
     ForEachEnabledBit(enabled_compute_uniform_buffer_mask, [&](u32 index) {
     ForEachEnabledBit(enabled_compute_uniform_buffer_mask, [&](u32 index) {
         Binding& binding = compute_uniform_buffers[index];
         Binding& binding = compute_uniform_buffers[index];
         binding = NULL_BINDING;
         binding = NULL_BINDING;
-        const auto& launch_desc = kepler_compute.launch_description;
+        const auto& launch_desc = kepler_compute->launch_description;
         if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) {
         if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) {
             const auto& cbuf = launch_desc.const_buffer_config[index];
             const auto& cbuf = launch_desc.const_buffer_config[index];
-            const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(cbuf.Address());
+            const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(cbuf.Address());
             if (cpu_addr) {
             if (cpu_addr) {
                 binding.cpu_addr = *cpu_addr;
                 binding.cpu_addr = *cpu_addr;
                 binding.size = cbuf.size;
                 binding.size = cbuf.size;
@@ -1567,6 +1565,8 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
     const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size);
     const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size);
     const u32 size = static_cast<u32>(overlap.end - overlap.begin);
     const u32 size = static_cast<u32>(overlap.end - overlap.begin);
     const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
     const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
+    auto& new_buffer = slot_buffers[new_buffer_id];
+    runtime.ClearBuffer(new_buffer, 0, new_buffer.SizeBytes(), 0);
     for (const BufferId overlap_id : overlap.ids) {
     for (const BufferId overlap_id : overlap.ids) {
         JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
         JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
     }
     }
@@ -1695,7 +1695,7 @@ void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes,
 
 
 template <class P>
 template <class P>
 bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
 bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
-                                  std::span<u8> inlined_buffer) {
+                                  std::span<const u8> inlined_buffer) {
     const bool is_dirty = IsRegionRegistered(dest_address, copy_size);
     const bool is_dirty = IsRegionRegistered(dest_address, copy_size);
     if (!is_dirty) {
     if (!is_dirty) {
         return false;
         return false;
@@ -1831,7 +1831,7 @@ void BufferCache<P>::NotifyBufferDeletion() {
         dirty_uniform_buffers.fill(~u32{0});
         dirty_uniform_buffers.fill(~u32{0});
         uniform_buffer_binding_sizes.fill({});
         uniform_buffer_binding_sizes.fill({});
     }
     }
-    auto& flags = maxwell3d.dirty.flags;
+    auto& flags = maxwell3d->dirty.flags;
     flags[Dirty::IndexBuffer] = true;
     flags[Dirty::IndexBuffer] = true;
     flags[Dirty::VertexBuffers] = true;
     flags[Dirty::VertexBuffers] = true;
     for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
     for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
@@ -1841,16 +1841,18 @@ void BufferCache<P>::NotifyBufferDeletion() {
 }
 }
 
 
 template <class P>
 template <class P>
-typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr) const {
-    const GPUVAddr gpu_addr = gpu_memory.Read<u64>(ssbo_addr);
-    const u32 size = gpu_memory.Read<u32>(ssbo_addr + 8);
-    const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr,
+                                                                      bool is_written) const {
+    const GPUVAddr gpu_addr = gpu_memory->Read<u64>(ssbo_addr);
+    const u32 size = gpu_memory->Read<u32>(ssbo_addr + 8);
+    const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
     if (!cpu_addr || size == 0) {
     if (!cpu_addr || size == 0) {
         return NULL_BINDING;
         return NULL_BINDING;
     }
     }
+    const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, Core::Memory::YUZU_PAGESIZE);
     const Binding binding{
     const Binding binding{
         .cpu_addr = *cpu_addr,
         .cpu_addr = *cpu_addr,
-        .size = size,
+        .size = is_written ? size : static_cast<u32>(cpu_end - *cpu_addr),
         .buffer_id = BufferId{},
         .buffer_id = BufferId{},
     };
     };
     return binding;
     return binding;
@@ -1859,7 +1861,7 @@ typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr s
 template <class P>
 template <class P>
 typename BufferCache<P>::TextureBufferBinding BufferCache<P>::GetTextureBufferBinding(
 typename BufferCache<P>::TextureBufferBinding BufferCache<P>::GetTextureBufferBinding(
     GPUVAddr gpu_addr, u32 size, PixelFormat format) {
     GPUVAddr gpu_addr, u32 size, PixelFormat format) {
-    const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+    const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
     TextureBufferBinding binding;
     TextureBufferBinding binding;
     if (!cpu_addr || size == 0) {
     if (!cpu_addr || size == 0) {
         binding.cpu_addr = 0;
         binding.cpu_addr = 0;

+ 16 - 13
src/video_core/cdma_pusher.cpp

@@ -2,20 +2,22 @@
 // SPDX-License-Identifier: MIT
 // SPDX-License-Identifier: MIT
 
 
 #include <bit>
 #include <bit>
-#include "command_classes/host1x.h"
-#include "command_classes/nvdec.h"
-#include "command_classes/vic.h"
 #include "video_core/cdma_pusher.h"
 #include "video_core/cdma_pusher.h"
-#include "video_core/command_classes/sync_manager.h"
 #include "video_core/engines/maxwell_3d.h"
 #include "video_core/engines/maxwell_3d.h"
-#include "video_core/gpu.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 {
 namespace Tegra {
-CDmaPusher::CDmaPusher(GPU& gpu_)
-    : gpu{gpu_}, nvdec_processor(std::make_shared<Nvdec>(gpu)),
-      vic_processor(std::make_unique<Vic>(gpu, nvdec_processor)),
-      host1x_processor(std::make_unique<Host1x>(gpu)),
-      sync_manager(std::make_unique<SyncptIncrManager>(gpu)) {}
+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;
 CDmaPusher::~CDmaPusher() = default;
 
 
@@ -109,16 +111,17 @@ void CDmaPusher::ExecuteCommand(u32 state_offset, u32 data) {
         case ThiMethod::SetMethod1:
         case ThiMethod::SetMethod1:
             LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})",
             LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})",
                       static_cast<u32>(vic_thi_state.method_0), data);
                       static_cast<u32>(vic_thi_state.method_0), data);
-            vic_processor->ProcessMethod(static_cast<Vic::Method>(vic_thi_state.method_0), data);
+            vic_processor->ProcessMethod(static_cast<Host1x::Vic::Method>(vic_thi_state.method_0),
+                                         data);
             break;
             break;
         default:
         default:
             break;
             break;
         }
         }
         break;
         break;
-    case ChClassId::Host1x:
+    case ChClassId::Control:
         // This device is mainly for syncpoint synchronization
         // This device is mainly for syncpoint synchronization
         LOG_DEBUG(Service_NVDRV, "Host1X Class Method");
         LOG_DEBUG(Service_NVDRV, "Host1X Class Method");
-        host1x_processor->ProcessMethod(static_cast<Host1x::Method>(offset), data);
+        host1x_processor->ProcessMethod(static_cast<Host1x::Control::Method>(offset), data);
         break;
         break;
     default:
     default:
         UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class));
         UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class));

+ 10 - 8
src/video_core/cdma_pusher.h

@@ -12,11 +12,13 @@
 
 
 namespace Tegra {
 namespace Tegra {
 
 
-class GPU;
+namespace Host1x {
+class Control;
 class Host1x;
 class Host1x;
 class Nvdec;
 class Nvdec;
 class SyncptIncrManager;
 class SyncptIncrManager;
 class Vic;
 class Vic;
+} // namespace Host1x
 
 
 enum class ChSubmissionMode : u32 {
 enum class ChSubmissionMode : u32 {
     SetClass = 0,
     SetClass = 0,
@@ -30,7 +32,7 @@ enum class ChSubmissionMode : u32 {
 
 
 enum class ChClassId : u32 {
 enum class ChClassId : u32 {
     NoClass = 0x0,
     NoClass = 0x0,
-    Host1x = 0x1,
+    Control = 0x1,
     VideoEncodeMpeg = 0x20,
     VideoEncodeMpeg = 0x20,
     VideoEncodeNvEnc = 0x21,
     VideoEncodeNvEnc = 0x21,
     VideoStreamingVi = 0x30,
     VideoStreamingVi = 0x30,
@@ -88,7 +90,7 @@ enum class ThiMethod : u32 {
 
 
 class CDmaPusher {
 class CDmaPusher {
 public:
 public:
-    explicit CDmaPusher(GPU& gpu_);
+    explicit CDmaPusher(Host1x::Host1x& host1x);
     ~CDmaPusher();
     ~CDmaPusher();
 
 
     /// Process the command entry
     /// Process the command entry
@@ -101,11 +103,11 @@ private:
     /// Write arguments value to the ThiRegisters member at the specified offset
     /// Write arguments value to the ThiRegisters member at the specified offset
     void ThiStateWrite(ThiRegisters& state, u32 offset, u32 argument);
     void ThiStateWrite(ThiRegisters& state, u32 offset, u32 argument);
 
 
-    GPU& gpu;
-    std::shared_ptr<Tegra::Nvdec> nvdec_processor;
-    std::unique_ptr<Tegra::Vic> vic_processor;
-    std::unique_ptr<Tegra::Host1x> host1x_processor;
-    std::unique_ptr<SyncptIncrManager> sync_manager;
+    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{};
     ChClassId current_class{};
     ThiRegisters vic_thi_state{};
     ThiRegisters vic_thi_state{};
     ThiRegisters nvdec_thi_state{};
     ThiRegisters nvdec_thi_state{};

+ 0 - 29
src/video_core/command_classes/host1x.cpp

@@ -1,29 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/assert.h"
-#include "video_core/command_classes/host1x.h"
-#include "video_core/gpu.h"
-
-Tegra::Host1x::Host1x(GPU& gpu_) : gpu(gpu_) {}
-
-Tegra::Host1x::~Host1x() = default;
-
-void Tegra::Host1x::ProcessMethod(Method method, u32 argument) {
-    switch (method) {
-    case Method::LoadSyncptPayload32:
-        syncpoint_value = argument;
-        break;
-    case Method::WaitSyncpt:
-    case Method::WaitSyncpt32:
-        Execute(argument);
-        break;
-    default:
-        UNIMPLEMENTED_MSG("Host1x method 0x{:X}", static_cast<u32>(method));
-        break;
-    }
-}
-
-void Tegra::Host1x::Execute(u32 data) {
-    gpu.WaitFence(data, syncpoint_value);
-}

+ 40 - 0
src/video_core/control/channel_state.cpp

@@ -0,0 +1,40 @@
+// 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) {
+    ASSERT(memory_manager);
+    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>();
+    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

+ 68 - 0
src/video_core/control/channel_state.h

@@ -0,0 +1,68 @@
+// 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);
+
+    void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
+
+    s32 bind_id = -1;
+    /// 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

+ 14 - 0
src/video_core/control/channel_state_cache.cpp

@@ -0,0 +1,14 @@
+// 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} {}
+
+template class VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo>;
+
+} // namespace VideoCommon

+ 101 - 0
src/video_core/control/channel_state_cache.h

@@ -0,0 +1,101 @@
+// 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;
+    ChannelInfo(ChannelInfo&& other) = default;
+    ChannelInfo& operator=(ChannelInfo&& other) = default;
+
+    Tegra::Engines::Maxwell3D& maxwell3d;
+    Tegra::Engines::KeplerCompute& kepler_compute;
+    Tegra::MemoryManager& gpu_memory;
+};
+
+template <class P>
+class ChannelSetupCaches {
+public:
+    /// Operations for seting the channel of execution.
+    virtual ~ChannelSetupCaches();
+
+    /// Create channel state.
+    virtual void CreateChannel(Tegra::Control::ChannelState& channel);
+
+    /// Bind a channel for execution.
+    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;
+
+    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 AddresSpaceRef {
+        size_t ref_count;
+        size_t storage_id;
+        Tegra::MemoryManager* gpu_memory;
+    };
+    std::unordered_map<size_t, AddresSpaceRef> address_spaces;
+    mutable std::mutex config_mutex;
+
+    virtual void OnGPUASRegister([[maybe_unused]] size_t map_id) {}
+};
+
+} // namespace VideoCommon

+ 86 - 0
src/video_core/control/channel_state_cache.inc

@@ -0,0 +1,86 @@
+// 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;
+    }
+    AddresSpaceRef 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;
+    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;
+    } 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

+ 32 - 0
src/video_core/control/scheduler.cpp

@@ -0,0 +1,32 @@
+// 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

+ 37 - 0
src/video_core/control/scheduler.h

@@ -0,0 +1,37 @@
+// 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

+ 15 - 11
src/video_core/dma_pusher.cpp

@@ -12,7 +12,10 @@
 
 
 namespace Tegra {
 namespace Tegra {
 
 
-DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_) : gpu{gpu_}, system{system_} {}
+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;
 DmaPusher::~DmaPusher() = default;
 
 
@@ -21,8 +24,6 @@ MICROPROFILE_DEFINE(DispatchCalls, "GPU", "Execute command buffer", MP_RGB(128,
 void DmaPusher::DispatchCalls() {
 void DmaPusher::DispatchCalls() {
     MICROPROFILE_SCOPE(DispatchCalls);
     MICROPROFILE_SCOPE(DispatchCalls);
 
 
-    gpu.SyncGuestHost();
-
     dma_pushbuffer_subindex = 0;
     dma_pushbuffer_subindex = 0;
 
 
     dma_state.is_last_call = true;
     dma_state.is_last_call = true;
@@ -33,7 +34,6 @@ void DmaPusher::DispatchCalls() {
         }
         }
     }
     }
     gpu.FlushCommands();
     gpu.FlushCommands();
-    gpu.SyncGuestHost();
     gpu.OnCommandListEnd();
     gpu.OnCommandListEnd();
 }
 }
 
 
@@ -76,11 +76,11 @@ bool DmaPusher::Step() {
         // Push buffer non-empty, read a word
         // Push buffer non-empty, read a word
         command_headers.resize(command_list_header.size);
         command_headers.resize(command_list_header.size);
         if (Settings::IsGPULevelHigh()) {
         if (Settings::IsGPULevelHigh()) {
-            gpu.MemoryManager().ReadBlock(dma_get, command_headers.data(),
-                                          command_list_header.size * sizeof(u32));
+            memory_manager.ReadBlock(dma_get, command_headers.data(),
+                                     command_list_header.size * sizeof(u32));
         } else {
         } else {
-            gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(),
-                                                command_list_header.size * sizeof(u32));
+            memory_manager.ReadBlockUnsafe(dma_get, command_headers.data(),
+                                           command_list_header.size * sizeof(u32));
         }
         }
     }
     }
     for (std::size_t index = 0; index < command_headers.size();) {
     for (std::size_t index = 0; index < command_headers.size();) {
@@ -154,7 +154,7 @@ void DmaPusher::SetState(const CommandHeader& command_header) {
 
 
 void DmaPusher::CallMethod(u32 argument) const {
 void DmaPusher::CallMethod(u32 argument) const {
     if (dma_state.method < non_puller_methods) {
     if (dma_state.method < non_puller_methods) {
-        gpu.CallMethod(GPU::MethodCall{
+        puller.CallPullerMethod(Engines::Puller::MethodCall{
             dma_state.method,
             dma_state.method,
             argument,
             argument,
             dma_state.subchannel,
             dma_state.subchannel,
@@ -168,12 +168,16 @@ void DmaPusher::CallMethod(u32 argument) const {
 
 
 void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const {
 void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const {
     if (dma_state.method < non_puller_methods) {
     if (dma_state.method < non_puller_methods) {
-        gpu.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods,
-                            dma_state.method_count);
+        puller.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods,
+                               dma_state.method_count);
     } else {
     } else {
         subchannels[dma_state.subchannel]->CallMultiMethod(dma_state.method, base_start,
         subchannels[dma_state.subchannel]->CallMultiMethod(dma_state.method, base_start,
                                                            num_methods, dma_state.method_count);
                                                            num_methods, dma_state.method_count);
     }
     }
 }
 }
 
 
+void DmaPusher::BindRasterizer(VideoCore::RasterizerInterface* rasterizer) {
+    puller.BindRasterizer(rasterizer);
+}
+
 } // namespace Tegra
 } // namespace Tegra

+ 29 - 10
src/video_core/dma_pusher.h

@@ -10,6 +10,7 @@
 #include "common/bit_field.h"
 #include "common/bit_field.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "video_core/engines/engine_interface.h"
 #include "video_core/engines/engine_interface.h"
+#include "video_core/engines/puller.h"
 
 
 namespace Core {
 namespace Core {
 class System;
 class System;
@@ -17,7 +18,12 @@ class System;
 
 
 namespace Tegra {
 namespace Tegra {
 
 
+namespace Control {
+struct ChannelState;
+}
+
 class GPU;
 class GPU;
+class MemoryManager;
 
 
 enum class SubmissionMode : u32 {
 enum class SubmissionMode : u32 {
     IncreasingOld = 0,
     IncreasingOld = 0,
@@ -31,24 +37,32 @@ enum class SubmissionMode : u32 {
 // Note that, traditionally, methods are treated as 4-byte addressable locations, and hence
 // 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.
 // 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.
 // 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 {
 enum class BufferMethods : u32 {
     BindObject = 0x0,
     BindObject = 0x0,
+    Illegal = 0x1,
     Nop = 0x2,
     Nop = 0x2,
     SemaphoreAddressHigh = 0x4,
     SemaphoreAddressHigh = 0x4,
     SemaphoreAddressLow = 0x5,
     SemaphoreAddressLow = 0x5,
-    SemaphoreSequence = 0x6,
-    SemaphoreTrigger = 0x7,
-    NotifyIntr = 0x8,
+    SemaphoreSequencePayload = 0x6,
+    SemaphoreOperation = 0x7,
+    NonStallInterrupt = 0x8,
     WrcacheFlush = 0x9,
     WrcacheFlush = 0x9,
-    Unk28 = 0xA,
-    UnkCacheFlush = 0xB,
+    MemOpA = 0xA,
+    MemOpB = 0xB,
+    MemOpC = 0xC,
+    MemOpD = 0xD,
     RefCnt = 0x14,
     RefCnt = 0x14,
     SemaphoreAcquire = 0x1A,
     SemaphoreAcquire = 0x1A,
     SemaphoreRelease = 0x1B,
     SemaphoreRelease = 0x1B,
-    FenceValue = 0x1C,
-    FenceAction = 0x1D,
-    WaitForInterrupt = 0x1E,
-    Unk7c = 0x1F,
+    SyncpointPayload = 0x1C,
+    SyncpointOperation = 0x1D,
+    WaitForIdle = 0x1E,
+    CRCCheck = 0x1F,
     Yield = 0x20,
     Yield = 0x20,
     NonPullerMethods = 0x40,
     NonPullerMethods = 0x40,
 };
 };
@@ -102,7 +116,8 @@ struct CommandList final {
  */
  */
 class DmaPusher final {
 class DmaPusher final {
 public:
 public:
-    explicit DmaPusher(Core::System& system_, GPU& gpu_);
+    explicit DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
+                       Control::ChannelState& channel_state_);
     ~DmaPusher();
     ~DmaPusher();
 
 
     void Push(CommandList&& entries) {
     void Push(CommandList&& entries) {
@@ -115,6 +130,8 @@ public:
         subchannels[subchannel_id] = engine;
         subchannels[subchannel_id] = engine;
     }
     }
 
 
+    void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
+
 private:
 private:
     static constexpr u32 non_puller_methods = 0x40;
     static constexpr u32 non_puller_methods = 0x40;
     static constexpr u32 max_subchannels = 8;
     static constexpr u32 max_subchannels = 8;
@@ -148,6 +165,8 @@ private:
 
 
     GPU& gpu;
     GPU& gpu;
     Core::System& system;
     Core::System& system;
+    MemoryManager& memory_manager;
+    mutable Engines::Puller puller;
 };
 };
 
 
 } // namespace Tegra
 } // namespace Tegra

+ 37 - 9
src/video_core/engines/engine_upload.cpp

@@ -3,6 +3,7 @@
 
 
 #include <cstring>
 #include <cstring>
 
 
+#include "common/algorithm.h"
 #include "common/assert.h"
 #include "common/assert.h"
 #include "video_core/engines/engine_upload.h"
 #include "video_core/engines/engine_upload.h"
 #include "video_core/memory_manager.h"
 #include "video_core/memory_manager.h"
@@ -34,21 +35,48 @@ void State::ProcessData(const u32 data, const bool is_last_call) {
     if (!is_last_call) {
     if (!is_last_call) {
         return;
         return;
     }
     }
+    ProcessData(inner_buffer);
+}
+
+void State::ProcessData(const u32* data, size_t num_data) {
+    std::span<const u8> read_buffer(reinterpret_cast<const u8*>(data), num_data * sizeof(u32));
+    ProcessData(read_buffer);
+}
+
+void State::ProcessData(std::span<const u8> read_buffer) {
     const GPUVAddr address{regs.dest.Address()};
     const GPUVAddr address{regs.dest.Address()};
     if (is_linear) {
     if (is_linear) {
-        rasterizer->AccelerateInlineToMemory(address, copy_size, inner_buffer);
+        if (regs.line_count == 1) {
+            rasterizer->AccelerateInlineToMemory(address, copy_size, read_buffer);
+        } else {
+            for (u32 line = 0; line < regs.line_count; ++line) {
+                const GPUVAddr dest_line = address + static_cast<size_t>(line) * regs.dest.pitch;
+                memory_manager.WriteBlockUnsafe(
+                    dest_line, read_buffer.data() + static_cast<size_t>(line) * regs.line_length_in,
+                    regs.line_length_in);
+            }
+            memory_manager.InvalidateRegion(address, regs.dest.pitch * regs.line_count);
+        }
     } else {
     } else {
-        UNIMPLEMENTED_IF(regs.dest.z != 0);
-        UNIMPLEMENTED_IF(regs.dest.depth != 1);
-        UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 0);
-        UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 0);
+        u32 width = regs.dest.width;
+        u32 x_elements = regs.line_length_in;
+        u32 x_offset = regs.dest.x;
+        const u32 bpp_shift = Common::FoldRight(
+            4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
+            width, x_elements, x_offset, static_cast<u32>(address));
+        width >>= bpp_shift;
+        x_elements >>= bpp_shift;
+        x_offset >>= bpp_shift;
+        const u32 bytes_per_pixel = 1U << bpp_shift;
         const std::size_t dst_size = Tegra::Texture::CalculateSize(
         const std::size_t dst_size = Tegra::Texture::CalculateSize(
-            true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 0);
+            true, bytes_per_pixel, width, regs.dest.height, regs.dest.depth,
+            regs.dest.BlockHeight(), regs.dest.BlockDepth());
         tmp_buffer.resize(dst_size);
         tmp_buffer.resize(dst_size);
         memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
         memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
-        Tegra::Texture::SwizzleKepler(regs.dest.width, regs.dest.height, regs.dest.x, regs.dest.y,
-                                      regs.dest.BlockHeight(), copy_size, inner_buffer.data(),
-                                      tmp_buffer.data());
+        Tegra::Texture::SwizzleSubrect(tmp_buffer, read_buffer, bytes_per_pixel, width,
+                                       regs.dest.height, regs.dest.depth, x_offset, regs.dest.y,
+                                       x_elements, regs.line_count, regs.dest.BlockHeight(),
+                                       regs.dest.BlockDepth(), regs.line_length_in);
         memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size);
         memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size);
     }
     }
 }
 }

+ 5 - 1
src/video_core/engines/engine_upload.h

@@ -3,6 +3,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <span>
 #include <vector>
 #include <vector>
 #include "common/bit_field.h"
 #include "common/bit_field.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
@@ -33,7 +34,7 @@ struct Registers {
         u32 width;
         u32 width;
         u32 height;
         u32 height;
         u32 depth;
         u32 depth;
-        u32 z;
+        u32 layer;
         u32 x;
         u32 x;
         u32 y;
         u32 y;
 
 
@@ -62,11 +63,14 @@ public:
 
 
     void ProcessExec(bool is_linear_);
     void ProcessExec(bool is_linear_);
     void ProcessData(u32 data, bool is_last_call);
     void ProcessData(u32 data, bool is_last_call);
+    void ProcessData(const u32* data, size_t num_data);
 
 
     /// Binds a rasterizer to this engine.
     /// Binds a rasterizer to this engine.
     void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
     void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
 
 
 private:
 private:
+    void ProcessData(std::span<const u8> read_buffer);
+
     u32 write_offset = 0;
     u32 write_offset = 0;
     u32 copy_size = 0;
     u32 copy_size = 0;
     std::vector<u8> inner_buffer;
     std::vector<u8> inner_buffer;

+ 9 - 4
src/video_core/engines/kepler_compute.cpp

@@ -36,8 +36,6 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
     }
     }
     case KEPLER_COMPUTE_REG_INDEX(data_upload): {
     case KEPLER_COMPUTE_REG_INDEX(data_upload): {
         upload_state.ProcessData(method_argument, is_last_call);
         upload_state.ProcessData(method_argument, is_last_call);
-        if (is_last_call) {
-        }
         break;
         break;
     }
     }
     case KEPLER_COMPUTE_REG_INDEX(launch):
     case KEPLER_COMPUTE_REG_INDEX(launch):
@@ -50,8 +48,15 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
 
 
 void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
 void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
                                     u32 methods_pending) {
                                     u32 methods_pending) {
-    for (std::size_t i = 0; i < amount; i++) {
-        CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+    switch (method) {
+    case KEPLER_COMPUTE_REG_INDEX(data_upload):
+        upload_state.ProcessData(base_start, static_cast<size_t>(amount));
+        return;
+    default:
+        for (std::size_t i = 0; i < amount; i++) {
+            CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+        }
+        break;
     }
     }
 }
 }
 
 

+ 9 - 4
src/video_core/engines/kepler_memory.cpp

@@ -33,8 +33,6 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call
     }
     }
     case KEPLERMEMORY_REG_INDEX(data): {
     case KEPLERMEMORY_REG_INDEX(data): {
         upload_state.ProcessData(method_argument, is_last_call);
         upload_state.ProcessData(method_argument, is_last_call);
-        if (is_last_call) {
-        }
         break;
         break;
     }
     }
     }
     }
@@ -42,8 +40,15 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call
 
 
 void KeplerMemory::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
 void KeplerMemory::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
                                    u32 methods_pending) {
                                    u32 methods_pending) {
-    for (std::size_t i = 0; i < amount; i++) {
-        CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+    switch (method) {
+    case KEPLERMEMORY_REG_INDEX(data):
+        upload_state.ProcessData(base_start, static_cast<size_t>(amount));
+        return;
+    default:
+        for (std::size_t i = 0; i < amount; i++) {
+            CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+        }
+        break;
     }
     }
 }
 }
 
 

+ 28 - 15
src/video_core/engines/maxwell_3d.cpp

@@ -219,6 +219,8 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
         regs.index_array.count = regs.small_index_2.count;
         regs.index_array.count = regs.small_index_2.count;
         regs.index_array.first = regs.small_index_2.first;
         regs.index_array.first = regs.small_index_2.first;
         dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
         dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
+        // a macro calls this one over and over, should it increase instancing?
+        // Used by Hades and likely other Vulkan games.
         return DrawArrays();
         return DrawArrays();
     case MAXWELL3D_REG_INDEX(topology_override):
     case MAXWELL3D_REG_INDEX(topology_override):
         use_topology_override = true;
         use_topology_override = true;
@@ -237,11 +239,12 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
         return upload_state.ProcessExec(regs.exec_upload.linear != 0);
         return upload_state.ProcessExec(regs.exec_upload.linear != 0);
     case MAXWELL3D_REG_INDEX(data_upload):
     case MAXWELL3D_REG_INDEX(data_upload):
         upload_state.ProcessData(argument, is_last_call);
         upload_state.ProcessData(argument, is_last_call);
-        if (is_last_call) {
-        }
         return;
         return;
     case MAXWELL3D_REG_INDEX(fragment_barrier):
     case MAXWELL3D_REG_INDEX(fragment_barrier):
         return rasterizer->FragmentBarrier();
         return rasterizer->FragmentBarrier();
+    case MAXWELL3D_REG_INDEX(invalidate_texture_data_cache):
+        rasterizer->InvalidateGPUCache();
+        return rasterizer->WaitForIdle();
     case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
     case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
         return rasterizer->TiledCacheBarrier();
         return rasterizer->TiledCacheBarrier();
     }
     }
@@ -311,6 +314,9 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
     case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15:
     case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15:
         ProcessCBMultiData(base_start, amount);
         ProcessCBMultiData(base_start, amount);
         break;
         break;
+    case MAXWELL3D_REG_INDEX(data_upload):
+        upload_state.ProcessData(base_start, static_cast<size_t>(amount));
+        return;
     default:
     default:
         for (std::size_t i = 0; i < amount; i++) {
         for (std::size_t i = 0; i < amount; i++) {
             CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
             CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
@@ -447,18 +453,10 @@ void Maxwell3D::ProcessFirmwareCall4() {
 }
 }
 
 
 void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
 void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
-    struct LongQueryResult {
-        u64_le value;
-        u64_le timestamp;
-    };
-    static_assert(sizeof(LongQueryResult) == 16, "LongQueryResult has wrong size");
     const GPUVAddr sequence_address{regs.query.QueryAddress()};
     const GPUVAddr sequence_address{regs.query.QueryAddress()};
     if (long_query) {
     if (long_query) {
-        // Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
-        // GPU, this command may actually take a while to complete in real hardware due to GPU
-        // wait queues.
-        LongQueryResult query_result{payload, system.GPU().GetTicks()};
-        memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result));
+        memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
+        memory_manager.Write<u64>(sequence_address, payload);
     } else {
     } else {
         memory_manager.Write<u32>(sequence_address, static_cast<u32>(payload));
         memory_manager.Write<u32>(sequence_address, static_cast<u32>(payload));
     }
     }
@@ -472,10 +470,25 @@ void Maxwell3D::ProcessQueryGet() {
 
 
     switch (regs.query.query_get.operation) {
     switch (regs.query.query_get.operation) {
     case Regs::QueryOperation::Release:
     case Regs::QueryOperation::Release:
-        if (regs.query.query_get.fence == 1) {
-            rasterizer->SignalSemaphore(regs.query.QueryAddress(), regs.query.query_sequence);
+        if (regs.query.query_get.fence == 1 || regs.query.query_get.short_query != 0) {
+            const GPUVAddr sequence_address{regs.query.QueryAddress()};
+            const u32 payload = regs.query.query_sequence;
+            std::function<void()> operation([this, sequence_address, payload] {
+                memory_manager.Write<u32>(sequence_address, payload);
+            });
+            rasterizer->SignalFence(std::move(operation));
         } else {
         } else {
-            StampQueryResult(regs.query.query_sequence, regs.query.query_get.short_query == 0);
+            struct LongQueryResult {
+                u64_le value;
+                u64_le timestamp;
+            };
+            const GPUVAddr sequence_address{regs.query.QueryAddress()};
+            const u32 payload = regs.query.query_sequence;
+            std::function<void()> operation([this, sequence_address, payload] {
+                memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
+                memory_manager.Write<u64>(sequence_address, payload);
+            });
+            rasterizer->SyncOperation(std::move(operation));
         }
         }
         break;
         break;
     case Regs::QueryOperation::Acquire:
     case Regs::QueryOperation::Acquire:

+ 71 - 40
src/video_core/engines/maxwell_dma.cpp

@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
 // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 
+#include "common/algorithm.h"
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "common/microprofile.h"
 #include "common/microprofile.h"
@@ -54,8 +55,6 @@ void MaxwellDMA::Launch() {
     const LaunchDMA& launch = regs.launch_dma;
     const LaunchDMA& launch = regs.launch_dma;
     ASSERT(launch.interrupt_type == LaunchDMA::InterruptType::NONE);
     ASSERT(launch.interrupt_type == LaunchDMA::InterruptType::NONE);
     ASSERT(launch.data_transfer_type == LaunchDMA::DataTransferType::NON_PIPELINED);
     ASSERT(launch.data_transfer_type == LaunchDMA::DataTransferType::NON_PIPELINED);
-    ASSERT(regs.dst_params.origin.x == 0);
-    ASSERT(regs.dst_params.origin.y == 0);
 
 
     const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH;
     const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH;
     const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH;
     const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH;
@@ -121,23 +120,40 @@ void MaxwellDMA::CopyPitchToPitch() {
 
 
 void MaxwellDMA::CopyBlockLinearToPitch() {
 void MaxwellDMA::CopyBlockLinearToPitch() {
     UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0);
     UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0);
-    UNIMPLEMENTED_IF(regs.src_params.block_size.depth != 0);
     UNIMPLEMENTED_IF(regs.src_params.layer != 0);
     UNIMPLEMENTED_IF(regs.src_params.layer != 0);
 
 
+    const bool is_remapping = regs.launch_dma.remap_enable != 0;
+
     // Optimized path for micro copies.
     // Optimized path for micro copies.
     const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
     const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
-    if (dst_size < GOB_SIZE && regs.pitch_out <= GOB_SIZE_X &&
+    if (!is_remapping && dst_size < GOB_SIZE && regs.pitch_out <= GOB_SIZE_X &&
         regs.src_params.height > GOB_SIZE_Y) {
         regs.src_params.height > GOB_SIZE_Y) {
         FastCopyBlockLinearToPitch();
         FastCopyBlockLinearToPitch();
         return;
         return;
     }
     }
 
 
     // Deswizzle the input and copy it over.
     // Deswizzle the input and copy it over.
-    UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0);
-    const u32 bytes_per_pixel =
-        regs.launch_dma.remap_enable ? regs.pitch_out / regs.line_length_in : 1;
     const Parameters& src_params = regs.src_params;
     const Parameters& src_params = regs.src_params;
-    const u32 width = src_params.width;
+
+    const u32 num_remap_components = regs.remap_const.num_dst_components_minus_one + 1;
+    const u32 remap_components_size = regs.remap_const.component_size_minus_one + 1;
+
+    const u32 base_bpp = !is_remapping ? 1U : num_remap_components * remap_components_size;
+
+    u32 width = src_params.width;
+    u32 x_elements = regs.line_length_in;
+    u32 x_offset = src_params.origin.x;
+    u32 bpp_shift = 0U;
+    if (!is_remapping) {
+        bpp_shift = Common::FoldRight(
+            4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
+            width, x_elements, x_offset, static_cast<u32>(regs.offset_in));
+        width >>= bpp_shift;
+        x_elements >>= bpp_shift;
+        x_offset >>= bpp_shift;
+    }
+
+    const u32 bytes_per_pixel = base_bpp << bpp_shift;
     const u32 height = src_params.height;
     const u32 height = src_params.height;
     const u32 depth = src_params.depth;
     const u32 depth = src_params.depth;
     const u32 block_height = src_params.block_size.height;
     const u32 block_height = src_params.block_size.height;
@@ -155,30 +171,45 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
     memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
     memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
     memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
     memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
 
 
-    UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, width, bytes_per_pixel,
-                     block_height, src_params.origin.x, src_params.origin.y, write_buffer.data(),
-                     read_buffer.data());
+    UnswizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
+                     src_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
+                     regs.pitch_out);
 
 
     memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
     memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
 }
 }
 
 
 void MaxwellDMA::CopyPitchToBlockLinear() {
 void MaxwellDMA::CopyPitchToBlockLinear() {
     UNIMPLEMENTED_IF_MSG(regs.dst_params.block_size.width != 0, "Block width is not one");
     UNIMPLEMENTED_IF_MSG(regs.dst_params.block_size.width != 0, "Block width is not one");
-    UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0);
+    UNIMPLEMENTED_IF(regs.dst_params.layer != 0);
+
+    const bool is_remapping = regs.launch_dma.remap_enable != 0;
+    const u32 num_remap_components = regs.remap_const.num_dst_components_minus_one + 1;
+    const u32 remap_components_size = regs.remap_const.component_size_minus_one + 1;
 
 
     const auto& dst_params = regs.dst_params;
     const auto& dst_params = regs.dst_params;
-    const u32 bytes_per_pixel =
-        regs.launch_dma.remap_enable ? regs.pitch_in / regs.line_length_in : 1;
-    const u32 width = dst_params.width;
+
+    const u32 base_bpp = !is_remapping ? 1U : num_remap_components * remap_components_size;
+
+    u32 width = dst_params.width;
+    u32 x_elements = regs.line_length_in;
+    u32 x_offset = dst_params.origin.x;
+    u32 bpp_shift = 0U;
+    if (!is_remapping) {
+        bpp_shift = Common::FoldRight(
+            4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
+            width, x_elements, x_offset, static_cast<u32>(regs.offset_out));
+        width >>= bpp_shift;
+        x_elements >>= bpp_shift;
+        x_offset >>= bpp_shift;
+    }
+
+    const u32 bytes_per_pixel = base_bpp << bpp_shift;
     const u32 height = dst_params.height;
     const u32 height = dst_params.height;
     const u32 depth = dst_params.depth;
     const u32 depth = dst_params.depth;
     const u32 block_height = dst_params.block_size.height;
     const u32 block_height = dst_params.block_size.height;
     const u32 block_depth = dst_params.block_size.depth;
     const u32 block_depth = dst_params.block_size.depth;
     const size_t dst_size =
     const size_t dst_size =
         CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
         CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
-    const size_t dst_layer_size =
-        CalculateSize(true, bytes_per_pixel, width, height, 1, block_height, block_depth);
-
     const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
     const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
 
 
     if (read_buffer.size() < src_size) {
     if (read_buffer.size() < src_size) {
@@ -188,32 +219,23 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
         write_buffer.resize(dst_size);
         write_buffer.resize(dst_size);
     }
     }
 
 
+    memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
     if (Settings::IsGPULevelExtreme()) {
     if (Settings::IsGPULevelExtreme()) {
-        memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
         memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
         memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
     } else {
     } else {
-        memory_manager.ReadBlockUnsafe(regs.offset_in, read_buffer.data(), src_size);
         memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
         memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
     }
     }
 
 
     // If the input is linear and the output is tiled, swizzle the input and copy it over.
     // If the input is linear and the output is tiled, swizzle the input and copy it over.
-    if (regs.dst_params.block_size.depth > 0) {
-        ASSERT(dst_params.layer == 0);
-        SwizzleSliceToVoxel(regs.line_length_in, regs.line_count, regs.pitch_in, width, height,
-                            bytes_per_pixel, block_height, block_depth, dst_params.origin.x,
-                            dst_params.origin.y, write_buffer.data(), read_buffer.data());
-    } else {
-        SwizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_in, width, bytes_per_pixel,
-                       write_buffer.data() + dst_layer_size * dst_params.layer, read_buffer.data(),
-                       block_height, dst_params.origin.x, dst_params.origin.y);
-    }
+    SwizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
+                   dst_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
+                   regs.pitch_in);
 
 
     memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
     memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
 }
 }
 
 
 void MaxwellDMA::FastCopyBlockLinearToPitch() {
 void MaxwellDMA::FastCopyBlockLinearToPitch() {
-    const u32 bytes_per_pixel =
-        regs.launch_dma.remap_enable ? regs.pitch_out / regs.line_length_in : 1;
+    const u32 bytes_per_pixel = 1U;
     const size_t src_size = GOB_SIZE;
     const size_t src_size = GOB_SIZE;
     const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
     const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
     u32 pos_x = regs.src_params.origin.x;
     u32 pos_x = regs.src_params.origin.x;
@@ -239,9 +261,10 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
         memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
         memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
     }
     }
 
 
-    UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, regs.src_params.width,
-                     bytes_per_pixel, regs.src_params.block_size.height, pos_x, pos_y,
-                     write_buffer.data(), read_buffer.data());
+    UnswizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, regs.src_params.width,
+                     regs.src_params.height, 1, pos_x, pos_y, regs.line_length_in, regs.line_count,
+                     regs.src_params.block_size.height, regs.src_params.block_size.depth,
+                     regs.pitch_out);
 
 
     memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
     memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
 }
 }
@@ -249,16 +272,24 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
 void MaxwellDMA::ReleaseSemaphore() {
 void MaxwellDMA::ReleaseSemaphore() {
     const auto type = regs.launch_dma.semaphore_type;
     const auto type = regs.launch_dma.semaphore_type;
     const GPUVAddr address = regs.semaphore.address;
     const GPUVAddr address = regs.semaphore.address;
+    const u32 payload = regs.semaphore.payload;
     switch (type) {
     switch (type) {
     case LaunchDMA::SemaphoreType::NONE:
     case LaunchDMA::SemaphoreType::NONE:
         break;
         break;
-    case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE:
-        memory_manager.Write<u32>(address, regs.semaphore.payload);
+    case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: {
+        std::function<void()> operation(
+            [this, address, payload] { memory_manager.Write<u32>(address, payload); });
+        rasterizer->SignalFence(std::move(operation));
         break;
         break;
-    case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE:
-        memory_manager.Write<u64>(address, static_cast<u64>(regs.semaphore.payload));
-        memory_manager.Write<u64>(address + 8, system.GPU().GetTicks());
+    }
+    case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: {
+        std::function<void()> operation([this, address, payload] {
+            memory_manager.Write<u64>(address + sizeof(u64), system.GPU().GetTicks());
+            memory_manager.Write<u64>(address, payload);
+        });
+        rasterizer->SignalFence(std::move(operation));
         break;
         break;
+    }
     default:
     default:
         ASSERT_MSG(false, "Unknown semaphore type: {}", static_cast<u32>(type.Value()));
         ASSERT_MSG(false, "Unknown semaphore type: {}", static_cast<u32>(type.Value()));
     }
     }

+ 6 - 0
src/video_core/engines/maxwell_dma.h

@@ -189,10 +189,16 @@ public:
             BitField<4, 3, Swizzle> dst_y;
             BitField<4, 3, Swizzle> dst_y;
             BitField<8, 3, Swizzle> dst_z;
             BitField<8, 3, Swizzle> dst_z;
             BitField<12, 3, Swizzle> dst_w;
             BitField<12, 3, Swizzle> dst_w;
+            BitField<0, 12, u32> dst_components_raw;
             BitField<16, 2, u32> component_size_minus_one;
             BitField<16, 2, u32> component_size_minus_one;
             BitField<20, 2, u32> num_src_components_minus_one;
             BitField<20, 2, u32> num_src_components_minus_one;
             BitField<24, 2, u32> num_dst_components_minus_one;
             BitField<24, 2, u32> num_dst_components_minus_one;
         };
         };
+
+        Swizzle GetComponent(size_t i) const {
+            const u32 raw = dst_components_raw;
+            return static_cast<Swizzle>((raw >> (i * 3)) & 0x7);
+        }
     };
     };
     static_assert(sizeof(RemapConst) == 12);
     static_assert(sizeof(RemapConst) == 12);
 
 

+ 306 - 0
src/video_core/engines/puller.cpp

@@ -0,0 +1,306 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/core.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/gpu.h"
+#include "video_core/memory_manager.h"
+#include "video_core/rasterizer_interface.h"
+
+namespace Tegra::Engines {
+
+Puller::Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher_,
+               Control::ChannelState& channel_state_)
+    : gpu{gpu_}, memory_manager{memory_manager_}, dma_pusher{dma_pusher_}, channel_state{
+                                                                               channel_state_} {}
+
+Puller::~Puller() = default;
+
+void Puller::ProcessBindMethod(const MethodCall& method_call) {
+    // Bind the current subchannel to the desired engine id.
+    LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel,
+              method_call.argument);
+    const auto engine_id = static_cast<EngineID>(method_call.argument);
+    bound_engines[method_call.subchannel] = static_cast<EngineID>(engine_id);
+    switch (engine_id) {
+    case EngineID::FERMI_TWOD_A:
+        dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel);
+        break;
+    case EngineID::MAXWELL_B:
+        dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel);
+        break;
+    case EngineID::KEPLER_COMPUTE_B:
+        dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel);
+        break;
+    case EngineID::MAXWELL_DMA_COPY_A:
+        dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel);
+        break;
+    case EngineID::KEPLER_INLINE_TO_MEMORY_B:
+        dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel);
+        break;
+    default:
+        UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
+    }
+}
+
+void Puller::ProcessFenceActionMethod() {
+    switch (regs.fence_action.op) {
+    case Puller::FenceOperation::Acquire:
+        // UNIMPLEMENTED_MSG("Channel Scheduling pending.");
+        // WaitFence(regs.fence_action.syncpoint_id, regs.fence_value);
+        rasterizer->ReleaseFences();
+        break;
+    case Puller::FenceOperation::Increment:
+        rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id);
+        break;
+    default:
+        UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
+    }
+}
+
+void Puller::ProcessSemaphoreTriggerMethod() {
+    const auto semaphoreOperationMask = 0xF;
+    const auto op =
+        static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
+    if (op == GpuSemaphoreOperation::WriteLong) {
+        const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
+        const u32 payload = regs.semaphore_sequence;
+        std::function<void()> operation([this, sequence_address, payload] {
+            memory_manager.Write<u64>(sequence_address + sizeof(u64), gpu.GetTicks());
+            memory_manager.Write<u64>(sequence_address, payload);
+        });
+        rasterizer->SignalFence(std::move(operation));
+    } else {
+        do {
+            const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())};
+            regs.acquire_source = true;
+            regs.acquire_value = regs.semaphore_sequence;
+            if (op == GpuSemaphoreOperation::AcquireEqual) {
+                regs.acquire_active = true;
+                regs.acquire_mode = false;
+                if (word != regs.acquire_value) {
+                    rasterizer->ReleaseFences();
+                    continue;
+                }
+            } else if (op == GpuSemaphoreOperation::AcquireGequal) {
+                regs.acquire_active = true;
+                regs.acquire_mode = true;
+                if (word < regs.acquire_value) {
+                    rasterizer->ReleaseFences();
+                    continue;
+                }
+            } else if (op == GpuSemaphoreOperation::AcquireMask) {
+                if (word && regs.semaphore_sequence == 0) {
+                    rasterizer->ReleaseFences();
+                    continue;
+                }
+            } else {
+                LOG_ERROR(HW_GPU, "Invalid semaphore operation");
+            }
+        } while (false);
+    }
+}
+
+void Puller::ProcessSemaphoreRelease() {
+    const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
+    const u32 payload = regs.semaphore_release;
+    std::function<void()> operation([this, sequence_address, payload] {
+        memory_manager.Write<u32>(sequence_address, payload);
+    });
+    rasterizer->SyncOperation(std::move(operation));
+}
+
+void Puller::ProcessSemaphoreAcquire() {
+    u32 word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
+    const auto value = regs.semaphore_acquire;
+    while (word != value) {
+        regs.acquire_active = true;
+        regs.acquire_value = value;
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+        rasterizer->ReleaseFences();
+        word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
+        // TODO(kemathe73) figure out how to do the acquire_timeout
+        regs.acquire_mode = false;
+        regs.acquire_source = false;
+    }
+}
+
+/// Calls a GPU puller method.
+void Puller::CallPullerMethod(const MethodCall& method_call) {
+    regs.reg_array[method_call.method] = method_call.argument;
+    const auto method = static_cast<BufferMethods>(method_call.method);
+
+    switch (method) {
+    case BufferMethods::BindObject: {
+        ProcessBindMethod(method_call);
+        break;
+    }
+    case BufferMethods::Nop:
+    case BufferMethods::SemaphoreAddressHigh:
+    case BufferMethods::SemaphoreAddressLow:
+    case BufferMethods::SemaphoreSequencePayload:
+    case BufferMethods::SyncpointPayload:
+        break;
+    case BufferMethods::WrcacheFlush:
+    case BufferMethods::RefCnt:
+        rasterizer->SignalReference();
+        break;
+    case BufferMethods::SyncpointOperation:
+        ProcessFenceActionMethod();
+        break;
+    case BufferMethods::WaitForIdle:
+        rasterizer->WaitForIdle();
+        break;
+    case BufferMethods::SemaphoreOperation: {
+        ProcessSemaphoreTriggerMethod();
+        break;
+    }
+    case BufferMethods::NonStallInterrupt: {
+        LOG_ERROR(HW_GPU, "Special puller engine method NonStallInterrupt not implemented");
+        break;
+    }
+    case BufferMethods::MemOpA: {
+        LOG_ERROR(HW_GPU, "Memory Operation A");
+        break;
+    }
+    case BufferMethods::MemOpB: {
+        // Implement this better.
+        rasterizer->InvalidateGPUCache();
+        break;
+    }
+    case BufferMethods::MemOpC:
+    case BufferMethods::MemOpD: {
+        LOG_ERROR(HW_GPU, "Memory Operation C,D");
+        break;
+    }
+    case BufferMethods::SemaphoreAcquire: {
+        ProcessSemaphoreAcquire();
+        break;
+    }
+    case BufferMethods::SemaphoreRelease: {
+        ProcessSemaphoreRelease();
+        break;
+    }
+    case BufferMethods::Yield: {
+        // TODO(Kmather73): Research and implement this method.
+        LOG_ERROR(HW_GPU, "Special puller engine method Yield not implemented");
+        break;
+    }
+    default:
+        LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method);
+        break;
+    }
+}
+
+/// Calls a GPU engine method.
+void Puller::CallEngineMethod(const MethodCall& method_call) {
+    const EngineID engine = bound_engines[method_call.subchannel];
+
+    switch (engine) {
+    case EngineID::FERMI_TWOD_A:
+        channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument,
+                                           method_call.IsLastCall());
+        break;
+    case EngineID::MAXWELL_B:
+        channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument,
+                                             method_call.IsLastCall());
+        break;
+    case EngineID::KEPLER_COMPUTE_B:
+        channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument,
+                                                 method_call.IsLastCall());
+        break;
+    case EngineID::MAXWELL_DMA_COPY_A:
+        channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument,
+                                              method_call.IsLastCall());
+        break;
+    case EngineID::KEPLER_INLINE_TO_MEMORY_B:
+        channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument,
+                                                method_call.IsLastCall());
+        break;
+    default:
+        UNIMPLEMENTED_MSG("Unimplemented engine");
+    }
+}
+
+/// Calls a GPU engine multivalue method.
+void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+                                   u32 methods_pending) {
+    const EngineID engine = bound_engines[subchannel];
+
+    switch (engine) {
+    case EngineID::FERMI_TWOD_A:
+        channel_state.fermi_2d->CallMultiMethod(method, base_start, amount, methods_pending);
+        break;
+    case EngineID::MAXWELL_B:
+        channel_state.maxwell_3d->CallMultiMethod(method, base_start, amount, methods_pending);
+        break;
+    case EngineID::KEPLER_COMPUTE_B:
+        channel_state.kepler_compute->CallMultiMethod(method, base_start, amount, methods_pending);
+        break;
+    case EngineID::MAXWELL_DMA_COPY_A:
+        channel_state.maxwell_dma->CallMultiMethod(method, base_start, amount, methods_pending);
+        break;
+    case EngineID::KEPLER_INLINE_TO_MEMORY_B:
+        channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
+        break;
+    default:
+        UNIMPLEMENTED_MSG("Unimplemented engine");
+    }
+}
+
+/// Calls a GPU method.
+void Puller::CallMethod(const MethodCall& method_call) {
+    LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method,
+              method_call.subchannel);
+
+    ASSERT(method_call.subchannel < bound_engines.size());
+
+    if (ExecuteMethodOnEngine(method_call.method)) {
+        CallEngineMethod(method_call);
+    } else {
+        CallPullerMethod(method_call);
+    }
+}
+
+/// Calls a GPU multivalue method.
+void Puller::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+                             u32 methods_pending) {
+    LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method, subchannel);
+
+    ASSERT(subchannel < bound_engines.size());
+
+    if (ExecuteMethodOnEngine(method)) {
+        CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending);
+    } else {
+        for (std::size_t i = 0; i < amount; i++) {
+            CallPullerMethod(MethodCall{
+                method,
+                base_start[i],
+                subchannel,
+                methods_pending - static_cast<u32>(i),
+            });
+        }
+    }
+}
+
+void Puller::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
+    rasterizer = rasterizer_;
+}
+
+/// Determines where the method should be executed.
+[[nodiscard]] bool Puller::ExecuteMethodOnEngine(u32 method) {
+    const auto buffer_method = static_cast<BufferMethods>(method);
+    return buffer_method >= BufferMethods::NonPullerMethods;
+}
+
+} // namespace Tegra::Engines

+ 177 - 0
src/video_core/engines/puller.h

@@ -0,0 +1,177 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/engines/engine_interface.h"
+
+namespace Core {
+class System;
+}
+
+namespace Tegra {
+class MemoryManager;
+class DmaPusher;
+
+enum class EngineID {
+    FERMI_TWOD_A = 0x902D, // 2D Engine
+    MAXWELL_B = 0xB197,    // 3D Engine
+    KEPLER_COMPUTE_B = 0xB1C0,
+    KEPLER_INLINE_TO_MEMORY_B = 0xA140,
+    MAXWELL_DMA_COPY_A = 0xB0B5,
+};
+
+namespace Control {
+struct ChannelState;
+}
+} // namespace Tegra
+
+namespace VideoCore {
+class RasterizerInterface;
+}
+
+namespace Tegra::Engines {
+
+class Puller final {
+public:
+    struct MethodCall {
+        u32 method{};
+        u32 argument{};
+        u32 subchannel{};
+        u32 method_count{};
+
+        explicit MethodCall(u32 method_, u32 argument_, u32 subchannel_ = 0, u32 method_count_ = 0)
+            : method(method_), argument(argument_), subchannel(subchannel_),
+              method_count(method_count_) {}
+
+        [[nodiscard]] bool IsLastCall() const {
+            return method_count <= 1;
+        }
+    };
+
+    enum class FenceOperation : u32 {
+        Acquire = 0,
+        Increment = 1,
+    };
+
+    union FenceAction {
+        u32 raw;
+        BitField<0, 1, FenceOperation> op;
+        BitField<8, 24, u32> syncpoint_id;
+    };
+
+    explicit Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher,
+                    Control::ChannelState& channel_state);
+    ~Puller();
+
+    void CallMethod(const MethodCall& method_call);
+
+    void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+                         u32 methods_pending);
+
+    void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
+
+    void CallPullerMethod(const MethodCall& method_call);
+
+    void CallEngineMethod(const MethodCall& method_call);
+
+    void CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
+                               u32 methods_pending);
+
+private:
+    Tegra::GPU& gpu;
+
+    MemoryManager& memory_manager;
+    DmaPusher& dma_pusher;
+    Control::ChannelState& channel_state;
+    VideoCore::RasterizerInterface* rasterizer = nullptr;
+
+    static constexpr std::size_t NUM_REGS = 0x800;
+    struct Regs {
+        static constexpr size_t NUM_REGS = 0x40;
+
+        union {
+            struct {
+                INSERT_PADDING_WORDS_NOINIT(0x4);
+                struct {
+                    u32 address_high;
+                    u32 address_low;
+
+                    [[nodiscard]] GPUVAddr SemaphoreAddress() const {
+                        return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+                                                     address_low);
+                    }
+                } semaphore_address;
+
+                u32 semaphore_sequence;
+                u32 semaphore_trigger;
+                INSERT_PADDING_WORDS_NOINIT(0xC);
+
+                // The pusher and the puller share the reference counter, the pusher only has read
+                // access
+                u32 reference_count;
+                INSERT_PADDING_WORDS_NOINIT(0x5);
+
+                u32 semaphore_acquire;
+                u32 semaphore_release;
+                u32 fence_value;
+                FenceAction fence_action;
+                INSERT_PADDING_WORDS_NOINIT(0xE2);
+
+                // Puller state
+                u32 acquire_mode;
+                u32 acquire_source;
+                u32 acquire_active;
+                u32 acquire_timeout;
+                u32 acquire_value;
+            };
+            std::array<u32, NUM_REGS> reg_array;
+        };
+    } regs{};
+
+    void ProcessBindMethod(const MethodCall& method_call);
+    void ProcessFenceActionMethod();
+    void ProcessSemaphoreAcquire();
+    void ProcessSemaphoreRelease();
+    void ProcessSemaphoreTriggerMethod();
+    [[nodiscard]] bool ExecuteMethodOnEngine(u32 method);
+
+    /// Mapping of command subchannels to their bound engine ids
+    std::array<EngineID, 8> bound_engines{};
+
+    enum class GpuSemaphoreOperation {
+        AcquireEqual = 0x1,
+        WriteLong = 0x2,
+        AcquireGequal = 0x4,
+        AcquireMask = 0x8,
+    };
+
+#define ASSERT_REG_POSITION(field_name, position)                                                  \
+    static_assert(offsetof(Regs, field_name) == position * 4,                                      \
+                  "Field " #field_name " has invalid position")
+
+    ASSERT_REG_POSITION(semaphore_address, 0x4);
+    ASSERT_REG_POSITION(semaphore_sequence, 0x6);
+    ASSERT_REG_POSITION(semaphore_trigger, 0x7);
+    ASSERT_REG_POSITION(reference_count, 0x14);
+    ASSERT_REG_POSITION(semaphore_acquire, 0x1A);
+    ASSERT_REG_POSITION(semaphore_release, 0x1B);
+    ASSERT_REG_POSITION(fence_value, 0x1C);
+    ASSERT_REG_POSITION(fence_action, 0x1D);
+
+    ASSERT_REG_POSITION(acquire_mode, 0x100);
+    ASSERT_REG_POSITION(acquire_source, 0x101);
+    ASSERT_REG_POSITION(acquire_active, 0x102);
+    ASSERT_REG_POSITION(acquire_timeout, 0x103);
+    ASSERT_REG_POSITION(acquire_value, 0x104);
+
+#undef ASSERT_REG_POSITION
+};
+
+} // namespace Tegra::Engines

+ 46 - 58
src/video_core/fence_manager.h

@@ -4,40 +4,24 @@
 #pragma once
 #pragma once
 
 
 #include <algorithm>
 #include <algorithm>
+#include <cstring>
+#include <deque>
+#include <functional>
+#include <memory>
 #include <queue>
 #include <queue>
 
 
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "video_core/delayed_destruction_ring.h"
 #include "video_core/delayed_destruction_ring.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/syncpoint_manager.h"
 #include "video_core/rasterizer_interface.h"
 #include "video_core/rasterizer_interface.h"
 
 
 namespace VideoCommon {
 namespace VideoCommon {
 
 
 class FenceBase {
 class FenceBase {
 public:
 public:
-    explicit FenceBase(u32 payload_, bool is_stubbed_)
-        : address{}, payload{payload_}, is_semaphore{false}, is_stubbed{is_stubbed_} {}
-
-    explicit FenceBase(GPUVAddr address_, u32 payload_, bool is_stubbed_)
-        : address{address_}, payload{payload_}, is_semaphore{true}, is_stubbed{is_stubbed_} {}
-
-    GPUVAddr GetAddress() const {
-        return address;
-    }
-
-    u32 GetPayload() const {
-        return payload;
-    }
-
-    bool IsSemaphore() const {
-        return is_semaphore;
-    }
-
-private:
-    GPUVAddr address;
-    u32 payload;
-    bool is_semaphore;
+    explicit FenceBase(bool is_stubbed_) : is_stubbed{is_stubbed_} {}
 
 
 protected:
 protected:
     bool is_stubbed;
     bool is_stubbed;
@@ -57,30 +41,28 @@ public:
         buffer_cache.AccumulateFlushes();
         buffer_cache.AccumulateFlushes();
     }
     }
 
 
-    void SignalSemaphore(GPUVAddr addr, u32 value) {
+    void SyncOperation(std::function<void()>&& func) {
+        uncommitted_operations.emplace_back(std::move(func));
+    }
+
+    void SignalFence(std::function<void()>&& func) {
         TryReleasePendingFences();
         TryReleasePendingFences();
         const bool should_flush = ShouldFlush();
         const bool should_flush = ShouldFlush();
         CommitAsyncFlushes();
         CommitAsyncFlushes();
-        TFence new_fence = CreateFence(addr, value, !should_flush);
+        uncommitted_operations.emplace_back(std::move(func));
+        CommitOperations();
+        TFence new_fence = CreateFence(!should_flush);
         fences.push(new_fence);
         fences.push(new_fence);
         QueueFence(new_fence);
         QueueFence(new_fence);
         if (should_flush) {
         if (should_flush) {
             rasterizer.FlushCommands();
             rasterizer.FlushCommands();
         }
         }
-        rasterizer.SyncGuestHost();
     }
     }
 
 
     void SignalSyncPoint(u32 value) {
     void SignalSyncPoint(u32 value) {
-        TryReleasePendingFences();
-        const bool should_flush = ShouldFlush();
-        CommitAsyncFlushes();
-        TFence new_fence = CreateFence(value, !should_flush);
-        fences.push(new_fence);
-        QueueFence(new_fence);
-        if (should_flush) {
-            rasterizer.FlushCommands();
-        }
-        rasterizer.SyncGuestHost();
+        syncpoint_manager.IncrementGuest(value);
+        std::function<void()> func([this, value] { syncpoint_manager.IncrementHost(value); });
+        SignalFence(std::move(func));
     }
     }
 
 
     void WaitPendingFences() {
     void WaitPendingFences() {
@@ -90,11 +72,10 @@ public:
                 WaitFence(current_fence);
                 WaitFence(current_fence);
             }
             }
             PopAsyncFlushes();
             PopAsyncFlushes();
-            if (current_fence->IsSemaphore()) {
-                gpu_memory.template Write<u32>(current_fence->GetAddress(),
-                                               current_fence->GetPayload());
-            } else {
-                gpu.IncrementSyncPoint(current_fence->GetPayload());
+            auto operations = std::move(pending_operations.front());
+            pending_operations.pop_front();
+            for (auto& operation : operations) {
+                operation();
             }
             }
             PopFence();
             PopFence();
         }
         }
@@ -104,16 +85,14 @@ protected:
     explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
     explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
                           TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
                           TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
                           TQueryCache& query_cache_)
                           TQueryCache& query_cache_)
-        : rasterizer{rasterizer_}, gpu{gpu_}, gpu_memory{gpu.MemoryManager()},
+        : rasterizer{rasterizer_}, gpu{gpu_}, syncpoint_manager{gpu.Host1x().GetSyncpointManager()},
           texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
           texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
 
 
     virtual ~FenceManager() = default;
     virtual ~FenceManager() = default;
 
 
-    /// Creates a Sync Point Fence Interface, does not create a backend fence if 'is_stubbed' is
+    /// Creates a Fence Interface, does not create a backend fence if 'is_stubbed' is
     /// true
     /// true
-    virtual TFence CreateFence(u32 value, bool is_stubbed) = 0;
-    /// Creates a Semaphore Fence Interface, does not create a backend fence if 'is_stubbed' is true
-    virtual TFence CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) = 0;
+    virtual TFence CreateFence(bool is_stubbed) = 0;
     /// Queues a fence into the backend if the fence isn't stubbed.
     /// Queues a fence into the backend if the fence isn't stubbed.
     virtual void QueueFence(TFence& fence) = 0;
     virtual void QueueFence(TFence& fence) = 0;
     /// Notifies that the backend fence has been signaled/reached in host GPU.
     /// Notifies that the backend fence has been signaled/reached in host GPU.
@@ -123,7 +102,7 @@ protected:
 
 
     VideoCore::RasterizerInterface& rasterizer;
     VideoCore::RasterizerInterface& rasterizer;
     Tegra::GPU& gpu;
     Tegra::GPU& gpu;
-    Tegra::MemoryManager& gpu_memory;
+    Tegra::Host1x::SyncpointManager& syncpoint_manager;
     TTextureCache& texture_cache;
     TTextureCache& texture_cache;
     TTBufferCache& buffer_cache;
     TTBufferCache& buffer_cache;
     TQueryCache& query_cache;
     TQueryCache& query_cache;
@@ -136,11 +115,10 @@ private:
                 return;
                 return;
             }
             }
             PopAsyncFlushes();
             PopAsyncFlushes();
-            if (current_fence->IsSemaphore()) {
-                gpu_memory.template Write<u32>(current_fence->GetAddress(),
-                                               current_fence->GetPayload());
-            } else {
-                gpu.IncrementSyncPoint(current_fence->GetPayload());
+            auto operations = std::move(pending_operations.front());
+            pending_operations.pop_front();
+            for (auto& operation : operations) {
+                operation();
             }
             }
             PopFence();
             PopFence();
         }
         }
@@ -159,16 +137,20 @@ private:
     }
     }
 
 
     void PopAsyncFlushes() {
     void PopAsyncFlushes() {
-        std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
-        texture_cache.PopAsyncFlushes();
-        buffer_cache.PopAsyncFlushes();
+        {
+            std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+            texture_cache.PopAsyncFlushes();
+            buffer_cache.PopAsyncFlushes();
+        }
         query_cache.PopAsyncFlushes();
         query_cache.PopAsyncFlushes();
     }
     }
 
 
     void CommitAsyncFlushes() {
     void CommitAsyncFlushes() {
-        std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
-        texture_cache.CommitAsyncFlushes();
-        buffer_cache.CommitAsyncFlushes();
+        {
+            std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+            texture_cache.CommitAsyncFlushes();
+            buffer_cache.CommitAsyncFlushes();
+        }
         query_cache.CommitAsyncFlushes();
         query_cache.CommitAsyncFlushes();
     }
     }
 
 
@@ -177,7 +159,13 @@ private:
         fences.pop();
         fences.pop();
     }
     }
 
 
+    void CommitOperations() {
+        pending_operations.emplace_back(std::move(uncommitted_operations));
+    }
+
     std::queue<TFence> fences;
     std::queue<TFence> fences;
+    std::deque<std::function<void()>> uncommitted_operations;
+    std::deque<std::deque<std::function<void()>>> pending_operations;
 
 
     DelayedDestructionRing<TFence, 6> delayed_destruction_ring;
     DelayedDestructionRing<TFence, 6> delayed_destruction_ring;
 };
 };

+ 173 - 533
src/video_core/gpu.cpp

@@ -14,10 +14,11 @@
 #include "core/core.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/core_timing.h"
 #include "core/frontend/emu_window.h"
 #include "core/frontend/emu_window.h"
-#include "core/hardware_interrupt_manager.h"
 #include "core/hle/service/nvdrv/nvdata.h"
 #include "core/hle/service/nvdrv/nvdata.h"
 #include "core/perf_stats.h"
 #include "core/perf_stats.h"
 #include "video_core/cdma_pusher.h"
 #include "video_core/cdma_pusher.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/control/scheduler.h"
 #include "video_core/dma_pusher.h"
 #include "video_core/dma_pusher.h"
 #include "video_core/engines/fermi_2d.h"
 #include "video_core/engines/fermi_2d.h"
 #include "video_core/engines/kepler_compute.h"
 #include "video_core/engines/kepler_compute.h"
@@ -26,75 +27,64 @@
 #include "video_core/engines/maxwell_dma.h"
 #include "video_core/engines/maxwell_dma.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu_thread.h"
 #include "video_core/gpu_thread.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/syncpoint_manager.h"
 #include "video_core/memory_manager.h"
 #include "video_core/memory_manager.h"
 #include "video_core/renderer_base.h"
 #include "video_core/renderer_base.h"
 #include "video_core/shader_notify.h"
 #include "video_core/shader_notify.h"
 
 
 namespace Tegra {
 namespace Tegra {
 
 
-MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
-
 struct GPU::Impl {
 struct GPU::Impl {
     explicit Impl(GPU& gpu_, Core::System& system_, bool is_async_, bool use_nvdec_)
     explicit Impl(GPU& gpu_, Core::System& system_, bool is_async_, bool use_nvdec_)
-        : gpu{gpu_}, system{system_}, memory_manager{std::make_unique<Tegra::MemoryManager>(
-                                          system)},
-          dma_pusher{std::make_unique<Tegra::DmaPusher>(system, gpu)}, use_nvdec{use_nvdec_},
-          maxwell_3d{std::make_unique<Engines::Maxwell3D>(system, *memory_manager)},
-          fermi_2d{std::make_unique<Engines::Fermi2D>()},
-          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)},
+        : gpu{gpu_}, system{system_}, host1x{system.Host1x()}, use_nvdec{use_nvdec_},
           shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_},
           shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_},
-          gpu_thread{system_, is_async_} {}
+          gpu_thread{system_, is_async_}, scheduler{std::make_unique<Control::Scheduler>(gpu)} {}
 
 
     ~Impl() = default;
     ~Impl() = default;
 
 
-    /// Binds a renderer to the GPU.
-    void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
-        renderer = std::move(renderer_);
-        rasterizer = renderer->ReadRasterizer();
-
-        memory_manager->BindRasterizer(rasterizer);
-        maxwell_3d->BindRasterizer(rasterizer);
-        fermi_2d->BindRasterizer(rasterizer);
-        kepler_compute->BindRasterizer(rasterizer);
-        kepler_memory->BindRasterizer(rasterizer);
-        maxwell_dma->BindRasterizer(rasterizer);
+    std::shared_ptr<Control::ChannelState> CreateChannel(s32 channel_id) {
+        auto channel_state = std::make_shared<Tegra::Control::ChannelState>(channel_id);
+        channels.emplace(channel_id, channel_state);
+        scheduler->DeclareChannel(channel_state);
+        return channel_state;
     }
     }
 
 
-    /// Calls a GPU method.
-    void CallMethod(const GPU::MethodCall& method_call) {
-        LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method,
-                  method_call.subchannel);
+    void BindChannel(s32 channel_id) {
+        if (bound_channel == channel_id) {
+            return;
+        }
+        auto it = channels.find(channel_id);
+        ASSERT(it != channels.end());
+        bound_channel = channel_id;
+        current_channel = it->second.get();
 
 
-        ASSERT(method_call.subchannel < bound_engines.size());
+        rasterizer->BindChannel(*current_channel);
+    }
 
 
-        if (ExecuteMethodOnEngine(method_call.method)) {
-            CallEngineMethod(method_call);
-        } else {
-            CallPullerMethod(method_call);
-        }
+    std::shared_ptr<Control::ChannelState> AllocateChannel() {
+        return CreateChannel(new_channel_id++);
     }
     }
 
 
-    /// Calls a GPU multivalue method.
-    void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
-                         u32 methods_pending) {
-        LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method, subchannel);
+    void InitChannel(Control::ChannelState& to_init) {
+        to_init.Init(system, gpu);
+        to_init.BindRasterizer(rasterizer);
+        rasterizer->InitializeChannel(to_init);
+    }
 
 
-        ASSERT(subchannel < bound_engines.size());
+    void InitAddressSpace(Tegra::MemoryManager& memory_manager) {
+        memory_manager.BindRasterizer(rasterizer);
+    }
 
 
-        if (ExecuteMethodOnEngine(method)) {
-            CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending);
-        } else {
-            for (std::size_t i = 0; i < amount; i++) {
-                CallPullerMethod(GPU::MethodCall{
-                    method,
-                    base_start[i],
-                    subchannel,
-                    methods_pending - static_cast<u32>(i),
-                });
-            }
-        }
+    void ReleaseChannel(Control::ChannelState& to_release) {
+        UNIMPLEMENTED();
+    }
+
+    /// Binds a renderer to the GPU.
+    void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
+        renderer = std::move(renderer_);
+        rasterizer = renderer->ReadRasterizer();
+        host1x.MemoryManager().BindRasterizer(rasterizer);
     }
     }
 
 
     /// Flush all current written commands into the host GPU for execution.
     /// Flush all current written commands into the host GPU for execution.
@@ -103,85 +93,82 @@ struct GPU::Impl {
     }
     }
 
 
     /// Synchronizes CPU writes with Host GPU memory.
     /// Synchronizes CPU writes with Host GPU memory.
-    void SyncGuestHost() {
-        rasterizer->SyncGuestHost();
+    void InvalidateGPUCache() {
+        rasterizer->InvalidateGPUCache();
     }
     }
 
 
     /// Signal the ending of command list.
     /// Signal the ending of command list.
     void OnCommandListEnd() {
     void OnCommandListEnd() {
-        if (is_async) {
-            // This command only applies to asynchronous GPU mode
-            gpu_thread.OnCommandListEnd();
-        }
+        gpu_thread.OnCommandListEnd();
     }
     }
 
 
     /// Request a host GPU memory flush from the CPU.
     /// Request a host GPU memory flush from the CPU.
-    [[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size) {
-        std::unique_lock lck{flush_request_mutex};
-        const u64 fence = ++last_flush_fence;
-        flush_requests.emplace_back(fence, addr, size);
+    template <typename Func>
+    [[nodiscard]] u64 RequestSyncOperation(Func&& action) {
+        std::unique_lock lck{sync_request_mutex};
+        const u64 fence = ++last_sync_fence;
+        sync_requests.emplace_back(action);
         return fence;
         return fence;
     }
     }
 
 
     /// Obtains current flush request fence id.
     /// Obtains current flush request fence id.
-    [[nodiscard]] u64 CurrentFlushRequestFence() const {
-        return current_flush_fence.load(std::memory_order_relaxed);
+    [[nodiscard]] u64 CurrentSyncRequestFence() const {
+        return current_sync_fence.load(std::memory_order_relaxed);
+    }
+
+    void WaitForSyncOperation(const u64 fence) {
+        std::unique_lock lck{sync_request_mutex};
+        sync_request_cv.wait(lck, [this, fence] { return CurrentSyncRequestFence() >= fence; });
     }
     }
 
 
     /// Tick pending requests within the GPU.
     /// Tick pending requests within the GPU.
     void TickWork() {
     void TickWork() {
-        std::unique_lock lck{flush_request_mutex};
-        while (!flush_requests.empty()) {
-            auto& request = flush_requests.front();
-            const u64 fence = request.fence;
-            const VAddr addr = request.addr;
-            const std::size_t size = request.size;
-            flush_requests.pop_front();
-            flush_request_mutex.unlock();
-            rasterizer->FlushRegion(addr, size);
-            current_flush_fence.store(fence);
-            flush_request_mutex.lock();
+        std::unique_lock lck{sync_request_mutex};
+        while (!sync_requests.empty()) {
+            auto request = std::move(sync_requests.front());
+            sync_requests.pop_front();
+            sync_request_mutex.unlock();
+            request();
+            current_sync_fence.fetch_add(1, std::memory_order_release);
+            sync_request_mutex.lock();
+            sync_request_cv.notify_all();
         }
         }
     }
     }
 
 
     /// Returns a reference to the Maxwell3D GPU engine.
     /// Returns a reference to the Maxwell3D GPU engine.
     [[nodiscard]] Engines::Maxwell3D& Maxwell3D() {
     [[nodiscard]] Engines::Maxwell3D& Maxwell3D() {
-        return *maxwell_3d;
+        ASSERT(current_channel);
+        return *current_channel->maxwell_3d;
     }
     }
 
 
     /// Returns a const reference to the Maxwell3D GPU engine.
     /// Returns a const reference to the Maxwell3D GPU engine.
     [[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const {
     [[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const {
-        return *maxwell_3d;
+        ASSERT(current_channel);
+        return *current_channel->maxwell_3d;
     }
     }
 
 
     /// Returns a reference to the KeplerCompute GPU engine.
     /// Returns a reference to the KeplerCompute GPU engine.
     [[nodiscard]] Engines::KeplerCompute& KeplerCompute() {
     [[nodiscard]] Engines::KeplerCompute& KeplerCompute() {
-        return *kepler_compute;
+        ASSERT(current_channel);
+        return *current_channel->kepler_compute;
     }
     }
 
 
     /// Returns a reference to the KeplerCompute GPU engine.
     /// Returns a reference to the KeplerCompute GPU engine.
     [[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const {
     [[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const {
-        return *kepler_compute;
-    }
-
-    /// Returns a reference to the GPU memory manager.
-    [[nodiscard]] Tegra::MemoryManager& MemoryManager() {
-        return *memory_manager;
-    }
-
-    /// Returns a const reference to the GPU memory manager.
-    [[nodiscard]] const Tegra::MemoryManager& MemoryManager() const {
-        return *memory_manager;
+        ASSERT(current_channel);
+        return *current_channel->kepler_compute;
     }
     }
 
 
     /// Returns a reference to the GPU DMA pusher.
     /// Returns a reference to the GPU DMA pusher.
     [[nodiscard]] Tegra::DmaPusher& DmaPusher() {
     [[nodiscard]] Tegra::DmaPusher& DmaPusher() {
-        return *dma_pusher;
+        ASSERT(current_channel);
+        return *current_channel->dma_pusher;
     }
     }
 
 
     /// Returns a const reference to the GPU DMA pusher.
     /// Returns a const reference to the GPU DMA pusher.
     [[nodiscard]] const Tegra::DmaPusher& DmaPusher() const {
     [[nodiscard]] const Tegra::DmaPusher& DmaPusher() const {
-        return *dma_pusher;
+        ASSERT(current_channel);
+        return *current_channel->dma_pusher;
     }
     }
 
 
     /// Returns a reference to the underlying renderer.
     /// Returns a reference to the underlying renderer.
@@ -204,77 +191,6 @@ struct GPU::Impl {
         return *shader_notify;
         return *shader_notify;
     }
     }
 
 
-    /// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame.
-    void WaitFence(u32 syncpoint_id, u32 value) {
-        // Synced GPU, is always in sync
-        if (!is_async) {
-            return;
-        }
-        if (syncpoint_id == UINT32_MAX) {
-            // TODO: Research what this does.
-            LOG_ERROR(HW_GPU, "Waiting for syncpoint -1 not implemented");
-            return;
-        }
-        MICROPROFILE_SCOPE(GPU_wait);
-        std::unique_lock lock{sync_mutex};
-        sync_cv.wait(lock, [=, this] {
-            if (shutting_down.load(std::memory_order_relaxed)) {
-                // We're shutting down, ensure no threads continue to wait for the next syncpoint
-                return true;
-            }
-            return syncpoints.at(syncpoint_id).load() >= value;
-        });
-    }
-
-    void IncrementSyncPoint(u32 syncpoint_id) {
-        auto& syncpoint = syncpoints.at(syncpoint_id);
-        syncpoint++;
-        std::scoped_lock lock{sync_mutex};
-        sync_cv.notify_all();
-        auto& interrupt = syncpt_interrupts.at(syncpoint_id);
-        if (!interrupt.empty()) {
-            u32 value = syncpoint.load();
-            auto it = interrupt.begin();
-            while (it != interrupt.end()) {
-                if (value >= *it) {
-                    TriggerCpuInterrupt(syncpoint_id, *it);
-                    it = interrupt.erase(it);
-                    continue;
-                }
-                it++;
-            }
-        }
-    }
-
-    [[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const {
-        return syncpoints.at(syncpoint_id).load();
-    }
-
-    void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value) {
-        std::scoped_lock lock{sync_mutex};
-        auto& interrupt = syncpt_interrupts.at(syncpoint_id);
-        bool contains = std::any_of(interrupt.begin(), interrupt.end(),
-                                    [value](u32 in_value) { return in_value == value; });
-        if (contains) {
-            return;
-        }
-        interrupt.emplace_back(value);
-    }
-
-    [[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value) {
-        std::scoped_lock lock{sync_mutex};
-        auto& interrupt = syncpt_interrupts.at(syncpoint_id);
-        const auto iter =
-            std::find_if(interrupt.begin(), interrupt.end(),
-                         [value](u32 interrupt_value) { return value == interrupt_value; });
-
-        if (iter == interrupt.end()) {
-            return false;
-        }
-        interrupt.erase(iter);
-        return true;
-    }
-
     [[nodiscard]] u64 GetTicks() const {
     [[nodiscard]] u64 GetTicks() const {
         // This values were reversed engineered by fincs from NVN
         // This values were reversed engineered by fincs from NVN
         // The gpu clock is reported in units of 385/625 nanoseconds
         // The gpu clock is reported in units of 385/625 nanoseconds
@@ -306,7 +222,7 @@ struct GPU::Impl {
     /// This can be used to launch any necessary threads and register any necessary
     /// This can be used to launch any necessary threads and register any necessary
     /// core timing events.
     /// core timing events.
     void Start() {
     void Start() {
-        gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher);
+        gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
         cpu_context = renderer->GetRenderWindow().CreateSharedContext();
         cpu_context = renderer->GetRenderWindow().CreateSharedContext();
         cpu_context->MakeCurrent();
         cpu_context->MakeCurrent();
     }
     }
@@ -328,8 +244,8 @@ struct GPU::Impl {
     }
     }
 
 
     /// Push GPU command entries to be processed
     /// Push GPU command entries to be processed
-    void PushGPUEntries(Tegra::CommandList&& entries) {
-        gpu_thread.SubmitList(std::move(entries));
+    void PushGPUEntries(s32 channel, Tegra::CommandList&& entries) {
+        gpu_thread.SubmitList(channel, std::move(entries));
     }
     }
 
 
     /// Push GPU command buffer entries to be processed
     /// Push GPU command buffer entries to be processed
@@ -339,7 +255,7 @@ struct GPU::Impl {
         }
         }
 
 
         if (!cdma_pushers.contains(id)) {
         if (!cdma_pushers.contains(id)) {
-            cdma_pushers.insert_or_assign(id, std::make_unique<Tegra::CDmaPusher>(gpu));
+            cdma_pushers.insert_or_assign(id, std::make_unique<Tegra::CDmaPusher>(host1x));
         }
         }
 
 
         // SubmitCommandBuffer would make the nvdec operations async, this is not currently working
         // SubmitCommandBuffer would make the nvdec operations async, this is not currently working
@@ -376,308 +292,55 @@ struct GPU::Impl {
         gpu_thread.FlushAndInvalidateRegion(addr, size);
         gpu_thread.FlushAndInvalidateRegion(addr, size);
     }
     }
 
 
-    void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const {
-        auto& interrupt_manager = system.InterruptManager();
-        interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value);
-    }
-
-    void ProcessBindMethod(const GPU::MethodCall& method_call) {
-        // Bind the current subchannel to the desired engine id.
-        LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel,
-                  method_call.argument);
-        const auto engine_id = static_cast<EngineID>(method_call.argument);
-        bound_engines[method_call.subchannel] = static_cast<EngineID>(engine_id);
-        switch (engine_id) {
-        case EngineID::FERMI_TWOD_A:
-            dma_pusher->BindSubchannel(fermi_2d.get(), method_call.subchannel);
-            break;
-        case EngineID::MAXWELL_B:
-            dma_pusher->BindSubchannel(maxwell_3d.get(), method_call.subchannel);
-            break;
-        case EngineID::KEPLER_COMPUTE_B:
-            dma_pusher->BindSubchannel(kepler_compute.get(), method_call.subchannel);
-            break;
-        case EngineID::MAXWELL_DMA_COPY_A:
-            dma_pusher->BindSubchannel(maxwell_dma.get(), method_call.subchannel);
-            break;
-        case EngineID::KEPLER_INLINE_TO_MEMORY_B:
-            dma_pusher->BindSubchannel(kepler_memory.get(), method_call.subchannel);
-            break;
-        default:
-            UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
-        }
-    }
-
-    void ProcessFenceActionMethod() {
-        switch (regs.fence_action.op) {
-        case GPU::FenceOperation::Acquire:
-            WaitFence(regs.fence_action.syncpoint_id, regs.fence_value);
-            break;
-        case GPU::FenceOperation::Increment:
-            IncrementSyncPoint(regs.fence_action.syncpoint_id);
-            break;
-        default:
-            UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
-        }
-    }
-
-    void ProcessWaitForInterruptMethod() {
-        // TODO(bunnei) ImplementMe
-        LOG_WARNING(HW_GPU, "(STUBBED) called");
-    }
-
-    void ProcessSemaphoreTriggerMethod() {
-        const auto semaphoreOperationMask = 0xF;
-        const auto op =
-            static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
-        if (op == GpuSemaphoreOperation::WriteLong) {
-            struct Block {
-                u32 sequence;
-                u32 zeros = 0;
-                u64 timestamp;
-            };
-
-            Block block{};
-            block.sequence = regs.semaphore_sequence;
-            // TODO(Kmather73): Generate a real GPU timestamp and write it here instead of
-            // CoreTiming
-            block.timestamp = GetTicks();
-            memory_manager->WriteBlock(regs.semaphore_address.SemaphoreAddress(), &block,
-                                       sizeof(block));
-        } else {
-            const u32 word{memory_manager->Read<u32>(regs.semaphore_address.SemaphoreAddress())};
-            if ((op == GpuSemaphoreOperation::AcquireEqual && word == regs.semaphore_sequence) ||
-                (op == GpuSemaphoreOperation::AcquireGequal &&
-                 static_cast<s32>(word - regs.semaphore_sequence) > 0) ||
-                (op == GpuSemaphoreOperation::AcquireMask && (word & regs.semaphore_sequence))) {
-                // Nothing to do in this case
+    void RequestSwapBuffers(const Tegra::FramebufferConfig* framebuffer,
+                            std::array<Service::Nvidia::NvFence, 4>& fences, size_t num_fences) {
+        size_t current_request_counter{};
+        {
+            std::unique_lock<std::mutex> lk(request_swap_mutex);
+            if (free_swap_counters.empty()) {
+                current_request_counter = request_swap_counters.size();
+                request_swap_counters.emplace_back(num_fences);
             } else {
             } else {
-                regs.acquire_source = true;
-                regs.acquire_value = regs.semaphore_sequence;
-                if (op == GpuSemaphoreOperation::AcquireEqual) {
-                    regs.acquire_active = true;
-                    regs.acquire_mode = false;
-                } else if (op == GpuSemaphoreOperation::AcquireGequal) {
-                    regs.acquire_active = true;
-                    regs.acquire_mode = true;
-                } else if (op == GpuSemaphoreOperation::AcquireMask) {
-                    // TODO(kemathe) The acquire mask operation waits for a value that, ANDed with
-                    // semaphore_sequence, gives a non-0 result
-                    LOG_ERROR(HW_GPU, "Invalid semaphore operation AcquireMask not implemented");
-                } else {
-                    LOG_ERROR(HW_GPU, "Invalid semaphore operation");
-                }
+                current_request_counter = free_swap_counters.front();
+                request_swap_counters[current_request_counter] = num_fences;
+                free_swap_counters.pop_front();
             }
             }
         }
         }
-    }
-
-    void ProcessSemaphoreRelease() {
-        memory_manager->Write<u32>(regs.semaphore_address.SemaphoreAddress(),
-                                   regs.semaphore_release);
-    }
-
-    void ProcessSemaphoreAcquire() {
-        const u32 word = memory_manager->Read<u32>(regs.semaphore_address.SemaphoreAddress());
-        const auto value = regs.semaphore_acquire;
-        if (word != value) {
-            regs.acquire_active = true;
-            regs.acquire_value = value;
-            // TODO(kemathe73) figure out how to do the acquire_timeout
-            regs.acquire_mode = false;
-            regs.acquire_source = false;
-        }
-    }
-
-    /// Calls a GPU puller method.
-    void CallPullerMethod(const GPU::MethodCall& method_call) {
-        regs.reg_array[method_call.method] = method_call.argument;
-        const auto method = static_cast<BufferMethods>(method_call.method);
-
-        switch (method) {
-        case BufferMethods::BindObject: {
-            ProcessBindMethod(method_call);
-            break;
-        }
-        case BufferMethods::Nop:
-        case BufferMethods::SemaphoreAddressHigh:
-        case BufferMethods::SemaphoreAddressLow:
-        case BufferMethods::SemaphoreSequence:
-            break;
-        case BufferMethods::UnkCacheFlush:
-            rasterizer->SyncGuestHost();
-            break;
-        case BufferMethods::WrcacheFlush:
-            rasterizer->SignalReference();
-            break;
-        case BufferMethods::FenceValue:
-            break;
-        case BufferMethods::RefCnt:
-            rasterizer->SignalReference();
-            break;
-        case BufferMethods::FenceAction:
-            ProcessFenceActionMethod();
-            break;
-        case BufferMethods::WaitForInterrupt:
-            rasterizer->WaitForIdle();
-            break;
-        case BufferMethods::SemaphoreTrigger: {
-            ProcessSemaphoreTriggerMethod();
-            break;
-        }
-        case BufferMethods::NotifyIntr: {
-            // TODO(Kmather73): Research and implement this method.
-            LOG_ERROR(HW_GPU, "Special puller engine method NotifyIntr not implemented");
-            break;
-        }
-        case BufferMethods::Unk28: {
-            // TODO(Kmather73): Research and implement this method.
-            LOG_ERROR(HW_GPU, "Special puller engine method Unk28 not implemented");
-            break;
-        }
-        case BufferMethods::SemaphoreAcquire: {
-            ProcessSemaphoreAcquire();
-            break;
-        }
-        case BufferMethods::SemaphoreRelease: {
-            ProcessSemaphoreRelease();
-            break;
-        }
-        case BufferMethods::Yield: {
-            // TODO(Kmather73): Research and implement this method.
-            LOG_ERROR(HW_GPU, "Special puller engine method Yield not implemented");
-            break;
-        }
-        default:
-            LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method);
-            break;
-        }
-    }
-
-    /// Calls a GPU engine method.
-    void CallEngineMethod(const GPU::MethodCall& method_call) {
-        const EngineID engine = bound_engines[method_call.subchannel];
-
-        switch (engine) {
-        case EngineID::FERMI_TWOD_A:
-            fermi_2d->CallMethod(method_call.method, method_call.argument,
-                                 method_call.IsLastCall());
-            break;
-        case EngineID::MAXWELL_B:
-            maxwell_3d->CallMethod(method_call.method, method_call.argument,
-                                   method_call.IsLastCall());
-            break;
-        case EngineID::KEPLER_COMPUTE_B:
-            kepler_compute->CallMethod(method_call.method, method_call.argument,
-                                       method_call.IsLastCall());
-            break;
-        case EngineID::MAXWELL_DMA_COPY_A:
-            maxwell_dma->CallMethod(method_call.method, method_call.argument,
-                                    method_call.IsLastCall());
-            break;
-        case EngineID::KEPLER_INLINE_TO_MEMORY_B:
-            kepler_memory->CallMethod(method_call.method, method_call.argument,
-                                      method_call.IsLastCall());
-            break;
-        default:
-            UNIMPLEMENTED_MSG("Unimplemented engine");
-        }
-    }
-
-    /// Calls a GPU engine multivalue method.
-    void CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
-                               u32 methods_pending) {
-        const EngineID engine = bound_engines[subchannel];
-
-        switch (engine) {
-        case EngineID::FERMI_TWOD_A:
-            fermi_2d->CallMultiMethod(method, base_start, amount, methods_pending);
-            break;
-        case EngineID::MAXWELL_B:
-            maxwell_3d->CallMultiMethod(method, base_start, amount, methods_pending);
-            break;
-        case EngineID::KEPLER_COMPUTE_B:
-            kepler_compute->CallMultiMethod(method, base_start, amount, methods_pending);
-            break;
-        case EngineID::MAXWELL_DMA_COPY_A:
-            maxwell_dma->CallMultiMethod(method, base_start, amount, methods_pending);
-            break;
-        case EngineID::KEPLER_INLINE_TO_MEMORY_B:
-            kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
-            break;
-        default:
-            UNIMPLEMENTED_MSG("Unimplemented engine");
-        }
-    }
-
-    /// Determines where the method should be executed.
-    [[nodiscard]] bool ExecuteMethodOnEngine(u32 method) {
-        const auto buffer_method = static_cast<BufferMethods>(method);
-        return buffer_method >= BufferMethods::NonPullerMethods;
-    }
-
-    struct Regs {
-        static constexpr size_t NUM_REGS = 0x40;
-
-        union {
-            struct {
-                INSERT_PADDING_WORDS_NOINIT(0x4);
-                struct {
-                    u32 address_high;
-                    u32 address_low;
-
-                    [[nodiscard]] GPUVAddr SemaphoreAddress() const {
-                        return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
-                                                     address_low);
+        const auto wait_fence =
+            RequestSyncOperation([this, current_request_counter, framebuffer, fences, num_fences] {
+                auto& syncpoint_manager = host1x.GetSyncpointManager();
+                if (num_fences == 0) {
+                    renderer->SwapBuffers(framebuffer);
+                }
+                const auto executer = [this, current_request_counter,
+                                       framebuffer_copy = *framebuffer]() {
+                    {
+                        std::unique_lock<std::mutex> lk(request_swap_mutex);
+                        if (--request_swap_counters[current_request_counter] != 0) {
+                            return;
+                        }
+                        free_swap_counters.push_back(current_request_counter);
                     }
                     }
-                } semaphore_address;
-
-                u32 semaphore_sequence;
-                u32 semaphore_trigger;
-                INSERT_PADDING_WORDS_NOINIT(0xC);
-
-                // The pusher and the puller share the reference counter, the pusher only has read
-                // access
-                u32 reference_count;
-                INSERT_PADDING_WORDS_NOINIT(0x5);
-
-                u32 semaphore_acquire;
-                u32 semaphore_release;
-                u32 fence_value;
-                GPU::FenceAction fence_action;
-                INSERT_PADDING_WORDS_NOINIT(0xE2);
-
-                // Puller state
-                u32 acquire_mode;
-                u32 acquire_source;
-                u32 acquire_active;
-                u32 acquire_timeout;
-                u32 acquire_value;
-            };
-            std::array<u32, NUM_REGS> reg_array;
-        };
-    } regs{};
+                    renderer->SwapBuffers(&framebuffer_copy);
+                };
+                for (size_t i = 0; i < num_fences; i++) {
+                    syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
+                }
+            });
+        gpu_thread.TickGPU();
+        WaitForSyncOperation(wait_fence);
+    }
 
 
     GPU& gpu;
     GPU& gpu;
     Core::System& system;
     Core::System& system;
-    std::unique_ptr<Tegra::MemoryManager> memory_manager;
-    std::unique_ptr<Tegra::DmaPusher> dma_pusher;
+    Host1x::Host1x& host1x;
+
     std::map<u32, std::unique_ptr<Tegra::CDmaPusher>> cdma_pushers;
     std::map<u32, std::unique_ptr<Tegra::CDmaPusher>> cdma_pushers;
     std::unique_ptr<VideoCore::RendererBase> renderer;
     std::unique_ptr<VideoCore::RendererBase> renderer;
     VideoCore::RasterizerInterface* rasterizer = nullptr;
     VideoCore::RasterizerInterface* rasterizer = nullptr;
     const bool use_nvdec;
     const bool use_nvdec;
 
 
-    /// Mapping of command subchannels to their bound engine ids
-    std::array<EngineID, 8> bound_engines{};
-    /// 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;
+    s32 new_channel_id{1};
     /// Shader build notifier
     /// Shader build notifier
     std::unique_ptr<VideoCore::ShaderNotify> shader_notify;
     std::unique_ptr<VideoCore::ShaderNotify> shader_notify;
     /// When true, we are about to shut down emulation session, so terminate outstanding tasks
     /// When true, we are about to shut down emulation session, so terminate outstanding tasks
@@ -692,51 +355,25 @@ struct GPU::Impl {
 
 
     std::condition_variable sync_cv;
     std::condition_variable sync_cv;
 
 
-    struct FlushRequest {
-        explicit FlushRequest(u64 fence_, VAddr addr_, std::size_t size_)
-            : fence{fence_}, addr{addr_}, size{size_} {}
-        u64 fence;
-        VAddr addr;
-        std::size_t size;
-    };
-
-    std::list<FlushRequest> flush_requests;
-    std::atomic<u64> current_flush_fence{};
-    u64 last_flush_fence{};
-    std::mutex flush_request_mutex;
+    std::list<std::function<void()>> sync_requests;
+    std::atomic<u64> current_sync_fence{};
+    u64 last_sync_fence{};
+    std::mutex sync_request_mutex;
+    std::condition_variable sync_request_cv;
 
 
     const bool is_async;
     const bool is_async;
 
 
     VideoCommon::GPUThread::ThreadManager gpu_thread;
     VideoCommon::GPUThread::ThreadManager gpu_thread;
     std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
     std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
 
 
-#define ASSERT_REG_POSITION(field_name, position)                                                  \
-    static_assert(offsetof(Regs, field_name) == position * 4,                                      \
-                  "Field " #field_name " has invalid position")
-
-    ASSERT_REG_POSITION(semaphore_address, 0x4);
-    ASSERT_REG_POSITION(semaphore_sequence, 0x6);
-    ASSERT_REG_POSITION(semaphore_trigger, 0x7);
-    ASSERT_REG_POSITION(reference_count, 0x14);
-    ASSERT_REG_POSITION(semaphore_acquire, 0x1A);
-    ASSERT_REG_POSITION(semaphore_release, 0x1B);
-    ASSERT_REG_POSITION(fence_value, 0x1C);
-    ASSERT_REG_POSITION(fence_action, 0x1D);
-
-    ASSERT_REG_POSITION(acquire_mode, 0x100);
-    ASSERT_REG_POSITION(acquire_source, 0x101);
-    ASSERT_REG_POSITION(acquire_active, 0x102);
-    ASSERT_REG_POSITION(acquire_timeout, 0x103);
-    ASSERT_REG_POSITION(acquire_value, 0x104);
-
-#undef ASSERT_REG_POSITION
-
-    enum class GpuSemaphoreOperation {
-        AcquireEqual = 0x1,
-        WriteLong = 0x2,
-        AcquireGequal = 0x4,
-        AcquireMask = 0x8,
-    };
+    std::unique_ptr<Tegra::Control::Scheduler> scheduler;
+    std::unordered_map<s32, std::shared_ptr<Tegra::Control::ChannelState>> channels;
+    Tegra::Control::ChannelState* current_channel;
+    s32 bound_channel{-1};
+
+    std::deque<size_t> free_swap_counters;
+    std::deque<size_t> request_swap_counters;
+    std::mutex request_swap_mutex;
 };
 };
 
 
 GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
 GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
@@ -744,25 +381,36 @@ GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
 
 
 GPU::~GPU() = default;
 GPU::~GPU() = default;
 
 
-void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer) {
-    impl->BindRenderer(std::move(renderer));
+std::shared_ptr<Control::ChannelState> GPU::AllocateChannel() {
+    return impl->AllocateChannel();
+}
+
+void GPU::InitChannel(Control::ChannelState& to_init) {
+    impl->InitChannel(to_init);
+}
+
+void GPU::BindChannel(s32 channel_id) {
+    impl->BindChannel(channel_id);
 }
 }
 
 
-void GPU::CallMethod(const MethodCall& method_call) {
-    impl->CallMethod(method_call);
+void GPU::ReleaseChannel(Control::ChannelState& to_release) {
+    impl->ReleaseChannel(to_release);
 }
 }
 
 
-void GPU::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
-                          u32 methods_pending) {
-    impl->CallMultiMethod(method, subchannel, base_start, amount, methods_pending);
+void GPU::InitAddressSpace(Tegra::MemoryManager& memory_manager) {
+    impl->InitAddressSpace(memory_manager);
+}
+
+void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer) {
+    impl->BindRenderer(std::move(renderer));
 }
 }
 
 
 void GPU::FlushCommands() {
 void GPU::FlushCommands() {
     impl->FlushCommands();
     impl->FlushCommands();
 }
 }
 
 
-void GPU::SyncGuestHost() {
-    impl->SyncGuestHost();
+void GPU::InvalidateGPUCache() {
+    impl->InvalidateGPUCache();
 }
 }
 
 
 void GPU::OnCommandListEnd() {
 void GPU::OnCommandListEnd() {
@@ -770,17 +418,32 @@ void GPU::OnCommandListEnd() {
 }
 }
 
 
 u64 GPU::RequestFlush(VAddr addr, std::size_t size) {
 u64 GPU::RequestFlush(VAddr addr, std::size_t size) {
-    return impl->RequestFlush(addr, size);
+    return impl->RequestSyncOperation(
+        [this, addr, size]() { impl->rasterizer->FlushRegion(addr, size); });
 }
 }
 
 
-u64 GPU::CurrentFlushRequestFence() const {
-    return impl->CurrentFlushRequestFence();
+u64 GPU::CurrentSyncRequestFence() const {
+    return impl->CurrentSyncRequestFence();
+}
+
+void GPU::WaitForSyncOperation(u64 fence) {
+    return impl->WaitForSyncOperation(fence);
 }
 }
 
 
 void GPU::TickWork() {
 void GPU::TickWork() {
     impl->TickWork();
     impl->TickWork();
 }
 }
 
 
+/// Gets a mutable reference to the Host1x interface
+Host1x::Host1x& GPU::Host1x() {
+    return impl->host1x;
+}
+
+/// Gets an immutable reference to the Host1x interface.
+const Host1x::Host1x& GPU::Host1x() const {
+    return impl->host1x;
+}
+
 Engines::Maxwell3D& GPU::Maxwell3D() {
 Engines::Maxwell3D& GPU::Maxwell3D() {
     return impl->Maxwell3D();
     return impl->Maxwell3D();
 }
 }
@@ -797,14 +460,6 @@ const Engines::KeplerCompute& GPU::KeplerCompute() const {
     return impl->KeplerCompute();
     return impl->KeplerCompute();
 }
 }
 
 
-Tegra::MemoryManager& GPU::MemoryManager() {
-    return impl->MemoryManager();
-}
-
-const Tegra::MemoryManager& GPU::MemoryManager() const {
-    return impl->MemoryManager();
-}
-
 Tegra::DmaPusher& GPU::DmaPusher() {
 Tegra::DmaPusher& GPU::DmaPusher() {
     return impl->DmaPusher();
     return impl->DmaPusher();
 }
 }
@@ -829,24 +484,9 @@ const VideoCore::ShaderNotify& GPU::ShaderNotify() const {
     return impl->ShaderNotify();
     return impl->ShaderNotify();
 }
 }
 
 
-void GPU::WaitFence(u32 syncpoint_id, u32 value) {
-    impl->WaitFence(syncpoint_id, value);
-}
-
-void GPU::IncrementSyncPoint(u32 syncpoint_id) {
-    impl->IncrementSyncPoint(syncpoint_id);
-}
-
-u32 GPU::GetSyncpointValue(u32 syncpoint_id) const {
-    return impl->GetSyncpointValue(syncpoint_id);
-}
-
-void GPU::RegisterSyncptInterrupt(u32 syncpoint_id, u32 value) {
-    impl->RegisterSyncptInterrupt(syncpoint_id, value);
-}
-
-bool GPU::CancelSyncptInterrupt(u32 syncpoint_id, u32 value) {
-    return impl->CancelSyncptInterrupt(syncpoint_id, value);
+void GPU::RequestSwapBuffers(const Tegra::FramebufferConfig* framebuffer,
+                             std::array<Service::Nvidia::NvFence, 4>& fences, size_t num_fences) {
+    impl->RequestSwapBuffers(framebuffer, fences, num_fences);
 }
 }
 
 
 u64 GPU::GetTicks() const {
 u64 GPU::GetTicks() const {
@@ -881,8 +521,8 @@ void GPU::ReleaseContext() {
     impl->ReleaseContext();
     impl->ReleaseContext();
 }
 }
 
 
-void GPU::PushGPUEntries(Tegra::CommandList&& entries) {
-    impl->PushGPUEntries(std::move(entries));
+void GPU::PushGPUEntries(s32 channel, Tegra::CommandList&& entries) {
+    impl->PushGPUEntries(channel, std::move(entries));
 }
 }
 
 
 void GPU::PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries) {
 void GPU::PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries) {

+ 32 - 61
src/video_core/gpu.h

@@ -89,73 +89,58 @@ class Maxwell3D;
 class KeplerCompute;
 class KeplerCompute;
 } // namespace Engines
 } // namespace Engines
 
 
-enum class EngineID {
-    FERMI_TWOD_A = 0x902D, // 2D Engine
-    MAXWELL_B = 0xB197,    // 3D Engine
-    KEPLER_COMPUTE_B = 0xB1C0,
-    KEPLER_INLINE_TO_MEMORY_B = 0xA140,
-    MAXWELL_DMA_COPY_A = 0xB0B5,
-};
+namespace Control {
+struct ChannelState;
+}
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
 
 
 class MemoryManager;
 class MemoryManager;
 
 
 class GPU final {
 class GPU final {
 public:
 public:
-    struct MethodCall {
-        u32 method{};
-        u32 argument{};
-        u32 subchannel{};
-        u32 method_count{};
-
-        explicit MethodCall(u32 method_, u32 argument_, u32 subchannel_ = 0, u32 method_count_ = 0)
-            : method(method_), argument(argument_), subchannel(subchannel_),
-              method_count(method_count_) {}
-
-        [[nodiscard]] bool IsLastCall() const {
-            return method_count <= 1;
-        }
-    };
-
-    enum class FenceOperation : u32 {
-        Acquire = 0,
-        Increment = 1,
-    };
-
-    union FenceAction {
-        u32 raw;
-        BitField<0, 1, FenceOperation> op;
-        BitField<8, 24, u32> syncpoint_id;
-    };
-
     explicit GPU(Core::System& system, bool is_async, bool use_nvdec);
     explicit GPU(Core::System& system, bool is_async, bool use_nvdec);
     ~GPU();
     ~GPU();
 
 
     /// Binds a renderer to the GPU.
     /// Binds a renderer to the GPU.
     void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer);
     void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer);
 
 
-    /// Calls a GPU method.
-    void CallMethod(const MethodCall& method_call);
-
-    /// Calls a GPU multivalue method.
-    void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
-                         u32 methods_pending);
-
     /// Flush all current written commands into the host GPU for execution.
     /// Flush all current written commands into the host GPU for execution.
     void FlushCommands();
     void FlushCommands();
     /// Synchronizes CPU writes with Host GPU memory.
     /// Synchronizes CPU writes with Host GPU memory.
-    void SyncGuestHost();
+    void InvalidateGPUCache();
     /// Signal the ending of command list.
     /// Signal the ending of command list.
     void OnCommandListEnd();
     void OnCommandListEnd();
 
 
+    std::shared_ptr<Control::ChannelState> AllocateChannel();
+
+    void InitChannel(Control::ChannelState& to_init);
+
+    void BindChannel(s32 channel_id);
+
+    void ReleaseChannel(Control::ChannelState& to_release);
+
+    void InitAddressSpace(Tegra::MemoryManager& memory_manager);
+
     /// Request a host GPU memory flush from the CPU.
     /// Request a host GPU memory flush from the CPU.
     [[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size);
     [[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size);
 
 
     /// Obtains current flush request fence id.
     /// Obtains current flush request fence id.
-    [[nodiscard]] u64 CurrentFlushRequestFence() const;
+    [[nodiscard]] u64 CurrentSyncRequestFence() const;
+
+    void WaitForSyncOperation(u64 fence);
 
 
     /// Tick pending requests within the GPU.
     /// Tick pending requests within the GPU.
     void TickWork();
     void TickWork();
 
 
+    /// Gets a mutable reference to the Host1x interface
+    [[nodiscard]] Host1x::Host1x& Host1x();
+
+    /// Gets an immutable reference to the Host1x interface.
+    [[nodiscard]] const Host1x::Host1x& Host1x() const;
+
     /// Returns a reference to the Maxwell3D GPU engine.
     /// Returns a reference to the Maxwell3D GPU engine.
     [[nodiscard]] Engines::Maxwell3D& Maxwell3D();
     [[nodiscard]] Engines::Maxwell3D& Maxwell3D();
 
 
@@ -168,12 +153,6 @@ public:
     /// Returns a reference to the KeplerCompute GPU engine.
     /// Returns a reference to the KeplerCompute GPU engine.
     [[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const;
     [[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const;
 
 
-    /// Returns a reference to the GPU memory manager.
-    [[nodiscard]] Tegra::MemoryManager& MemoryManager();
-
-    /// Returns a const reference to the GPU memory manager.
-    [[nodiscard]] const Tegra::MemoryManager& MemoryManager() const;
-
     /// Returns a reference to the GPU DMA pusher.
     /// Returns a reference to the GPU DMA pusher.
     [[nodiscard]] Tegra::DmaPusher& DmaPusher();
     [[nodiscard]] Tegra::DmaPusher& DmaPusher();
 
 
@@ -192,17 +171,6 @@ public:
     /// Returns a const reference to the shader notifier.
     /// Returns a const reference to the shader notifier.
     [[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const;
     [[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const;
 
 
-    /// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame.
-    void WaitFence(u32 syncpoint_id, u32 value);
-
-    void IncrementSyncPoint(u32 syncpoint_id);
-
-    [[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const;
-
-    void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value);
-
-    [[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value);
-
     [[nodiscard]] u64 GetTicks() const;
     [[nodiscard]] u64 GetTicks() const;
 
 
     [[nodiscard]] bool IsAsync() const;
     [[nodiscard]] bool IsAsync() const;
@@ -211,6 +179,9 @@ public:
 
 
     void RendererFrameEndNotify();
     void RendererFrameEndNotify();
 
 
+    void RequestSwapBuffers(const Tegra::FramebufferConfig* framebuffer,
+                            std::array<Service::Nvidia::NvFence, 4>& fences, size_t num_fences);
+
     /// Performs any additional setup necessary in order to begin GPU emulation.
     /// Performs any additional setup necessary in order to begin GPU emulation.
     /// This can be used to launch any necessary threads and register any necessary
     /// This can be used to launch any necessary threads and register any necessary
     /// core timing events.
     /// core timing events.
@@ -226,7 +197,7 @@ public:
     void ReleaseContext();
     void ReleaseContext();
 
 
     /// Push GPU command entries to be processed
     /// Push GPU command entries to be processed
-    void PushGPUEntries(Tegra::CommandList&& entries);
+    void PushGPUEntries(s32 channel, Tegra::CommandList&& entries);
 
 
     /// Push GPU command buffer entries to be processed
     /// Push GPU command buffer entries to be processed
     void PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries);
     void PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries);
@@ -248,7 +219,7 @@ public:
 
 
 private:
 private:
     struct Impl;
     struct Impl;
-    std::unique_ptr<Impl> impl;
+    mutable std::unique_ptr<Impl> impl;
 };
 };
 
 
 } // namespace Tegra
 } // namespace Tegra

+ 13 - 9
src/video_core/gpu_thread.cpp

@@ -8,6 +8,7 @@
 #include "common/thread.h"
 #include "common/thread.h"
 #include "core/core.h"
 #include "core/core.h"
 #include "core/frontend/emu_window.h"
 #include "core/frontend/emu_window.h"
+#include "video_core/control/scheduler.h"
 #include "video_core/dma_pusher.h"
 #include "video_core/dma_pusher.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu.h"
 #include "video_core/gpu_thread.h"
 #include "video_core/gpu_thread.h"
@@ -18,7 +19,7 @@ namespace VideoCommon::GPUThread {
 /// Runs the GPU thread
 /// Runs the GPU thread
 static void RunThread(std::stop_token stop_token, Core::System& system,
 static void RunThread(std::stop_token stop_token, Core::System& system,
                       VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
                       VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
-                      Tegra::DmaPusher& dma_pusher, SynchState& state) {
+                      Tegra::Control::Scheduler& scheduler, SynchState& state) {
     std::string name = "GPU";
     std::string name = "GPU";
     MicroProfileOnThreadCreate(name.c_str());
     MicroProfileOnThreadCreate(name.c_str());
     SCOPE_EXIT({ MicroProfileOnThreadExit(); });
     SCOPE_EXIT({ MicroProfileOnThreadExit(); });
@@ -36,8 +37,7 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
             break;
             break;
         }
         }
         if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
         if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
-            dma_pusher.Push(std::move(submit_list->entries));
-            dma_pusher.DispatchCalls();
+            scheduler.Push(submit_list->channel, std::move(submit_list->entries));
         } else if (const auto* data = std::get_if<SwapBuffersCommand>(&next.data)) {
         } else if (const auto* data = std::get_if<SwapBuffersCommand>(&next.data)) {
             renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
             renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
         } else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) {
         } else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) {
@@ -68,14 +68,14 @@ ThreadManager::~ThreadManager() = default;
 
 
 void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
 void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
                                 Core::Frontend::GraphicsContext& context,
                                 Core::Frontend::GraphicsContext& context,
-                                Tegra::DmaPusher& dma_pusher) {
+                                Tegra::Control::Scheduler& scheduler) {
     rasterizer = renderer.ReadRasterizer();
     rasterizer = renderer.ReadRasterizer();
     thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
     thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
-                          std::ref(dma_pusher), std::ref(state));
+                          std::ref(scheduler), std::ref(state));
 }
 }
 
 
-void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
-    PushCommand(SubmitListCommand(std::move(entries)));
+void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
+    PushCommand(SubmitListCommand(channel, std::move(entries)));
 }
 }
 
 
 void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
 void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
@@ -93,8 +93,12 @@ void ThreadManager::FlushRegion(VAddr addr, u64 size) {
     }
     }
     auto& gpu = system.GPU();
     auto& gpu = system.GPU();
     u64 fence = gpu.RequestFlush(addr, size);
     u64 fence = gpu.RequestFlush(addr, size);
-    PushCommand(GPUTickCommand(), true);
-    ASSERT(fence <= gpu.CurrentFlushRequestFence());
+    TickGPU();
+    gpu.WaitForSyncOperation(fence);
+}
+
+void ThreadManager::TickGPU() {
+    PushCommand(GPUTickCommand());
 }
 }
 
 
 void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
 void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {

+ 10 - 4
src/video_core/gpu_thread.h

@@ -15,7 +15,9 @@
 
 
 namespace Tegra {
 namespace Tegra {
 struct FramebufferConfig;
 struct FramebufferConfig;
-class DmaPusher;
+namespace Control {
+class Scheduler;
+}
 } // namespace Tegra
 } // namespace Tegra
 
 
 namespace Core {
 namespace Core {
@@ -34,8 +36,10 @@ namespace VideoCommon::GPUThread {
 
 
 /// Command to signal to the GPU thread that a command list is ready for processing
 /// Command to signal to the GPU thread that a command list is ready for processing
 struct SubmitListCommand final {
 struct SubmitListCommand final {
-    explicit SubmitListCommand(Tegra::CommandList&& entries_) : entries{std::move(entries_)} {}
+    explicit SubmitListCommand(s32 channel_, Tegra::CommandList&& entries_)
+        : channel{channel_}, entries{std::move(entries_)} {}
 
 
+    s32 channel;
     Tegra::CommandList entries;
     Tegra::CommandList entries;
 };
 };
 
 
@@ -112,10 +116,10 @@ public:
 
 
     /// Creates and starts the GPU thread.
     /// Creates and starts the GPU thread.
     void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
     void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
-                     Tegra::DmaPusher& dma_pusher);
+                     Tegra::Control::Scheduler& scheduler);
 
 
     /// Push GPU command entries to be processed
     /// Push GPU command entries to be processed
-    void SubmitList(Tegra::CommandList&& entries);
+    void SubmitList(s32 channel, Tegra::CommandList&& entries);
 
 
     /// Swap buffers (render frame)
     /// Swap buffers (render frame)
     void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
     void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
@@ -131,6 +135,8 @@ public:
 
 
     void OnCommandListEnd();
     void OnCommandListEnd();
 
 
+    void TickGPU();
+
 private:
 private:
     /// Pushes a command to be executed by the GPU thread
     /// Pushes a command to be executed by the GPU thread
     u64 PushCommand(CommandData&& command_data, bool block = false);
     u64 PushCommand(CommandData&& command_data, bool block = false);

+ 22 - 22
src/video_core/command_classes/codecs/codec.cpp → src/video_core/host1x/codecs/codec.cpp

@@ -6,11 +6,11 @@
 #include <vector>
 #include <vector>
 #include "common/assert.h"
 #include "common/assert.h"
 #include "common/settings.h"
 #include "common/settings.h"
-#include "video_core/command_classes/codecs/codec.h"
-#include "video_core/command_classes/codecs/h264.h"
-#include "video_core/command_classes/codecs/vp8.h"
-#include "video_core/command_classes/codecs/vp9.h"
-#include "video_core/gpu.h"
+#include "video_core/host1x/codecs/codec.h"
+#include "video_core/host1x/codecs/h264.h"
+#include "video_core/host1x/codecs/vp8.h"
+#include "video_core/host1x/codecs/vp9.h"
+#include "video_core/host1x/host1x.h"
 #include "video_core/memory_manager.h"
 #include "video_core/memory_manager.h"
 
 
 extern "C" {
 extern "C" {
@@ -73,10 +73,10 @@ void AVFrameDeleter(AVFrame* ptr) {
     av_frame_free(&ptr);
     av_frame_free(&ptr);
 }
 }
 
 
-Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
-    : gpu(gpu_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(gpu)),
-      vp8_decoder(std::make_unique<Decoder::VP8>(gpu)),
-      vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {}
+Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs)
+    : host1x(host1x_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)),
+      vp8_decoder(std::make_unique<Decoder::VP8>(host1x)),
+      vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {}
 
 
 Codec::~Codec() {
 Codec::~Codec() {
     if (!initialized) {
     if (!initialized) {
@@ -168,11 +168,11 @@ void Codec::InitializeGpuDecoder() {
 void Codec::Initialize() {
 void Codec::Initialize() {
     const AVCodecID codec = [&] {
     const AVCodecID codec = [&] {
         switch (current_codec) {
         switch (current_codec) {
-        case NvdecCommon::VideoCodec::H264:
+        case Host1x::NvdecCommon::VideoCodec::H264:
             return AV_CODEC_ID_H264;
             return AV_CODEC_ID_H264;
-        case NvdecCommon::VideoCodec::VP8:
+        case Host1x::NvdecCommon::VideoCodec::VP8:
             return AV_CODEC_ID_VP8;
             return AV_CODEC_ID_VP8;
-        case NvdecCommon::VideoCodec::VP9:
+        case Host1x::NvdecCommon::VideoCodec::VP9:
             return AV_CODEC_ID_VP9;
             return AV_CODEC_ID_VP9;
         default:
         default:
             UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
             UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
@@ -197,7 +197,7 @@ void Codec::Initialize() {
     initialized = true;
     initialized = true;
 }
 }
 
 
-void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
+void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) {
     if (current_codec != codec) {
     if (current_codec != codec) {
         current_codec = codec;
         current_codec = codec;
         LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", GetCurrentCodecName());
         LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", GetCurrentCodecName());
@@ -215,11 +215,11 @@ void Codec::Decode() {
     bool vp9_hidden_frame = false;
     bool vp9_hidden_frame = false;
     const auto& frame_data = [&]() {
     const auto& frame_data = [&]() {
         switch (current_codec) {
         switch (current_codec) {
-        case Tegra::NvdecCommon::VideoCodec::H264:
+        case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
             return h264_decoder->ComposeFrame(state, is_first_frame);
             return h264_decoder->ComposeFrame(state, is_first_frame);
-        case Tegra::NvdecCommon::VideoCodec::VP8:
+        case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
             return vp8_decoder->ComposeFrame(state);
             return vp8_decoder->ComposeFrame(state);
-        case Tegra::NvdecCommon::VideoCodec::VP9:
+        case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
             vp9_decoder->ComposeFrame(state);
             vp9_decoder->ComposeFrame(state);
             vp9_hidden_frame = vp9_decoder->WasFrameHidden();
             vp9_hidden_frame = vp9_decoder->WasFrameHidden();
             return vp9_decoder->GetFrameBytes();
             return vp9_decoder->GetFrameBytes();
@@ -287,21 +287,21 @@ AVFramePtr Codec::GetCurrentFrame() {
     return frame;
     return frame;
 }
 }
 
 
-NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
+Host1x::NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
     return current_codec;
     return current_codec;
 }
 }
 
 
 std::string_view Codec::GetCurrentCodecName() const {
 std::string_view Codec::GetCurrentCodecName() const {
     switch (current_codec) {
     switch (current_codec) {
-    case NvdecCommon::VideoCodec::None:
+    case Host1x::NvdecCommon::VideoCodec::None:
         return "None";
         return "None";
-    case NvdecCommon::VideoCodec::H264:
+    case Host1x::NvdecCommon::VideoCodec::H264:
         return "H264";
         return "H264";
-    case NvdecCommon::VideoCodec::VP8:
+    case Host1x::NvdecCommon::VideoCodec::VP8:
         return "VP8";
         return "VP8";
-    case NvdecCommon::VideoCodec::H265:
+    case Host1x::NvdecCommon::VideoCodec::H265:
         return "H265";
         return "H265";
-    case NvdecCommon::VideoCodec::VP9:
+    case Host1x::NvdecCommon::VideoCodec::VP9:
         return "VP9";
         return "VP9";
     default:
     default:
         return "Unknown";
         return "Unknown";

+ 12 - 9
src/video_core/command_classes/codecs/codec.h → src/video_core/host1x/codecs/codec.h

@@ -6,8 +6,8 @@
 #include <memory>
 #include <memory>
 #include <string_view>
 #include <string_view>
 #include <queue>
 #include <queue>
-
-#include "video_core/command_classes/nvdec_common.h"
+#include "common/common_types.h"
+#include "video_core/host1x/nvdec_common.h"
 
 
 extern "C" {
 extern "C" {
 #if defined(__GNUC__) || defined(__clang__)
 #if defined(__GNUC__) || defined(__clang__)
@@ -21,7 +21,6 @@ extern "C" {
 }
 }
 
 
 namespace Tegra {
 namespace Tegra {
-class GPU;
 
 
 void AVFrameDeleter(AVFrame* ptr);
 void AVFrameDeleter(AVFrame* ptr);
 using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
 using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
@@ -32,16 +31,20 @@ class VP8;
 class VP9;
 class VP9;
 } // namespace Decoder
 } // namespace Decoder
 
 
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
 class Codec {
 class Codec {
 public:
 public:
-    explicit Codec(GPU& gpu, const NvdecCommon::NvdecRegisters& regs);
+    explicit Codec(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs);
     ~Codec();
     ~Codec();
 
 
     /// Initialize the codec, returning success or failure
     /// Initialize the codec, returning success or failure
     void Initialize();
     void Initialize();
 
 
     /// Sets NVDEC video stream codec
     /// Sets NVDEC video stream codec
-    void SetTargetCodec(NvdecCommon::VideoCodec codec);
+    void SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec);
 
 
     /// Call decoders to construct headers, decode AVFrame with ffmpeg
     /// Call decoders to construct headers, decode AVFrame with ffmpeg
     void Decode();
     void Decode();
@@ -50,7 +53,7 @@ public:
     [[nodiscard]] AVFramePtr GetCurrentFrame();
     [[nodiscard]] AVFramePtr GetCurrentFrame();
 
 
     /// Returns the value of current_codec
     /// Returns the value of current_codec
-    [[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const;
+    [[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const;
 
 
     /// Return name of the current codec
     /// Return name of the current codec
     [[nodiscard]] std::string_view GetCurrentCodecName() const;
     [[nodiscard]] std::string_view GetCurrentCodecName() const;
@@ -63,14 +66,14 @@ private:
     bool CreateGpuAvDevice();
     bool CreateGpuAvDevice();
 
 
     bool initialized{};
     bool initialized{};
-    NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
+    Host1x::NvdecCommon::VideoCodec current_codec{Host1x::NvdecCommon::VideoCodec::None};
 
 
     const AVCodec* av_codec{nullptr};
     const AVCodec* av_codec{nullptr};
     AVCodecContext* av_codec_ctx{nullptr};
     AVCodecContext* av_codec_ctx{nullptr};
     AVBufferRef* av_gpu_decoder{nullptr};
     AVBufferRef* av_gpu_decoder{nullptr};
 
 
-    GPU& gpu;
-    const NvdecCommon::NvdecRegisters& state;
+    Host1x::Host1x& host1x;
+    const Host1x::NvdecCommon::NvdecRegisters& state;
     std::unique_ptr<Decoder::H264> h264_decoder;
     std::unique_ptr<Decoder::H264> h264_decoder;
     std::unique_ptr<Decoder::VP8> vp8_decoder;
     std::unique_ptr<Decoder::VP8> vp8_decoder;
     std::unique_ptr<Decoder::VP9> vp9_decoder;
     std::unique_ptr<Decoder::VP9> vp9_decoder;

+ 9 - 8
src/video_core/command_classes/codecs/h264.cpp → src/video_core/host1x/codecs/h264.cpp

@@ -5,8 +5,8 @@
 #include <bit>
 #include <bit>
 
 
 #include "common/settings.h"
 #include "common/settings.h"
-#include "video_core/command_classes/codecs/h264.h"
-#include "video_core/gpu.h"
+#include "video_core/host1x/codecs/h264.h"
+#include "video_core/host1x/host1x.h"
 #include "video_core/memory_manager.h"
 #include "video_core/memory_manager.h"
 
 
 namespace Tegra::Decoder {
 namespace Tegra::Decoder {
@@ -24,19 +24,20 @@ constexpr std::array<u8, 16> zig_zag_scan{
 };
 };
 } // Anonymous namespace
 } // Anonymous namespace
 
 
-H264::H264(GPU& gpu_) : gpu(gpu_) {}
+H264::H264(Host1x::Host1x& host1x_) : host1x{host1x_} {}
 
 
 H264::~H264() = default;
 H264::~H264() = default;
 
 
-const std::vector<u8>& H264::ComposeFrame(const NvdecCommon::NvdecRegisters& state,
+const std::vector<u8>& H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state,
                                           bool is_first_frame) {
                                           bool is_first_frame) {
     H264DecoderContext context;
     H264DecoderContext context;
-    gpu.MemoryManager().ReadBlock(state.picture_info_offset, &context, sizeof(H264DecoderContext));
+    host1x.MemoryManager().ReadBlock(state.picture_info_offset, &context,
+                                     sizeof(H264DecoderContext));
 
 
     const s64 frame_number = context.h264_parameter_set.frame_number.Value();
     const s64 frame_number = context.h264_parameter_set.frame_number.Value();
     if (!is_first_frame && frame_number != 0) {
     if (!is_first_frame && frame_number != 0) {
         frame.resize(context.stream_len);
         frame.resize(context.stream_len);
-        gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
+        host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
         return frame;
         return frame;
     }
     }
 
 
@@ -155,8 +156,8 @@ const std::vector<u8>& H264::ComposeFrame(const NvdecCommon::NvdecRegisters& sta
     frame.resize(encoded_header.size() + context.stream_len);
     frame.resize(encoded_header.size() + context.stream_len);
     std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
     std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
 
 
-    gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset,
-                                  frame.data() + encoded_header.size(), context.stream_len);
+    host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset,
+                                     frame.data() + encoded_header.size(), context.stream_len);
 
 
     return frame;
     return frame;
 }
 }

+ 10 - 6
src/video_core/command_classes/codecs/h264.h → src/video_core/host1x/codecs/h264.h

@@ -8,10 +8,14 @@
 #include "common/bit_field.h"
 #include "common/bit_field.h"
 #include "common/common_funcs.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
-#include "video_core/command_classes/nvdec_common.h"
+#include "video_core/host1x/nvdec_common.h"
 
 
 namespace Tegra {
 namespace Tegra {
-class GPU;
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
 namespace Decoder {
 namespace Decoder {
 
 
 class H264BitWriter {
 class H264BitWriter {
@@ -55,16 +59,16 @@ private:
 
 
 class H264 {
 class H264 {
 public:
 public:
-    explicit H264(GPU& gpu);
+    explicit H264(Host1x::Host1x& host1x);
     ~H264();
     ~H264();
 
 
     /// Compose the H264 frame for FFmpeg decoding
     /// Compose the H264 frame for FFmpeg decoding
-    [[nodiscard]] const std::vector<u8>& ComposeFrame(const NvdecCommon::NvdecRegisters& state,
-                                                      bool is_first_frame = false);
+    [[nodiscard]] const std::vector<u8>& ComposeFrame(
+        const Host1x::NvdecCommon::NvdecRegisters& state, bool is_first_frame = false);
 
 
 private:
 private:
     std::vector<u8> frame;
     std::vector<u8> frame;
-    GPU& gpu;
+    Host1x::Host1x& host1x;
 
 
     struct H264ParameterSet {
     struct H264ParameterSet {
         s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
         s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00

+ 6 - 6
src/video_core/command_classes/codecs/vp8.cpp → src/video_core/host1x/codecs/vp8.cpp

@@ -3,18 +3,18 @@
 
 
 #include <vector>
 #include <vector>
 
 
-#include "video_core/command_classes/codecs/vp8.h"
-#include "video_core/gpu.h"
+#include "video_core/host1x/codecs/vp8.h"
+#include "video_core/host1x/host1x.h"
 #include "video_core/memory_manager.h"
 #include "video_core/memory_manager.h"
 
 
 namespace Tegra::Decoder {
 namespace Tegra::Decoder {
-VP8::VP8(GPU& gpu_) : gpu(gpu_) {}
+VP8::VP8(Host1x::Host1x& host1x_) : host1x{host1x_} {}
 
 
 VP8::~VP8() = default;
 VP8::~VP8() = default;
 
 
-const std::vector<u8>& VP8::ComposeFrame(const NvdecCommon::NvdecRegisters& state) {
+const std::vector<u8>& VP8::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state) {
     VP8PictureInfo info;
     VP8PictureInfo info;
-    gpu.MemoryManager().ReadBlock(state.picture_info_offset, &info, sizeof(VP8PictureInfo));
+    host1x.MemoryManager().ReadBlock(state.picture_info_offset, &info, sizeof(VP8PictureInfo));
 
 
     const bool is_key_frame = info.key_frame == 1u;
     const bool is_key_frame = info.key_frame == 1u;
     const auto bitstream_size = static_cast<size_t>(info.vld_buffer_size);
     const auto bitstream_size = static_cast<size_t>(info.vld_buffer_size);
@@ -45,7 +45,7 @@ const std::vector<u8>& VP8::ComposeFrame(const NvdecCommon::NvdecRegisters& stat
         frame[9] = static_cast<u8>(((info.frame_height >> 8) & 0x3f));
         frame[9] = static_cast<u8>(((info.frame_height >> 8) & 0x3f));
     }
     }
     const u64 bitstream_offset = state.frame_bitstream_offset;
     const u64 bitstream_offset = state.frame_bitstream_offset;
-    gpu.MemoryManager().ReadBlock(bitstream_offset, frame.data() + header_size, bitstream_size);
+    host1x.MemoryManager().ReadBlock(bitstream_offset, frame.data() + header_size, bitstream_size);
 
 
     return frame;
     return frame;
 }
 }

+ 10 - 5
src/video_core/command_classes/codecs/vp8.h → src/video_core/host1x/codecs/vp8.h

@@ -8,23 +8,28 @@
 
 
 #include "common/common_funcs.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
-#include "video_core/command_classes/nvdec_common.h"
+#include "video_core/host1x/nvdec_common.h"
 
 
 namespace Tegra {
 namespace Tegra {
-class GPU;
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
 namespace Decoder {
 namespace Decoder {
 
 
 class VP8 {
 class VP8 {
 public:
 public:
-    explicit VP8(GPU& gpu);
+    explicit VP8(Host1x::Host1x& host1x);
     ~VP8();
     ~VP8();
 
 
     /// Compose the VP8 frame for FFmpeg decoding
     /// Compose the VP8 frame for FFmpeg decoding
-    [[nodiscard]] const std::vector<u8>& ComposeFrame(const NvdecCommon::NvdecRegisters& state);
+    [[nodiscard]] const std::vector<u8>& ComposeFrame(
+        const Host1x::NvdecCommon::NvdecRegisters& state);
 
 
 private:
 private:
     std::vector<u8> frame;
     std::vector<u8> frame;
-    GPU& gpu;
+    Host1x::Host1x& host1x;
 
 
     struct VP8PictureInfo {
     struct VP8PictureInfo {
         INSERT_PADDING_WORDS_NOINIT(14);
         INSERT_PADDING_WORDS_NOINIT(14);

+ 12 - 11
src/video_core/command_classes/codecs/vp9.cpp → src/video_core/host1x/codecs/vp9.cpp

@@ -4,8 +4,8 @@
 #include <algorithm> // for std::copy
 #include <algorithm> // for std::copy
 #include <numeric>
 #include <numeric>
 #include "common/assert.h"
 #include "common/assert.h"
-#include "video_core/command_classes/codecs/vp9.h"
-#include "video_core/gpu.h"
+#include "video_core/host1x/codecs/vp9.h"
+#include "video_core/host1x/host1x.h"
 #include "video_core/memory_manager.h"
 #include "video_core/memory_manager.h"
 
 
 namespace Tegra::Decoder {
 namespace Tegra::Decoder {
@@ -236,7 +236,7 @@ constexpr std::array<u8, 254> map_lut{
 }
 }
 } // Anonymous namespace
 } // Anonymous namespace
 
 
-VP9::VP9(GPU& gpu_) : gpu{gpu_} {}
+VP9::VP9(Host1x::Host1x& host1x_) : host1x{host1x_} {}
 
 
 VP9::~VP9() = default;
 VP9::~VP9() = default;
 
 
@@ -355,9 +355,9 @@ void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_
     }
     }
 }
 }
 
 
-Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state) {
+Vp9PictureInfo VP9::GetVp9PictureInfo(const Host1x::NvdecCommon::NvdecRegisters& state) {
     PictureInfo picture_info;
     PictureInfo picture_info;
-    gpu.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo));
+    host1x.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo));
     Vp9PictureInfo vp9_info = picture_info.Convert();
     Vp9PictureInfo vp9_info = picture_info.Convert();
 
 
     InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
     InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
@@ -372,18 +372,19 @@ Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state)
 
 
 void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
 void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
     EntropyProbs entropy;
     EntropyProbs entropy;
-    gpu.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs));
+    host1x.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs));
     entropy.Convert(dst);
     entropy.Convert(dst);
 }
 }
 
 
-Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state) {
+Vp9FrameContainer VP9::GetCurrentFrame(const Host1x::NvdecCommon::NvdecRegisters& state) {
     Vp9FrameContainer current_frame{};
     Vp9FrameContainer current_frame{};
     {
     {
-        gpu.SyncGuestHost();
+        // gpu.SyncGuestHost(); epic, why?
         current_frame.info = GetVp9PictureInfo(state);
         current_frame.info = GetVp9PictureInfo(state);
         current_frame.bit_stream.resize(current_frame.info.bitstream_size);
         current_frame.bit_stream.resize(current_frame.info.bitstream_size);
-        gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(),
-                                      current_frame.info.bitstream_size);
+        host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset,
+                                         current_frame.bit_stream.data(),
+                                         current_frame.info.bitstream_size);
     }
     }
     if (!next_frame.bit_stream.empty()) {
     if (!next_frame.bit_stream.empty()) {
         Vp9FrameContainer temp{
         Vp9FrameContainer temp{
@@ -769,7 +770,7 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
     return uncomp_writer;
     return uncomp_writer;
 }
 }
 
 
-void VP9::ComposeFrame(const NvdecCommon::NvdecRegisters& state) {
+void VP9::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state) {
     std::vector<u8> bitstream;
     std::vector<u8> bitstream;
     {
     {
         Vp9FrameContainer curr_frame = GetCurrentFrame(state);
         Vp9FrameContainer curr_frame = GetCurrentFrame(state);

+ 14 - 8
src/video_core/command_classes/codecs/vp9.h → src/video_core/host1x/codecs/vp9.h

@@ -8,11 +8,15 @@
 
 
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "common/stream.h"
 #include "common/stream.h"
-#include "video_core/command_classes/codecs/vp9_types.h"
-#include "video_core/command_classes/nvdec_common.h"
+#include "video_core/host1x/codecs/vp9_types.h"
+#include "video_core/host1x/nvdec_common.h"
 
 
 namespace Tegra {
 namespace Tegra {
-class GPU;
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
 namespace Decoder {
 namespace Decoder {
 
 
 /// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
 /// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
@@ -106,7 +110,7 @@ private:
 
 
 class VP9 {
 class VP9 {
 public:
 public:
-    explicit VP9(GPU& gpu_);
+    explicit VP9(Host1x::Host1x& host1x);
     ~VP9();
     ~VP9();
 
 
     VP9(const VP9&) = delete;
     VP9(const VP9&) = delete;
@@ -117,7 +121,7 @@ public:
 
 
     /// Composes the VP9 frame from the GPU state information.
     /// Composes the VP9 frame from the GPU state information.
     /// Based on the official VP9 spec documentation
     /// Based on the official VP9 spec documentation
-    void ComposeFrame(const NvdecCommon::NvdecRegisters& state);
+    void ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state);
 
 
     /// Returns true if the most recent frame was a hidden frame.
     /// Returns true if the most recent frame was a hidden frame.
     [[nodiscard]] bool WasFrameHidden() const {
     [[nodiscard]] bool WasFrameHidden() const {
@@ -162,19 +166,21 @@ private:
     void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
     void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
 
 
     /// Returns VP9 information from NVDEC provided offset and size
     /// Returns VP9 information from NVDEC provided offset and size
-    [[nodiscard]] Vp9PictureInfo GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state);
+    [[nodiscard]] Vp9PictureInfo GetVp9PictureInfo(
+        const Host1x::NvdecCommon::NvdecRegisters& state);
 
 
     /// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct
     /// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct
     void InsertEntropy(u64 offset, Vp9EntropyProbs& dst);
     void InsertEntropy(u64 offset, Vp9EntropyProbs& dst);
 
 
     /// Returns frame to be decoded after buffering
     /// Returns frame to be decoded after buffering
-    [[nodiscard]] Vp9FrameContainer GetCurrentFrame(const NvdecCommon::NvdecRegisters& state);
+    [[nodiscard]] Vp9FrameContainer GetCurrentFrame(
+        const Host1x::NvdecCommon::NvdecRegisters& state);
 
 
     /// Use NVDEC providied information to compose the headers for the current frame
     /// Use NVDEC providied information to compose the headers for the current frame
     [[nodiscard]] std::vector<u8> ComposeCompressedHeader();
     [[nodiscard]] std::vector<u8> ComposeCompressedHeader();
     [[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
     [[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
 
 
-    GPU& gpu;
+    Host1x::Host1x& host1x;
     std::vector<u8> frame;
     std::vector<u8> frame;
 
 
     std::array<s8, 4> loop_filter_ref_deltas{};
     std::array<s8, 4> loop_filter_ref_deltas{};

+ 0 - 1
src/video_core/command_classes/codecs/vp9_types.h → src/video_core/host1x/codecs/vp9_types.h

@@ -9,7 +9,6 @@
 #include "common/common_types.h"
 #include "common/common_types.h"
 
 
 namespace Tegra {
 namespace Tegra {
-class GPU;
 
 
 namespace Decoder {
 namespace Decoder {
 struct Vp9FrameDimensions {
 struct Vp9FrameDimensions {

+ 33 - 0
src/video_core/host1x/control.cpp

@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/assert.h"
+#include "video_core/host1x/control.h"
+#include "video_core/host1x/host1x.h"
+
+namespace Tegra::Host1x {
+
+Control::Control(Host1x& host1x_) : host1x(host1x_) {}
+
+Control::~Control() = default;
+
+void Control::ProcessMethod(Method method, u32 argument) {
+    switch (method) {
+    case Method::LoadSyncptPayload32:
+        syncpoint_value = argument;
+        break;
+    case Method::WaitSyncpt:
+    case Method::WaitSyncpt32:
+        Execute(argument);
+        break;
+    default:
+        UNIMPLEMENTED_MSG("Control method 0x{:X}", static_cast<u32>(method));
+        break;
+    }
+}
+
+void Control::Execute(u32 data) {
+    host1x.GetSyncpointManager().WaitHost(data, syncpoint_value);
+}
+
+} // namespace Tegra::Host1x

+ 13 - 7
src/video_core/command_classes/host1x.h → src/video_core/host1x/control.h

@@ -1,15 +1,19 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
 
 
 #pragma once
 #pragma once
 
 
 #include "common/common_types.h"
 #include "common/common_types.h"
 
 
 namespace Tegra {
 namespace Tegra {
-class GPU;
+
+namespace Host1x {
+
+class Host1x;
 class Nvdec;
 class Nvdec;
 
 
-class Host1x {
+class Control {
 public:
 public:
     enum class Method : u32 {
     enum class Method : u32 {
         WaitSyncpt = 0x8,
         WaitSyncpt = 0x8,
@@ -17,8 +21,8 @@ public:
         WaitSyncpt32 = 0x50,
         WaitSyncpt32 = 0x50,
     };
     };
 
 
-    explicit Host1x(GPU& gpu);
-    ~Host1x();
+    explicit Control(Host1x& host1x);
+    ~Control();
 
 
     /// Writes the method into the state, Invoke Execute() if encountered
     /// Writes the method into the state, Invoke Execute() if encountered
     void ProcessMethod(Method method, u32 argument);
     void ProcessMethod(Method method, u32 argument);
@@ -28,7 +32,9 @@ private:
     void Execute(u32 data);
     void Execute(u32 data);
 
 
     u32 syncpoint_value{};
     u32 syncpoint_value{};
-    GPU& gpu;
+    Host1x& host1x;
 };
 };
 
 
+} // namespace Host1x
+
 } // namespace Tegra
 } // namespace Tegra

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff