Move presentation to separate thread/improve sync (#303)

* video_out: Move presentation to separate thread

* liverpool: Better sync for CPU flips

* driver: Make flip blocking

* videoout: Proper flip rate and vblank management

* config: Add vblank divider option

* clang format

* videoout: added `sceVideoOutWaitVblank`

* clang format

* vk_scheduler: Silly merge conflict

* externals: Add renderdoc API

* clang format

* reuse

* rdoc: manual capture trigger

* clang fmt

---------

Co-authored-by: psucien <168137814+psucien@users.noreply.github.com>
This commit is contained in:
TheTurtle
2024-07-28 16:54:09 +03:00
committed by GitHub
parent 361412031c
commit 0d6edaa0a0
32 changed files with 1259 additions and 224 deletions

View File

@@ -3,14 +3,16 @@
#include <pthread.h>
#include "common/assert.h"
#include "common/config.h"
#include "common/debug.h"
#include "common/thread.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/kernel/time_management.h"
#include "core/libraries/videoout/driver.h"
#include "core/platform.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
extern std::unique_ptr<Vulkan::RendererVulkan> renderer;
extern std::unique_ptr<AmdGpu::Liverpool> liverpool;
namespace Libraries::VideoOut {
@@ -41,20 +43,18 @@ VideoOutDriver::VideoOutDriver(u32 width, u32 height) {
main_port.resolution.fullHeight = height;
main_port.resolution.paneWidth = width;
main_port.resolution.paneHeight = height;
present_thread = std::jthread([&](std::stop_token token) { PresentThread(token); });
}
VideoOutDriver::~VideoOutDriver() = default;
int VideoOutDriver::Open(const ServiceThreadParams* params) {
std::scoped_lock lock{mutex};
if (main_port.is_open) {
return ORBIS_VIDEO_OUT_ERROR_RESOURCE_BUSY;
}
int handle = 1;
main_port.is_open = true;
return handle;
liverpool->SetVoPort(&main_port);
return 1;
}
void VideoOutDriver::Close(s32 handle) {
@@ -158,31 +158,22 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) {
return ORBIS_OK;
}
void VideoOutDriver::Flip(std::chrono::microseconds timeout) {
Request req;
{
std::unique_lock lock{mutex};
submit_cond.wait_for(lock, timeout, [&] { return !requests.empty(); });
if (requests.empty()) {
renderer->ShowSplash();
return;
}
// Retrieve the request.
req = requests.front();
requests.pop();
std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
if (!req) {
return std::chrono::microseconds{0};
}
const auto start = std::chrono::high_resolution_clock::now();
// Whatever the game is rendering show splash if it is active
if (!renderer->ShowSplash(req.frame)) {
// Present the frame.
renderer->Present(req.frame);
}
std::scoped_lock lock{mutex};
// Update flip status.
auto& flip_status = req.port->flip_status;
auto* port = req.port;
auto& flip_status = port->flip_status;
flip_status.count++;
flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
flip_status.tsc = Libraries::Kernel::sceKernelReadTsc();
@@ -192,7 +183,7 @@ void VideoOutDriver::Flip(std::chrono::microseconds timeout) {
flip_status.flipPendingNum = static_cast<int>(requests.size());
// Trigger flip events for the port.
for (auto& event : req.port->flip_events) {
for (auto& event : port->flip_events) {
if (event != nullptr) {
event->TriggerEvent(SCE_VIDEO_OUT_EVENT_FLIP, Kernel::SceKernelEvent::Filter::VideoOut,
reinterpret_cast<void*>(req.flip_arg));
@@ -201,21 +192,23 @@ void VideoOutDriver::Flip(std::chrono::microseconds timeout) {
// Reset flip label
if (req.index != -1) {
req.port->buffer_labels[req.index] = 0;
port->buffer_labels[req.index] = 0;
port->SignalVoLabel();
}
const auto end = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
}
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg,
bool is_eop /*= false*/) {
std::scoped_lock lock{mutex};
Vulkan::Frame* frame;
if (index == -1) {
frame = renderer->PrepareBlankFrame();
} else {
const auto& buffer = port->buffer_slots[index];
const auto& group = port->groups[buffer.group_index];
frame = renderer->PrepareFrame(group, buffer.address_left);
frame = renderer->PrepareFrame(group, buffer.address_left, is_eop);
}
if (index != -1 && requests.size() >= port->NumRegisteredBuffers()) {
@@ -223,6 +216,7 @@ bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg,
return false;
}
std::scoped_lock lock{mutex};
requests.push({
.frame = frame,
.port = port,
@@ -234,24 +228,53 @@ bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg,
port->flip_status.flipPendingNum = static_cast<int>(requests.size());
port->flip_status.gcQueueNum = 0;
submit_cond.notify_one();
return true;
}
void VideoOutDriver::Vblank() {
std::scoped_lock lock{mutex};
void VideoOutDriver::PresentThread(std::stop_token token) {
static constexpr std::chrono::milliseconds VblankPeriod{16};
Common::SetCurrentThreadName("PresentThread");
auto& vblank_status = main_port.vblank_status;
vblank_status.count++;
vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc();
const auto receive_request = [this] -> Request {
std::scoped_lock lk{mutex};
if (!requests.empty()) {
const auto request = requests.front();
requests.pop();
return request;
}
return {};
};
// Trigger flip events for the port.
for (auto& event : main_port.vblank_events) {
if (event != nullptr) {
event->TriggerEvent(SCE_VIDEO_OUT_EVENT_VBLANK,
Kernel::SceKernelEvent::Filter::VideoOut, nullptr);
auto vblank_period = VblankPeriod / Config::vblankDiv();
auto delay = std::chrono::microseconds{0};
while (!token.stop_requested()) {
// Sleep for most of the vblank duration.
std::this_thread::sleep_for(vblank_period - delay);
// Check if it's time to take a request.
auto& vblank_status = main_port.vblank_status;
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
const auto request = receive_request();
delay = Flip(request);
FRAME_END;
}
{
// Needs lock here as can be concurrently read by `sceVideoOutGetVblankStatus`
std::unique_lock lock{main_port.vo_mutex};
vblank_status.count++;
vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc();
main_port.vblank_cv.notify_all();
}
// Trigger flip events for the port.
for (auto& event : main_port.vblank_events) {
if (event != nullptr) {
event->TriggerEvent(SCE_VIDEO_OUT_EVENT_VBLANK,
Kernel::SceKernelEvent::Filter::VideoOut, nullptr);
}
}
}
}