/ src / video_core / renderer_vulkan / vk_scheduler.h
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