summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar ReinUsesLisp2019-12-13 02:24:48 -0300
committerGravatar ReinUsesLisp2019-12-13 02:24:48 -0300
commit2df9a2dcaf73bb548eea8584f2b47d18b18ea116 (patch)
tree03552b867c0577604a8348908c0b900a1d7a2344 /src
parentMerge pull request #3217 from jhol/fix-boost-include (diff)
downloadyuzu-2df9a2dcaf73bb548eea8584f2b47d18b18ea116.tar.gz
yuzu-2df9a2dcaf73bb548eea8584f2b47d18b18ea116.tar.xz
yuzu-2df9a2dcaf73bb548eea8584f2b47d18b18ea116.zip
vk_scheduler: Delegate commands to a worker thread and state track
Introduce a worker thread approach for delegating Vulkan work derived from dxvk's approach. https://github.com/doitsujin/dxvk Now that the scheduler is what handles all Vulkan work related to command streaming, store state tracking in itself. This way we can know when to reupload Vulkan dynamic state to the queue (since this one is invalidated between command buffers unlike NVN). We can also store the renderpass state and graphics pipeline bound to avoid redundant binds and renderpass begins/ends.
Diffstat (limited to 'src')
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp150
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h198
2 files changed, 311 insertions, 37 deletions
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 0f8116458..d66133ad1 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -3,7 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/logging/log.h" 6#include "common/microprofile.h"
7#include "video_core/renderer_vulkan/declarations.h" 7#include "video_core/renderer_vulkan/declarations.h"
8#include "video_core/renderer_vulkan/vk_device.h" 8#include "video_core/renderer_vulkan/vk_device.h"
9#include "video_core/renderer_vulkan/vk_resource_manager.h" 9#include "video_core/renderer_vulkan/vk_resource_manager.h"
@@ -11,46 +11,172 @@
11 11
12namespace Vulkan { 12namespace Vulkan {
13 13
14MICROPROFILE_DECLARE(Vulkan_WaitForWorker);
15
16void VKScheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf,
17 const vk::DispatchLoaderDynamic& dld) {
18 auto command = first;
19 while (command != nullptr) {
20 auto next = command->GetNext();
21 command->Execute(cmdbuf, dld);
22 command->~Command();
23 command = next;
24 }
25
26 command_offset = 0;
27 first = nullptr;
28 last = nullptr;
29}
30
14VKScheduler::VKScheduler(const VKDevice& device, VKResourceManager& resource_manager) 31VKScheduler::VKScheduler(const VKDevice& device, VKResourceManager& resource_manager)
15 : device{device}, resource_manager{resource_manager} { 32 : device{device}, resource_manager{resource_manager}, next_fence{
16 next_fence = &resource_manager.CommitFence(); 33 &resource_manager.CommitFence()} {
34 AcquireNewChunk();
17 AllocateNewContext(); 35 AllocateNewContext();
36 worker_thread = std::thread(&VKScheduler::WorkerThread, this);
18} 37}
19 38
20VKScheduler::~VKScheduler() = default; 39VKScheduler::~VKScheduler() {
40 quit = true;
41 cv.notify_all();
42 worker_thread.join();
43}
21 44
22void VKScheduler::Flush(bool release_fence, vk::Semaphore semaphore) { 45void VKScheduler::Flush(bool release_fence, vk::Semaphore semaphore) {
23 SubmitExecution(semaphore); 46 SubmitExecution(semaphore);
24 if (release_fence) 47 if (release_fence) {
25 current_fence->Release(); 48 current_fence->Release();
49 }
26 AllocateNewContext(); 50 AllocateNewContext();
27} 51}
28 52
29void VKScheduler::Finish(bool release_fence, vk::Semaphore semaphore) { 53void VKScheduler::Finish(bool release_fence, vk::Semaphore semaphore) {
30 SubmitExecution(semaphore); 54 SubmitExecution(semaphore);
31 current_fence->Wait(); 55 current_fence->Wait();
32 if (release_fence) 56 if (release_fence) {
33 current_fence->Release(); 57 current_fence->Release();
58 }
34 AllocateNewContext(); 59 AllocateNewContext();
35} 60}
36 61
62void VKScheduler::WaitWorker() {
63 MICROPROFILE_SCOPE(Vulkan_WaitForWorker);
64 DispatchWork();
65
66 bool finished = false;
67 do {
68 cv.notify_all();
69 std::unique_lock lock{mutex};
70 finished = chunk_queue.Empty();
71 } while (!finished);
72}
73
74void VKScheduler::DispatchWork() {
75 if (chunk->Empty()) {
76 return;
77 }
78 chunk_queue.Push(std::move(chunk));
79 cv.notify_all();
80 AcquireNewChunk();
81}
82
83void VKScheduler::RequestRenderpass(const vk::RenderPassBeginInfo& renderpass_bi) {
84 if (state.renderpass && renderpass_bi == *state.renderpass) {
85 return;
86 }
87 const bool end_renderpass = state.renderpass.has_value();
88 state.renderpass = renderpass_bi;
89 Record([renderpass_bi, end_renderpass](auto cmdbuf, auto& dld) {
90 if (end_renderpass) {
91 cmdbuf.endRenderPass(dld);
92 }
93 cmdbuf.beginRenderPass(renderpass_bi, vk::SubpassContents::eInline, dld);
94 });
95}
96
97void VKScheduler::RequestOutsideRenderPassOperationContext() {
98 EndRenderPass();
99}
100
101void VKScheduler::BindGraphicsPipeline(vk::Pipeline pipeline) {
102 if (state.graphics_pipeline == pipeline) {
103 return;
104 }
105 state.graphics_pipeline = pipeline;
106 Record([pipeline](auto cmdbuf, auto& dld) {
107 cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, dld);
108 });
109}
110
111void VKScheduler::WorkerThread() {
112 std::unique_lock lock{mutex};
113 do {
114 cv.wait(lock, [this] { return !chunk_queue.Empty() || quit; });
115 if (quit) {
116 continue;
117 }
118 auto extracted_chunk = std::move(chunk_queue.Front());
119 chunk_queue.Pop();
120 extracted_chunk->ExecuteAll(current_cmdbuf, device.GetDispatchLoader());
121 chunk_reserve.Push(std::move(extracted_chunk));
122 } while (!quit);
123}
124
37void VKScheduler::SubmitExecution(vk::Semaphore semaphore) { 125void VKScheduler::SubmitExecution(vk::Semaphore semaphore) {
126 EndPendingOperations();
127 InvalidateState();
128 WaitWorker();
129
130 std::unique_lock lock{mutex};
131
132 const auto queue = device.GetGraphicsQueue();
38 const auto& dld = device.GetDispatchLoader(); 133 const auto& dld = device.GetDispatchLoader();
39 current_cmdbuf.end(dld); 134 current_cmdbuf.end(dld);
40 135
41 const auto queue = device.GetGraphicsQueue(); 136 const vk::SubmitInfo submit_info(0, nullptr, nullptr, 1, &current_cmdbuf, semaphore ? 1U : 0U,
42 const vk::SubmitInfo submit_info(0, nullptr, nullptr, 1, &current_cmdbuf, semaphore ? 1u : 0u,
43 &semaphore); 137 &semaphore);
44 queue.submit({submit_info}, *current_fence, dld); 138 queue.submit({submit_info}, static_cast<vk::Fence>(*current_fence), dld);
45} 139}
46 140
47void VKScheduler::AllocateNewContext() { 141void VKScheduler::AllocateNewContext() {
142 std::unique_lock lock{mutex};
48 current_fence = next_fence; 143 current_fence = next_fence;
49 current_cmdbuf = resource_manager.CommitCommandBuffer(*current_fence);
50 next_fence = &resource_manager.CommitFence(); 144 next_fence = &resource_manager.CommitFence();
51 145
52 const auto& dld = device.GetDispatchLoader(); 146 current_cmdbuf = resource_manager.CommitCommandBuffer(*current_fence);
53 current_cmdbuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}, dld); 147 current_cmdbuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit},
148 device.GetDispatchLoader());
149}
150
151void VKScheduler::InvalidateState() {
152 state.graphics_pipeline = nullptr;
153 state.viewports = false;
154 state.scissors = false;
155 state.depth_bias = false;
156 state.blend_constants = false;
157 state.depth_bounds = false;
158 state.stencil_values = false;
159}
160
161void VKScheduler::EndPendingOperations() {
162 EndRenderPass();
163}
164
165void VKScheduler::EndRenderPass() {
166 if (!state.renderpass) {
167 return;
168 }
169 state.renderpass = std::nullopt;
170 Record([](auto cmdbuf, auto& dld) { cmdbuf.endRenderPass(dld); });
171}
172
173void VKScheduler::AcquireNewChunk() {
174 if (chunk_reserve.Empty()) {
175 chunk = std::make_unique<CommandChunk>();
176 return;
177 }
178 chunk = std::move(chunk_reserve.Front());
179 chunk_reserve.Pop();
54} 180}
55 181
56} // namespace Vulkan 182} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 0e5b49c7f..bcdffbba0 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -4,7 +4,14 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <condition_variable>
8#include <memory>
9#include <optional>
10#include <stack>
11#include <thread>
12#include <utility>
7#include "common/common_types.h" 13#include "common/common_types.h"
14#include "common/threadsafe_queue.h"
8#include "video_core/renderer_vulkan/declarations.h" 15#include "video_core/renderer_vulkan/declarations.h"
9 16
10namespace Vulkan { 17namespace Vulkan {
@@ -30,56 +37,197 @@ private:
30 VKFence* const& fence; 37 VKFence* const& fence;
31}; 38};
32 39
33class VKCommandBufferView { 40/// The scheduler abstracts command buffer and fence management with an interface that's able to do
41/// OpenGL-like operations on Vulkan command buffers.
42class VKScheduler {
34public: 43public:
35 VKCommandBufferView() = default; 44 explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager);
36 VKCommandBufferView(const vk::CommandBuffer& cmdbuf) : cmdbuf{cmdbuf} {} 45 ~VKScheduler();
46
47 /// Sends the current execution context to the GPU.
48 void Flush(bool release_fence = true, vk::Semaphore semaphore = nullptr);
49
50 /// Sends the current execution context to the GPU and waits for it to complete.
51 void Finish(bool release_fence = true, vk::Semaphore semaphore = nullptr);
52
53 /// Waits for the worker thread to finish executing everything. After this function returns it's
54 /// safe to touch worker resources.
55 void WaitWorker();
56
57 /// Sends currently recorded work to the worker thread.
58 void DispatchWork();
59
60 /// Requests to begin a renderpass.
61 void RequestRenderpass(const vk::RenderPassBeginInfo& renderpass_bi);
62
63 /// Requests the current executino context to be able to execute operations only allowed outside
64 /// of a renderpass.
65 void RequestOutsideRenderPassOperationContext();
66
67 /// Binds a pipeline to the current execution context.
68 void BindGraphicsPipeline(vk::Pipeline pipeline);
37 69
38 const vk::CommandBuffer* operator->() const noexcept { 70 /// Returns true when viewports have been set in the current command buffer.
39 return &cmdbuf; 71 bool TouchViewports() {
72 return std::exchange(state.viewports, true);
40 } 73 }
41 74
42 operator vk::CommandBuffer() const noexcept { 75 /// Returns true when scissors have been set in the current command buffer.
43 return cmdbuf; 76 bool TouchScissors() {
77 return std::exchange(state.scissors, true);
44 } 78 }
45 79
46private: 80 /// Returns true when depth bias have been set in the current command buffer.
47 const vk::CommandBuffer& cmdbuf; 81 bool TouchDepthBias() {
48}; 82 return std::exchange(state.depth_bias, true);
83 }
49 84
50/// The scheduler abstracts command buffer and fence management with an interface that's able to do 85 /// Returns true when blend constants have been set in the current command buffer.
51/// OpenGL-like operations on Vulkan command buffers. 86 bool TouchBlendConstants() {
52class VKScheduler { 87 return std::exchange(state.blend_constants, true);
53public: 88 }
54 explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager); 89
55 ~VKScheduler(); 90 /// Returns true when depth bounds have been set in the current command buffer.
91 bool TouchDepthBounds() {
92 return std::exchange(state.depth_bounds, true);
93 }
94
95 /// Returns true when stencil values have been set in the current command buffer.
96 bool TouchStencilValues() {
97 return std::exchange(state.stencil_values, true);
98 }
99
100 /// Send work to a separate thread.
101 template <typename T>
102 void Record(T&& command) {
103 if (chunk->Record(command)) {
104 return;
105 }
106 DispatchWork();
107 (void)chunk->Record(command);
108 }
56 109
57 /// Gets a reference to the current fence. 110 /// Gets a reference to the current fence.
58 VKFenceView GetFence() const { 111 VKFenceView GetFence() const {
59 return current_fence; 112 return current_fence;
60 } 113 }
61 114
62 /// Gets a reference to the current command buffer. 115private:
63 VKCommandBufferView GetCommandBuffer() const { 116 class Command {
64 return current_cmdbuf; 117 public:
65 } 118 virtual ~Command() = default;
66 119
67 /// Sends the current execution context to the GPU. 120 virtual void Execute(vk::CommandBuffer cmdbuf,
68 void Flush(bool release_fence = true, vk::Semaphore semaphore = nullptr); 121 const vk::DispatchLoaderDynamic& dld) const = 0;
69 122
70 /// Sends the current execution context to the GPU and waits for it to complete. 123 Command* GetNext() const {
71 void Finish(bool release_fence = true, vk::Semaphore semaphore = nullptr); 124 return next;
125 }
126
127 void SetNext(Command* next_) {
128 next = next_;
129 }
130
131 private:
132 Command* next = nullptr;
133 };
134
135 template <typename T>
136 class TypedCommand final : public Command {
137 public:
138 explicit TypedCommand(T&& command) : command{std::move(command)} {}
139 ~TypedCommand() override = default;
140
141 TypedCommand(TypedCommand&&) = delete;
142 TypedCommand& operator=(TypedCommand&&) = delete;
143
144 void Execute(vk::CommandBuffer cmdbuf,
145 const vk::DispatchLoaderDynamic& dld) const override {
146 command(cmdbuf, dld);
147 }
148
149 private:
150 T command;
151 };
152
153 class CommandChunk final {
154 public:
155 void ExecuteAll(vk::CommandBuffer cmdbuf, const vk::DispatchLoaderDynamic& dld);
156
157 template <typename T>
158 bool Record(T& command) {
159 using FuncType = TypedCommand<T>;
160 static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large");
161
162 if (command_offset > sizeof(data) - sizeof(FuncType)) {
163 return false;
164 }
165
166 Command* current_last = last;
167
168 last = new (data.data() + command_offset) FuncType(std::move(command));
169
170 if (current_last) {
171 current_last->SetNext(last);
172 } else {
173 first = last;
174 }
175
176 command_offset += sizeof(FuncType);
177 return true;
178 }
179
180 bool Empty() const {
181 return command_offset == 0;
182 }
183
184 private:
185 Command* first = nullptr;
186 Command* last = nullptr;
187
188 std::size_t command_offset = 0;
189 std::array<u8, 0x8000> data{};
190 };
191
192 void WorkerThread();
72 193
73private:
74 void SubmitExecution(vk::Semaphore semaphore); 194 void SubmitExecution(vk::Semaphore semaphore);
75 195
76 void AllocateNewContext(); 196 void AllocateNewContext();
77 197
198 void InvalidateState();
199
200 void EndPendingOperations();
201
202 void EndRenderPass();
203
204 void AcquireNewChunk();
205
78 const VKDevice& device; 206 const VKDevice& device;
79 VKResourceManager& resource_manager; 207 VKResourceManager& resource_manager;
80 vk::CommandBuffer current_cmdbuf; 208 vk::CommandBuffer current_cmdbuf;
81 VKFence* current_fence = nullptr; 209 VKFence* current_fence = nullptr;
82 VKFence* next_fence = nullptr; 210 VKFence* next_fence = nullptr;
211
212 struct State {
213 std::optional<vk::RenderPassBeginInfo> renderpass;
214 vk::Pipeline graphics_pipeline;
215 bool viewports = false;
216 bool scissors = false;
217 bool depth_bias = false;
218 bool blend_constants = false;
219 bool depth_bounds = false;
220 bool stencil_values = false;
221 } state;
222
223 std::unique_ptr<CommandChunk> chunk;
224 std::thread worker_thread;
225
226 Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_queue;
227 Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_reserve;
228 std::mutex mutex;
229 std::condition_variable cv;
230 bool quit = false;
83}; 231};
84 232
85} // namespace Vulkan 233} // namespace Vulkan