summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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