mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 08:13:05 +00:00
graphics: stall waitUntilSafeForRendering in its command processor instead of the GPU thread
This commit is contained in:
1 parent
a951a91703
commit
a358b847c1
6 files changed
+141
-21
No files matched your search
@@ -676,6 +676,8 @@ if(BUILD_TESTING)
|
|||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||||
add_test(NAME gpu_command_lane
|
add_test(NAME gpu_command_lane
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||||
|
add_test(NAME wait_until_safe_for_rendering
|
||||||
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --wait-until-safe-for-rendering-only)
|
||||||
add_test(NAME pm4_context_state
|
add_test(NAME pm4_context_state
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
||||||
add_test(NAME gpu_tiler
|
add_test(NAME gpu_tiler
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <semaphore>
|
#include <semaphore>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Libs::Graphics {
|
namespace Libs::Graphics {
|
||||||
@@ -91,6 +92,12 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
|
|||||||
m_work_available.Signal();
|
m_work_available.Signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GuestGpu::Wake() {
|
||||||
|
Common::LockGuard lock(m_queue_mutex);
|
||||||
|
m_wake_pending = true;
|
||||||
|
m_work_available.Signal();
|
||||||
|
}
|
||||||
|
|
||||||
void GuestGpu::ProcessCommands() {
|
void GuestGpu::ProcessCommands() {
|
||||||
EXIT_IF(!IsGpuThread());
|
EXIT_IF(!IsGpuThread());
|
||||||
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
||||||
@@ -464,7 +471,10 @@ void GuestGpu::ThreadRun(void* data) {
|
|||||||
}
|
}
|
||||||
if (selected_queue < 0) {
|
if (selected_queue < 0) {
|
||||||
gpu->m_processing = false;
|
gpu->m_processing = false;
|
||||||
|
// 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);
|
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||||
|
}
|
||||||
for (auto& queue: gpu->m_queues) {
|
for (auto& queue: gpu->m_queues) {
|
||||||
if (!queue.empty()) {
|
if (!queue.empty()) {
|
||||||
queue.front().blocked = false;
|
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) {
|
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||||
BufferFlush();
|
// 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),
|
||||||
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
static_cast<int>(display_buffer_index))) {
|
||||||
static_cast<int>(display_buffer_index));
|
SuspendPm4();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ public:
|
|||||||
[[nodiscard]] bool IsStopping();
|
[[nodiscard]] bool IsStopping();
|
||||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||||
void SendCommandSync(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.
|
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||||
void Submit(std::span<const uint32_t> draw_commands,
|
void Submit(std::span<const uint32_t> draw_commands,
|
||||||
@@ -87,6 +89,7 @@ private:
|
|||||||
bool m_processing = false;
|
bool m_processing = false;
|
||||||
bool m_accepting = true;
|
bool m_accepting = true;
|
||||||
bool m_stopping = false;
|
bool m_stopping = false;
|
||||||
|
bool m_wake_pending = false;
|
||||||
bool m_shutdown_complete = false;
|
bool m_shutdown_complete = false;
|
||||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ public:
|
|||||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||||
bool Flip(uint32_t micros);
|
bool Flip(uint32_t micros);
|
||||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||||
void Wait(VideoOutConfig& cfg, int index);
|
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
||||||
@@ -240,6 +240,7 @@ private:
|
|||||||
Graphics::Presenter::Frame* frame;
|
Graphics::Presenter::Frame* frame;
|
||||||
bool premultiplied_alpha;
|
bool premultiplied_alpha;
|
||||||
};
|
};
|
||||||
|
void SignalDoneLocked();
|
||||||
|
|
||||||
Graphics::Presenter& m_presenter;
|
Graphics::Presenter& m_presenter;
|
||||||
Common::Mutex m_mutex;
|
Common::Mutex m_mutex;
|
||||||
@@ -250,6 +251,7 @@ private:
|
|||||||
std::list<Request> m_cpu_requests;
|
std::list<Request> m_cpu_requests;
|
||||||
std::list<Request> m_cancelled_requests;
|
std::list<Request> m_cancelled_requests;
|
||||||
bool m_processing = false;
|
bool m_processing = false;
|
||||||
|
bool m_gpu_waiting = false;
|
||||||
uint64_t m_next_request_id = 1;
|
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) {
|
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
||||||
@@ -1033,7 +1035,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
|||||||
it = queue->erase(it);
|
it = queue->erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_done_cond_var.SignalAll();
|
SignalDoneLocked();
|
||||||
m_submit_slot_cond_var.SignalAll();
|
m_submit_slot_cond_var.SignalAll();
|
||||||
m_submit_cond_var.SignalAll();
|
m_submit_cond_var.SignalAll();
|
||||||
m_mutex.Unlock();
|
m_mutex.Unlock();
|
||||||
@@ -1205,19 +1207,26 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
|
void FlipQueue::SignalDoneLocked() {
|
||||||
Common::LockGuard lock(m_mutex);
|
m_done_cond_var.SignalAll();
|
||||||
|
// A stalled GPU cannot drain and shut down without re-checking under m_mutex, so it is alive here.
|
||||||
auto has_request = [this, &cfg, index] {
|
if (m_gpu_waiting) {
|
||||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
m_gpu_waiting = false;
|
||||||
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
|
m_presenter.Renderer().GetGpu().Wake();
|
||||||
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
|
||||||
};
|
|
||||||
while (has_request()) {
|
|
||||||
m_done_cond_var.Wait(&m_mutex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
bool FlipQueue::Flip(uint32_t micros) {
|
||||||
KYTY_PROFILER_BLOCK("FlipQueue::Flip");
|
KYTY_PROFILER_BLOCK("FlipQueue::Flip");
|
||||||
|
|
||||||
@@ -1311,7 +1320,7 @@ bool FlipQueue::Flip(uint32_t micros) {
|
|||||||
it = m_requests.erase(it);
|
it = m_requests.erase(it);
|
||||||
}
|
}
|
||||||
m_processing = false;
|
m_processing = false;
|
||||||
m_done_cond_var.SignalAll();
|
SignalDoneLocked();
|
||||||
m_submit_slot_cond_var.SignalAll();
|
m_submit_slot_cond_var.SignalAll();
|
||||||
m_mutex.Unlock();
|
m_mutex.Unlock();
|
||||||
for (size_t i = count; i != 0; i--) {
|
for (size_t i = count; i != 0; i--) {
|
||||||
@@ -1694,12 +1703,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
|||||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
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);
|
auto* ctx = m_impl->Get(handle);
|
||||||
EXIT_IF(ctx == nullptr);
|
EXIT_IF(ctx == nullptr);
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
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) {
|
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public:
|
|||||||
void CompleteFlip(uint64_t request_id);
|
void CompleteFlip(uint64_t request_id);
|
||||||
void SubmitFlipPreparation(uint64_t request_id);
|
void SubmitFlipPreparation(uint64_t request_id);
|
||||||
void WaitForSubmitSlot(int handle);
|
void WaitForSubmitSlot(int handle);
|
||||||
void WaitFlipDone(int handle, int index);
|
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
|
||||||
|
|
||||||
[[nodiscard]] Impl& State() noexcept;
|
[[nodiscard]] Impl& State() noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
#include "graphics/host_gpu/renderer/sync.h"
|
#include "graphics/host_gpu/renderer/sync.h"
|
||||||
#include "graphics/host_gpu/vulkanCommon.h"
|
#include "graphics/host_gpu/vulkanCommon.h"
|
||||||
#include <vulkan/vulkan_format_traits.hpp>
|
#include <vulkan/vulkan_format_traits.hpp>
|
||||||
|
#include "graphics/presentation/videoOut.h"
|
||||||
#include "graphics/presentation/window/windowInternal.h"
|
#include "graphics/presentation/window/windowInternal.h"
|
||||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||||
@@ -49108,6 +49109,94 @@ void CheckPm4IndirectControlFlow(RenderContext &renderer) {
|
|||||||
std::printf("[host] %-32s ok\n", name);
|
std::printf("[host] %-32s ok\n", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitUntilSafeForRendering stalls only its own command processor (SDK), and a display buffer
|
||||||
|
// stays busy while its flip is pending or while it is on screen.
|
||||||
|
void CheckWaitUntilSafeForRendering() {
|
||||||
|
constexpr const char *name = "WaitUntilSafeForRendering";
|
||||||
|
Require(name, "video", SDL_InitSubSystem(SDL_INIT_VIDEO), "SDL video init failed");
|
||||||
|
WindowContext window;
|
||||||
|
window.graphic_ctx.screen_width = 64;
|
||||||
|
window.graphic_ctx.screen_height = 64;
|
||||||
|
window.window = SDL_CreateWindow(name, 64, 64, SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN);
|
||||||
|
Require(name, "window", window.window != nullptr, "hidden Vulkan window failed");
|
||||||
|
window.CreateVulkan();
|
||||||
|
auto &video_out = VideoOut::VideoOutInit(64, 64, *window.presenter);
|
||||||
|
auto &renderer = *window.render_context;
|
||||||
|
renderer.InitializeGpu(&video_out);
|
||||||
|
auto &gpu = renderer.GetGpu();
|
||||||
|
|
||||||
|
constexpr int user_system = 255;
|
||||||
|
constexpr int bus_main = 0;
|
||||||
|
constexpr int flip_mode_vsync = 1;
|
||||||
|
// Special buffer indices flip without a registered surface.
|
||||||
|
constexpr int index_blank = -1;
|
||||||
|
constexpr int index_black = -2;
|
||||||
|
const int handle = VideoOut::VideoOutOpen(user_system, bus_main, 0, nullptr);
|
||||||
|
Require(name, "open", handle > 0, "video-out open failed");
|
||||||
|
const auto wait_until = [](const auto &done) {
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||||
|
while (!done() && std::chrono::steady_clock::now() < deadline) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
return done();
|
||||||
|
};
|
||||||
|
const auto address = [](const void *value) { return reinterpret_cast<uint64_t>(value); };
|
||||||
|
|
||||||
|
// Hold the graphics queue so the CPU flip of BLACK, prepared behind it, stays pending.
|
||||||
|
std::atomic_uint32_t gate{0};
|
||||||
|
std::array<uint32_t, 7> hold{};
|
||||||
|
hold[0] = KYTY_PM4(7, Pm4::IT_WAIT_REG_MEM, 0);
|
||||||
|
hold[1] = 0x10u | 3u;
|
||||||
|
hold[2] = static_cast<uint32_t>(address(&gate));
|
||||||
|
hold[3] = static_cast<uint32_t>(address(&gate) >> 32u);
|
||||||
|
hold[4] = 1;
|
||||||
|
hold[5] = UINT32_MAX;
|
||||||
|
gpu.Submit(hold, {});
|
||||||
|
Require(name, "flip black",
|
||||||
|
VideoOut::VideoOutSubmitFlip(handle, index_black, flip_mode_vsync, 0) == 0,
|
||||||
|
"CPU flip failed");
|
||||||
|
|
||||||
|
std::atomic_uint32_t rendered{0};
|
||||||
|
std::array<uint32_t, 12> render{};
|
||||||
|
render[0] = KYTY_PM4(7, Pm4::IT_NOP, Pm4::R_WAIT_FLIP_DONE);
|
||||||
|
render[1] = static_cast<uint32_t>(handle);
|
||||||
|
render[2] = static_cast<uint32_t>(index_black);
|
||||||
|
render[7] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
||||||
|
render[9] = static_cast<uint32_t>(address(&rendered));
|
||||||
|
render[10] = static_cast<uint32_t>(address(&rendered) >> 32u);
|
||||||
|
render[11] = 1;
|
||||||
|
gpu.SubmitCompute(0x20, render);
|
||||||
|
|
||||||
|
std::binary_semaphore lane{0};
|
||||||
|
std::jthread command_lane([&] {
|
||||||
|
gpu.SendCommandSync([] {});
|
||||||
|
lane.release();
|
||||||
|
});
|
||||||
|
Require(name, "command lane", lane.try_acquire_for(std::chrono::seconds(2)),
|
||||||
|
"a busy display buffer blocked the GPU thread");
|
||||||
|
Require(name, "pending", rendered.load() == 0,
|
||||||
|
"rendering started while its display buffer had a pending flip");
|
||||||
|
|
||||||
|
gate.store(1);
|
||||||
|
Require(name, "flip shown", wait_until([handle] { return VideoOut::VideoOutIsFlipPending(handle) == 0; }),
|
||||||
|
"the BLACK flip was not presented");
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
Require(name, "displayed", rendered.load() == 0,
|
||||||
|
"rendering started while its display buffer was on screen");
|
||||||
|
|
||||||
|
Require(name, "flip blank",
|
||||||
|
VideoOut::VideoOutSubmitFlip(handle, index_blank, flip_mode_vsync, 0) == 0,
|
||||||
|
"CPU flip failed");
|
||||||
|
Require(name, "resume", wait_until([&] { return rendered.load() == 1; }),
|
||||||
|
"rendering did not resume once its display buffer left the screen");
|
||||||
|
|
||||||
|
gpu.WaitForIdle();
|
||||||
|
Require(name, "close", VideoOut::VideoOutClose(handle) == 0, "video-out close failed");
|
||||||
|
renderer.ShutdownGpu();
|
||||||
|
VideoOut::VideoOutShutdown();
|
||||||
|
std::printf("[host] %-32s ok\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
void CheckPm4WaitResume(RenderContext &renderer) {
|
void CheckPm4WaitResume(RenderContext &renderer) {
|
||||||
GraphicsInitJmpTables();
|
GraphicsInitJmpTables();
|
||||||
CommandProcessor processor(renderer, 0);
|
CommandProcessor processor(renderer, 0);
|
||||||
@@ -49987,6 +50076,10 @@ int main(int argc, char **argv) {
|
|||||||
vulkan.CheckScalarLeafCall();
|
vulkan.CheckScalarLeafCall();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (argc == 2 && std::strcmp(argv[1], "--wait-until-safe-for-rendering-only") == 0) {
|
||||||
|
CheckWaitUntilSafeForRendering();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (argc == 2 && std::strcmp(argv[1], "--gpu-command-lane-only") == 0) {
|
if (argc == 2 && std::strcmp(argv[1], "--gpu-command-lane-only") == 0) {
|
||||||
VulkanHarness vulkan;
|
VulkanHarness vulkan;
|
||||||
vulkan.CheckGpuCommandLane();
|
vulkan.CheckGpuCommandLane();
|
||||||
@@ -50540,6 +50633,8 @@ int main(int argc, char **argv) {
|
|||||||
vulkan.CheckGpuSuspendPoint();
|
vulkan.CheckGpuSuspendPoint();
|
||||||
vulkan.CheckGpuCommandLane();
|
vulkan.CheckGpuCommandLane();
|
||||||
vulkan.CheckScalarLeafCall();
|
vulkan.CheckScalarLeafCall();
|
||||||
|
// Last: its window installs global GPU resources.
|
||||||
|
CheckWaitUntilSafeForRendering();
|
||||||
if (skipped_device_checks) {
|
if (skipped_device_checks) {
|
||||||
std::printf(
|
std::printf(
|
||||||
"ShaderRecompilerComputeTests: device rasterization checks skipped, this "
|
"ShaderRecompilerComputeTests: device rasterization checks skipped, this "
|
||||||
|
|||||||
Reference in new issue
Block a user