presenter: render the previous frame to keep the render rendering

This commit is contained in:
Vinicius Rangel 2025-01-14 22:12:12 -03:00
parent f423d151cf
commit 421acfcf4e
No known key found for this signature in database
GPG Key ID: A5B154D904B761D9
13 changed files with 130 additions and 35 deletions

View File

@ -131,7 +131,7 @@ class DebugStateImpl {
friend class Core::Devtools::Widget::FrameGraph; friend class Core::Devtools::Widget::FrameGraph;
friend class Core::Devtools::Widget::ShaderList; friend class Core::Devtools::Widget::ShaderList;
static bool showing_debug_menu_bar; bool showing_debug_menu_bar = false;
std::queue<std::string> debug_message_popup; std::queue<std::string> debug_message_popup;
@ -155,6 +155,9 @@ class DebugStateImpl {
std::vector<ShaderDump> shader_dump_list{}; std::vector<ShaderDump> shader_dump_list{};
public: public:
float Framerate = 1.0f / 60.0f;
float FrameDeltaTime;
void ShowDebugMessage(std::string message) { void ShowDebugMessage(std::string message) {
if (message.empty()) { if (message.empty()) {
return; return;

View File

@ -24,8 +24,6 @@ using namespace ImGui;
using namespace Core::Devtools; using namespace Core::Devtools;
using L = Core::Devtools::Layer; using L = Core::Devtools::Layer;
bool DebugStateType::DebugStateImpl::showing_debug_menu_bar = false;
static bool show_simple_fps = false; static bool show_simple_fps = false;
static bool visibility_toggled = false; static bool visibility_toggled = false;
@ -254,8 +252,8 @@ void L::DrawAdvanced() {
} }
void L::DrawSimple() { void L::DrawSimple() {
const auto io = GetIO(); const float frameRate = DebugState.Framerate;
Text("%.1f FPS (%.2f ms)", io.Framerate, 1000.0f / io.Framerate); Text("%d FPS (%.1f ms)", static_cast<int>(std::round(1.0f / frameRate)), frameRate * 1000.0f);
} }
static void LoadSettings(const char* line) { static void LoadSettings(const char* line) {
@ -337,7 +335,7 @@ void L::Draw() {
if (!DebugState.IsGuestThreadsPaused()) { if (!DebugState.IsGuestThreadsPaused()) {
const auto fn = DebugState.flip_frame_count.load(); const auto fn = DebugState.flip_frame_count.load();
frame_graph.AddFrame(fn, io.DeltaTime); frame_graph.AddFrame(fn, DebugState.FrameDeltaTime);
} }
if (IsKeyPressed(ImGuiKey_F10, false)) { if (IsKeyPressed(ImGuiKey_F10, false)) {

View File

@ -83,9 +83,6 @@ void FrameGraph::Draw() {
auto isSystemPaused = DebugState.IsGuestThreadsPaused(); auto isSystemPaused = DebugState.IsGuestThreadsPaused();
static float deltaTime;
static float frameRate;
if (!isSystemPaused) { if (!isSystemPaused) {
deltaTime = io.DeltaTime * 1000.0f; deltaTime = io.DeltaTime * 1000.0f;
frameRate = 1000.0f / deltaTime; frameRate = 1000.0f / deltaTime;

View File

@ -16,6 +16,9 @@ class FrameGraph {
std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list{}; std::array<FrameInfo, FRAME_BUFFER_SIZE> frame_list{};
float deltaTime{};
float frameRate{};
void DrawFrameGraph(); void DrawFrameGraph();
public: public:

View File

@ -1,8 +1,6 @@
// 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 "common/assert.h" #include "common/assert.h"
#include "common/config.h" #include "common/config.h"
#include "common/debug.h" #include "common/debug.h"
@ -207,6 +205,13 @@ void VideoOutDriver::DrawBlankFrame() {
presenter->Present(empty_frame); presenter->Present(empty_frame);
} }
void VideoOutDriver::DrawLastFrame() {
const auto frame = presenter->PrepareLastFrame();
if (frame != nullptr) {
presenter->Present(frame, true);
}
}
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg,
bool is_eop /*= false*/) { bool is_eop /*= false*/) {
{ {
@ -278,17 +283,24 @@ void VideoOutDriver::PresentThread(std::stop_token token) {
return {}; return {};
}; };
auto delay = std::chrono::microseconds{0};
while (!token.stop_requested()) { while (!token.stop_requested()) {
timer.Start(); timer.Start();
if (DebugState.IsGuestThreadsPaused()) {
DrawLastFrame();
timer.End();
continue;
}
// 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) {
if (!main_port.is_open || DebugState.IsGuestThreadsPaused()) { if (!main_port.is_open) {
DrawBlankFrame(); DrawBlankFrame();
} else {
DrawLastFrame();
} }
} else { } else {
Flip(request); Flip(request);

View File

@ -102,7 +102,8 @@ private:
}; };
void 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(); // Video port out not open
void DrawLastFrame(); // Used when there is no flip request
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);

View File

@ -168,7 +168,7 @@ bool ProcessEvent(SDL_Event* event) {
} }
} }
ImGuiID NewFrame() { ImGuiID NewFrame(bool is_reusing_frame) {
{ {
std::scoped_lock lock{change_layers_mutex}; std::scoped_lock lock{change_layers_mutex};
while (!change_layers.empty()) { while (!change_layers.empty()) {
@ -183,7 +183,7 @@ ImGuiID NewFrame() {
} }
} }
Sdl::NewFrame(); Sdl::NewFrame(is_reusing_frame);
ImGui::NewFrame(); ImGui::NewFrame();
ImGuiWindowFlags flags = ImGuiDockNodeFlags_PassthruCentralNode; ImGuiWindowFlags flags = ImGuiDockNodeFlags_PassthruCentralNode;

View File

@ -26,7 +26,7 @@ void Shutdown(const vk::Device& device);
bool ProcessEvent(SDL_Event* event); bool ProcessEvent(SDL_Event* event);
ImGuiID NewFrame(); ImGuiID NewFrame(bool is_reusing_frame = false);
void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view, void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
const vk::Extent2D& extent); const vk::Extent2D& extent);

View File

@ -5,6 +5,7 @@
#include <imgui.h> #include <imgui.h>
#include "common/config.h" #include "common/config.h"
#include "core/debug_state.h"
#include "imgui_impl_sdl3.h" #include "imgui_impl_sdl3.h"
// SDL // SDL
@ -26,6 +27,7 @@ struct SdlData {
SDL_Window* window{}; SDL_Window* window{};
SDL_WindowID window_id{}; SDL_WindowID window_id{};
Uint64 time{}; Uint64 time{};
Uint64 nonReusedtime{};
const char* clipboard_text_data{}; const char* clipboard_text_data{};
// IME handling // IME handling
@ -785,7 +787,7 @@ static void UpdateGamepads() {
+thumb_dead_zone, +32767); +thumb_dead_zone, +32767);
} }
void NewFrame() { void NewFrame(bool is_reusing_frame) {
SdlData* bd = GetBackendData(); SdlData* bd = GetBackendData();
IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@ -798,9 +800,26 @@ void NewFrame() {
if (current_time <= bd->time) if (current_time <= bd->time)
current_time = bd->time + 1; current_time = bd->time + 1;
io.DeltaTime = bd->time > 0 ? (float)((double)(current_time - bd->time) / (double)frequency) io.DeltaTime = bd->time > 0 ? (float)((double)(current_time - bd->time) / (double)frequency)
: (float)(1.0f / 60.0f); : 1.0f / 60.0f;
bd->time = current_time; bd->time = current_time;
if (!is_reusing_frame) {
if (current_time <= bd->nonReusedtime)
current_time = bd->nonReusedtime + 1;
float deltaTime =
bd->nonReusedtime > 0
? (float)((double)(current_time - bd->nonReusedtime) / (double)frequency)
: 1.0f / 60.0f;
bd->nonReusedtime = current_time;
DebugState.FrameDeltaTime = deltaTime;
float distribution = 0.016f / deltaTime / 10.0f;
if (distribution > 1.0f) {
distribution = 1.0f;
}
DebugState.Framerate =
deltaTime * distribution + DebugState.Framerate * (1.0f - distribution);
}
if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() && if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() &&
bd->mouse_buttons_down == 0) { bd->mouse_buttons_down == 0) {
bd->mouse_window_id = 0; bd->mouse_window_id = 0;

View File

@ -14,7 +14,7 @@ namespace ImGui::Sdl {
bool Init(SDL_Window* window); bool Init(SDL_Window* window);
void Shutdown(); void Shutdown();
void NewFrame(); void NewFrame(bool is_reusing);
bool ProcessEvent(const SDL_Event* event); bool ProcessEvent(const SDL_Event* event);
void OnResize(); void OnResize();

View File

@ -383,8 +383,8 @@ bool Instance::CreateDevice() {
.extendedDynamicState = true, .extendedDynamicState = true,
}, },
vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{ vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{
.extendedDynamicState3ColorWriteMask = true,
.extendedDynamicState3ColorBlendEnable = true, .extendedDynamicState3ColorBlendEnable = true,
.extendedDynamicState3ColorWriteMask = true,
}, },
vk::PhysicalDeviceDepthClipControlFeaturesEXT{ vk::PhysicalDeviceDepthClipControlFeaturesEXT{
.depthClipControl = true, .depthClipControl = true,

View File

@ -411,6 +411,61 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
frame->imgui_texture->disable_blend = true; frame->imgui_texture->disable_blend = true;
} }
Frame* Presenter::PrepareLastFrame() {
if (last_submit_frame == nullptr) {
return nullptr;
}
Frame* frame = last_submit_frame;
while (true) {
vk::Result result = instance.GetDevice().waitForFences(frame->present_done, false,
std::numeric_limits<u64>::max());
if (result == vk::Result::eSuccess) {
break;
}
if (result == vk::Result::eTimeout) {
continue;
}
ASSERT_MSG(result != vk::Result::eErrorDeviceLost,
"Device lost during waiting for a frame");
}
auto& scheduler = flip_scheduler;
scheduler.EndRendering();
const auto cmdbuf = scheduler.CommandBuffer();
const auto frame_subresources = vk::ImageSubresourceRange{
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
};
const auto pre_barrier =
vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
.srcAccessMask = vk::AccessFlagBits2::eColorAttachmentRead,
.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite,
.oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
.newLayout = vk::ImageLayout::eGeneral,
.image = frame->image,
.subresourceRange{frame_subresources}};
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &pre_barrier,
});
// Flush frame creation commands.
frame->ready_semaphore = scheduler.GetMasterSemaphore()->Handle();
frame->ready_tick = scheduler.CurrentTick();
SubmitInfo info{};
scheduler.Flush(info);
return frame;
}
bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) { bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) {
const auto* splash = Common::Singleton<Splash>::Instance(); const auto* splash = Common::Singleton<Splash>::Instance();
if (splash->GetImageData().empty()) { if (splash->GetImageData().empty()) {
@ -529,8 +584,8 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
}; };
const auto pre_barrier = const auto pre_barrier =
vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eTransfer, vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
.srcAccessMask = vk::AccessFlagBits2::eTransferRead, .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentRead,
.dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput,
.dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite,
.oldLayout = vk::ImageLayout::eUndefined, .oldLayout = vk::ImageLayout::eUndefined,
@ -641,12 +696,15 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
return frame; return frame;
} }
void Presenter::Present(Frame* frame) { void Presenter::Present(Frame* frame, bool is_reusing_frame) {
// Free the frame for reuse // Free the frame for reuse
const auto free_frame = [&] { const auto free_frame = [&] {
std::scoped_lock fl{free_mutex}; if (!is_reusing_frame) {
free_queue.push(frame); last_submit_frame = frame;
free_cv.notify_one(); std::scoped_lock fl{free_mutex};
free_queue.push(frame);
free_cv.notify_one();
}
}; };
// Recreate the swapchain if the window was resized. // Recreate the swapchain if the window was resized.
@ -669,7 +727,7 @@ void Presenter::Present(Frame* frame) {
// the frame's present fence and future GetRenderFrame() call will hang waiting for this frame. // the frame's present fence and future GetRenderFrame() call will hang waiting for this frame.
instance.GetDevice().resetFences(frame->present_done); instance.GetDevice().resetFences(frame->present_done);
ImGuiID dockId = ImGui::Core::NewFrame(); ImGuiID dockId = ImGui::Core::NewFrame(is_reusing_frame);
const vk::Image swapchain_image = swapchain.Image(); const vk::Image swapchain_image = swapchain.Image();
const vk::ImageView swapchain_image_view = swapchain.ImageView(); const vk::ImageView swapchain_image_view = swapchain.ImageView();
@ -744,13 +802,13 @@ void Presenter::Present(Frame* frame) {
ImGui::SetNextWindowDockID(dockId, ImGuiCond_Once); ImGui::SetNextWindowDockID(dockId, ImGuiCond_Once);
ImGui::Begin("Display##game_display", nullptr, ImGuiWindowFlags_NoNav); ImGui::Begin("Display##game_display", nullptr, ImGuiWindowFlags_NoNav);
ImVec2 contentArea = ImGui::GetContentRegionMax(); ImVec2 contentArea = ImGui::GetContentRegionAvail();
const vk::Rect2D imgRect = const vk::Rect2D imgRect =
FitImage(frame->width, frame->height, (s32)contentArea.x, (s32)contentArea.y); FitImage(frame->width, frame->height, (s32)contentArea.x, (s32)contentArea.y);
ImGui::SetCursorPos({ ImGui::SetCursorPos(ImGui::GetCursorStartPos() + ImVec2{
(float)imgRect.offset.x, (float)imgRect.offset.x,
(float)imgRect.offset.y, (float)imgRect.offset.y,
}); });
ImGui::Image(frame->imgui_texture, { ImGui::Image(frame->imgui_texture, {
static_cast<float>(imgRect.extent.width), static_cast<float>(imgRect.extent.width),
static_cast<float>(imgRect.extent.height), static_cast<float>(imgRect.extent.height),
@ -784,7 +842,9 @@ void Presenter::Present(Frame* frame) {
} }
free_frame(); free_frame();
DebugState.IncFlipFrameNum(); if (!is_reusing_frame) {
DebugState.IncFlipFrameNum();
}
} }
Frame* Presenter::GetRenderFrame() { Frame* Presenter::GetRenderFrame() {

View File

@ -89,8 +89,9 @@ public:
} }
bool ShowSplash(Frame* frame = nullptr); bool ShowSplash(Frame* frame = nullptr);
void Present(Frame* frame); void Present(Frame* frame, bool is_reusing_frame = false);
void RecreateFrame(Frame* frame, u32 width, u32 height); void RecreateFrame(Frame* frame, u32 width, u32 height);
Frame* PrepareLastFrame();
void FlushDraw() { void FlushDraw() {
SubmitInfo info{}; SubmitInfo info{};
@ -124,6 +125,7 @@ private:
vk::UniqueCommandPool command_pool; vk::UniqueCommandPool command_pool;
std::vector<Frame> present_frames; std::vector<Frame> present_frames;
std::queue<Frame*> free_queue; std::queue<Frame*> free_queue;
Frame* last_submit_frame;
std::mutex free_mutex; std::mutex free_mutex;
std::condition_variable free_cv; std::condition_variable free_cv;
std::condition_variable_any frame_cv; std::condition_variable_any frame_cv;