|
@@ -4,7 +4,14 @@
|
|
|
|
|
|
|
|
#pragma once
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
+#include <condition_variable>
|
|
|
|
|
+#include <memory>
|
|
|
|
|
+#include <optional>
|
|
|
|
|
+#include <stack>
|
|
|
|
|
+#include <thread>
|
|
|
|
|
+#include <utility>
|
|
|
#include "common/common_types.h"
|
|
#include "common/common_types.h"
|
|
|
|
|
+#include "common/threadsafe_queue.h"
|
|
|
#include "video_core/renderer_vulkan/declarations.h"
|
|
#include "video_core/renderer_vulkan/declarations.h"
|
|
|
|
|
|
|
|
namespace Vulkan {
|
|
namespace Vulkan {
|
|
@@ -30,56 +37,197 @@ private:
|
|
|
VKFence* const& fence;
|
|
VKFence* const& fence;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-class VKCommandBufferView {
|
|
|
|
|
|
|
+/// The scheduler abstracts command buffer and fence management with an interface that's able to do
|
|
|
|
|
+/// OpenGL-like operations on Vulkan command buffers.
|
|
|
|
|
+class VKScheduler {
|
|
|
public:
|
|
public:
|
|
|
- VKCommandBufferView() = default;
|
|
|
|
|
- VKCommandBufferView(const vk::CommandBuffer& cmdbuf) : cmdbuf{cmdbuf} {}
|
|
|
|
|
|
|
+ explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager);
|
|
|
|
|
+ ~VKScheduler();
|
|
|
|
|
+
|
|
|
|
|
+ /// Sends the current execution context to the GPU.
|
|
|
|
|
+ void Flush(bool release_fence = true, vk::Semaphore semaphore = nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ /// Sends the current execution context to the GPU and waits for it to complete.
|
|
|
|
|
+ void Finish(bool release_fence = true, vk::Semaphore semaphore = nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ /// Waits for the worker thread to finish executing everything. After this function returns it's
|
|
|
|
|
+ /// safe to touch worker resources.
|
|
|
|
|
+ void WaitWorker();
|
|
|
|
|
+
|
|
|
|
|
+ /// Sends currently recorded work to the worker thread.
|
|
|
|
|
+ void DispatchWork();
|
|
|
|
|
+
|
|
|
|
|
+ /// Requests to begin a renderpass.
|
|
|
|
|
+ void RequestRenderpass(const vk::RenderPassBeginInfo& renderpass_bi);
|
|
|
|
|
+
|
|
|
|
|
+ /// Requests the current executino context to be able to execute operations only allowed outside
|
|
|
|
|
+ /// of a renderpass.
|
|
|
|
|
+ void RequestOutsideRenderPassOperationContext();
|
|
|
|
|
+
|
|
|
|
|
+ /// Binds a pipeline to the current execution context.
|
|
|
|
|
+ void BindGraphicsPipeline(vk::Pipeline pipeline);
|
|
|
|
|
|
|
|
- const vk::CommandBuffer* operator->() const noexcept {
|
|
|
|
|
- return &cmdbuf;
|
|
|
|
|
|
|
+ /// Returns true when viewports have been set in the current command buffer.
|
|
|
|
|
+ bool TouchViewports() {
|
|
|
|
|
+ return std::exchange(state.viewports, true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- operator vk::CommandBuffer() const noexcept {
|
|
|
|
|
- return cmdbuf;
|
|
|
|
|
|
|
+ /// Returns true when scissors have been set in the current command buffer.
|
|
|
|
|
+ bool TouchScissors() {
|
|
|
|
|
+ return std::exchange(state.scissors, true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-private:
|
|
|
|
|
- const vk::CommandBuffer& cmdbuf;
|
|
|
|
|
-};
|
|
|
|
|
|
|
+ /// Returns true when depth bias have been set in the current command buffer.
|
|
|
|
|
+ bool TouchDepthBias() {
|
|
|
|
|
+ return std::exchange(state.depth_bias, true);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-/// The scheduler abstracts command buffer and fence management with an interface that's able to do
|
|
|
|
|
-/// OpenGL-like operations on Vulkan command buffers.
|
|
|
|
|
-class VKScheduler {
|
|
|
|
|
-public:
|
|
|
|
|
- explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager);
|
|
|
|
|
- ~VKScheduler();
|
|
|
|
|
|
|
+ /// Returns true when blend constants have been set in the current command buffer.
|
|
|
|
|
+ bool TouchBlendConstants() {
|
|
|
|
|
+ return std::exchange(state.blend_constants, true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Returns true when depth bounds have been set in the current command buffer.
|
|
|
|
|
+ bool TouchDepthBounds() {
|
|
|
|
|
+ return std::exchange(state.depth_bounds, true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Returns true when stencil values have been set in the current command buffer.
|
|
|
|
|
+ bool TouchStencilValues() {
|
|
|
|
|
+ return std::exchange(state.stencil_values, true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Send work to a separate thread.
|
|
|
|
|
+ template <typename T>
|
|
|
|
|
+ void Record(T&& command) {
|
|
|
|
|
+ if (chunk->Record(command)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ DispatchWork();
|
|
|
|
|
+ (void)chunk->Record(command);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
/// Gets a reference to the current fence.
|
|
/// Gets a reference to the current fence.
|
|
|
VKFenceView GetFence() const {
|
|
VKFenceView GetFence() const {
|
|
|
return current_fence;
|
|
return current_fence;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// Gets a reference to the current command buffer.
|
|
|
|
|
- VKCommandBufferView GetCommandBuffer() const {
|
|
|
|
|
- return current_cmdbuf;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+private:
|
|
|
|
|
+ class Command {
|
|
|
|
|
+ public:
|
|
|
|
|
+ virtual ~Command() = default;
|
|
|
|
|
|
|
|
- /// Sends the current execution context to the GPU.
|
|
|
|
|
- void Flush(bool release_fence = true, vk::Semaphore semaphore = nullptr);
|
|
|
|
|
|
|
+ virtual void Execute(vk::CommandBuffer cmdbuf,
|
|
|
|
|
+ const vk::DispatchLoaderDynamic& dld) const = 0;
|
|
|
|
|
|
|
|
- /// Sends the current execution context to the GPU and waits for it to complete.
|
|
|
|
|
- void Finish(bool release_fence = true, vk::Semaphore semaphore = nullptr);
|
|
|
|
|
|
|
+ Command* GetNext() const {
|
|
|
|
|
+ return next;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void SetNext(Command* next_) {
|
|
|
|
|
+ next = next_;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private:
|
|
|
|
|
+ Command* next = nullptr;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ template <typename T>
|
|
|
|
|
+ class TypedCommand final : public Command {
|
|
|
|
|
+ public:
|
|
|
|
|
+ explicit TypedCommand(T&& command) : command{std::move(command)} {}
|
|
|
|
|
+ ~TypedCommand() override = default;
|
|
|
|
|
+
|
|
|
|
|
+ TypedCommand(TypedCommand&&) = delete;
|
|
|
|
|
+ TypedCommand& operator=(TypedCommand&&) = delete;
|
|
|
|
|
+
|
|
|
|
|
+ void Execute(vk::CommandBuffer cmdbuf,
|
|
|
|
|
+ const vk::DispatchLoaderDynamic& dld) const override {
|
|
|
|
|
+ command(cmdbuf, dld);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private:
|
|
|
|
|
+ T command;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ class CommandChunk final {
|
|
|
|
|
+ public:
|
|
|
|
|
+ void ExecuteAll(vk::CommandBuffer cmdbuf, const vk::DispatchLoaderDynamic& dld);
|
|
|
|
|
+
|
|
|
|
|
+ template <typename T>
|
|
|
|
|
+ bool Record(T& command) {
|
|
|
|
|
+ using FuncType = TypedCommand<T>;
|
|
|
|
|
+ static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large");
|
|
|
|
|
+
|
|
|
|
|
+ if (command_offset > sizeof(data) - sizeof(FuncType)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Command* current_last = last;
|
|
|
|
|
+
|
|
|
|
|
+ last = new (data.data() + command_offset) FuncType(std::move(command));
|
|
|
|
|
+
|
|
|
|
|
+ if (current_last) {
|
|
|
|
|
+ current_last->SetNext(last);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ first = last;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ command_offset += sizeof(FuncType);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool Empty() const {
|
|
|
|
|
+ return command_offset == 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private:
|
|
|
|
|
+ Command* first = nullptr;
|
|
|
|
|
+ Command* last = nullptr;
|
|
|
|
|
+
|
|
|
|
|
+ std::size_t command_offset = 0;
|
|
|
|
|
+ std::array<u8, 0x8000> data{};
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ void WorkerThread();
|
|
|
|
|
|
|
|
-private:
|
|
|
|
|
void SubmitExecution(vk::Semaphore semaphore);
|
|
void SubmitExecution(vk::Semaphore semaphore);
|
|
|
|
|
|
|
|
void AllocateNewContext();
|
|
void AllocateNewContext();
|
|
|
|
|
|
|
|
|
|
+ void InvalidateState();
|
|
|
|
|
+
|
|
|
|
|
+ void EndPendingOperations();
|
|
|
|
|
+
|
|
|
|
|
+ void EndRenderPass();
|
|
|
|
|
+
|
|
|
|
|
+ void AcquireNewChunk();
|
|
|
|
|
+
|
|
|
const VKDevice& device;
|
|
const VKDevice& device;
|
|
|
VKResourceManager& resource_manager;
|
|
VKResourceManager& resource_manager;
|
|
|
vk::CommandBuffer current_cmdbuf;
|
|
vk::CommandBuffer current_cmdbuf;
|
|
|
VKFence* current_fence = nullptr;
|
|
VKFence* current_fence = nullptr;
|
|
|
VKFence* next_fence = nullptr;
|
|
VKFence* next_fence = nullptr;
|
|
|
|
|
+
|
|
|
|
|
+ struct State {
|
|
|
|
|
+ std::optional<vk::RenderPassBeginInfo> renderpass;
|
|
|
|
|
+ vk::Pipeline graphics_pipeline;
|
|
|
|
|
+ bool viewports = false;
|
|
|
|
|
+ bool scissors = false;
|
|
|
|
|
+ bool depth_bias = false;
|
|
|
|
|
+ bool blend_constants = false;
|
|
|
|
|
+ bool depth_bounds = false;
|
|
|
|
|
+ bool stencil_values = false;
|
|
|
|
|
+ } state;
|
|
|
|
|
+
|
|
|
|
|
+ std::unique_ptr<CommandChunk> chunk;
|
|
|
|
|
+ std::thread worker_thread;
|
|
|
|
|
+
|
|
|
|
|
+ Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_queue;
|
|
|
|
|
+ Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_reserve;
|
|
|
|
|
+ std::mutex mutex;
|
|
|
|
|
+ std::condition_variable cv;
|
|
|
|
|
+ bool quit = false;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
} // namespace Vulkan
|
|
} // namespace Vulkan
|