#include "RenderCadenceController.h" #include #include #include namespace { int gFailures = 0; using Clock = RenderCadenceController::Clock; using Duration = RenderCadenceController::Duration; using TimePoint = RenderCadenceController::TimePoint; void Expect(bool condition, const char* message) { if (condition) return; std::cerr << "FAIL: " << message << "\n"; ++gFailures; } Duration Ms(int64_t value) { return std::chrono::duration_cast(std::chrono::milliseconds(value)); } void TestExactCadenceAdvancesFrameIndexAndNextTick() { RenderCadenceController controller; const TimePoint start = Clock::time_point(Ms(1000)); controller.Configure(Ms(20), start); RenderCadenceDecision first = controller.Tick(start); Expect(first.action == RenderCadenceAction::Render, "first exact tick renders"); Expect(first.frameIndex == 0, "first exact tick renders frame zero"); Expect(first.renderTargetTime == start, "first exact target is configured start"); Expect(first.nextRenderTime == start + Ms(20), "first exact tick advances next render time"); Expect(first.skippedTicks == 0, "first exact tick skips no ticks"); Expect(first.lateness == Duration::zero(), "first exact tick records no lateness"); RenderCadenceDecision second = controller.Tick(start + Ms(20)); Expect(second.action == RenderCadenceAction::Render, "second exact tick renders"); Expect(second.frameIndex == 1, "second exact tick renders frame one"); Expect(controller.NextFrameIndex() == 2, "controller tracks next frame index after exact ticks"); Expect(controller.Metrics().renderedFrameCount == 2, "metrics count exact rendered frames"); } void TestEarlyTickWaitsWithoutAdvancing() { RenderCadenceController controller; const TimePoint start = Clock::time_point(Ms(0)); controller.Configure(Ms(20), start); (void)controller.Tick(start); RenderCadenceDecision decision = controller.Tick(start + Ms(10)); Expect(decision.action == RenderCadenceAction::Wait, "early tick waits"); Expect(decision.waitDuration == Ms(10), "early tick reports wait duration"); Expect(decision.frameIndex == 1, "early tick reports next pending frame"); Expect(controller.NextFrameIndex() == 1, "early tick does not advance frame index"); Expect(controller.NextRenderTime() == start + Ms(20), "early tick does not advance next render time"); } void TestSlightLatenessRendersAndRecordsMetrics() { RenderCadencePolicy policy; policy.skipThresholdFrames = 3.0; RenderCadenceController controller; const TimePoint start = Clock::time_point(Ms(0)); controller.Configure(Ms(20), start, policy); RenderCadenceDecision decision = controller.Tick(start + Ms(5)); Expect(decision.action == RenderCadenceAction::Render, "slightly late tick renders"); Expect(decision.frameIndex == 0, "slightly late tick keeps pending frame"); Expect(decision.skippedTicks == 0, "slightly late tick skips no ticks"); Expect(decision.lateness == Ms(5), "slightly late tick reports lateness"); Expect(controller.Metrics().lateFrameCount == 1, "metrics count late rendered frame"); Expect(controller.Metrics().lastLateness == Ms(5), "metrics keep last lateness"); Expect(controller.Metrics().maxLateness == Ms(5), "metrics keep max lateness"); } void TestLargeLatenessSkipsTicksAccordingToPolicy() { RenderCadencePolicy policy; policy.skipLateTicks = true; policy.skipThresholdFrames = 2.0; policy.maxSkippedTicksPerDecision = 8; RenderCadenceController controller; const TimePoint start = Clock::time_point(Ms(0)); controller.Configure(Ms(20), start, policy); RenderCadenceDecision decision = controller.Tick(start + Ms(70)); Expect(decision.action == RenderCadenceAction::Render, "large late tick renders newest allowed frame"); Expect(decision.skippedTicks == 3, "large late tick skips elapsed render ticks"); Expect(decision.frameIndex == 3, "large late tick renders skipped-to frame"); Expect(decision.renderTargetTime == start + Ms(60), "large late tick targets newest elapsed tick"); Expect(decision.lateness == Ms(10), "large late tick measures residual lateness"); Expect(controller.NextFrameIndex() == 4, "large late tick advances past rendered frame"); Expect(controller.NextRenderTime() == start + Ms(80), "large late tick advances to following cadence"); Expect(controller.Metrics().skippedTickCount == 3, "metrics count skipped ticks"); } void TestSkipPolicyCanDisableOrCapSkippedTicks() { const TimePoint start = Clock::time_point(Ms(0)); RenderCadencePolicy disabledPolicy; disabledPolicy.skipLateTicks = false; RenderCadenceController disabledController; disabledController.Configure(Ms(20), start, disabledPolicy); RenderCadenceDecision disabled = disabledController.Tick(start + Ms(90)); Expect(disabled.skippedTicks == 0, "disabled skip policy renders pending frame"); Expect(disabled.frameIndex == 0, "disabled skip policy preserves pending frame index"); RenderCadencePolicy cappedPolicy; cappedPolicy.skipThresholdFrames = 1.0; cappedPolicy.maxSkippedTicksPerDecision = 2; RenderCadenceController cappedController; cappedController.Configure(Ms(20), start, cappedPolicy); RenderCadenceDecision capped = cappedController.Tick(start + Ms(90)); Expect(capped.skippedTicks == 2, "skip policy caps skipped ticks"); Expect(capped.frameIndex == 2, "capped skip renders capped frame index"); } void TestResetRestartsCadenceAndMetrics() { RenderCadenceController controller; const TimePoint start = Clock::time_point(Ms(0)); controller.Configure(Ms(20), start); (void)controller.Tick(start + Ms(50)); const TimePoint restarted = start + Ms(200); controller.Reset(restarted); Expect(controller.NextFrameIndex() == 0, "reset restarts frame index"); Expect(controller.NextRenderTime() == restarted, "reset restarts next render time"); Expect(controller.Metrics().renderedFrameCount == 0, "reset clears rendered metrics"); RenderCadenceDecision decision = controller.Tick(restarted); Expect(decision.action == RenderCadenceAction::Render, "reset cadence renders at new start"); Expect(decision.frameIndex == 0, "reset cadence renders frame zero"); } void TestActionNames() { Expect(RenderCadenceActionName(RenderCadenceAction::Render) == std::string("Render"), "render action has name"); Expect(RenderCadenceActionName(RenderCadenceAction::Wait) == std::string("Wait"), "wait action has name"); } } int main() { TestExactCadenceAdvancesFrameIndexAndNextTick(); TestEarlyTickWaitsWithoutAdvancing(); TestSlightLatenessRendersAndRecordsMetrics(); TestLargeLatenessSkipsTicksAccordingToPolicy(); TestSkipPolicyCanDisableOrCapSkippedTicks(); TestResetRestartsCadenceAndMetrics(); TestActionNames(); if (gFailures != 0) { std::cerr << gFailures << " RenderCadenceController test failure(s).\n"; return 1; } std::cout << "RenderCadenceController tests passed.\n"; return 0; }