#pragma once #include #include enum class RenderCadenceAction { Wait, Render }; struct RenderCadencePolicy { bool skipLateTicks = true; uint64_t maxSkippedTicksPerDecision = 4; double skipThresholdFrames = 2.0; }; struct RenderCadenceDecision { RenderCadenceAction action = RenderCadenceAction::Wait; uint64_t frameIndex = 0; uint64_t skippedTicks = 0; std::chrono::steady_clock::time_point renderTargetTime; std::chrono::steady_clock::time_point nextRenderTime; std::chrono::steady_clock::duration waitDuration = std::chrono::steady_clock::duration::zero(); std::chrono::steady_clock::duration lateness = std::chrono::steady_clock::duration::zero(); const char* reason = "waiting-for-next-render-tick"; }; struct RenderCadenceMetrics { uint64_t nextFrameIndex = 0; uint64_t renderedFrameCount = 0; uint64_t skippedTickCount = 0; uint64_t lateFrameCount = 0; std::chrono::steady_clock::duration lastLateness = std::chrono::steady_clock::duration::zero(); std::chrono::steady_clock::duration maxLateness = std::chrono::steady_clock::duration::zero(); }; class RenderCadenceController { public: using Clock = std::chrono::steady_clock; using TimePoint = Clock::time_point; using Duration = Clock::duration; void Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy = RenderCadencePolicy()); void Reset(TimePoint firstRenderTime); RenderCadenceDecision Tick(TimePoint now); Duration TargetFrameDuration() const { return mTargetFrameDuration; } TimePoint NextRenderTime() const { return mNextRenderTime; } uint64_t NextFrameIndex() const { return mNextFrameIndex; } const RenderCadenceMetrics& Metrics() const { return mMetrics; } private: uint64_t SkippedTicksForLateness(Duration lateness) const; static bool IsPositive(Duration duration); Duration mTargetFrameDuration = std::chrono::milliseconds(16); TimePoint mNextRenderTime; uint64_t mNextFrameIndex = 0; RenderCadencePolicy mPolicy; RenderCadenceMetrics mMetrics; }; const char* RenderCadenceActionName(RenderCadenceAction action);