#include "RenderCadenceController.h" #include #include void RenderCadenceController::Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy) { mTargetFrameDuration = IsPositive(targetFrameDuration) ? targetFrameDuration : std::chrono::milliseconds(1); mPolicy = policy; if (mPolicy.skipThresholdFrames < 1.0) mPolicy.skipThresholdFrames = 1.0; Reset(firstRenderTime); } void RenderCadenceController::Reset(TimePoint firstRenderTime) { mNextRenderTime = firstRenderTime; mNextFrameIndex = 0; mMetrics = RenderCadenceMetrics(); } RenderCadenceDecision RenderCadenceController::Tick(TimePoint now) { RenderCadenceDecision decision; decision.frameIndex = mNextFrameIndex; decision.renderTargetTime = mNextRenderTime; decision.nextRenderTime = mNextRenderTime; if (now < mNextRenderTime) { decision.action = RenderCadenceAction::Wait; decision.waitDuration = mNextRenderTime - now; decision.reason = "waiting-for-next-render-tick"; return decision; } const Duration lateness = now - mNextRenderTime; const uint64_t skippedTicks = SkippedTicksForLateness(lateness); if (skippedTicks > 0) { decision.skippedTicks = skippedTicks; decision.frameIndex = mNextFrameIndex + skippedTicks; decision.renderTargetTime = mNextRenderTime + (mTargetFrameDuration * skippedTicks); decision.reason = "late-skip-render-ticks"; mMetrics.skippedTickCount += skippedTicks; } else { decision.reason = IsPositive(lateness) ? "late-render-now" : "on-time-render"; } decision.action = RenderCadenceAction::Render; decision.lateness = now > decision.renderTargetTime ? now - decision.renderTargetTime : Duration::zero(); mNextFrameIndex = decision.frameIndex + 1; mNextRenderTime = decision.renderTargetTime + mTargetFrameDuration; decision.nextRenderTime = mNextRenderTime; ++mMetrics.renderedFrameCount; mMetrics.nextFrameIndex = mNextFrameIndex; mMetrics.lastLateness = decision.lateness; if (IsPositive(decision.lateness)) { ++mMetrics.lateFrameCount; mMetrics.maxLateness = (std::max)(mMetrics.maxLateness, decision.lateness); } return decision; } uint64_t RenderCadenceController::SkippedTicksForLateness(Duration lateness) const { if (!mPolicy.skipLateTicks || !IsPositive(lateness) || !IsPositive(mTargetFrameDuration)) return 0; const double lateFrames = static_cast(lateness.count()) / static_cast(mTargetFrameDuration.count()); if (lateFrames < mPolicy.skipThresholdFrames) return 0; const uint64_t elapsedTicks = static_cast(std::floor(lateFrames)); if (elapsedTicks == 0) return 0; return (std::min)(elapsedTicks, mPolicy.maxSkippedTicksPerDecision); } bool RenderCadenceController::IsPositive(Duration duration) { return duration > Duration::zero(); } const char* RenderCadenceActionName(RenderCadenceAction action) { switch (action) { case RenderCadenceAction::Render: return "Render"; case RenderCadenceAction::Wait: default: return "Wait"; } }