mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-05-14 08:12:16 +00:00
Frame graph + Precise 60 fps timing (#998)
* video info: add frame graph Toggle advanced info with CTRL+F10. Also fixed imgui using gamepad for nav in wrong situations * 60fps! Implemented a timer that accumulates the time spent sleeping and sleeps for the remaining time. Also measure entire PresentThread time instead of just the time spent in Flip. * sceKernelGettimeofday: replace chrono by win32 api. Better performance bb uses this function too much. Consuming almost 30% of cpu time
This commit is contained in:
parent
a016792371
commit
5a8e8f5936
8 changed files with 183 additions and 26 deletions
|
@ -3,10 +3,12 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "common/error.h"
|
#include "common/error.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
|
#include "ntapi.h"
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
#include <mach/mach_time.h>
|
#include <mach/mach_time.h>
|
||||||
|
@ -102,6 +104,16 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||||
SetThreadPriority(handle, windows_priority);
|
SetThreadPriority(handle, windows_priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void AccurateSleep(std::chrono::nanoseconds duration) {
|
||||||
|
LARGE_INTEGER interval{
|
||||||
|
.QuadPart = -1 * (duration.count() / 100u),
|
||||||
|
};
|
||||||
|
HANDLE timer = ::CreateWaitableTimer(NULL, TRUE, NULL);
|
||||||
|
SetWaitableTimer(timer, &interval, 0, NULL, NULL, 0);
|
||||||
|
WaitForSingleObject(timer, INFINITE);
|
||||||
|
::CloseHandle(timer);
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||||
|
@ -122,6 +134,10 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||||
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
pthread_setschedparam(this_thread, scheduling_type, ¶ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void AccurateSleep(std::chrono::nanoseconds duration) {
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
@ -164,4 +180,22 @@ void SetCurrentThreadName(const char*) {
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
AccurateTimer::AccurateTimer(std::chrono::nanoseconds target_interval)
|
||||||
|
: target_interval(target_interval) {}
|
||||||
|
|
||||||
|
void AccurateTimer::Start() {
|
||||||
|
auto begin_sleep = std::chrono::high_resolution_clock::now();
|
||||||
|
if (total_wait.count() > 0) {
|
||||||
|
AccurateSleep(total_wait);
|
||||||
|
}
|
||||||
|
start_time = std::chrono::high_resolution_clock::now();
|
||||||
|
total_wait -= std::chrono::duration_cast<std::chrono::nanoseconds>(start_time - begin_sleep);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccurateTimer::End() {
|
||||||
|
auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
total_wait +=
|
||||||
|
target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|
|
@ -23,4 +23,18 @@ void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||||
|
|
||||||
void SetCurrentThreadName(const char* name);
|
void SetCurrentThreadName(const char* name);
|
||||||
|
|
||||||
|
class AccurateTimer {
|
||||||
|
std::chrono::nanoseconds target_interval{};
|
||||||
|
std::chrono::nanoseconds total_wait{};
|
||||||
|
|
||||||
|
std::chrono::high_resolution_clock::time_point start_time;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AccurateTimer(std::chrono::nanoseconds target_interval);
|
||||||
|
|
||||||
|
void Start();
|
||||||
|
|
||||||
|
void End();
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|
|
@ -147,13 +147,20 @@ int PS4_SYSV_ABI sceKernelGettimeofday(OrbisKernelTimeval* tp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN64
|
#ifdef _WIN64
|
||||||
auto now = std::chrono::system_clock::now();
|
FILETIME filetime;
|
||||||
auto duration = now.time_since_epoch();
|
GetSystemTimeAsFileTime(&filetime);
|
||||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration);
|
|
||||||
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(duration - seconds);
|
|
||||||
|
|
||||||
tp->tv_sec = seconds.count();
|
constexpr u64 UNIX_TIME_START = 0x295E9648864000;
|
||||||
tp->tv_usec = microsecs.count();
|
constexpr u64 TICKS_PER_SECOND = 1000000;
|
||||||
|
|
||||||
|
u64 ticks = filetime.dwHighDateTime;
|
||||||
|
ticks <<= 32;
|
||||||
|
ticks |= filetime.dwLowDateTime;
|
||||||
|
ticks /= 10;
|
||||||
|
ticks -= UNIX_TIME_START;
|
||||||
|
|
||||||
|
tp->tv_sec = ticks / TICKS_PER_SECOND;
|
||||||
|
tp->tv_usec = ticks % TICKS_PER_SECOND;
|
||||||
#else
|
#else
|
||||||
timeval tv;
|
timeval tv;
|
||||||
gettimeofday(&tv, nullptr);
|
gettimeofday(&tv, nullptr);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
@ -160,9 +161,7 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) {
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
|
void VideoOutDriver::Flip(const Request& req) {
|
||||||
const auto start = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
// Whatever the game is rendering show splash if it is active
|
// Whatever the game is rendering show splash if it is active
|
||||||
if (!renderer->ShowSplash(req.frame)) {
|
if (!renderer->ShowSplash(req.frame)) {
|
||||||
// Present the frame.
|
// Present the frame.
|
||||||
|
@ -198,9 +197,6 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
|
||||||
port->buffer_labels[req.index] = 0;
|
port->buffer_labels[req.index] = 0;
|
||||||
port->SignalVoLabel();
|
port->SignalVoLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto end = std::chrono::high_resolution_clock::now();
|
|
||||||
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoOutDriver::DrawBlankFrame() {
|
void VideoOutDriver::DrawBlankFrame() {
|
||||||
|
@ -267,6 +263,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
||||||
Common::SetCurrentThreadName("PresentThread");
|
Common::SetCurrentThreadName("PresentThread");
|
||||||
Common::SetCurrentThreadRealtime(vblank_period);
|
Common::SetCurrentThreadRealtime(vblank_period);
|
||||||
|
|
||||||
|
Common::AccurateTimer timer{vblank_period};
|
||||||
|
|
||||||
const auto receive_request = [this] -> Request {
|
const auto receive_request = [this] -> Request {
|
||||||
std::scoped_lock lk{mutex};
|
std::scoped_lock lk{mutex};
|
||||||
if (!requests.empty()) {
|
if (!requests.empty()) {
|
||||||
|
@ -279,20 +277,18 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
||||||
|
|
||||||
auto delay = std::chrono::microseconds{0};
|
auto delay = std::chrono::microseconds{0};
|
||||||
while (!token.stop_requested()) {
|
while (!token.stop_requested()) {
|
||||||
// Sleep for most of the vblank duration.
|
timer.Start();
|
||||||
std::this_thread::sleep_for(vblank_period - delay);
|
|
||||||
|
|
||||||
// Check if it's time to take a request.
|
// Check if it's time to take a request.
|
||||||
auto& vblank_status = main_port.vblank_status;
|
auto& vblank_status = main_port.vblank_status;
|
||||||
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
|
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
|
||||||
const auto request = receive_request();
|
const auto request = receive_request();
|
||||||
if (!request) {
|
if (!request) {
|
||||||
delay = std::chrono::microseconds{0};
|
|
||||||
if (!main_port.is_open) {
|
if (!main_port.is_open) {
|
||||||
DrawBlankFrame();
|
DrawBlankFrame();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delay = Flip(request);
|
Flip(request);
|
||||||
FRAME_END;
|
FRAME_END;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,6 +309,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
|
||||||
Kernel::SceKernelEvent::Filter::VideoOut, nullptr);
|
Kernel::SceKernelEvent::Filter::VideoOut, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timer.End();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ private:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::chrono::microseconds Flip(const Request& req);
|
void Flip(const Request& req);
|
||||||
void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date
|
void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date
|
||||||
void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
||||||
void PresentThread(std::stop_token token);
|
void PresentThread(std::stop_token token);
|
||||||
|
|
|
@ -2,16 +2,121 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include "common/config.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "imgui_internal.h"
|
||||||
#include "video_info.h"
|
#include "video_info.h"
|
||||||
|
|
||||||
void ImGui::Layers::VideoInfo::Draw() {
|
using namespace ImGui;
|
||||||
const ImGuiIO& io = GetIO();
|
|
||||||
|
|
||||||
m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show;
|
struct FrameInfo {
|
||||||
|
u32 num;
|
||||||
|
float delta;
|
||||||
|
};
|
||||||
|
|
||||||
if (m_show) {
|
static bool show = false;
|
||||||
if (Begin("Video Info", 0, ImGuiWindowFlags_NoNav)) {
|
static bool show_advanced = false;
|
||||||
|
|
||||||
|
static u32 current_frame = 0;
|
||||||
|
constexpr float TARGET_FPS = 60.0f;
|
||||||
|
constexpr u32 FRAME_BUFFER_SIZE = 1024;
|
||||||
|
constexpr float BAR_WIDTH_MULT = 1.4f;
|
||||||
|
constexpr float BAR_HEIGHT_MULT = 1.25f;
|
||||||
|
constexpr float FRAME_GRAPH_PADDING_Y = 3.0f;
|
||||||
|
static std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list;
|
||||||
|
static float frame_graph_height = 50.0f;
|
||||||
|
|
||||||
|
static void DrawSimple() {
|
||||||
|
const auto io = GetIO();
|
||||||
Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
|
Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawAdvanced() {
|
||||||
|
const auto& ctx = *GetCurrentContext();
|
||||||
|
const auto& io = ctx.IO;
|
||||||
|
const auto& window = *ctx.CurrentWindow;
|
||||||
|
auto& draw_list = *window.DrawList;
|
||||||
|
|
||||||
|
Text("Frame time: %.3f ms (%.1f FPS)", io.DeltaTime * 1000.0f, io.Framerate);
|
||||||
|
|
||||||
|
SeparatorText("Frame graph");
|
||||||
|
const float full_width = GetContentRegionAvail().x;
|
||||||
|
{ // Frame graph - inspired by
|
||||||
|
// https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times
|
||||||
|
auto pos = GetCursorScreenPos();
|
||||||
|
const ImVec2 size{full_width, frame_graph_height + FRAME_GRAPH_PADDING_Y * 2.0f};
|
||||||
|
ItemSize(size);
|
||||||
|
if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv());
|
||||||
|
float cur_pos_x = pos.x + full_width;
|
||||||
|
pos.y += FRAME_GRAPH_PADDING_Y;
|
||||||
|
const float final_pos_y = pos.y + frame_graph_height;
|
||||||
|
|
||||||
|
draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y},
|
||||||
|
{pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y},
|
||||||
|
IM_COL32(0x33, 0x33, 0x33, 0xFF));
|
||||||
|
draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true);
|
||||||
|
for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) {
|
||||||
|
const auto& frame_info = frame_list[(current_frame - i) % FRAME_BUFFER_SIZE];
|
||||||
|
const float dt_factor = target_dt / frame_info.delta;
|
||||||
|
|
||||||
|
const float width = std::ceil(BAR_WIDTH_MULT / dt_factor);
|
||||||
|
const float height =
|
||||||
|
std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * frame_graph_height;
|
||||||
|
|
||||||
|
ImU32 color;
|
||||||
|
if (dt_factor >= 0.95f) { // BLUE
|
||||||
|
color = IM_COL32(0x33, 0x33, 0xFF, 0xFF);
|
||||||
|
} else if (dt_factor >= 0.5f) { // GREEN <> YELLOW
|
||||||
|
float t = 1.0f - (dt_factor - 0.5f) * 2.0f;
|
||||||
|
int r = (int)(0xFF * t);
|
||||||
|
color = IM_COL32(r, 0xFF, 0, 0xFF);
|
||||||
|
} else { // YELLOW <> RED
|
||||||
|
float t = dt_factor * 2.0f;
|
||||||
|
int g = (int)(0xFF * t);
|
||||||
|
color = IM_COL32(0xFF, g, 0, 0xFF);
|
||||||
|
}
|
||||||
|
draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height},
|
||||||
|
{cur_pos_x, final_pos_y}, color);
|
||||||
|
cur_pos_x -= width;
|
||||||
|
if (cur_pos_x < width) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
draw_list.PopClipRect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Layers::VideoInfo::Draw() {
|
||||||
|
const auto io = GetIO();
|
||||||
|
|
||||||
|
const FrameInfo frame_info{
|
||||||
|
.num = ++current_frame,
|
||||||
|
.delta = io.DeltaTime,
|
||||||
|
};
|
||||||
|
frame_list[current_frame % FRAME_BUFFER_SIZE] = frame_info;
|
||||||
|
|
||||||
|
if (IsKeyPressed(ImGuiKey_F10, false)) {
|
||||||
|
const bool changed_ctrl = io.KeyCtrl != show_advanced;
|
||||||
|
show_advanced = io.KeyCtrl;
|
||||||
|
show = changed_ctrl || !show;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
if (show_advanced) {
|
||||||
|
if (Begin("Video debug info", &show, 0)) {
|
||||||
|
DrawAdvanced();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Begin("Video Info", nullptr,
|
||||||
|
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
DrawSimple();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
End();
|
End();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ class RendererVulkan;
|
||||||
namespace ImGui::Layers {
|
namespace ImGui::Layers {
|
||||||
|
|
||||||
class VideoInfo : public Layer {
|
class VideoInfo : public Layer {
|
||||||
bool m_show = false;
|
|
||||||
::Vulkan::RendererVulkan* renderer{};
|
::Vulkan::RendererVulkan* renderer{};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -703,8 +703,8 @@ static void UpdateGamepads() {
|
||||||
const int thumb_dead_zone = 8000; // SDL_gamepad.h suggests using this value.
|
const int thumb_dead_zone = 8000; // SDL_gamepad.h suggests using this value.
|
||||||
UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart, SDL_GAMEPAD_BUTTON_START);
|
UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart, SDL_GAMEPAD_BUTTON_START);
|
||||||
UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack, SDL_GAMEPAD_BUTTON_BACK);
|
UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack, SDL_GAMEPAD_BUTTON_BACK);
|
||||||
UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft,
|
/*UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft,
|
||||||
SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square
|
SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square*/ // Disable to avoid menu toggle
|
||||||
UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight,
|
UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight,
|
||||||
SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle
|
SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle
|
||||||
UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp,
|
UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp,
|
||||||
|
|
Loading…
Add table
Reference in a new issue