From 00401062900e9b871a1539519b3042adf1b9f5e5 Mon Sep 17 00:00:00 2001 From: JC Date: Sun, 13 Oct 2024 13:16:51 -0700 Subject: [PATCH] frame rate limiter --- engine/core.cpp | 3 ++- engine/core.h | 5 ++-- engine/engine.cpp | 4 ++-- engine/platform/Vulkan/VKgraphicsContext.cpp | 25 +++++--------------- engine/platform/Vulkan/VKgraphicsContext.h | 15 ++++++++---- engine/platform/Vulkan/VKswapChain.cpp | 8 +++++++ engine/renderer/graphicsContext.h | 8 ++++++- 7 files changed, 39 insertions(+), 29 deletions(-) diff --git a/engine/core.cpp b/engine/core.cpp index d7c456aa..665e95a7 100644 --- a/engine/core.cpp +++ b/engine/core.cpp @@ -166,9 +166,10 @@ namespace GfxRenderEngine } m_EventQueue.clear(); } + m_StartTime = GetTime(); } - void Engine::OnRender() { m_GraphicsContext->SwapBuffers(); } + void Engine::PostRender() { m_GraphicsContext->LimitFrameRate(m_StartTime); } void Engine::SignalHandler(int signal) { diff --git a/engine/core.h b/engine/core.h index cba64852..12343a44 100644 --- a/engine/core.h +++ b/engine/core.h @@ -56,8 +56,8 @@ namespace GfxRenderEngine bool Start(); void WaitInitialized(); void OnUpdate(); - void OnRender(); void OnEvent(Event& event); + void PostRender(); void QueueEvent(std::unique_ptr& event); void Shutdown(bool switchOffComputer = false); void Quit(); @@ -141,7 +141,8 @@ namespace GfxRenderEngine LayerStack m_LayerStack; Timestep m_Timestep; - std::chrono::time_point m_TimeLastFrame; + Chrono::TimePoint m_TimeLastFrame; + Chrono::TimePoint m_StartTime; bool m_Running, m_Paused, m_GraphicsContextInitialized; std::vector> m_EventQueue; diff --git a/engine/engine.cpp b/engine/engine.cpp index d0dfc73e..aafdd758 100644 --- a/engine/engine.cpp +++ b/engine/engine.cpp @@ -87,8 +87,8 @@ int engine(int argc, char* argv[]) engine->RunScripts(application.get()); } { - ZoneScopedN("engine->OnRender()"); - engine->OnRender(); + ZoneScopedN("engine->PostRender()"); + engine->PostRender(); } } else diff --git a/engine/platform/Vulkan/VKgraphicsContext.cpp b/engine/platform/Vulkan/VKgraphicsContext.cpp index eadc40d6..9dffa39d 100644 --- a/engine/platform/Vulkan/VKgraphicsContext.cpp +++ b/engine/platform/Vulkan/VKgraphicsContext.cpp @@ -33,7 +33,7 @@ namespace GfxRenderEngine { VK_Context::VK_Context(VK_Window* window, ThreadPool& threadPoolPrimary, ThreadPool& threadPoolSecondary) - : m_Window{window}, m_FrameDuration{16.667ms}, m_VSyncIsWorking{10}, m_Initialized{false} + : m_Window{window}, m_Initialized{false} { // create a device m_Device = std::make_unique(window, threadPoolPrimary, threadPoolSecondary); @@ -55,30 +55,17 @@ namespace GfxRenderEngine void VK_Context::SetVSync(int interval) {} - void VK_Context::SwapBuffers() + void VK_Context::LimitFrameRate(Chrono::TimePoint startTime) { - ZoneScopedN("SwapBuffers"); + ZoneScopedN("LimitFrameRate"); #ifndef MACOSX - auto diffTime = Engine::m_Engine->GetTime() - m_StartTime; - auto sleepTime = m_FrameDuration - diffTime - 150us; - if (sleepTime < 0s) - sleepTime = 0s; + auto diffTime = Engine::m_Engine->GetTime() - startTime; + auto sleepTime = m_MinFrameDuration - diffTime; - // here ends the frame - if (m_VSyncIsWorking) - { - if (diffTime < (m_FrameDuration / 2)) - { - // time difference too short - m_VSyncIsWorking--; - } - } - else + if (sleepTime > 0s) { std::this_thread::sleep_for(sleepTime); } - // here starts the new frame - m_StartTime = Engine::m_Engine->GetTime(); #else std::this_thread::sleep_for(10ms); auto oldStartTime = m_StartTime; diff --git a/engine/platform/Vulkan/VKgraphicsContext.h b/engine/platform/Vulkan/VKgraphicsContext.h index 537f2be8..7a53e667 100644 --- a/engine/platform/Vulkan/VKgraphicsContext.h +++ b/engine/platform/Vulkan/VKgraphicsContext.h @@ -42,7 +42,7 @@ namespace GfxRenderEngine virtual bool Init() override; virtual void SetVSync(int interval) override; - virtual void SwapBuffers() override; + virtual void LimitFrameRate(Chrono::TimePoint) override; virtual bool IsInitialized() const override { return m_Initialized; } virtual Renderer* GetRenderer() const override { return m_Renderer.get(); } @@ -68,8 +68,15 @@ namespace GfxRenderEngine std::unique_ptr m_Device; std::unique_ptr m_Renderer; - std::chrono::time_point m_StartTime; - std::chrono::duration m_FrameDuration; - int m_VSyncIsWorking; + Chrono::TimePoint m_StartTime; + + // *** m_MinFrameDuration *** + // The main thread must use at least m_MinFrameDuration of CPU time. + // If the app is using less, LimitFrameRate() pads the remainder via sleep(). + // Without the frame limiter, vkQueuesubmit() would pad the remainder, + // and we don't want that: + // vkQueuesubmit() is blocking the acces mutex and thus background operations + // on the queue (such as resource loading) are blocked as well. + Chrono::Duration m_MinFrameDuration{16.0ms}; }; } // namespace GfxRenderEngine diff --git a/engine/platform/Vulkan/VKswapChain.cpp b/engine/platform/Vulkan/VKswapChain.cpp index 64f4ce53..e171d781 100644 --- a/engine/platform/Vulkan/VKswapChain.cpp +++ b/engine/platform/Vulkan/VKswapChain.cpp @@ -283,6 +283,14 @@ namespace GfxRenderEngine VkPresentModeKHR VK_SwapChain::ChooseSwapPresentMode(const std::vector& availablePresentModes) { + for (auto& presentMode : availablePresentModes) + { + if (presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) + { + return presentMode; + } + } + // guaranteed to be supported return VK_PRESENT_MODE_FIFO_KHR; } diff --git a/engine/renderer/graphicsContext.h b/engine/renderer/graphicsContext.h index b322d097..8296f1ad 100644 --- a/engine/renderer/graphicsContext.h +++ b/engine/renderer/graphicsContext.h @@ -39,6 +39,12 @@ namespace GfxRenderEngine { + namespace Chrono + { + using TimePoint = std::chrono::time_point; + using Duration = std::chrono::duration; + } // namespace Chrono + class GraphicsContext { @@ -47,7 +53,7 @@ namespace GfxRenderEngine virtual bool Init() = 0; virtual void SetVSync(int interval) = 0; - virtual void SwapBuffers() = 0; + virtual void LimitFrameRate(Chrono::TimePoint) = 0; virtual bool IsInitialized() const = 0; virtual Renderer* GetRenderer() const = 0;