mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 13:56:59 +01:00
growable command buffer pool
This commit is contained in:
@@ -99,7 +99,10 @@ bool CommandScheduler::InDeferredOperation() noexcept {
|
|||||||
|
|
||||||
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
|
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
|
||||||
: m_master(graphics), m_context(context), m_graphics(graphics),
|
: m_master(graphics), m_context(context), m_graphics(graphics),
|
||||||
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {}
|
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {
|
||||||
|
m_buffers.reserve(CommandBufferGrowStep);
|
||||||
|
m_buffer_ticks.reserve(CommandBufferGrowStep);
|
||||||
|
}
|
||||||
|
|
||||||
CommandScheduler::~CommandScheduler() {
|
CommandScheduler::~CommandScheduler() {
|
||||||
Shutdown();
|
Shutdown();
|
||||||
@@ -154,10 +157,7 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
|
|||||||
m_shaders = &shaders;
|
m_shaders = &shaders;
|
||||||
|
|
||||||
if (!Active()) {
|
if (!Active()) {
|
||||||
for (auto& buffer: m_buffers) {
|
m_current = static_cast<int>(GrowCommandBuffers());
|
||||||
buffer = std::make_unique<RenderCommandBuffer>(*this);
|
|
||||||
}
|
|
||||||
m_current = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BindCurrent();
|
BindCurrent();
|
||||||
@@ -235,7 +235,13 @@ void CommandScheduler::Wait(uint64_t tick) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommandScheduler::PopPendingOperations() {
|
void CommandScheduler::PopPendingOperations() {
|
||||||
m_master.Refresh();
|
PopPendingOperations(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
|
||||||
|
if (refresh_gpu_tick) {
|
||||||
|
m_master.Refresh();
|
||||||
|
}
|
||||||
for (;;) {
|
for (;;) {
|
||||||
Common::UniqueFunction<void> callback;
|
Common::UniqueFunction<void> callback;
|
||||||
{
|
{
|
||||||
@@ -362,7 +368,7 @@ uint64_t CommandScheduler::NextSubmitSequence() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommandScheduler::CheckActive() const {
|
void CommandScheduler::CheckActive() const {
|
||||||
EXIT_IF(!Active() || m_current >= BufferCount);
|
EXIT_IF(!Active() || static_cast<size_t>(m_current) >= m_buffers.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderCommandBuffer& CommandScheduler::Current() const {
|
RenderCommandBuffer& CommandScheduler::Current() const {
|
||||||
@@ -384,15 +390,52 @@ CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
|
|||||||
const auto signal_tick = m_master.NextTick();
|
const auto signal_tick = m_master.NextTick();
|
||||||
submit.AddSignal(m_master.Handle(), signal_tick);
|
submit.AddSignal(m_master.Handle(), signal_tick);
|
||||||
submitted.Execute(submit);
|
submitted.Execute(submit);
|
||||||
m_recording = false;
|
m_buffer_ticks[static_cast<size_t>(m_current)] = signal_tick;
|
||||||
|
m_recording = false;
|
||||||
return submitted;
|
return submitted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CommandScheduler::FindReusableBuffer(uint64_t gpu_tick) const {
|
||||||
|
EXIT_IF(m_buffer_ticks.size() != m_buffers.size());
|
||||||
|
const auto buffer_count = m_buffers.size();
|
||||||
|
for (size_t offset = 1; offset <= buffer_count; ++offset) {
|
||||||
|
const auto candidate = (static_cast<size_t>(m_current) + offset) % buffer_count;
|
||||||
|
if (gpu_tick >= m_buffer_ticks[candidate]) {
|
||||||
|
return static_cast<int>(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CommandScheduler::GrowCommandBuffers() {
|
||||||
|
const auto first = m_buffers.size();
|
||||||
|
const auto end = first + CommandBufferGrowStep;
|
||||||
|
m_buffers.reserve(end);
|
||||||
|
m_buffer_ticks.reserve(end);
|
||||||
|
for (size_t i = first; i < end; ++i) {
|
||||||
|
m_buffers.emplace_back(std::make_unique<RenderCommandBuffer>(*this));
|
||||||
|
m_buffer_ticks.push_back(0);
|
||||||
|
}
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
void CommandScheduler::BeginNext() {
|
void CommandScheduler::BeginNext() {
|
||||||
EXIT_IF(m_recording);
|
EXIT_IF(m_recording);
|
||||||
m_current = (m_current + 1) % BufferCount;
|
|
||||||
|
auto candidate = FindReusableBuffer(m_master.KnownGpuTick());
|
||||||
|
bool refreshed = false;
|
||||||
|
if (candidate < 0) {
|
||||||
|
m_master.Refresh();
|
||||||
|
refreshed = true;
|
||||||
|
candidate = FindReusableBuffer(m_master.KnownGpuTick());
|
||||||
|
}
|
||||||
|
if (candidate < 0) {
|
||||||
|
candidate = static_cast<int>(GrowCommandBuffers());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_current = candidate;
|
||||||
Current().WaitForFenceAndReset();
|
Current().WaitForFenceAndReset();
|
||||||
PopPendingOperations();
|
PopPendingOperations(!refreshed);
|
||||||
BindCurrent();
|
BindCurrent();
|
||||||
Current().Begin();
|
Current().Begin();
|
||||||
m_recording = true;
|
m_recording = true;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Libs::Graphics {
|
namespace Libs::Graphics {
|
||||||
|
|
||||||
@@ -31,8 +32,6 @@ struct CommandSlot {
|
|||||||
|
|
||||||
class CommandScheduler {
|
class CommandScheduler {
|
||||||
public:
|
public:
|
||||||
static constexpr int BufferCount = 8;
|
|
||||||
|
|
||||||
CommandScheduler(RenderContext& context, GraphicContext& graphics);
|
CommandScheduler(RenderContext& context, GraphicContext& graphics);
|
||||||
~CommandScheduler();
|
~CommandScheduler();
|
||||||
KYTY_CLASS_NO_COPY(CommandScheduler);
|
KYTY_CLASS_NO_COPY(CommandScheduler);
|
||||||
@@ -65,6 +64,8 @@ public:
|
|||||||
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
|
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr size_t CommandBufferGrowStep = 4;
|
||||||
|
|
||||||
class CommandPool {
|
class CommandPool {
|
||||||
public:
|
public:
|
||||||
CommandPool() = default;
|
CommandPool() = default;
|
||||||
@@ -94,30 +95,34 @@ private:
|
|||||||
void BindCurrent() const;
|
void BindCurrent() const;
|
||||||
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
|
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
|
||||||
void BeginNext();
|
void BeginNext();
|
||||||
|
void PopPendingOperations(bool refresh_gpu_tick);
|
||||||
|
[[nodiscard]] int FindReusableBuffer(uint64_t gpu_tick) const;
|
||||||
|
[[nodiscard]] size_t GrowCommandBuffers();
|
||||||
void PriorityOperationsThread(std::stop_token stop);
|
void PriorityOperationsThread(std::stop_token stop);
|
||||||
void RunOperation(Common::UniqueFunction<void>&& operation);
|
void RunOperation(Common::UniqueFunction<void>&& operation);
|
||||||
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
|
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
|
||||||
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
|
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
|
||||||
|
|
||||||
MasterSemaphore m_master;
|
MasterSemaphore m_master;
|
||||||
RenderContext& m_context;
|
RenderContext& m_context;
|
||||||
GraphicContext& m_graphics;
|
GraphicContext& m_graphics;
|
||||||
CommandPool m_command_pool;
|
CommandPool m_command_pool;
|
||||||
std::array<std::unique_ptr<RenderCommandBuffer>, BufferCount> m_buffers;
|
std::vector<std::unique_ptr<RenderCommandBuffer>> m_buffers;
|
||||||
std::queue<PendingOperation> m_pending_operations;
|
std::vector<uint64_t> m_buffer_ticks;
|
||||||
std::queue<PendingOperation> m_priority_operations;
|
std::queue<PendingOperation> m_pending_operations;
|
||||||
std::mutex m_operation_mutex;
|
std::queue<PendingOperation> m_priority_operations;
|
||||||
std::condition_variable m_operation_available;
|
std::mutex m_operation_mutex;
|
||||||
std::jthread m_priority_thread;
|
std::condition_variable m_operation_available;
|
||||||
bool m_priority_active = false;
|
std::jthread m_priority_thread;
|
||||||
uint64_t m_priority_active_tick = 0;
|
bool m_priority_active = false;
|
||||||
OperationState m_operation_state = OperationState::Open;
|
uint64_t m_priority_active_tick = 0;
|
||||||
int m_current = -1;
|
OperationState m_operation_state = OperationState::Open;
|
||||||
bool m_recording = false;
|
int m_current = -1;
|
||||||
HW::Context* m_registers = nullptr;
|
bool m_recording = false;
|
||||||
HW::UserConfig* m_user_config = nullptr;
|
HW::Context* m_registers = nullptr;
|
||||||
HW::Shader* m_shaders = nullptr;
|
HW::UserConfig* m_user_config = nullptr;
|
||||||
std::atomic<uint64_t> m_submit_sequence = 0;
|
HW::Shader* m_shaders = nullptr;
|
||||||
|
std::atomic<uint64_t> m_submit_sequence = 0;
|
||||||
|
|
||||||
friend class CommandBuffer;
|
friend class CommandBuffer;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1323,10 +1323,28 @@ public:
|
|||||||
const auto external_wait_tick = scheduler.CurrentTick();
|
const auto external_wait_tick = scheduler.CurrentTick();
|
||||||
SubmitInfo external_wait;
|
SubmitInfo external_wait;
|
||||||
external_wait.AddWait(external_timeline, external_wait_value);
|
external_wait.AddWait(external_timeline, external_wait_value);
|
||||||
scheduler.Flush(external_wait);
|
constexpr size_t blocked_submission_count = 6;
|
||||||
Require("SchedulerTimeline", "external timeline blocked",
|
std::array<vk::CommandBuffer, blocked_submission_count> blocked_handles {};
|
||||||
!scheduler.IsFree(external_wait_tick),
|
for (size_t i = 0; i < blocked_submission_count; ++i) {
|
||||||
"submission ignored the external timeline wait value");
|
blocked_handles[i] = scheduler.Current().Handle();
|
||||||
|
if (i == 0) {
|
||||||
|
scheduler.Flush(external_wait);
|
||||||
|
} else {
|
||||||
|
scheduler.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool distinct_handles = true;
|
||||||
|
for (size_t i = 0; i < blocked_handles.size(); ++i) {
|
||||||
|
for (size_t j = i + 1; j < blocked_handles.size(); ++j) {
|
||||||
|
distinct_handles &= blocked_handles[i] != blocked_handles[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto last_blocked_tick = scheduler.CurrentTick() - 1;
|
||||||
|
Require("SchedulerTimeline", "external timeline overflow",
|
||||||
|
distinct_handles && last_blocked_tick ==
|
||||||
|
external_wait_tick + blocked_submission_count - 1 &&
|
||||||
|
!scheduler.IsFree(last_blocked_tick),
|
||||||
|
"blocked submissions did not grow the command-buffer pool");
|
||||||
vk::SemaphoreSignalInfo signal_info {};
|
vk::SemaphoreSignalInfo signal_info {};
|
||||||
signal_info.sType = vk::StructureType::eSemaphoreSignalInfo;
|
signal_info.sType = vk::StructureType::eSemaphoreSignalInfo;
|
||||||
signal_info.semaphore = external_timeline;
|
signal_info.semaphore = external_timeline;
|
||||||
@@ -1334,8 +1352,14 @@ public:
|
|||||||
Require("SchedulerTimeline", "external timeline signal",
|
Require("SchedulerTimeline", "external timeline signal",
|
||||||
m_runtime_context.device.signalSemaphore(&signal_info) == vk::Result::eSuccess,
|
m_runtime_context.device.signalSemaphore(&signal_info) == vk::Result::eSuccess,
|
||||||
"failed to signal the external timeline semaphore");
|
"failed to signal the external timeline semaphore");
|
||||||
scheduler.Wait(external_wait_tick);
|
scheduler.Wait(last_blocked_tick);
|
||||||
m_runtime_context.device.destroySemaphore(external_timeline, nullptr);
|
m_runtime_context.device.destroySemaphore(external_timeline, nullptr);
|
||||||
|
scheduler.Flush();
|
||||||
|
scheduler.Flush();
|
||||||
|
Require("SchedulerTimeline", "timeline pool reuse",
|
||||||
|
std::ranges::find(blocked_handles, scheduler.Current().Handle()) !=
|
||||||
|
blocked_handles.end(),
|
||||||
|
"completed command buffers were not reused after timeline progress");
|
||||||
scheduler.Shutdown();
|
scheduler.Shutdown();
|
||||||
|
|
||||||
CommandScheduler draining(Renderer(), m_runtime_context);
|
CommandScheduler draining(Renderer(), m_runtime_context);
|
||||||
|
|||||||
Reference in New Issue
Block a user