diff --git a/src/graphics/host_gpu/renderer/commandScheduler.cpp b/src/graphics/host_gpu/renderer/commandScheduler.cpp index 5bdfbe953..de5f32989 100644 --- a/src/graphics/host_gpu/renderer/commandScheduler.cpp +++ b/src/graphics/host_gpu/renderer/commandScheduler.cpp @@ -99,7 +99,10 @@ bool CommandScheduler::InDeferredOperation() noexcept { CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& 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() { Shutdown(); @@ -154,10 +157,7 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config m_shaders = &shaders; if (!Active()) { - for (auto& buffer: m_buffers) { - buffer = std::make_unique(*this); - } - m_current = 0; + m_current = static_cast(GrowCommandBuffers()); } BindCurrent(); @@ -235,7 +235,13 @@ void CommandScheduler::Wait(uint64_t tick) { } void CommandScheduler::PopPendingOperations() { - m_master.Refresh(); + PopPendingOperations(true); +} + +void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) { + if (refresh_gpu_tick) { + m_master.Refresh(); + } for (;;) { Common::UniqueFunction callback; { @@ -362,7 +368,7 @@ uint64_t CommandScheduler::NextSubmitSequence() noexcept { } void CommandScheduler::CheckActive() const { - EXIT_IF(!Active() || m_current >= BufferCount); + EXIT_IF(!Active() || static_cast(m_current) >= m_buffers.size()); } RenderCommandBuffer& CommandScheduler::Current() const { @@ -384,15 +390,52 @@ CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) { const auto signal_tick = m_master.NextTick(); submit.AddSignal(m_master.Handle(), signal_tick); submitted.Execute(submit); - m_recording = false; + m_buffer_ticks[static_cast(m_current)] = signal_tick; + m_recording = false; 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(m_current) + offset) % buffer_count; + if (gpu_tick >= m_buffer_ticks[candidate]) { + return static_cast(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(*this)); + m_buffer_ticks.push_back(0); + } + return first; +} + void CommandScheduler::BeginNext() { 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(GrowCommandBuffers()); + } + + m_current = candidate; Current().WaitForFenceAndReset(); - PopPendingOperations(); + PopPendingOperations(!refreshed); BindCurrent(); Current().Begin(); m_recording = true; diff --git a/src/graphics/host_gpu/renderer/commandScheduler.h b/src/graphics/host_gpu/renderer/commandScheduler.h index bca2b22b7..c079e9ab9 100644 --- a/src/graphics/host_gpu/renderer/commandScheduler.h +++ b/src/graphics/host_gpu/renderer/commandScheduler.h @@ -16,6 +16,7 @@ #include #include +#include namespace Libs::Graphics { @@ -31,8 +32,6 @@ struct CommandSlot { class CommandScheduler { public: - static constexpr int BufferCount = 8; - CommandScheduler(RenderContext& context, GraphicContext& graphics); ~CommandScheduler(); KYTY_CLASS_NO_COPY(CommandScheduler); @@ -65,6 +64,8 @@ public: [[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; } private: + static constexpr size_t CommandBufferGrowStep = 4; + class CommandPool { public: CommandPool() = default; @@ -94,30 +95,34 @@ private: void BindCurrent() const; CommandBuffer& SubmitCurrent(SubmitInfo& submit); 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 RunOperation(Common::UniqueFunction&& operation); [[nodiscard]] CommandSlot* AllocateCommandBuffer(); [[nodiscard]] uint64_t NextSubmitSequence() noexcept; - MasterSemaphore m_master; - RenderContext& m_context; - GraphicContext& m_graphics; - CommandPool m_command_pool; - std::array, BufferCount> m_buffers; - std::queue m_pending_operations; - std::queue m_priority_operations; - std::mutex m_operation_mutex; - std::condition_variable m_operation_available; - std::jthread m_priority_thread; - bool m_priority_active = false; - uint64_t m_priority_active_tick = 0; - OperationState m_operation_state = OperationState::Open; - int m_current = -1; - bool m_recording = false; - HW::Context* m_registers = nullptr; - HW::UserConfig* m_user_config = nullptr; - HW::Shader* m_shaders = nullptr; - std::atomic m_submit_sequence = 0; + MasterSemaphore m_master; + RenderContext& m_context; + GraphicContext& m_graphics; + CommandPool m_command_pool; + std::vector> m_buffers; + std::vector m_buffer_ticks; + std::queue m_pending_operations; + std::queue m_priority_operations; + std::mutex m_operation_mutex; + std::condition_variable m_operation_available; + std::jthread m_priority_thread; + bool m_priority_active = false; + uint64_t m_priority_active_tick = 0; + OperationState m_operation_state = OperationState::Open; + int m_current = -1; + bool m_recording = false; + HW::Context* m_registers = nullptr; + HW::UserConfig* m_user_config = nullptr; + HW::Shader* m_shaders = nullptr; + std::atomic m_submit_sequence = 0; friend class CommandBuffer; }; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index f1321f20d..944fc684d 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1323,10 +1323,28 @@ public: const auto external_wait_tick = scheduler.CurrentTick(); SubmitInfo external_wait; external_wait.AddWait(external_timeline, external_wait_value); - scheduler.Flush(external_wait); - Require("SchedulerTimeline", "external timeline blocked", - !scheduler.IsFree(external_wait_tick), - "submission ignored the external timeline wait value"); + constexpr size_t blocked_submission_count = 6; + std::array blocked_handles {}; + for (size_t i = 0; i < blocked_submission_count; ++i) { + 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 {}; signal_info.sType = vk::StructureType::eSemaphoreSignalInfo; signal_info.semaphore = external_timeline; @@ -1334,8 +1352,14 @@ public: Require("SchedulerTimeline", "external timeline signal", m_runtime_context.device.signalSemaphore(&signal_info) == vk::Result::eSuccess, "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); + 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(); CommandScheduler draining(Renderer(), m_runtime_context);