Start up settle

This commit is contained in:
Aiden
2026-05-12 22:04:46 +10:00
parent 3a83d9617f
commit 13586c611a
12 changed files with 62 additions and 2 deletions

View File

@@ -54,6 +54,7 @@ Included now:
- async PBO readback - async PBO readback
- latest-N system-memory frame exchange - latest-N system-memory frame exchange
- rendered-frame warmup - rendered-frame warmup
- eight completed output warmup frames before DeckLink preroll, with DeckLink scheduled depth still targeted at four
- background Slang compile of `shaders/happy-accident` - background Slang compile of `shaders/happy-accident`
- app-owned display/render layer model for shader build readiness - app-owned display/render layer model for shader build readiness
- app-owned submission of a completed shader artifact - app-owned submission of a completed shader artifact
@@ -198,6 +199,7 @@ Currently consumed fields:
- `autoReload` - `autoReload`
- `maxTemporalHistoryFrames` - `maxTemporalHistoryFrames`
- `previewFps` - `previewFps`
- `startupSettleMs`
- `enableExternalKeying` - `enableExternalKeying`
The loaded config is treated as a read-only startup snapshot. Subsystems that need config should receive this snapshot or a narrowed config struct from app orchestration; they should not reload files independently. The loaded config is treated as a read-only startup snapshot. Subsystems that need config should receive this snapshot or a narrowed config struct from app orchestration; they should not reload files independently.

View File

@@ -29,8 +29,9 @@ AppConfig DefaultAppConfig()
config.autoReload = true; config.autoReload = true;
config.maxTemporalHistoryFrames = 12; config.maxTemporalHistoryFrames = 12;
config.previewFps = 30.0; config.previewFps = 30.0;
config.warmupCompletedFrames = 4; config.warmupCompletedFrames = 8;
config.warmupTimeout = std::chrono::seconds(3); config.warmupTimeout = std::chrono::seconds(3);
config.startupSettle = std::chrono::seconds(5);
config.prerollTimeout = std::chrono::seconds(3); config.prerollTimeout = std::chrono::seconds(3);
config.prerollPoll = std::chrono::milliseconds(2); config.prerollPoll = std::chrono::milliseconds(2);
config.runtimeShaderId = "happy-accident"; config.runtimeShaderId = "happy-accident";

View File

@@ -30,8 +30,9 @@ struct AppConfig
bool autoReload = true; bool autoReload = true;
std::size_t maxTemporalHistoryFrames = 12; std::size_t maxTemporalHistoryFrames = 12;
double previewFps = 30.0; double previewFps = 30.0;
std::size_t warmupCompletedFrames = 4; std::size_t warmupCompletedFrames = 8;
std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3); std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3);
std::chrono::milliseconds startupSettle = std::chrono::seconds(5);
std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3); std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3);
std::chrono::milliseconds prerollPoll = std::chrono::milliseconds(2); std::chrono::milliseconds prerollPoll = std::chrono::milliseconds(2);
std::string runtimeShaderId = "happy-accident"; std::string runtimeShaderId = "happy-accident";

View File

@@ -133,6 +133,9 @@ bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& err
ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames); ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames);
ApplyDouble(root, "previewFps", mConfig.previewFps); ApplyDouble(root, "previewFps", mConfig.previewFps);
ApplyBool(root, "enableExternalKeying", mConfig.deckLink.externalKeyingEnabled); ApplyBool(root, "enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
std::size_t startupSettleMilliseconds = static_cast<std::size_t>(mConfig.startupSettle.count());
ApplySize(root, "startupSettleMs", startupSettleMilliseconds);
mConfig.startupSettle = std::chrono::milliseconds(startupSettleMilliseconds);
mLoadedFromFile = true; mLoadedFromFile = true;
error.clear(); error.clear();

View File

@@ -96,6 +96,20 @@ public:
return false; return false;
} }
if (mConfig.startupSettle > std::chrono::milliseconds::zero())
{
Log("app", "Settling render cadence before DeckLink output for " + std::to_string(mConfig.startupSettle.count()) + " ms.");
std::this_thread::sleep_for(mConfig.startupSettle);
Log("app", "Waiting for rendered reserve after startup settle.");
if (!mFrameExchange.WaitForCompletedDepth(mConfig.warmupCompletedFrames, mConfig.warmupTimeout))
{
error = "Timed out waiting for rendered reserve after startup settle.";
LogError("app", error);
Stop();
return false;
}
}
StartOptionalVideoOutput(); StartOptionalVideoOutput();
mTelemetryHealth.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread); mTelemetryHealth.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
StartHttpServer(); StartHttpServer();

