Start up settle
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -11,5 +11,6 @@
|
|||||||
"autoReload": true,
|
"autoReload": true,
|
||||||
"maxTemporalHistoryFrames": 12,
|
"maxTemporalHistoryFrames": 12,
|
||||||
"previewFps": 30,
|
"previewFps": 30,
|
||||||
|
"startupSettleMs": 5000,
|
||||||
"enableExternalKeying": true
|
"enableExternalKeying": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,"
|
||||||
|
|||||||
Reference in New Issue
Block a user