diff --git a/include/boo/IApplication.hpp b/include/boo/IApplication.hpp index 36811fb..0b7cc8e 100644 --- a/include/boo/IApplication.hpp +++ b/include/boo/IApplication.hpp @@ -56,20 +56,21 @@ public: int ApplicationRun(IApplication::EPlatformType platform, IApplicationCallback& cb, SystemStringView uniqueName, SystemStringView friendlyName, SystemStringView pname, const std::vector& args, std::string_view gfxApi = {}, uint32_t samples = 1, uint32_t anisotropy = 1, bool deepColor = false, - bool singleInstance = true); + int64_t targetFrameTime = 0, bool singleInstance = true); extern IApplication* APP; static inline int ApplicationRun(IApplication::EPlatformType platform, IApplicationCallback& cb, SystemStringView uniqueName, SystemStringView friendlyName, int argc, const SystemChar** argv, std::string_view gfxApi = {}, uint32_t samples = 1, - uint32_t anisotropy = 1, bool deepColor = false, bool singleInstance = true) { + uint32_t anisotropy = 1, bool deepColor = false, int64_t targetFrameTime = 0, + bool singleInstance = true) { if (APP) return 1; std::vector args; for (int i = 1; i < argc; ++i) args.push_back(argv[i]); return ApplicationRun(platform, cb, uniqueName, friendlyName, argv[0], args, gfxApi, samples, anisotropy, deepColor, - singleInstance); + targetFrameTime, singleInstance); } } // namespace boo diff --git a/include/boo/graphicsdev/Vulkan.hpp b/include/boo/graphicsdev/Vulkan.hpp index 95f0455..aa093f7 100644 --- a/include/boo/graphicsdev/Vulkan.hpp +++ b/include/boo/graphicsdev/Vulkan.hpp @@ -97,6 +97,7 @@ struct VulkanContext { VkSampleCountFlags m_sampleCountDepth = VK_SAMPLE_COUNT_1_BIT; float m_anisotropy = 1.f; bool m_deepColor = false; + int64_t m_targetFrameTime = 0; std::unordered_map m_samplers; diff --git a/lib/graphicsdev/Common.cpp b/lib/graphicsdev/Common.cpp index 9e1e8a7..1febbe8 100644 --- a/lib/graphicsdev/Common.cpp +++ b/lib/graphicsdev/Common.cpp @@ -1,5 +1,8 @@ #include "Common.hpp" + #include +#include +#include namespace boo { @@ -12,4 +15,25 @@ void UpdateGammaLUT(ITextureD* tex, float gamma) { tex->unmap(); } +void Limiter::Sleep(nanotime_t targetFrameTime) { + if (targetFrameTime == 0) { + return; + } + + auto start = delta_clock::now(); + nanotime_t sleepTime = targetFrameTime - TimeSince(m_oldTime); + m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), nanotime_t{}) / + static_cast(m_overheadTimes.size()); + if (sleepTime > m_overhead) { + nanotime_t adjustedSleepTime = sleepTime - m_overhead; + std::this_thread::sleep_for(std::chrono::nanoseconds(adjustedSleepTime)); + nanotime_t overslept = TimeSince(start) - adjustedSleepTime; + if (overslept < targetFrameTime) { + m_overheadTimes[m_overheadTimeIdx] = overslept; + m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size(); + } + } + m_oldTime = delta_clock::now(); +} + } // namespace boo diff --git a/lib/graphicsdev/Common.hpp b/lib/graphicsdev/Common.hpp index c589343..e7cb3ca 100644 --- a/lib/graphicsdev/Common.hpp +++ b/lib/graphicsdev/Common.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -283,4 +284,22 @@ public: #define SCOPED_GRAPHICS_DEBUG_GROUP(...) #endif +class Limiter { + using delta_clock = std::chrono::steady_clock; + using nanotime_t = std::chrono::nanoseconds::rep; + +public: + void Sleep(nanotime_t targetFrameTimeNs); + +private: + delta_clock::time_point m_oldTime; + std::array m_overheadTimes{}; + size_t m_overheadTimeIdx = 0; + nanotime_t m_overhead = 0; + + nanotime_t TimeSince(delta_clock::time_point start) { + return std::chrono::duration_cast(delta_clock::now() - start).count(); + } +}; + } // namespace boo diff --git a/lib/graphicsdev/Vulkan.cpp b/lib/graphicsdev/Vulkan.cpp index 536f3d9..cb36a23 100644 --- a/lib/graphicsdev/Vulkan.cpp +++ b/lib/graphicsdev/Vulkan.cpp @@ -2871,6 +2871,8 @@ struct VulkanCommandQueue final : IGraphicsCommandQueue { std::vector> m_drawResTokens[2]; + Limiter m_frameLimiter; + void resetCommandBuffer() { ThrowIfFailed(vk::ResetCommandBuffer(m_cmdBufs[m_fillBuf], 0)); VkCommandBufferBeginInfo cmdBufBeginInfo = {}; @@ -4076,6 +4078,7 @@ void VulkanCommandQueue::execute() { present.pResults = nullptr; ThrowIfFailed(vk::QueuePresentKHR(m_ctx->m_queue, &present)); + m_frameLimiter.Sleep(m_ctx->m_targetFrameTime); } resetCommandBuffer(); diff --git a/lib/mac/ApplicationCocoa.mm b/lib/mac/ApplicationCocoa.mm index 00b1c09..baa4cf3 100644 --- a/lib/mac/ApplicationCocoa.mm +++ b/lib/mac/ApplicationCocoa.mm @@ -209,6 +209,7 @@ int ApplicationRun(IApplication::EPlatformType platform, uint32_t samples, uint32_t anisotropy, bool deepColor, + int64_t targetFrameTime, bool singleInstance) { std::string thrName = std::string(friendlyName) + " Main Thread"; logvisor::RegisterThreadName(thrName.c_str()); diff --git a/lib/win/ApplicationWin32.cpp b/lib/win/ApplicationWin32.cpp index a025a12..50b5e10 100644 --- a/lib/win/ApplicationWin32.cpp +++ b/lib/win/ApplicationWin32.cpp @@ -97,7 +97,7 @@ class ApplicationWin32 final : public IApplication { public: ApplicationWin32(IApplicationCallback& callback, SystemStringView uniqueName, SystemStringView friendlyName, SystemStringView pname, const std::vector& args, std::string_view gfxApi, - uint32_t samples, uint32_t anisotropy, bool deepColor, bool singleInstance) + uint32_t samples, uint32_t anisotropy, bool deepColor, int64_t targetFrameTime, bool singleInstance) : m_callback(callback), m_uniqueName(uniqueName), m_friendlyName(friendlyName), m_pname(pname), m_args(args) { m_3dCtx.m_ctx11.m_sampleCount = samples; m_3dCtx.m_ctx11.m_anisotropy = anisotropy; @@ -112,6 +112,7 @@ public: g_VulkanContext.m_sampleCountDepth = samples; g_VulkanContext.m_anisotropy = anisotropy; g_VulkanContext.m_deepColor = deepColor; + g_VulkanContext.m_targetFrameTime = targetFrameTime; #endif HMODULE dxgilib = LoadLibraryW(L"dxgi.dll"); @@ -472,7 +473,7 @@ IApplication* APP = nullptr; int ApplicationRun(IApplication::EPlatformType platform, IApplicationCallback& cb, SystemStringView uniqueName, SystemStringView friendlyName, SystemStringView pname, const std::vector& args, std::string_view gfxApi, uint32_t samples, uint32_t anisotropy, bool deepColor, - bool singleInstance) { + int64_t targetFrameTime, bool singleInstance) { std::string thrName = WCSTMBS(friendlyName.data()) + " Main Thread"; logvisor::RegisterThreadName(thrName.c_str()); if (APP) @@ -501,7 +502,7 @@ int ApplicationRun(IApplication::EPlatformType platform, IApplicationCallback& c RegisterClassW(&wndClass); APP = new ApplicationWin32(cb, uniqueName, friendlyName, pname, args, gfxApi, samples, anisotropy, deepColor, - singleInstance); + targetFrameTime, singleInstance); int ret = APP->run(); delete APP; APP = nullptr; diff --git a/lib/x11/ApplicationUnix.cpp b/lib/x11/ApplicationUnix.cpp index 441eb7a..2ee67b8 100644 --- a/lib/x11/ApplicationUnix.cpp +++ b/lib/x11/ApplicationUnix.cpp @@ -125,7 +125,7 @@ namespace boo { int ApplicationRun(IApplication::EPlatformType platform, IApplicationCallback& cb, std::string_view uniqueName, std::string_view friendlyName, std::string_view pname, const std::vector& args, std::string_view gfxApi, uint32_t samples, uint32_t anisotropy, bool deepColor, - bool singleInstance) { + int64_t targetFrameTime, bool singleInstance) { std::string thrName = std::string(friendlyName) + " Main"; logvisor::RegisterThreadName(thrName.c_str()); if (APP) @@ -135,7 +135,7 @@ int ApplicationRun(IApplication::EPlatformType platform, IApplicationCallback& c singleInstance); else if (platform == IApplication::EPlatformType::Xlib || platform == IApplication::EPlatformType::Auto) APP = new ApplicationXlib(cb, uniqueName, friendlyName, pname, args, gfxApi, samples, anisotropy, deepColor, - singleInstance); + targetFrameTime, singleInstance); else return 1; int ret = APP->run(); diff --git a/lib/x11/ApplicationXlib.hpp b/lib/x11/ApplicationXlib.hpp index e55ec39..ae9f609 100644 --- a/lib/x11/ApplicationXlib.hpp +++ b/lib/x11/ApplicationXlib.hpp @@ -202,7 +202,7 @@ class ApplicationXlib final : public IApplication { public: ApplicationXlib(IApplicationCallback& callback, std::string_view uniqueName, std::string_view friendlyName, std::string_view pname, const std::vector& args, std::string_view gfxApi, - uint32_t samples, uint32_t anisotropy, bool deepColor, bool singleInstance) + uint32_t samples, uint32_t anisotropy, bool deepColor, int64_t targetFrameTime, bool singleInstance) : m_callback(callback) , m_uniqueName(uniqueName) , m_friendlyName(friendlyName) @@ -217,6 +217,7 @@ public: g_VulkanContext.m_sampleCountDepth = samples; g_VulkanContext.m_anisotropy = anisotropy; g_VulkanContext.m_deepColor = deepColor; + g_VulkanContext.m_targetFrameTime = targetFrameTime; /* Check for Vulkan presence and preference */ bool tryVulkan = true;