diff --git a/Linux/CMakeLists.txt b/Linux/CMakeLists.txt index a390f1b7c..54100e3e0 100644 --- a/Linux/CMakeLists.txt +++ b/Linux/CMakeLists.txt @@ -300,6 +300,8 @@ set(SIV3D_INTERNAL_SOURCES ../Siv3D/src/Siv3D/FormatInt/SivFormatInt.cpp ../Siv3D/src/Siv3D/Formatter/SivFormatter.cpp ../Siv3D/src/Siv3D/FormatUtility/SivFormatUtility.cpp + ../Siv3D/src/Siv3D/FrameRateLimit/SivFrameRateLimit.cpp + ../Siv3D/src/Siv3D/FrameRateLimit/FrameRateLimitFactory.cpp ../Siv3D/src/Siv3D/Gamepad/GamepadFactory.cpp ../Siv3D/src/Siv3D/Gamepad/SivGamepad.cpp ../Siv3D/src/Siv3D/GamepadInfo/SivGamepadInfo.cpp diff --git a/Siv3D/include/Siv3D/Graphics.hpp b/Siv3D/include/Siv3D/Graphics.hpp index 2efad8edf..5505a8e0e 100644 --- a/Siv3D/include/Siv3D/Graphics.hpp +++ b/Siv3D/include/Siv3D/Graphics.hpp @@ -11,6 +11,7 @@ # pragma once # include "Common.hpp" +# include "Optional.hpp" namespace s3d { @@ -27,5 +28,15 @@ namespace s3d /// @return VSync が有効である場合 true, 無効である場合 false [[nodiscard]] bool IsVSyncEnabled(); + + /// @brief VSync 無効時の目標フレームレートを設定します。 + /// @param fps 目標フレームレート(FPS)。フレームレート制限を無効にする場合は none + /// @remark デフォルトでは none です。 + void SetTargetFrameRate(const Optional& fps); + + /// @brief VSync 無効時の目標フレームレートを取得します。 + /// @return 目標フレームレート(FPS)。フレームレート制限が無効の場合は none + [[nodiscard]] + Optional GetTargetFrameRate(); } } diff --git a/Siv3D/src/Siv3D-Platform/Linux/Siv3D/System/CSystem.cpp b/Siv3D/src/Siv3D-Platform/Linux/Siv3D/System/CSystem.cpp index dbbca3f69..63d2ce5a1 100644 --- a/Siv3D/src/Siv3D-Platform/Linux/Siv3D/System/CSystem.cpp +++ b/Siv3D/src/Siv3D-Platform/Linux/Siv3D/System/CSystem.cpp @@ -51,6 +51,7 @@ # include # include # include +# include # include # include # include "CSystem.hpp" @@ -131,6 +132,7 @@ namespace s3d SIV3D_ENGINE(Renderer)->present(); SIV3D_ENGINE(ScreenCapture)->update(); SIV3D_ENGINE(Addon)->postPresent(); + SIV3D_ENGINE(FrameRateLimit)->update(); // // previous frame diff --git a/Siv3D/src/Siv3D-Platform/Web/Siv3D/System/CSystem.cpp b/Siv3D/src/Siv3D-Platform/Web/Siv3D/System/CSystem.cpp index bd8d48fba..cb13e37cc 100644 --- a/Siv3D/src/Siv3D-Platform/Web/Siv3D/System/CSystem.cpp +++ b/Siv3D/src/Siv3D-Platform/Web/Siv3D/System/CSystem.cpp @@ -50,6 +50,7 @@ # include # include # include +# include # include # include # include @@ -151,6 +152,7 @@ namespace s3d SIV3D_ENGINE(Renderer)->present(); SIV3D_ENGINE(ScreenCapture)->update(); SIV3D_ENGINE(Addon)->postPresent(); + SIV3D_ENGINE(FrameRateLimit)->update(); detail::siv3dRequestAnimationFrame(); diff --git a/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/Renderer/D3D11/SwapChain/D3D11SwapChain.cpp b/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/Renderer/D3D11/SwapChain/D3D11SwapChain.cpp index c2b4f2f21..fad37a4e5 100644 --- a/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/Renderer/D3D11/SwapChain/D3D11SwapChain.cpp +++ b/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/Renderer/D3D11/SwapChain/D3D11SwapChain.cpp @@ -138,9 +138,6 @@ namespace s3d m_previousWindowBounds = windowBounds; } - //const bool vSync = (not m_targetFrameRateHz.has_value()) - // || ((30.0 <= m_targetFrameRateHz) && (AbsDiff(m_targetFrameRateHz.value(), m_displayFrequency) <= 3.0)); - return vSync ? presentVSync() : presentNonVSync(); } @@ -149,16 +146,6 @@ namespace s3d return m_displayFrequency; } - //void D3D11SwapChain::setTargetFrameRateHz(const Optional& targetFrameRateHz) - //{ - // m_targetFrameRateHz = targetFrameRateHz; - //} - - //const Optional& D3D11SwapChain::getTargetFrameRateHz() const noexcept - //{ - // return m_targetFrameRateHz; - //} - IDXGISwapChain1* D3D11SwapChain::getSwapChain1() const noexcept { return m_swapChain1.Get(); @@ -245,37 +232,6 @@ namespace s3d bool D3D11SwapChain::presentNonVSync() { - /* - const double targetRefreshRateHz = m_targetFrameRateHz.value(); - const double targetRefreshPeriodMillisec = (1000.0 / targetRefreshRateHz); - const double displayRefreshPeriodMillisec = detail::GetDisplayRefreshPeriodMillisec(); - - LARGE_INTEGER counter; - ::QueryPerformanceCounter(&counter); - - { - m_context->Flush(); - - double timeToSleepMillisec = 0.0; - ::timeBeginPeriod(1); - - do - { - ::QueryPerformanceCounter(&counter); - const double timeSinceFlipMillisec = detail::ToMillisec(counter.QuadPart - m_lastPresentTime); - - timeToSleepMillisec = (targetRefreshPeriodMillisec - timeSinceFlipMillisec); - - if (timeToSleepMillisec > 0.0) - { - ::Sleep(static_cast(timeToSleepMillisec)); - } - } while (timeToSleepMillisec > 0.5); - - ::timeEndPeriod(1); - } - */ - const UINT presentFlags = m_tearingSupport ? DXGI_PRESENT_ALLOW_TEARING : 0; const HRESULT hr = m_swapChain1->Present(0, presentFlags); @@ -294,8 +250,6 @@ namespace s3d return false; } - //m_lastPresentTime = counter.QuadPart; - return true; } } diff --git a/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/Renderer/D3D11/SwapChain/D3D11SwapChain.hpp b/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/Renderer/D3D11/SwapChain/D3D11SwapChain.hpp index f1b609f35..2e2e5ec42 100644 --- a/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/Renderer/D3D11/SwapChain/D3D11SwapChain.hpp +++ b/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/Renderer/D3D11/SwapChain/D3D11SwapChain.hpp @@ -59,11 +59,6 @@ namespace s3d [[nodiscard]] double getDisplayFrequency() const noexcept; - //void setTargetFrameRateHz(const Optional& targetFrameRateHz); - - //[[nodiscard]] - //const Optional& getTargetFrameRateHz() const noexcept; - [[nodiscard]] IDXGISwapChain1* getSwapChain1() const noexcept; diff --git a/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/System/CSystem.cpp b/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/System/CSystem.cpp index 9e8ad3a9b..3235a7a92 100644 --- a/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/System/CSystem.cpp +++ b/Siv3D/src/Siv3D-Platform/WindowsDesktop/Siv3D/System/CSystem.cpp @@ -52,6 +52,7 @@ # include # include # include +# include # include # include # include @@ -177,6 +178,7 @@ namespace s3d SIV3D_ENGINE(Renderer)->present(); SIV3D_ENGINE(ScreenCapture)->update(); SIV3D_ENGINE(Addon)->postPresent(); + SIV3D_ENGINE(FrameRateLimit)->update(); // // previous frame diff --git a/Siv3D/src/Siv3D-Platform/macOS/Siv3D/System/CSystem.cpp b/Siv3D/src/Siv3D-Platform/macOS/Siv3D/System/CSystem.cpp index fc81cd85a..69c9ee4a4 100644 --- a/Siv3D/src/Siv3D-Platform/macOS/Siv3D/System/CSystem.cpp +++ b/Siv3D/src/Siv3D-Platform/macOS/Siv3D/System/CSystem.cpp @@ -51,6 +51,7 @@ # include # include # include +# include # include # include # include "CSystem.hpp" @@ -131,6 +132,7 @@ namespace s3d SIV3D_ENGINE(Renderer)->present(); SIV3D_ENGINE(ScreenCapture)->update(); SIV3D_ENGINE(Addon)->postPresent(); + SIV3D_ENGINE(FrameRateLimit)->update(); // // previous frame diff --git a/Siv3D/src/Siv3D/Common/Siv3DEngine.cpp b/Siv3D/src/Siv3D/Common/Siv3DEngine.cpp index dc0272e59..c43d8e28a 100644 --- a/Siv3D/src/Siv3D/Common/Siv3DEngine.cpp +++ b/Siv3D/src/Siv3D/Common/Siv3DEngine.cpp @@ -58,6 +58,7 @@ # include # include # include +# include namespace s3d { diff --git a/Siv3D/src/Siv3D/Common/Siv3DEngine.hpp b/Siv3D/src/Siv3D/Common/Siv3DEngine.hpp index cf68b3f45..413a98cf2 100644 --- a/Siv3D/src/Siv3D/Common/Siv3DEngine.hpp +++ b/Siv3D/src/Siv3D/Common/Siv3DEngine.hpp @@ -63,6 +63,7 @@ namespace s3d class ISiv3DTrailRenderer; class ISiv3DScript; class ISiv3DAddon; + class ISiv3DFrameRateLimit; class Siv3DEngine { @@ -118,7 +119,8 @@ namespace s3d Siv3DComponent, Siv3DComponent, Siv3DComponent, - Siv3DComponent> m_components; + Siv3DComponent, + Siv3DComponent> m_components; public: diff --git a/Siv3D/src/Siv3D/FrameRateLimit/CFrameRateLimit.hpp b/Siv3D/src/Siv3D/FrameRateLimit/CFrameRateLimit.hpp new file mode 100644 index 000000000..dd2ea6379 --- /dev/null +++ b/Siv3D/src/Siv3D/FrameRateLimit/CFrameRateLimit.hpp @@ -0,0 +1,86 @@ +//----------------------------------------------- +// +// This file is part of the Siv3D Engine. +// +// Copyright (c) 2008-2023 Ryo Suzuki +// Copyright (c) 2016-2023 OpenSiv3D Project +// +// Licensed under the MIT License. +// +//----------------------------------------------- + +# pragma once +# include "IFrameRateLimit.hpp" + +namespace s3d +{ + class CFrameRateLimit final : public ISiv3DFrameRateLimit + { + public: + + using clock_type = std::chrono::steady_clock; + + /// @brief フレームレート制限を実行します。 + void update() override; + + /// @brief 目標フレームレートを設定します。 + /// @param fps 目標フレームレート(FPS)。フレームレート制限を無効にする場合は none + void setTargetFrameRate(const Optional& fps) override; + + /// @brief 目標フレームレートを取得します。 + /// @return 目標フレームレート(FPS)。フレームレート制限が無効の場合は none + SIV3D_NODISCARD_CXX20 + Optional getTargetFrameRate() const override; + + private: + + class Limiter + { + public: + + /// @brief コンストラクタ + /// @param fps 目標フレームレート(FPS) + /// @throw Error fps が無効な値の場合 + SIV3D_NODISCARD_CXX20 + explicit Limiter(double fps); + + /// @brief sleepを実行し、フレームを次に進めます。 + void doSleep(); + + /// @brief 目標フレームレートを設定します。 + /// @param targetFrameRate 目標フレームレート(FPS) + /// @throw Error fps が無効な値の場合 + void setTargetFrameRate(double fps); + + /// @brief 目標フレームレートを取得します。 + /// @return 目標フレームレート(FPS) + SIV3D_NODISCARD_CXX20 + double getTargetFrameRate() const noexcept { return m_fps; } + + private: + + /// @brief 1秒間 + static constexpr clock_type::duration OneSecond = std::chrono::seconds{ 1 }; + + /// @brief 目標フレームレート(FPS) + double m_fps; + + /// @brief 1フレームあたりの時間 + clock_type::duration m_oneFrameDuration; + + /// @brief 目標sleep時刻 + clock_type::time_point m_sleepUntil; + + /// @brief フレームレートから1フレームあたりの時間の長さを計算します。 + /// @param fps フレームレート(FPS) + /// @return 1フレームあたりの時間の長さ + /// @throw Error fps が無効な値の場合 + SIV3D_NODISCARD_CXX20 + static clock_type::duration FPSToOneFrameDuration(double fps); + }; + + /// @brief フレームレート制限 + /// @remark フレームレート制限が無効の場合は nullptr + std::unique_ptr m_limiter; + }; +} diff --git a/Siv3D/src/Siv3D/FrameRateLimit/FrameRateLimitFactory.cpp b/Siv3D/src/Siv3D/FrameRateLimit/FrameRateLimitFactory.cpp new file mode 100644 index 000000000..af7b79665 --- /dev/null +++ b/Siv3D/src/Siv3D/FrameRateLimit/FrameRateLimitFactory.cpp @@ -0,0 +1,20 @@ +//----------------------------------------------- +// +// This file is part of the Siv3D Engine. +// +// Copyright (c) 2008-2023 Ryo Suzuki +// Copyright (c) 2016-2023 OpenSiv3D Project +// +// Licensed under the MIT License. +// +//----------------------------------------------- + +# include "CFrameRateLimit.hpp" + +namespace s3d +{ + ISiv3DFrameRateLimit* ISiv3DFrameRateLimit::Create() + { + return new CFrameRateLimit; + } +} diff --git a/Siv3D/src/Siv3D/FrameRateLimit/IFrameRateLimit.hpp b/Siv3D/src/Siv3D/FrameRateLimit/IFrameRateLimit.hpp new file mode 100644 index 000000000..02fa65245 --- /dev/null +++ b/Siv3D/src/Siv3D/FrameRateLimit/IFrameRateLimit.hpp @@ -0,0 +1,32 @@ +//----------------------------------------------- +// +// This file is part of the Siv3D Engine. +// +// Copyright (c) 2008-2023 Ryo Suzuki +// Copyright (c) 2016-2023 OpenSiv3D Project +// +// Licensed under the MIT License. +// +//----------------------------------------------- + +# pragma once +# include +# include + +namespace s3d +{ + class SIV3D_NOVTABLE ISiv3DFrameRateLimit + { + public: + + static ISiv3DFrameRateLimit* Create(); + + virtual ~ISiv3DFrameRateLimit() = default; + + virtual void update() = 0; + + virtual void setTargetFrameRate(const Optional& fps) = 0; + + virtual Optional getTargetFrameRate() const = 0; + }; +} diff --git a/Siv3D/src/Siv3D/FrameRateLimit/SivFrameRateLimit.cpp b/Siv3D/src/Siv3D/FrameRateLimit/SivFrameRateLimit.cpp new file mode 100644 index 000000000..34d4b9e44 --- /dev/null +++ b/Siv3D/src/Siv3D/FrameRateLimit/SivFrameRateLimit.cpp @@ -0,0 +1,107 @@ +//----------------------------------------------- +// +// This file is part of the Siv3D Engine. +// +// Copyright (c) 2008-2023 Ryo Suzuki +// Copyright (c) 2016-2023 OpenSiv3D Project +// +// Licensed under the MIT License. +// +//----------------------------------------------- + +# include "CFrameRateLimit.hpp" +# include "Siv3D/Graphics.hpp" +# include "Siv3D/Error.hpp" +# include "Siv3D/Duration.hpp" + +namespace s3d +{ + void CFrameRateLimit::update() + { + if (Graphics::IsVSyncEnabled()) + { + // VSync が有効の場合はフレームレート制限を無視 + return; + } + + if (not m_limiter) + { + // フレームレート制限が無効の場合は何もしない + return; + } + + m_limiter->doSleep(); + } + + void CFrameRateLimit::setTargetFrameRate(const Optional& fps) + { + if (not fps) + { + // フレームレート制限を無効化 + m_limiter.reset(); + return; + } + + if (m_limiter) + { + // 既にフレームレート制限が有効の場合は目標フレームレートを設定 + m_limiter->setTargetFrameRate(*fps); + } + else + { + // フレームレート制限を有効化 + m_limiter = std::make_unique(*fps); + } + } + + Optional CFrameRateLimit::getTargetFrameRate() const + { + if (m_limiter) + { + return m_limiter->getTargetFrameRate(); + } + else + { + return none; + } + } + + CFrameRateLimit::Limiter::Limiter(double fps) + : m_fps(fps) + , m_oneFrameDuration(FPSToOneFrameDuration(fps)) + , m_sleepUntil(clock_type::now()) + { + } + + void CFrameRateLimit::Limiter::doSleep() + { + const auto now = clock_type::now(); + const auto sleepUntil = Max(m_sleepUntil + m_oneFrameDuration, now); + + if (now < sleepUntil) + { + std::this_thread::sleep_until(sleepUntil); + } + + m_sleepUntil = sleepUntil; + } + + void CFrameRateLimit::Limiter::setTargetFrameRate(double fps) + { + m_fps = fps; + m_oneFrameDuration = FPSToOneFrameDuration(fps); + + // setTargetFrameRate が毎フレーム呼び出された場合に sleep 時間に誤差が生じないよう, + // m_sleepUntil はここでは更新しない + } + + CFrameRateLimit::clock_type::duration CFrameRateLimit::Limiter::FPSToOneFrameDuration(double fps) + { + if (fps <= 0.0) + { + throw Error{ U"FrameRateLimit::Limiter::FPSToOneFrameDuration(): Invalid fps" }; + } + + return DurationCast(OneSecond / fps); + } +} diff --git a/Siv3D/src/Siv3D/Graphics/SivGraphics.cpp b/Siv3D/src/Siv3D/Graphics/SivGraphics.cpp index 743628959..e5ced4781 100644 --- a/Siv3D/src/Siv3D/Graphics/SivGraphics.cpp +++ b/Siv3D/src/Siv3D/Graphics/SivGraphics.cpp @@ -11,6 +11,7 @@ # include # include +# include # include namespace s3d @@ -26,5 +27,15 @@ namespace s3d { return SIV3D_ENGINE(Renderer)->isVSyncEnabled(); } + + void SetTargetFrameRate(const Optional& fps) + { + SIV3D_ENGINE(FrameRateLimit)->setTargetFrameRate(fps); + } + + Optional GetTargetFrameRate() + { + return SIV3D_ENGINE(FrameRateLimit)->getTargetFrameRate(); + } } } diff --git a/Web/CMakeLists.txt b/Web/CMakeLists.txt index 53534c136..6b5e41cc2 100644 --- a/Web/CMakeLists.txt +++ b/Web/CMakeLists.txt @@ -341,6 +341,8 @@ set(SIV3D_INTERNAL_SOURCES ../Siv3D/src/Siv3D/FormatInt/SivFormatInt.cpp ../Siv3D/src/Siv3D/Formatter/SivFormatter.cpp ../Siv3D/src/Siv3D/FormatUtility/SivFormatUtility.cpp + ../Siv3D/src/Siv3D/FrameRateLimit/SivFrameRateLimit.cpp + ../Siv3D/src/Siv3D/FrameRateLimit/FrameRateLimitFactory.cpp ../Siv3D/src/Siv3D/Gamepad/GamepadFactory.cpp ../Siv3D/src/Siv3D/Gamepad/SivGamepad.cpp ../Siv3D/src/Siv3D/GamepadInfo/SivGamepadInfo.cpp diff --git a/WindowsDesktop/Siv3D.vcxproj b/WindowsDesktop/Siv3D.vcxproj index 91942a80f..ee7a7ebf4 100644 --- a/WindowsDesktop/Siv3D.vcxproj +++ b/WindowsDesktop/Siv3D.vcxproj @@ -1141,6 +1141,8 @@ + + @@ -2292,6 +2294,8 @@ + + diff --git a/WindowsDesktop/Siv3D.vcxproj.filters b/WindowsDesktop/Siv3D.vcxproj.filters index 3212fc28d..26ac6cf22 100644 --- a/WindowsDesktop/Siv3D.vcxproj.filters +++ b/WindowsDesktop/Siv3D.vcxproj.filters @@ -1738,6 +1738,9 @@ {37ac6af4-6c9f-4772-9dee-c6f5ebe74b61} + + {4c2ac354-f8e6-466f-a4b0-8c3a7d382331} + @@ -7842,6 +7845,12 @@ src\Siv3D\Shader + + src\Siv3D\FrameRateLimit + + + src\Siv3D\FrameRateLimit + @@ -11285,6 +11294,12 @@ src\Siv3D\OpenAI + + src\Siv3D\FrameRateLimit + + + src\Siv3D\FrameRateLimit +