diff --git a/src/core/debug_state.h b/src/core/debug_state.h index 71f48b994..aab741fd0 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -131,7 +131,7 @@ class DebugStateImpl { friend class Core::Devtools::Widget::FrameGraph; friend class Core::Devtools::Widget::ShaderList; - static bool showing_debug_menu_bar; + bool showing_debug_menu_bar = false; std::queue debug_message_popup; @@ -155,6 +155,9 @@ class DebugStateImpl { std::vector shader_dump_list{}; public: + float Framerate = 1.0f / 60.0f; + float FrameDeltaTime; + void ShowDebugMessage(std::string message) { if (message.empty()) { return; diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 6d0889497..11990a56f 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -24,8 +24,6 @@ using namespace ImGui; using namespace Core::Devtools; using L = Core::Devtools::Layer; -bool DebugStateType::DebugStateImpl::showing_debug_menu_bar = false; - static bool show_simple_fps = false; static bool visibility_toggled = false; @@ -254,8 +252,8 @@ void L::DrawAdvanced() { } void L::DrawSimple() { - const auto io = GetIO(); - Text("%.1f FPS (%.2f ms)", io.Framerate, 1000.0f / io.Framerate); + const float frameRate = DebugState.Framerate; + Text("%d FPS (%.1f ms)", static_cast(std::round(1.0f / frameRate)), frameRate * 1000.0f); } static void LoadSettings(const char* line) { @@ -337,7 +335,7 @@ void L::Draw() { if (!DebugState.IsGuestThreadsPaused()) { 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)) { diff --git a/src/core/devtools/widget/frame_graph.cpp b/src/core/devtools/widget/frame_graph.cpp index 0e170db38..cd5a6c59d 100644 --- a/src/core/devtools/widget/frame_graph.cpp +++ b/src/core/devtools/widget/frame_graph.cpp @@ -83,9 +83,6 @@ void FrameGraph::Draw() { auto isSystemPaused = DebugState.IsGuestThreadsPaused(); - static float deltaTime; - static float frameRate; - if (!isSystemPaused) { deltaTime = io.DeltaTime * 1000.0f; frameRate = 1000.0f / deltaTime; diff --git a/src/core/devtools/widget/frame_graph.h b/src/core/devtools/widget/frame_graph.h index 40a68ffa7..aef3c0747 100644 --- a/src/core/devtools/widget/frame_graph.h +++ b/src/core/devtools/widget/frame_graph.h @@ -16,6 +16,9 @@ class FrameGraph { std::array frame_list{}; + float deltaTime{}; + float frameRate{}; + void DrawFrameGraph(); public: diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index f6c25afe3..29948f242 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include - #include "common/assert.h" #include "common/config.h" #include "common/debug.h" @@ -207,6 +205,13 @@ void VideoOutDriver::DrawBlankFrame() { 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 is_eop /*= false*/) { { @@ -278,17 +283,24 @@ void VideoOutDriver::PresentThread(std::stop_token token) { return {}; }; - auto delay = std::chrono::microseconds{0}; while (!token.stop_requested()) { timer.Start(); + if (DebugState.IsGuestThreadsPaused()) { + DrawLastFrame(); + timer.End(); + continue; + } + // 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(); if (!request) { - if (!main_port.is_open || DebugState.IsGuestThreadsPaused()) { + if (!main_port.is_open) { DrawBlankFrame(); + } else { + DrawLastFrame(); } } else { Flip(request); diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index ec01b621f..ad7c7bec2 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -102,7 +102,8 @@ private: }; 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 PresentThread(std::stop_token token); diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 2180b3b4c..b63f50340 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -168,7 +168,7 @@ bool ProcessEvent(SDL_Event* event) { } } -ImGuiID NewFrame() { +ImGuiID NewFrame(bool is_reusing_frame) { { std::scoped_lock lock{change_layers_mutex}; while (!change_layers.empty()) { @@ -183,7 +183,7 @@ ImGuiID NewFrame() { } } - Sdl::NewFrame(); + Sdl::NewFrame(is_reusing_frame); ImGui::NewFrame(); ImGuiWindowFlags flags = ImGuiDockNodeFlags_PassthruCentralNode; diff --git a/src/imgui/renderer/imgui_core.h b/src/imgui/renderer/imgui_core.h index 6ad09f4bd..7d5279bd6 100644 --- a/src/imgui/renderer/imgui_core.h +++ b/src/imgui/renderer/imgui_core.h @@ -26,7 +26,7 @@ void Shutdown(const vk::Device& device); 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, const vk::Extent2D& extent); diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index e67bdc775..ddd532cd0 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -5,6 +5,7 @@ #include #include "common/config.h" +#include "core/debug_state.h" #include "imgui_impl_sdl3.h" // SDL @@ -26,6 +27,7 @@ struct SdlData { SDL_Window* window{}; SDL_WindowID window_id{}; Uint64 time{}; + Uint64 nonReusedtime{}; const char* clipboard_text_data{}; // IME handling @@ -785,7 +787,7 @@ static void UpdateGamepads() { +thumb_dead_zone, +32767); } -void NewFrame() { +void NewFrame(bool is_reusing_frame) { SdlData* bd = GetBackendData(); IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); @@ -798,9 +800,26 @@ void NewFrame() { if (current_time <= bd->time) current_time = bd->time + 1; 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; + 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() && bd->mouse_buttons_down == 0) { bd->mouse_window_id = 0; diff --git a/src/imgui/renderer/imgui_impl_sdl3.h b/src/imgui/renderer/imgui_impl_sdl3.h index 59b1a6856..fe626a962 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.h +++ b/src/imgui/renderer/imgui_impl_sdl3.h @@ -14,7 +14,7 @@ namespace ImGui::Sdl { bool Init(SDL_Window* window); void Shutdown(); -void NewFrame(); +void NewFrame(bool is_reusing); bool ProcessEvent(const SDL_Event* event); void OnResize(); diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index a9a68d7c7..323b30816 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -383,8 +383,8 @@ bool Instance::CreateDevice() { .extendedDynamicState = true, }, vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{ - .extendedDynamicState3ColorWriteMask = true, .extendedDynamicState3ColorBlendEnable = true, + .extendedDynamicState3ColorWriteMask = true, }, vk::PhysicalDeviceDepthClipControlFeaturesEXT{ .depthClipControl = true, diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 03e02e5d2..f0d92c02e 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -411,6 +411,61 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) { 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::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*/) { const auto* splash = Common::Singleton::Instance(); if (splash->GetImageData().empty()) { @@ -529,8 +584,8 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) }; const auto pre_barrier = - vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eTransfer, - .srcAccessMask = vk::AccessFlagBits2::eTransferRead, + vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentRead, .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, .oldLayout = vk::ImageLayout::eUndefined, @@ -641,12 +696,15 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) return frame; } -void Presenter::Present(Frame* frame) { +void Presenter::Present(Frame* frame, bool is_reusing_frame) { // Free the frame for reuse const auto free_frame = [&] { - std::scoped_lock fl{free_mutex}; - free_queue.push(frame); - free_cv.notify_one(); + if (!is_reusing_frame) { + last_submit_frame = frame; + std::scoped_lock fl{free_mutex}; + free_queue.push(frame); + free_cv.notify_one(); + } }; // 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. 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::ImageView swapchain_image_view = swapchain.ImageView(); @@ -744,13 +802,13 @@ void Presenter::Present(Frame* frame) { ImGui::SetNextWindowDockID(dockId, ImGuiCond_Once); ImGui::Begin("Display##game_display", nullptr, ImGuiWindowFlags_NoNav); - ImVec2 contentArea = ImGui::GetContentRegionMax(); + ImVec2 contentArea = ImGui::GetContentRegionAvail(); const vk::Rect2D imgRect = FitImage(frame->width, frame->height, (s32)contentArea.x, (s32)contentArea.y); - ImGui::SetCursorPos({ - (float)imgRect.offset.x, - (float)imgRect.offset.y, - }); + ImGui::SetCursorPos(ImGui::GetCursorStartPos() + ImVec2{ + (float)imgRect.offset.x, + (float)imgRect.offset.y, + }); ImGui::Image(frame->imgui_texture, { static_cast(imgRect.extent.width), static_cast(imgRect.extent.height), @@ -784,7 +842,9 @@ void Presenter::Present(Frame* frame) { } free_frame(); - DebugState.IncFlipFrameNum(); + if (!is_reusing_frame) { + DebugState.IncFlipFrameNum(); + } } Frame* Presenter::GetRenderFrame() { diff --git a/src/video_core/renderer_vulkan/vk_presenter.h b/src/video_core/renderer_vulkan/vk_presenter.h index 0e0da1867..63cb30834 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.h +++ b/src/video_core/renderer_vulkan/vk_presenter.h @@ -89,8 +89,9 @@ public: } 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); + Frame* PrepareLastFrame(); void FlushDraw() { SubmitInfo info{}; @@ -124,6 +125,7 @@ private: vk::UniqueCommandPool command_pool; std::vector present_frames; std::queue free_queue; + Frame* last_submit_frame; std::mutex free_mutex; std::condition_variable free_cv; std::condition_variable_any frame_cv;