vk_scheduler.h
1 // Copyright 2019 yuzu Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #pragma once 6 7 #include <memory> 8 #include <utility> 9 #include "common/alignment.h" 10 #include "common/common_funcs.h" 11 #include "common/polyfill_thread.h" 12 #include "video_core/renderer_vulkan/vk_master_semaphore.h" 13 #include "video_core/renderer_vulkan/vk_resource_pool.h" 14 15 namespace Vulkan { 16 17 enum class StateFlags { 18 AllDirty = 0, 19 Pipeline = 1 << 0, 20 DescriptorSets = 1 << 1, 21 }; 22 DECLARE_ENUM_FLAG_OPERATORS(StateFlags) 23 24 class Instance; 25 26 /// The scheduler abstracts command buffer and fence management with an interface that's able to do 27 /// OpenGL-like operations on Vulkan command buffers. 28 class Scheduler { 29 public: 30 explicit Scheduler(const Instance& instance); 31 ~Scheduler(); 32 33 /// Sends the current execution context to the GPU. 34 void Flush(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr); 35 36 /// Sends the current execution context to the GPU and waits for it to complete. 37 void Finish(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr); 38 39 /// Waits for the worker thread to finish executing everything. After this function returns it's 40 /// safe to touch worker resources. 41 void WaitWorker(); 42 43 /// Waits for the given tick to trigger on the GPU. 44 void Wait(u64 tick); 45 46 /// Sends currently recorded work to the worker thread. 47 void DispatchWork(); 48 49 /// Records the command to the current chunk. 50 template <typename T> 51 void Record(T&& command) { 52 if (!use_worker_thread) { 53 command(current_cmdbuf); 54 return; 55 } 56 57 if (chunk->Record(command)) { 58 return; 59 } 60 DispatchWork(); 61 (void)chunk->Record(command); 62 } 63 64 /// Marks the provided state as non dirty 65 void MarkStateNonDirty(StateFlags flag) noexcept { 66 state |= flag; 67 } 68 69 /// Marks the provided state as dirty 70 void MakeDirty(StateFlags flag) noexcept { 71 state &= ~flag; 72 } 73 74 /// Returns true if the state is dirty 75 [[nodiscard]] bool IsStateDirty(StateFlags flag) const noexcept { 76 return False(state & flag); 77 } 78 79 /// Returns the current command buffer tick. 80 [[nodiscard]] u64 CurrentTick() const noexcept { 81 return master_semaphore->CurrentTick(); 82 } 83 84 /// Returns true when a tick has been triggered by the GPU. 85 [[nodiscard]] bool IsFree(u64 tick) const noexcept { 86 return master_semaphore->IsFree(tick); 87 } 88 89 /// Returns the master timeline semaphore. 90 [[nodiscard]] MasterSemaphore* GetMasterSemaphore() noexcept { 91 return master_semaphore.get(); 92 } 93 94 std::mutex submit_mutex; 95 96 private: 97 class Command { 98 public: 99 virtual ~Command() = default; 100 101 virtual void Execute(vk::CommandBuffer cmdbuf) const = 0; 102 103 Command* GetNext() const { 104 return next; 105 } 106 107 void SetNext(Command* next_) { 108 next = next_; 109 } 110 111 private: 112 Command* next = nullptr; 113 }; 114 115 template <typename T> 116 class TypedCommand final : public Command { 117 public: 118 explicit TypedCommand(T&& command_) : command{std::move(command_)} {} 119 ~TypedCommand() override = default; 120 121 TypedCommand(TypedCommand&&) = delete; 122 TypedCommand& operator=(TypedCommand&&) = delete; 123 124 void Execute(vk::CommandBuffer cmdbuf) const override { 125 command(cmdbuf); 126 } 127 128 private: 129 T command; 130 }; 131 132 class CommandChunk final { 133 public: 134 void ExecuteAll(vk::CommandBuffer cmdbuf); 135 136 template <typename T> 137 bool Record(T& command) { 138 using FuncType = TypedCommand<T>; 139 static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large"); 140 141 recorded_counts++; 142 command_offset = Common::AlignUp(command_offset, alignof(FuncType)); 143 if (command_offset > sizeof(data) - sizeof(FuncType)) { 144 return false; 145 } 146 Command* const current_last = last; 147 last = new (data.data() + command_offset) FuncType(std::move(command)); 148 149 if (current_last) { 150 current_last->SetNext(last); 151 } else { 152 first = last; 153 } 154 command_offset += sizeof(FuncType); 155 return true; 156 } 157 158 void MarkSubmit() { 159 submit = true; 160 } 161 162 bool Empty() const { 163 return recorded_counts == 0; 164 } 165 166 bool HasSubmit() const { 167 return submit; 168 } 169 170 private: 171 Command* first = nullptr; 172 Command* last = nullptr; 173 174 std::size_t recorded_counts = 0; 175 std::size_t command_offset = 0; 176 bool submit = false; 177 alignas(std::max_align_t) std::array<u8, 0x8000> data{}; 178 }; 179 180 private: 181 void WorkerThread(std::stop_token stop_token); 182 183 void AllocateWorkerCommandBuffers(); 184 185 void SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore); 186 187 void AcquireNewChunk(); 188 189 private: 190 std::unique_ptr<MasterSemaphore> master_semaphore; 191 CommandPool command_pool; 192 std::unique_ptr<CommandChunk> chunk; 193 std::queue<std::unique_ptr<CommandChunk>> work_queue; 194 std::vector<std::unique_ptr<CommandChunk>> chunk_reserve; 195 vk::CommandBuffer current_cmdbuf; 196 StateFlags state{}; 197 std::mutex execution_mutex; 198 std::mutex reserve_mutex; 199 std::mutex queue_mutex; 200 std::condition_variable_any event_cv; 201 std::jthread worker_thread; 202 bool use_worker_thread; 203 }; 204 205 } // namespace Vulkan