View File

@@ -251,6 +251,7 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(input.config.maxTemporalHistoryFrames)); writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(input.config.maxTemporalHistoryFrames));
writer.KeyDouble("previewFps", input.config.previewFps); writer.KeyDouble("previewFps", input.config.previewFps);
writer.KeyBool("enableExternalKeying", input.config.deckLink.externalKeyingEnabled); writer.KeyBool("enableExternalKeying", input.config.deckLink.externalKeyingEnabled);
writer.KeyUInt("startupSettleMs", static_cast<uint64_t>(input.config.startupSettle.count()));
writer.KeyString("inputVideoFormat", input.config.inputVideoFormat); writer.KeyString("inputVideoFormat", input.config.inputVideoFormat);
writer.KeyString("inputFrameRate", input.config.inputFrameRate); writer.KeyString("inputFrameRate", input.config.inputFrameRate);
writer.KeyString("outputVideoFormat", input.config.outputVideoFormat); writer.KeyString("outputVideoFormat", input.config.outputVideoFormat);

View File

@@ -22,6 +22,8 @@ struct CadenceTelemetrySnapshot
uint64_t completions = 0; uint64_t completions = 0;
uint64_t displayedLate = 0; uint64_t displayedLate = 0;
uint64_t dropped = 0; uint64_t dropped = 0;
uint64_t clockOverruns = 0;
uint64_t clockSkippedFrames = 0;
uint64_t shaderBuildsCommitted = 0; uint64_t shaderBuildsCommitted = 0;
uint64_t shaderBuildFailures = 0; uint64_t shaderBuildFailures = 0;
uint64_t inputFramesReceived = 0; uint64_t inputFramesReceived = 0;
@@ -104,6 +106,8 @@ public:
{ {
CadenceTelemetrySnapshot snapshot = Sample(exchange, output, outputThread); CadenceTelemetrySnapshot snapshot = Sample(exchange, output, outputThread);
const auto renderMetrics = renderThread.GetMetrics(); const auto renderMetrics = renderThread.GetMetrics();
snapshot.clockOverruns = renderMetrics.clockOverruns;
snapshot.clockSkippedFrames = renderMetrics.skippedFrames;
snapshot.shaderBuildsCommitted = renderMetrics.shaderBuildsCommitted; snapshot.shaderBuildsCommitted = renderMetrics.shaderBuildsCommitted;
snapshot.shaderBuildFailures = renderMetrics.shaderBuildFailures; snapshot.shaderBuildFailures = renderMetrics.shaderBuildFailures;
snapshot.inputFramesReceived = renderMetrics.inputFramesReceived; snapshot.inputFramesReceived = renderMetrics.inputFramesReceived;

View File

@@ -24,6 +24,10 @@ inline void WriteCadenceTelemetryJson(JsonWriter& writer, const CadenceTelemetry
writer.KeyUInt("completions", snapshot.completions); writer.KeyUInt("completions", snapshot.completions);
writer.KeyUInt("late", snapshot.displayedLate); writer.KeyUInt("late", snapshot.displayedLate);
writer.KeyUInt("dropped", snapshot.dropped); writer.KeyUInt("dropped", snapshot.dropped);
writer.KeyUInt("clockOverruns", snapshot.clockOverruns);
writer.KeyUInt("clockSkippedFrames", snapshot.clockSkippedFrames);
writer.KeyUInt("clockOveruns", snapshot.clockOverruns);
writer.KeyUInt("clockSkipped", snapshot.clockSkippedFrames);
writer.KeyUInt("shaderCommitted", snapshot.shaderBuildsCommitted); writer.KeyUInt("shaderCommitted", snapshot.shaderBuildsCommitted);
writer.KeyUInt("shaderFailures", snapshot.shaderBuildFailures); writer.KeyUInt("shaderFailures", snapshot.shaderBuildFailures);
writer.KeyUInt("inputFramesReceived", snapshot.inputFramesReceived); writer.KeyUInt("inputFramesReceived", snapshot.inputFramesReceived);

View File

@@ -11,5 +11,6 @@
"autoReload": true, "autoReload": true,
"maxTemporalHistoryFrames": 12, "maxTemporalHistoryFrames": 12,
"previewFps": 30, "previewFps": 30,
"startupSettleMs": 5000,
"enableExternalKeying": true "enableExternalKeying": true
} }

View File

@@ -557,6 +557,8 @@ components:
type: number type: number
previewFps: previewFps:
type: number type: number
startupSettleMs:
type: number
enableExternalKeying: enableExternalKeying:
type: boolean type: boolean
inputVideoFormat: inputVideoFormat:
@@ -654,6 +656,20 @@ components:
CadenceTelemetry: CadenceTelemetry:
type: object type: object
properties: properties:
clockOverruns:
type: number
description: Render cadence overruns where the render thread was late enough to skip one or more frame intervals.
clockSkippedFrames:
type: number
description: Total render cadence frame intervals skipped instead of catch-up rendering.
clockOveruns:
type: number
deprecated: true
description: Deprecated misspelled alias for clockOverruns.
clockSkipped:
type: number
deprecated: true
description: Deprecated alias for clockSkippedFrames.
inputFramesReceived: inputFramesReceived:
type: number type: number
inputFramesDropped: inputFramesDropped:

View File

@@ -1,5 +1,6 @@
#include "AppConfigProvider.h" #include "AppConfigProvider.h"
#include <chrono>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
@@ -36,6 +37,7 @@ std::filesystem::path WriteConfigFixture()
<< " \"autoReload\": false,\n" << " \"autoReload\": false,\n"
<< " \"maxTemporalHistoryFrames\": 8,\n" << " \"maxTemporalHistoryFrames\": 8,\n"
<< " \"previewFps\": 24,\n" << " \"previewFps\": 24,\n"
<< " \"startupSettleMs\": 2500,\n"
<< " \"enableExternalKeying\": true\n" << " \"enableExternalKeying\": true\n"
<< "}\n"; << "}\n";
return path; return path;
@@ -66,6 +68,7 @@ void TestLoadsRuntimeHostConfig()
Expect(!config.autoReload, "auto reload loads"); Expect(!config.autoReload, "auto reload loads");
Expect(config.maxTemporalHistoryFrames == 8, "history length loads"); Expect(config.maxTemporalHistoryFrames == 8, "history length loads");
Expect(config.previewFps == 24.0, "preview fps loads"); Expect(config.previewFps == 24.0, "preview fps loads");
Expect(config.startupSettle == std::chrono::milliseconds(2500), "startup settle loads");
Expect(config.deckLink.externalKeyingEnabled, "external keying loads"); Expect(config.deckLink.externalKeyingEnabled, "external keying loads");
std::filesystem::remove(path); std::filesystem::remove(path);

View File

@@ -65,6 +65,8 @@ struct FakeOutput
struct FakeRenderThreadMetrics struct FakeRenderThreadMetrics
{ {
uint64_t clockOverruns = 0;
uint64_t skippedFrames = 0;
uint64_t shaderBuildsCommitted = 0; uint64_t shaderBuildsCommitted = 0;
uint64_t shaderBuildFailures = 0; uint64_t shaderBuildFailures = 0;
uint64_t inputFramesReceived = 0; uint64_t inputFramesReceived = 0;
@@ -104,6 +106,8 @@ void TestTelemetrySamplesCompletedPollMissesAndShaderCounts()
outputThread.metrics.scheduleFailures = 0; outputThread.metrics.scheduleFailures = 0;
FakeRenderThread renderThread; FakeRenderThread renderThread;
renderThread.metrics.clockOverruns = 5;
renderThread.metrics.skippedFrames = 8;
renderThread.metrics.shaderBuildsCommitted = 1; renderThread.metrics.shaderBuildsCommitted = 1;
renderThread.metrics.shaderBuildFailures = 0; renderThread.metrics.shaderBuildFailures = 0;
renderThread.metrics.inputFramesReceived = 9; renderThread.metrics.inputFramesReceived = 9;
@@ -122,6 +126,8 @@ void TestTelemetrySamplesCompletedPollMissesAndShaderCounts()
Expect(snapshot.completedFrames == 1, "completed frame count is sampled"); Expect(snapshot.completedFrames == 1, "completed frame count is sampled");
Expect(snapshot.scheduledFrames == 4, "scheduled frame count is sampled"); Expect(snapshot.scheduledFrames == 4, "scheduled frame count is sampled");
Expect(snapshot.completedPollMisses == 12, "completed poll misses are sampled"); Expect(snapshot.completedPollMisses == 12, "completed poll misses are sampled");
Expect(snapshot.clockOverruns == 5, "clock overrun count is sampled");
Expect(snapshot.clockSkippedFrames == 8, "clock skipped frame count is sampled");
Expect(snapshot.shaderBuildsCommitted == 1, "shader committed count is sampled"); Expect(snapshot.shaderBuildsCommitted == 1, "shader committed count is sampled");
Expect(snapshot.shaderBuildFailures == 0, "shader failure count is sampled"); Expect(snapshot.shaderBuildFailures == 0, "shader failure count is sampled");
Expect(snapshot.inputFramesReceived == 9, "input received count is sampled"); Expect(snapshot.inputFramesReceived == 9, "input received count is sampled");
@@ -176,6 +182,8 @@ void TestTelemetrySerializesToJson()
snapshot.completions = 117; snapshot.completions = 117;
snapshot.displayedLate = 1; snapshot.displayedLate = 1;
snapshot.dropped = 2; snapshot.dropped = 2;
snapshot.clockOverruns = 3;
snapshot.clockSkippedFrames = 5;
snapshot.shaderBuildsCommitted = 1; snapshot.shaderBuildsCommitted = 1;
snapshot.shaderBuildFailures = 0; snapshot.shaderBuildFailures = 0;
snapshot.inputFramesReceived = 10; snapshot.inputFramesReceived = 10;
@@ -206,6 +214,8 @@ void TestTelemetrySerializesToJson()
"\"renderedTotal\":120,\"scheduledTotal\":118," "\"renderedTotal\":120,\"scheduledTotal\":118,"
"\"completedPollMisses\":3,\"scheduleFailures\":0," "\"completedPollMisses\":3,\"scheduleFailures\":0,"
"\"completions\":117,\"late\":1,\"dropped\":2," "\"completions\":117,\"late\":1,\"dropped\":2,"
"\"clockOverruns\":3,\"clockSkippedFrames\":5,"
"\"clockOveruns\":3,\"clockSkipped\":5,"
"\"shaderCommitted\":1,\"shaderFailures\":0," "\"shaderCommitted\":1,\"shaderFailures\":0,"
"\"inputFramesReceived\":10,\"inputFramesDropped\":1," "\"inputFramesReceived\":10,\"inputFramesDropped\":1,"
"\"inputConsumeMisses\":2,\"inputUploadMisses\":3," "\"inputConsumeMisses\":2,\"inputUploadMisses\":3,"