Files
video-shader-toys/tests/RenderCadenceCompositorTelemetryTests.cpp
Aiden 5c66cfdc64
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m56s
CI / Windows Release Package (push) Has been skipped
Input telemetry
2026-05-12 21:13:22 +10:00

252 lines
8.4 KiB
C++

#include "CadenceTelemetry.h"
#include "CadenceTelemetryJson.h"
#include <chrono>
#include <cstdint>
#include <iostream>
#include <thread>
namespace
{
int gFailures = 0;
void Expect(bool condition, const char* message)
{
if (condition)
return;
std::cerr << "FAIL: " << message << "\n";
++gFailures;
}
struct FakeExchangeMetrics
{
std::size_t freeCount = 0;
std::size_t completedCount = 0;
std::size_t scheduledCount = 0;
uint64_t completedFrames = 0;
uint64_t scheduledFrames = 0;
};
struct FakeExchange
{
FakeExchangeMetrics metrics;
FakeExchangeMetrics Metrics() const { return metrics; }
};
struct FakeOutputThreadMetrics
{
uint64_t completedPollMisses = 0;
uint64_t scheduleFailures = 0;
};
struct FakeOutputThread
{
FakeOutputThreadMetrics metrics;
FakeOutputThreadMetrics Metrics() const { return metrics; }
};
struct FakeOutputMetrics
{
uint64_t completions = 0;
uint64_t displayedLate = 0;
uint64_t dropped = 0;
uint64_t scheduleFailures = 0;
bool actualBufferedFramesAvailable = false;
uint64_t actualBufferedFrames = 0;
double scheduleCallMilliseconds = 0.0;
};
struct FakeOutput
{
FakeOutputMetrics metrics;
FakeOutputMetrics Metrics() const { return metrics; }
};
struct FakeRenderThreadMetrics
{
uint64_t shaderBuildsCommitted = 0;
uint64_t shaderBuildFailures = 0;
uint64_t inputFramesReceived = 0;
uint64_t inputFramesDropped = 0;
uint64_t inputConsumeMisses = 0;
uint64_t inputUploadMisses = 0;
std::size_t inputReadyFrames = 0;
std::size_t inputReadingFrames = 0;
double inputLatestAgeMilliseconds = 0.0;
double inputUploadMilliseconds = 0.0;
bool inputFormatSupported = true;
bool inputSignalPresent = false;
};
struct FakeRenderThread
{
FakeRenderThreadMetrics metrics;
FakeRenderThreadMetrics GetMetrics() const { return metrics; }
};
void TestTelemetrySamplesCompletedPollMissesAndShaderCounts()
{
RenderCadenceCompositor::CadenceTelemetry telemetry;
FakeExchange exchange;
exchange.metrics.freeCount = 7;
exchange.metrics.completedCount = 1;
exchange.metrics.scheduledCount = 4;
exchange.metrics.completedFrames = 100;
exchange.metrics.scheduledFrames = 96;
FakeOutput output;
output.metrics.actualBufferedFramesAvailable = true;
output.metrics.actualBufferedFrames = 4;
FakeOutputThread outputThread;
outputThread.metrics.completedPollMisses = 12;
outputThread.metrics.scheduleFailures = 0;
FakeRenderThread renderThread;
renderThread.metrics.shaderBuildsCommitted = 1;
renderThread.metrics.shaderBuildFailures = 0;
renderThread.metrics.inputFramesReceived = 9;
renderThread.metrics.inputFramesDropped = 2;
renderThread.metrics.inputConsumeMisses = 3;
renderThread.metrics.inputUploadMisses = 4;
renderThread.metrics.inputReadyFrames = 1;
renderThread.metrics.inputReadingFrames = 0;
renderThread.metrics.inputLatestAgeMilliseconds = 4.5;
renderThread.metrics.inputUploadMilliseconds = 0.25;
renderThread.metrics.inputFormatSupported = true;
renderThread.metrics.inputSignalPresent = true;
const auto snapshot = telemetry.Sample(exchange, output, outputThread, renderThread);
Expect(snapshot.freeFrames == 7, "free frame count is sampled");
Expect(snapshot.completedFrames == 1, "completed frame count is sampled");
Expect(snapshot.scheduledFrames == 4, "scheduled frame count is sampled");
Expect(snapshot.completedPollMisses == 12, "completed poll misses are sampled");
Expect(snapshot.shaderBuildsCommitted == 1, "shader committed count is sampled");
Expect(snapshot.shaderBuildFailures == 0, "shader failure count is sampled");
Expect(snapshot.inputFramesReceived == 9, "input received count is sampled");
Expect(snapshot.inputFramesDropped == 2, "input dropped count is sampled");
Expect(snapshot.inputConsumeMisses == 3, "input consume miss count is sampled");
Expect(snapshot.inputUploadMisses == 4, "input upload miss count is sampled");
Expect(snapshot.inputReadyFrames == 1, "input ready frame count is sampled");
Expect(snapshot.inputReadingFrames == 0, "input reading frame count is sampled");
Expect(snapshot.inputLatestAgeMilliseconds == 4.5, "input latest age is sampled");
Expect(snapshot.inputUploadMilliseconds == 0.25, "input upload timing is sampled");
Expect(snapshot.inputFormatSupported, "input format support is sampled");
Expect(snapshot.inputSignalPresent, "input signal present is sampled");
Expect(snapshot.deckLinkBufferedAvailable, "buffer telemetry availability is sampled");
Expect(snapshot.deckLinkBuffered == 4, "buffer depth is sampled");
}
void TestTelemetryComputesRatesFromDeltas()
{
RenderCadenceCompositor::CadenceTelemetry telemetry;
FakeExchange exchange;
FakeOutput output;
FakeOutputThread outputThread;
FakeRenderThread renderThread;
exchange.metrics.completedFrames = 10;
exchange.metrics.scheduledFrames = 10;
(void)telemetry.Sample(exchange, output, outputThread, renderThread);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
exchange.metrics.completedFrames = 20;
exchange.metrics.scheduledFrames = 19;
const auto snapshot = telemetry.Sample(exchange, output, outputThread, renderThread);
Expect(snapshot.sampleSeconds > 0.0, "second telemetry sample has elapsed time");
Expect(snapshot.renderFps > 0.0, "render fps is computed from completed frame delta");
Expect(snapshot.scheduleFps > 0.0, "schedule fps is computed from scheduled frame delta");
}
void TestTelemetrySerializesToJson()
{
RenderCadenceCompositor::CadenceTelemetrySnapshot snapshot;
snapshot.sampleSeconds = 1.0;
snapshot.renderFps = 59.94;
snapshot.scheduleFps = 60.0;
snapshot.freeFrames = 7;
snapshot.completedFrames = 1;
snapshot.scheduledFrames = 4;
snapshot.renderedTotal = 120;
snapshot.scheduledTotal = 118;
snapshot.completedPollMisses = 3;
snapshot.scheduleFailures = 0;
snapshot.completions = 117;
snapshot.displayedLate = 1;
snapshot.dropped = 2;
snapshot.shaderBuildsCommitted = 1;
snapshot.shaderBuildFailures = 0;
snapshot.inputFramesReceived = 10;
snapshot.inputFramesDropped = 1;
snapshot.inputConsumeMisses = 2;
snapshot.inputUploadMisses = 3;
snapshot.inputReadyFrames = 1;
snapshot.inputReadingFrames = 0;
snapshot.inputLatestAgeMilliseconds = 3.5;
snapshot.inputUploadMilliseconds = 0.75;
snapshot.inputFormatSupported = true;
snapshot.inputSignalPresent = true;
snapshot.inputCaptureFps = 59.94;
snapshot.inputConvertMilliseconds = 4.25;
snapshot.inputSubmitMilliseconds = 0.35;
snapshot.inputNoSignalFrames = 2;
snapshot.inputUnsupportedFrames = 3;
snapshot.inputSubmitMisses = 4;
snapshot.inputCaptureFormat = "UYVY8";
snapshot.deckLinkBufferedAvailable = true;
snapshot.deckLinkBuffered = 4;
snapshot.deckLinkScheduleCallMilliseconds = 1.25;
const std::string json = RenderCadenceCompositor::CadenceTelemetryToJson(snapshot);
const std::string expected =
"{\"sampleSeconds\":1,\"renderFps\":59.94,\"scheduleFps\":60,"
"\"free\":7,\"completed\":1,\"scheduled\":4,"
"\"renderedTotal\":120,\"scheduledTotal\":118,"
"\"completedPollMisses\":3,\"scheduleFailures\":0,"
"\"completions\":117,\"late\":1,\"dropped\":2,"
"\"shaderCommitted\":1,\"shaderFailures\":0,"
"\"inputFramesReceived\":10,\"inputFramesDropped\":1,"
"\"inputConsumeMisses\":2,\"inputUploadMisses\":3,"
"\"inputReadyFrames\":1,\"inputReadingFrames\":0,"
"\"inputLatestAgeMs\":3.5,\"inputUploadMs\":0.75,"
"\"inputFormatSupported\":true,\"inputSignalPresent\":true,"
"\"inputCaptureFps\":59.94,\"inputConvertMs\":4.25,"
"\"inputSubmitMs\":0.35,\"inputNoSignalFrames\":2,"
"\"inputUnsupportedFrames\":3,\"inputSubmitMisses\":4,"
"\"inputCaptureFormat\":\"UYVY8\","
"\"deckLinkBufferedAvailable\":true,\"deckLinkBuffered\":4,"
"\"scheduleCallMs\":1.25}";
Expect(json == expected, "telemetry snapshot serializes to stable JSON");
}
void TestUnavailableDeckLinkBufferSerializesAsNull()
{
RenderCadenceCompositor::CadenceTelemetrySnapshot snapshot;
snapshot.deckLinkBufferedAvailable = false;
const std::string json = RenderCadenceCompositor::CadenceTelemetryToJson(snapshot);
Expect(
json.find("\"deckLinkBufferedAvailable\":false,\"deckLinkBuffered\":null") != std::string::npos,
"unavailable DeckLink buffer depth serializes as null");
}
}
int main()
{
TestTelemetrySamplesCompletedPollMissesAndShaderCounts();
TestTelemetryComputesRatesFromDeltas();
TestTelemetrySerializesToJson();
TestUnavailableDeckLinkBufferSerializesAsNull();
if (gFailures != 0)
{
std::cerr << gFailures << " telemetry test failure(s).\n";
return 1;
}
std::cout << "RenderCadenceCompositor telemetry tests passed.\n";
return 0;
}