graphics: stall waitUntilSafeForRendering in its command processor instead of the GPU thread

This commit is contained in:
nmzik
2026-10-22 18:26:35 +02:00
parent a951a91703
commit a358b847c1
6 changed files with 142 additions and 22 deletions
+16 -5
View File
@@ -30,6 +30,7 @@
#include <mutex>
#include <semaphore>
#include <thread>
#include <utility>
#include <vector>
namespace Libs::Graphics {
@@ -91,6 +92,12 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
m_work_available.Signal();
}
void GuestGpu::Wake() {
Common::LockGuard lock(m_queue_mutex);
m_wake_pending = true;
m_work_available.Signal();
}
void GuestGpu::ProcessCommands() {
EXIT_IF(!IsGpuThread());
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
@@ -464,7 +471,10 @@ void GuestGpu::ThreadRun(void* data) {
}
if (selected_queue < 0) {
gpu->m_processing = false;
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
// A wake that came before this wait must not be lost.
if (!std::exchange(gpu->m_wake_pending, false)) {
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
}
for (auto& queue: gpu->m_queues) {
if (!queue.empty()) {
queue.front().blocked = false;
@@ -998,10 +1008,11 @@ void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
}
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
BufferFlush();
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
static_cast<int>(display_buffer_index));
// The SDK stalls only this command processor, not the host command lane or other queues.
if (!m_renderer.GetVideoOut().IsSafeForRendering(static_cast<int>(video_out_handle),
static_cast<int>(display_buffer_index))) {
SuspendPm4();
}
}
template <typename T>
+3
View File
@@ -31,6 +31,8 @@ public:
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
// Retries blocked submissions now instead of after the 100 ms stall timeout.
void Wake();
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
void Submit(std::span<const uint32_t> draw_commands,
@@ -87,6 +89,7 @@ private:
bool m_processing = false;
bool m_accepting = true;
bool m_stopping = false;
bool m_wake_pending = false;
bool m_shutdown_complete = false;
// Completion callbacks can outlive GuestGpu during renderer shutdown.
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
+25 -16
View File
@@ -221,7 +221,7 @@ public:
void WaitForSubmitSlot(VideoOutConfig& cfg);
bool Flip(uint32_t micros);
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
void Wait(VideoOutConfig& cfg, int index);
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
private:
enum class RequestState { Reserved, Recording, Ready, Presenting };
@@ -240,6 +240,7 @@ private:
Graphics::Presenter::Frame* frame;
bool premultiplied_alpha;
};
void SignalDoneLocked();
Graphics::Presenter& m_presenter;
Common::Mutex m_mutex;
@@ -250,6 +251,7 @@ private:
std::list<Request> m_cpu_requests;
std::list<Request> m_cancelled_requests;
bool m_processing = false;
bool m_gpu_waiting = false;
uint64_t m_next_request_id = 1;
};
@@ -953,7 +955,7 @@ bool FlipQueue::Reserve(VideoOutConfig& cfg, int index, int64_t flip_arg, FlipRe
}
}
}
m_done_cond_var.SignalAll();
SignalDoneLocked();
}
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
@@ -1033,7 +1035,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
it = queue->erase(it);
}
}
m_done_cond_var.SignalAll();
SignalDoneLocked();
m_submit_slot_cond_var.SignalAll();
m_submit_cond_var.SignalAll();
m_mutex.Unlock();
@@ -1205,19 +1207,26 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
}
}
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
Common::LockGuard lock(m_mutex);
auto has_request = [this, &cfg, index] {
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
};
while (has_request()) {
m_done_cond_var.Wait(&m_mutex);
void FlipQueue::SignalDoneLocked() {
m_done_cond_var.SignalAll();
// A stalled GPU cannot drain and shut down without re-checking under m_mutex, so it is alive here.
if (m_gpu_waiting) {
m_gpu_waiting = false;
m_presenter.Renderer().GetGpu().Wake();
}
}
// A display buffer is safe for rendering once it is neither displayed nor waiting to be.
bool FlipQueue::IsSafeForRendering(VideoOutConfig& cfg, int index) {
Common::LockGuard lock(m_mutex);
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
const bool safe = cfg.flip_status.currentBuffer != index &&
std::none_of(m_requests.begin(), m_requests.end(), matches) &&
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
m_gpu_waiting = !safe;
return safe;
}
bool FlipQueue::Flip(uint32_t micros) {
KYTY_PROFILER_BLOCK("FlipQueue::Flip");
@@ -1311,7 +1320,7 @@ bool FlipQueue::Flip(uint32_t micros) {
it = m_requests.erase(it);
}
m_processing = false;
m_done_cond_var.SignalAll();
SignalDoneLocked();
m_submit_slot_cond_var.SignalAll();
m_mutex.Unlock();
for (size_t i = count; i != 0; i--) {
@@ -1694,12 +1703,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
}
void VideoOutDriver::WaitFlipDone(int handle, int index) {
bool VideoOutDriver::IsSafeForRendering(int handle, int index) {
auto* ctx = m_impl->Get(handle);
EXIT_IF(ctx == nullptr);
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
m_impl->GetFlipQueue().Wait(*ctx, index);
return m_impl->GetFlipQueue().IsSafeForRendering(*ctx, index);
}
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
+1 -1
View File
@@ -38,7 +38,7 @@ public:
void CompleteFlip(uint64_t request_id);
void SubmitFlipPreparation(uint64_t request_id);
void WaitForSubmitSlot(int handle);
void WaitFlipDone(int handle, int index);
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
[[nodiscard]] Impl& State() noexcept;