173 lines
6.7 KiB
C++
173 lines
6.7 KiB
C++
#include "RenderCadenceController.h"
|
|
|
|
#include <chrono>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
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<Duration>(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;
|
|
}
|