Clean up pass
This commit is contained in:
@@ -309,6 +309,7 @@ set(RENDER_CADENCE_APP_SOURCES
|
|||||||
"${RENDER_CADENCE_APP_DIR}/app/RenderCadenceApp.h"
|
"${RENDER_CADENCE_APP_DIR}/app/RenderCadenceApp.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/control/HttpControlServer.cpp"
|
"${RENDER_CADENCE_APP_DIR}/control/HttpControlServer.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/control/HttpControlServer.h"
|
"${RENDER_CADENCE_APP_DIR}/control/HttpControlServer.h"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/control/RuntimeStateJson.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.cpp"
|
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.h"
|
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameExchange.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameTypes.h"
|
"${RENDER_CADENCE_APP_DIR}/frames/SystemFrameTypes.h"
|
||||||
@@ -338,7 +339,7 @@ set(RENDER_CADENCE_APP_SOURCES
|
|||||||
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.h"
|
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetryJson.h"
|
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetryJson.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetry.h"
|
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetry.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/telemetry/TelemetryPrinter.h"
|
"${RENDER_CADENCE_APP_DIR}/telemetry/TelemetryHealthMonitor.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutput.cpp"
|
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutput.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutput.h"
|
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutput.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutputThread.h"
|
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutputThread.h"
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Startup warms up real rendered frames before DeckLink scheduled playback starts.
|
|||||||
Included now:
|
Included now:
|
||||||
|
|
||||||
- output-only DeckLink
|
- output-only DeckLink
|
||||||
|
- non-blocking startup when DeckLink output is unavailable
|
||||||
- hidden render-thread-owned OpenGL context
|
- hidden render-thread-owned OpenGL context
|
||||||
- simple smooth-motion renderer
|
- simple smooth-motion renderer
|
||||||
- BGRA8-only output
|
- BGRA8-only output
|
||||||
@@ -49,7 +50,7 @@ Included now:
|
|||||||
- background logging with `log`, `warning`, and `error` levels
|
- background logging with `log`, `warning`, and `error` levels
|
||||||
- local HTTP control server matching the OpenAPI route surface
|
- local HTTP control server matching the OpenAPI route surface
|
||||||
- startup config provider for `config/runtime-host.json`
|
- startup config provider for `config/runtime-host.json`
|
||||||
- compact telemetry
|
- quiet telemetry health monitor
|
||||||
- non-GL frame-exchange tests
|
- non-GL frame-exchange tests
|
||||||
|
|
||||||
Intentionally not included yet:
|
Intentionally not included yet:
|
||||||
@@ -150,11 +151,28 @@ Current endpoints:
|
|||||||
|
|
||||||
The HTTP server runs on its own thread. It samples/copies telemetry through callbacks and does not call render work or DeckLink scheduling.
|
The HTTP server runs on its own thread. It samples/copies telemetry through callbacks and does not call render work or DeckLink scheduling.
|
||||||
|
|
||||||
The app prints one line per second:
|
## Optional DeckLink Output
|
||||||
|
|
||||||
```text
|
DeckLink output is an optional edge service in this app.
|
||||||
renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 completedPollMisses=0 scheduleFailures=0 completions=119 late=0 dropped=0 shaderCommitted=1 shaderFailures=0 decklinkBuffered=4 scheduleCallMs=0.0
|
|
||||||
```
|
Startup order is:
|
||||||
|
|
||||||
|
1. start render thread
|
||||||
|
2. warm up rendered system-memory frames
|
||||||
|
3. try to attach DeckLink output
|
||||||
|
4. start telemetry and HTTP either way
|
||||||
|
|
||||||
|
If DeckLink discovery or output setup fails, the app logs a warning and continues running without starting the output scheduler or scheduled playback. This keeps render cadence, runtime shader testing, HTTP state, and logging available on machines without DeckLink hardware or drivers.
|
||||||
|
|
||||||
|
`/api/state` reports the output status in `videoIO.statusMessage`.
|
||||||
|
|
||||||
|
The app samples telemetry once per second.
|
||||||
|
|
||||||
|
Normal cadence samples are available through `GET /api/state` and are not printed to the console. The telemetry monitor only logs health events:
|
||||||
|
|
||||||
|
- warning when DeckLink late/dropped-frame counters increase
|
||||||
|
- warning when schedule failures increase
|
||||||
|
- error when the app/DeckLink output buffer is starved
|
||||||
|
|
||||||
Healthy first-run signs:
|
Healthy first-run signs:
|
||||||
|
|
||||||
@@ -234,10 +252,11 @@ This app keeps the same core behavior but splits it into modules that can grow:
|
|||||||
- `frames/`: system-memory handoff
|
- `frames/`: system-memory handoff
|
||||||
- `platform/`: COM/Win32/hidden GL context support
|
- `platform/`: COM/Win32/hidden GL context support
|
||||||
- `render/`: cadence, simple rendering, PBO readback
|
- `render/`: cadence, simple rendering, PBO readback
|
||||||
- `control/`: local HTTP API edge
|
- `control/`: local HTTP API edge and runtime-state JSON presentation
|
||||||
- `json/`: compact JSON serialization helpers
|
- `json/`: compact JSON serialization helpers
|
||||||
- `video/`: DeckLink output wrapper and scheduling thread
|
- `video/`: DeckLink output wrapper and scheduling thread
|
||||||
- `telemetry/`: cadence telemetry
|
- `telemetry/`: cadence telemetry
|
||||||
|
- `telemetry/TelemetryHealthMonitor`: quiet health event logging from telemetry samples
|
||||||
- `app/`: startup/shutdown orchestration
|
- `app/`: startup/shutdown orchestration
|
||||||
- `app/AppConfigProvider`: startup config loading and CLI overrides
|
- `app/AppConfigProvider`: startup config loading and CLI overrides
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ int main(int argc, char** argv)
|
|||||||
{
|
{
|
||||||
RenderCadenceCompositor::AppConfigProvider configProvider;
|
RenderCadenceCompositor::AppConfigProvider configProvider;
|
||||||
std::string configError;
|
std::string configError;
|
||||||
if (!configProvider.Load("config/runtime-host.json", configError))
|
if (!configProvider.LoadDefault(configError))
|
||||||
{
|
{
|
||||||
RenderCadenceCompositor::Logger::Instance().Start(RenderCadenceCompositor::DefaultAppConfig().logging);
|
RenderCadenceCompositor::Logger::Instance().Start(RenderCadenceCompositor::DefaultAppConfig().logging);
|
||||||
RenderCadenceCompositor::LogError("app", "Config load failed: " + configError);
|
RenderCadenceCompositor::LogError("app", "Config load failed: " + configError);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include "../control/HttpControlServer.h"
|
#include "../control/HttpControlServer.h"
|
||||||
#include "../logging/Logger.h"
|
#include "../logging/Logger.h"
|
||||||
#include "../telemetry/TelemetryPrinter.h"
|
#include "../telemetry/TelemetryHealthMonitor.h"
|
||||||
#include "../video/DeckLinkOutput.h"
|
#include "../video/DeckLinkOutput.h"
|
||||||
#include "../video/DeckLinkOutputThread.h"
|
#include "../video/DeckLinkOutputThread.h"
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ struct AppConfig
|
|||||||
{
|
{
|
||||||
DeckLinkOutputConfig deckLink;
|
DeckLinkOutputConfig deckLink;
|
||||||
DeckLinkOutputThreadConfig outputThread;
|
DeckLinkOutputThreadConfig outputThread;
|
||||||
TelemetryPrinterConfig telemetry;
|
TelemetryHealthMonitorConfig telemetry;
|
||||||
LoggerConfig logging;
|
LoggerConfig logging;
|
||||||
HttpControlServerConfig http;
|
HttpControlServerConfig http;
|
||||||
std::string shaderLibrary = "shaders";
|
std::string shaderLibrary = "shaders";
|
||||||
|
|||||||
@@ -7,11 +7,22 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
namespace RenderCadenceCompositor
|
namespace RenderCadenceCompositor
|
||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
std::filesystem::path ExecutableDirectory()
|
||||||
|
{
|
||||||
|
char path[MAX_PATH] = {};
|
||||||
|
const DWORD length = GetModuleFileNameA(nullptr, path, static_cast<DWORD>(sizeof(path)));
|
||||||
|
if (length == 0 || length >= sizeof(path))
|
||||||
|
return std::filesystem::current_path();
|
||||||
|
return std::filesystem::path(path).parent_path();
|
||||||
|
}
|
||||||
|
|
||||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error)
|
std::string ReadTextFile(const std::filesystem::path& path, std::string& error)
|
||||||
{
|
{
|
||||||
std::ifstream input(path, std::ios::binary);
|
std::ifstream input(path, std::ios::binary);
|
||||||
@@ -76,6 +87,17 @@ AppConfigProvider::AppConfigProvider() :
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AppConfigProvider::LoadDefault(std::string& error)
|
||||||
|
{
|
||||||
|
const std::filesystem::path path = FindConfigFile();
|
||||||
|
if (path.empty())
|
||||||
|
{
|
||||||
|
error = "Could not locate config/runtime-host.json from current directory or executable directory.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Load(path, error);
|
||||||
|
}
|
||||||
|
|
||||||
bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& error)
|
bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& error)
|
||||||
{
|
{
|
||||||
mConfig = DefaultAppConfig();
|
mConfig = DefaultAppConfig();
|
||||||
@@ -183,4 +205,28 @@ void VideoFormatDimensions(const std::string& formatName, unsigned& width, unsig
|
|||||||
width = 1920;
|
width = 1920;
|
||||||
height = 1080;
|
height = 1080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::filesystem::path FindConfigFile(const std::filesystem::path& relativePath)
|
||||||
|
{
|
||||||
|
std::vector<std::filesystem::path> starts;
|
||||||
|
starts.push_back(std::filesystem::current_path());
|
||||||
|
starts.push_back(ExecutableDirectory());
|
||||||
|
|
||||||
|
for (std::filesystem::path start : starts)
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const std::filesystem::path candidate = start / relativePath;
|
||||||
|
if (std::filesystem::exists(candidate))
|
||||||
|
return candidate;
|
||||||
|
|
||||||
|
const std::filesystem::path parent = start.parent_path();
|
||||||
|
if (parent.empty() || parent == start)
|
||||||
|
break;
|
||||||
|
start = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::filesystem::path();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ class AppConfigProvider
|
|||||||
public:
|
public:
|
||||||
AppConfigProvider();
|
AppConfigProvider();
|
||||||
|
|
||||||
bool Load(const std::filesystem::path& path, std::string& error);
|
bool Load(const std::filesystem::path& path, std::string& error);
|
||||||
|
bool LoadDefault(std::string& error);
|
||||||
void ApplyCommandLine(int argc, char** argv);
|
void ApplyCommandLine(int argc, char** argv);
|
||||||
|
|
||||||
const AppConfig& Config() const { return mConfig; }
|
const AppConfig& Config() const { return mConfig; }
|
||||||
@@ -27,4 +28,5 @@ private:
|
|||||||
|
|
||||||
double FrameDurationMillisecondsFromRateString(const std::string& rateText, double fallbackRate = 59.94);
|
double FrameDurationMillisecondsFromRateString(const std::string& rateText, double fallbackRate = 59.94);
|
||||||
void VideoFormatDimensions(const std::string& formatName, unsigned& width, unsigned& height);
|
void VideoFormatDimensions(const std::string& formatName, unsigned& width, unsigned& height);
|
||||||
|
std::filesystem::path FindConfigFile(const std::filesystem::path& relativePath = "config/runtime-host.json");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AppConfig.h"
|
#include "AppConfig.h"
|
||||||
#include "AppConfigProvider.h"
|
|
||||||
#include "../json/JsonWriter.h"
|
|
||||||
#include "../logging/Logger.h"
|
#include "../logging/Logger.h"
|
||||||
#include "../runtime/RuntimeShaderBridge.h"
|
#include "../runtime/RuntimeShaderBridge.h"
|
||||||
#include "../telemetry/CadenceTelemetryJson.h"
|
#include "../control/RuntimeStateJson.h"
|
||||||
#include "../telemetry/TelemetryPrinter.h"
|
#include "../telemetry/TelemetryHealthMonitor.h"
|
||||||
#include "../video/DeckLinkOutput.h"
|
#include "../video/DeckLinkOutput.h"
|
||||||
#include "../video/DeckLinkOutputThread.h"
|
#include "../video/DeckLinkOutputThread.h"
|
||||||
|
|
||||||
@@ -55,7 +53,7 @@ public:
|
|||||||
mFrameExchange(frameExchange),
|
mFrameExchange(frameExchange),
|
||||||
mConfig(config),
|
mConfig(config),
|
||||||
mOutputThread(mOutput, mFrameExchange, mConfig.outputThread),
|
mOutputThread(mOutput, mFrameExchange, mConfig.outputThread),
|
||||||
mTelemetry(mConfig.telemetry)
|
mTelemetryHealth(mConfig.telemetry)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,18 +67,6 @@ public:
|
|||||||
|
|
||||||
bool Start(std::string& error)
|
bool Start(std::string& error)
|
||||||
{
|
{
|
||||||
Log("app", "Initializing DeckLink output.");
|
|
||||||
if (!mOutput.Initialize(
|
|
||||||
mConfig.deckLink,
|
|
||||||
[this](const VideoIOCompletion& completion) {
|
|
||||||
mFrameExchange.ReleaseScheduledByBytes(completion.outputFrameBuffer);
|
|
||||||
},
|
|
||||||
error))
|
|
||||||
{
|
|
||||||
LogError("app", "DeckLink output initialization failed: " + error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log("app", "Starting render thread.");
|
Log("app", "Starting render thread.");
|
||||||
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
||||||
{
|
{
|
||||||
@@ -99,33 +85,8 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log("app", "Starting DeckLink output thread.");
|
StartOptionalVideoOutput();
|
||||||
if (!mOutputThread.Start())
|
mTelemetryHealth.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
||||||
{
|
|
||||||
error = "DeckLink output thread failed to start.";
|
|
||||||
LogError("app", error);
|
|
||||||
Stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log("app", "Waiting for DeckLink preroll frames.");
|
|
||||||
if (!WaitForPreroll())
|
|
||||||
{
|
|
||||||
error = "Timed out waiting for DeckLink preroll frames.";
|
|
||||||
LogError("app", error);
|
|
||||||
Stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log("app", "Starting DeckLink scheduled playback.");
|
|
||||||
if (!mOutput.StartScheduledPlayback(error))
|
|
||||||
{
|
|
||||||
LogError("app", "DeckLink scheduled playback failed: " + error);
|
|
||||||
Stop();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mTelemetry.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
|
||||||
StartHttpServer();
|
StartHttpServer();
|
||||||
Log("app", "RenderCadenceCompositor started.");
|
Log("app", "RenderCadenceCompositor started.");
|
||||||
mStarted = true;
|
mStarted = true;
|
||||||
@@ -135,7 +96,7 @@ public:
|
|||||||
void Stop()
|
void Stop()
|
||||||
{
|
{
|
||||||
mHttpServer.Stop();
|
mHttpServer.Stop();
|
||||||
mTelemetry.Stop();
|
mTelemetryHealth.Stop();
|
||||||
mOutputThread.Stop();
|
mOutputThread.Stop();
|
||||||
mOutput.Stop();
|
mOutput.Stop();
|
||||||
StopRuntimeShaderBuild();
|
StopRuntimeShaderBuild();
|
||||||
@@ -150,6 +111,58 @@ public:
|
|||||||
const DeckLinkOutput& Output() const { return mOutput; }
|
const DeckLinkOutput& Output() const { return mOutput; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void StartOptionalVideoOutput()
|
||||||
|
{
|
||||||
|
std::string outputError;
|
||||||
|
Log("app", "Initializing optional DeckLink output.");
|
||||||
|
if (!mOutput.Initialize(
|
||||||
|
mConfig.deckLink,
|
||||||
|
[this](const VideoIOCompletion& completion) {
|
||||||
|
mFrameExchange.ReleaseScheduledByBytes(completion.outputFrameBuffer);
|
||||||
|
},
|
||||||
|
outputError))
|
||||||
|
{
|
||||||
|
DisableVideoOutput("DeckLink output unavailable: " + outputError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("app", "Starting DeckLink output thread.");
|
||||||
|
if (!mOutputThread.Start())
|
||||||
|
{
|
||||||
|
DisableVideoOutput("DeckLink output thread failed to start.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("app", "Waiting for DeckLink preroll frames.");
|
||||||
|
if (!WaitForPreroll())
|
||||||
|
{
|
||||||
|
DisableVideoOutput("Timed out waiting for DeckLink preroll frames.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("app", "Starting DeckLink scheduled playback.");
|
||||||
|
if (!mOutput.StartScheduledPlayback(outputError))
|
||||||
|
{
|
||||||
|
DisableVideoOutput("DeckLink scheduled playback failed: " + outputError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mVideoOutputEnabled = true;
|
||||||
|
mVideoOutputStatus = "DeckLink scheduled output running.";
|
||||||
|
Log("app", mVideoOutputStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisableVideoOutput(const std::string& reason)
|
||||||
|
{
|
||||||
|
mOutputThread.Stop();
|
||||||
|
mOutput.Stop();
|
||||||
|
mOutput.ReleaseResources();
|
||||||
|
mFrameExchange.Clear();
|
||||||
|
mVideoOutputEnabled = false;
|
||||||
|
mVideoOutputStatus = reason;
|
||||||
|
LogWarning("app", reason + " Continuing without video output.");
|
||||||
|
}
|
||||||
|
|
||||||
void StartHttpServer()
|
void StartHttpServer()
|
||||||
{
|
{
|
||||||
HttpControlServerCallbacks callbacks;
|
HttpControlServerCallbacks callbacks;
|
||||||
@@ -168,65 +181,13 @@ private:
|
|||||||
std::string BuildStateJson()
|
std::string BuildStateJson()
|
||||||
{
|
{
|
||||||
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
||||||
|
return RuntimeStateToJson(RuntimeStateJsonInput{
|
||||||
JsonWriter writer;
|
mConfig,
|
||||||
writer.BeginObject();
|
telemetry,
|
||||||
writer.Key("app");
|
mHttpServer.Port(),
|
||||||
writer.BeginObject();
|
mVideoOutputEnabled,
|
||||||
writer.KeyUInt("serverPort", mHttpServer.Port());
|
mVideoOutputStatus
|
||||||
writer.KeyUInt("oscPort", mConfig.oscPort);
|
});
|
||||||
writer.KeyString("oscBindAddress", mConfig.oscBindAddress);
|
|
||||||
writer.KeyDouble("oscSmoothing", mConfig.oscSmoothing);
|
|
||||||
writer.KeyBool("autoReload", mConfig.autoReload);
|
|
||||||
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(mConfig.maxTemporalHistoryFrames));
|
|
||||||
writer.KeyDouble("previewFps", mConfig.previewFps);
|
|
||||||
writer.KeyBool("enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
|
|
||||||
writer.KeyString("inputVideoFormat", mConfig.inputVideoFormat);
|
|
||||||
writer.KeyString("inputFrameRate", mConfig.inputFrameRate);
|
|
||||||
writer.KeyString("outputVideoFormat", mConfig.outputVideoFormat);
|
|
||||||
writer.KeyString("outputFrameRate", mConfig.outputFrameRate);
|
|
||||||
writer.EndObject();
|
|
||||||
|
|
||||||
writer.Key("runtime");
|
|
||||||
writer.BeginObject();
|
|
||||||
writer.KeyUInt("layerCount", 0);
|
|
||||||
writer.KeyBool("compileSucceeded", true);
|
|
||||||
writer.KeyString("compileMessage", "Runtime state is not ported into RenderCadenceCompositor yet.");
|
|
||||||
writer.EndObject();
|
|
||||||
|
|
||||||
writer.KeyNull("video");
|
|
||||||
writer.KeyNull("decklink");
|
|
||||||
writer.KeyNull("videoIO");
|
|
||||||
|
|
||||||
writer.Key("performance");
|
|
||||||
writer.BeginObject();
|
|
||||||
writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(mConfig.outputFrameRate));
|
|
||||||
writer.KeyNull("renderMs");
|
|
||||||
writer.KeyNull("smoothedRenderMs");
|
|
||||||
writer.KeyNull("budgetUsedPercent");
|
|
||||||
writer.KeyNull("completionIntervalMs");
|
|
||||||
writer.KeyNull("smoothedCompletionIntervalMs");
|
|
||||||
writer.KeyNull("maxCompletionIntervalMs");
|
|
||||||
writer.KeyUInt("lateFrameCount", telemetry.displayedLate);
|
|
||||||
writer.KeyUInt("droppedFrameCount", telemetry.dropped);
|
|
||||||
writer.KeyNull("flushedFrameCount");
|
|
||||||
writer.Key("cadence");
|
|
||||||
WriteCadenceTelemetryJson(writer, telemetry);
|
|
||||||
writer.EndObject();
|
|
||||||
|
|
||||||
writer.KeyNull("backendPlayout");
|
|
||||||
writer.KeyNull("runtimeEvents");
|
|
||||||
writer.Key("shaders");
|
|
||||||
writer.BeginArray();
|
|
||||||
writer.EndArray();
|
|
||||||
writer.Key("stackPresets");
|
|
||||||
writer.BeginArray();
|
|
||||||
writer.EndArray();
|
|
||||||
writer.Key("layers");
|
|
||||||
writer.BeginArray();
|
|
||||||
writer.EndArray();
|
|
||||||
writer.EndObject();
|
|
||||||
return writer.StringValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaitForPreroll() const
|
bool WaitForPreroll() const
|
||||||
@@ -270,10 +231,12 @@ private:
|
|||||||
AppConfig mConfig;
|
AppConfig mConfig;
|
||||||
DeckLinkOutput mOutput;
|
DeckLinkOutput mOutput;
|
||||||
DeckLinkOutputThread<SystemFrameExchange> mOutputThread;
|
DeckLinkOutputThread<SystemFrameExchange> mOutputThread;
|
||||||
TelemetryPrinter mTelemetry;
|
TelemetryHealthMonitor mTelemetryHealth;
|
||||||
CadenceTelemetry mHttpTelemetry;
|
CadenceTelemetry mHttpTelemetry;
|
||||||
HttpControlServer mHttpServer;
|
HttpControlServer mHttpServer;
|
||||||
RuntimeShaderBridge mShaderBridge;
|
RuntimeShaderBridge mShaderBridge;
|
||||||
bool mStarted = false;
|
bool mStarted = false;
|
||||||
|
bool mVideoOutputEnabled = false;
|
||||||
|
std::string mVideoOutputStatus = "DeckLink output not started.";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
108
apps/RenderCadenceCompositor/control/RuntimeStateJson.h
Normal file
108
apps/RenderCadenceCompositor/control/RuntimeStateJson.h
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../app/AppConfig.h"
|
||||||
|
#include "../app/AppConfigProvider.h"
|
||||||
|
#include "../json/JsonWriter.h"
|
||||||
|
#include "../telemetry/CadenceTelemetryJson.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
struct RuntimeStateJsonInput
|
||||||
|
{
|
||||||
|
const AppConfig& config;
|
||||||
|
const CadenceTelemetrySnapshot& telemetry;
|
||||||
|
unsigned short serverPort = 0;
|
||||||
|
bool videoOutputEnabled = false;
|
||||||
|
std::string videoOutputStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||||
|
{
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyString("backend", "decklink");
|
||||||
|
writer.KeyNull("modelName");
|
||||||
|
writer.KeyBool("supportsInternalKeying", false);
|
||||||
|
writer.KeyBool("supportsExternalKeying", false);
|
||||||
|
writer.KeyBool("keyerInterfaceAvailable", false);
|
||||||
|
writer.KeyBool("externalKeyingRequested", input.config.deckLink.externalKeyingEnabled);
|
||||||
|
writer.KeyBool("externalKeyingActive", input.videoOutputEnabled && input.config.deckLink.externalKeyingEnabled);
|
||||||
|
writer.KeyString("statusMessage", input.videoOutputStatus);
|
||||||
|
writer.EndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
||||||
|
{
|
||||||
|
JsonWriter writer;
|
||||||
|
writer.BeginObject();
|
||||||
|
|
||||||
|
writer.Key("app");
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyUInt("serverPort", input.serverPort);
|
||||||
|
writer.KeyUInt("oscPort", input.config.oscPort);
|
||||||
|
writer.KeyString("oscBindAddress", input.config.oscBindAddress);
|
||||||
|
writer.KeyDouble("oscSmoothing", input.config.oscSmoothing);
|
||||||
|
writer.KeyBool("autoReload", input.config.autoReload);
|
||||||
|
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(input.config.maxTemporalHistoryFrames));
|
||||||
|
writer.KeyDouble("previewFps", input.config.previewFps);
|
||||||
|
writer.KeyBool("enableExternalKeying", input.config.deckLink.externalKeyingEnabled);
|
||||||
|
writer.KeyString("inputVideoFormat", input.config.inputVideoFormat);
|
||||||
|
writer.KeyString("inputFrameRate", input.config.inputFrameRate);
|
||||||
|
writer.KeyString("outputVideoFormat", input.config.outputVideoFormat);
|
||||||
|
writer.KeyString("outputFrameRate", input.config.outputFrameRate);
|
||||||
|
writer.EndObject();
|
||||||
|
|
||||||
|
writer.Key("runtime");
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyUInt("layerCount", 0);
|
||||||
|
writer.KeyBool("compileSucceeded", true);
|
||||||
|
writer.KeyString("compileMessage", "Runtime state is not ported into RenderCadenceCompositor yet.");
|
||||||
|
writer.EndObject();
|
||||||
|
|
||||||
|
writer.Key("video");
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyBool("hasSignal", false);
|
||||||
|
writer.KeyNull("width");
|
||||||
|
writer.KeyNull("height");
|
||||||
|
writer.KeyString("modeName", "output-only");
|
||||||
|
writer.EndObject();
|
||||||
|
|
||||||
|
writer.Key("decklink");
|
||||||
|
WriteVideoIoStatusJson(writer, input);
|
||||||
|
writer.Key("videoIO");
|
||||||
|
WriteVideoIoStatusJson(writer, input);
|
||||||
|
|
||||||
|
writer.Key("performance");
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(input.config.outputFrameRate));
|
||||||
|
writer.KeyNull("renderMs");
|
||||||
|
writer.KeyNull("smoothedRenderMs");
|
||||||
|
writer.KeyNull("budgetUsedPercent");
|
||||||
|
writer.KeyNull("completionIntervalMs");
|
||||||
|
writer.KeyNull("smoothedCompletionIntervalMs");
|
||||||
|
writer.KeyNull("maxCompletionIntervalMs");
|
||||||
|
writer.KeyUInt("lateFrameCount", input.telemetry.displayedLate);
|
||||||
|
writer.KeyUInt("droppedFrameCount", input.telemetry.dropped);
|
||||||
|
writer.KeyNull("flushedFrameCount");
|
||||||
|
writer.Key("cadence");
|
||||||
|
WriteCadenceTelemetryJson(writer, input.telemetry);
|
||||||
|
writer.EndObject();
|
||||||
|
|
||||||
|
writer.KeyNull("backendPlayout");
|
||||||
|
writer.KeyNull("runtimeEvents");
|
||||||
|
writer.Key("shaders");
|
||||||
|
writer.BeginArray();
|
||||||
|
writer.EndArray();
|
||||||
|
writer.Key("stackPresets");
|
||||||
|
writer.BeginArray();
|
||||||
|
writer.EndArray();
|
||||||
|
writer.Key("layers");
|
||||||
|
writer.BeginArray();
|
||||||
|
writer.EndArray();
|
||||||
|
|
||||||
|
writer.EndObject();
|
||||||
|
return writer.StringValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
116
apps/RenderCadenceCompositor/telemetry/TelemetryHealthMonitor.h
Normal file
116
apps/RenderCadenceCompositor/telemetry/TelemetryHealthMonitor.h
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CadenceTelemetry.h"
|
||||||
|
#include "../logging/Logger.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
struct TelemetryHealthMonitorConfig
|
||||||
|
{
|
||||||
|
std::chrono::milliseconds interval = std::chrono::seconds(1);
|
||||||
|
std::size_t scheduledStarvationThreshold = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TelemetryHealthMonitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TelemetryHealthMonitor(TelemetryHealthMonitorConfig config = TelemetryHealthMonitorConfig()) :
|
||||||
|
mConfig(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TelemetryHealthMonitor(const TelemetryHealthMonitor&) = delete;
|
||||||
|
TelemetryHealthMonitor& operator=(const TelemetryHealthMonitor&) = delete;
|
||||||
|
|
||||||
|
~TelemetryHealthMonitor()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SystemFrameExchange, typename Output, typename OutputThread, typename RenderThread>
|
||||||
|
void Start(const SystemFrameExchange& exchange, const Output& output, const OutputThread& outputThread, const RenderThread& renderThread)
|
||||||
|
{
|
||||||
|
if (mRunning)
|
||||||
|
return;
|
||||||
|
mStopping = false;
|
||||||
|
mThread = std::thread([this, &exchange, &output, &outputThread, &renderThread]() {
|
||||||
|
CadenceTelemetry telemetry;
|
||||||
|
CadenceTelemetrySnapshot previous;
|
||||||
|
bool hasPrevious = false;
|
||||||
|
while (!mStopping)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(mConfig.interval);
|
||||||
|
const CadenceTelemetrySnapshot snapshot = telemetry.Sample(exchange, output, outputThread, renderThread);
|
||||||
|
ReportHealth(snapshot, hasPrevious ? &previous : nullptr);
|
||||||
|
previous = snapshot;
|
||||||
|
hasPrevious = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop()
|
||||||
|
{
|
||||||
|
mStopping = true;
|
||||||
|
if (mThread.joinable())
|
||||||
|
mThread.join();
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ReportHealth(const CadenceTelemetrySnapshot& snapshot, const CadenceTelemetrySnapshot* previous) const
|
||||||
|
{
|
||||||
|
if (!previous)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const uint64_t lateDelta = snapshot.displayedLate - previous->displayedLate;
|
||||||
|
const uint64_t droppedDelta = snapshot.dropped - previous->dropped;
|
||||||
|
const uint64_t scheduleFailureDelta = snapshot.scheduleFailures - previous->scheduleFailures;
|
||||||
|
|
||||||
|
if (droppedDelta > 0 || lateDelta > 0)
|
||||||
|
{
|
||||||
|
std::ostringstream message;
|
||||||
|
message << "DeckLink reported frame timing issue: lateDelta=" << lateDelta
|
||||||
|
<< " droppedDelta=" << droppedDelta
|
||||||
|
<< " totalLate=" << snapshot.displayedLate
|
||||||
|
<< " totalDropped=" << snapshot.dropped;
|
||||||
|
LogWarning("telemetry", message.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduleFailureDelta > 0)
|
||||||
|
{
|
||||||
|
std::ostringstream message;
|
||||||
|
message << "DeckLink schedule failures increased: delta=" << scheduleFailureDelta
|
||||||
|
<< " total=" << snapshot.scheduleFailures;
|
||||||
|
LogWarning("telemetry", message.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool appScheduledStarved = snapshot.scheduledFrames <= mConfig.scheduledStarvationThreshold
|
||||||
|
&& snapshot.scheduledTotal > 0;
|
||||||
|
const bool deckLinkStarved = snapshot.deckLinkBufferedAvailable && snapshot.deckLinkBuffered == 0;
|
||||||
|
if (appScheduledStarved || deckLinkStarved)
|
||||||
|
{
|
||||||
|
std::ostringstream message;
|
||||||
|
message << "Output buffer starvation detected: scheduled=" << snapshot.scheduledFrames
|
||||||
|
<< " decklinkBuffered=";
|
||||||
|
if (snapshot.deckLinkBufferedAvailable)
|
||||||
|
message << snapshot.deckLinkBuffered;
|
||||||
|
else
|
||||||
|
message << "n/a";
|
||||||
|
message << " renderFps=" << snapshot.renderFps
|
||||||
|
<< " scheduleFps=" << snapshot.scheduleFps;
|
||||||
|
LogError("telemetry", message.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TelemetryHealthMonitorConfig mConfig;
|
||||||
|
std::thread mThread;
|
||||||
|
std::atomic<bool> mStopping{ false };
|
||||||
|
std::atomic<bool> mRunning{ false };
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CadenceTelemetry.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <iostream>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace RenderCadenceCompositor
|
|
||||||
{
|
|
||||||
struct TelemetryPrinterConfig
|
|
||||||
{
|
|
||||||
std::chrono::milliseconds interval = std::chrono::seconds(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
class TelemetryPrinter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit TelemetryPrinter(TelemetryPrinterConfig config = TelemetryPrinterConfig()) :
|
|
||||||
mConfig(config)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TelemetryPrinter(const TelemetryPrinter&) = delete;
|
|
||||||
TelemetryPrinter& operator=(const TelemetryPrinter&) = delete;
|
|
||||||
|
|
||||||
~TelemetryPrinter()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename SystemFrameExchange, typename Output, typename OutputThread, typename RenderThread>
|
|
||||||
void Start(const SystemFrameExchange& exchange, const Output& output, const OutputThread& outputThread, const RenderThread& renderThread)
|
|
||||||
{
|
|
||||||
if (mRunning)
|
|
||||||
return;
|
|
||||||
mStopping = false;
|
|
||||||
mThread = std::thread([this, &exchange, &output, &outputThread, &renderThread]() {
|
|
||||||
CadenceTelemetry telemetry;
|
|
||||||
while (!mStopping)
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(mConfig.interval);
|
|
||||||
Print(telemetry.Sample(exchange, output, outputThread, renderThread));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mRunning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Stop()
|
|
||||||
{
|
|
||||||
mStopping = true;
|
|
||||||
if (mThread.joinable())
|
|
||||||
mThread.join();
|
|
||||||
mRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static void Print(const CadenceTelemetrySnapshot& snapshot)
|
|
||||||
{
|
|
||||||
std::cout << std::fixed << std::setprecision(1)
|
|
||||||
<< "renderFps=" << snapshot.renderFps
|
|
||||||
<< " scheduleFps=" << snapshot.scheduleFps
|
|
||||||
<< " free=" << snapshot.freeFrames
|
|
||||||
<< " completed=" << snapshot.completedFrames
|
|
||||||
<< " scheduled=" << snapshot.scheduledFrames
|
|
||||||
<< " completedPollMisses=" << snapshot.completedPollMisses
|
|
||||||
<< " scheduleFailures=" << snapshot.scheduleFailures
|
|
||||||
<< " completions=" << snapshot.completions
|
|
||||||
<< " late=" << snapshot.displayedLate
|
|
||||||
<< " dropped=" << snapshot.dropped
|
|
||||||
<< " shaderCommitted=" << snapshot.shaderBuildsCommitted
|
|
||||||
<< " shaderFailures=" << snapshot.shaderBuildFailures
|
|
||||||
<< " decklinkBuffered=";
|
|
||||||
if (snapshot.deckLinkBufferedAvailable)
|
|
||||||
std::cout << snapshot.deckLinkBuffered;
|
|
||||||
else
|
|
||||||
std::cout << "n/a";
|
|
||||||
std::cout << " scheduleCallMs=" << snapshot.deckLinkScheduleCallMilliseconds << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
TelemetryPrinterConfig mConfig;
|
|
||||||
std::thread mThread;
|
|
||||||
std::atomic<bool> mStopping{ false };
|
|
||||||
std::atomic<bool> mRunning{ false };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -104,6 +104,13 @@ apps/RenderCadenceCompositor/
|
|||||||
RenderCadenceApp.h
|
RenderCadenceApp.h
|
||||||
AppConfig.cpp
|
AppConfig.cpp
|
||||||
AppConfig.h
|
AppConfig.h
|
||||||
|
AppConfigProvider.cpp
|
||||||
|
AppConfigProvider.h
|
||||||
|
|
||||||
|
control/
|
||||||
|
HttpControlServer.cpp
|
||||||
|
HttpControlServer.h
|
||||||
|
RuntimeStateJson.h
|
||||||
|
|
||||||
platform/
|
platform/
|
||||||
ComInit.cpp
|
ComInit.cpp
|
||||||
@@ -139,8 +146,16 @@ apps/RenderCadenceCompositor/
|
|||||||
telemetry/
|
telemetry/
|
||||||
CadenceTelemetry.cpp
|
CadenceTelemetry.cpp
|
||||||
CadenceTelemetry.h
|
CadenceTelemetry.h
|
||||||
TelemetryPrinter.cpp
|
CadenceTelemetryJson.h
|
||||||
TelemetryPrinter.h
|
TelemetryHealthMonitor.h
|
||||||
|
|
||||||
|
logging/
|
||||||
|
Logger.cpp
|
||||||
|
Logger.h
|
||||||
|
|
||||||
|
json/
|
||||||
|
JsonWriter.cpp
|
||||||
|
JsonWriter.h
|
||||||
```
|
```
|
||||||
|
|
||||||
The new app can reuse selected existing source files from the current app at first:
|
The new app can reuse selected existing source files from the current app at first:
|
||||||
@@ -355,15 +370,17 @@ Initial counters:
|
|||||||
- free/rendering/completed/scheduled slot counts
|
- free/rendering/completed/scheduled slot counts
|
||||||
- actual DeckLink buffered frames
|
- actual DeckLink buffered frames
|
||||||
|
|
||||||
### `TelemetryPrinter`
|
### `TelemetryHealthMonitor`
|
||||||
|
|
||||||
Prints one stable line per interval, matching the probe where possible.
|
Samples cadence telemetry once per interval and logs only health events.
|
||||||
|
|
||||||
Example:
|
Normal telemetry is available through the HTTP state endpoint. The console should not receive a healthy once-per-second cadence line.
|
||||||
|
|
||||||
```text
|
Health events:
|
||||||
renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 drops=0 pboMiss=0 completions=119 late=0 dropped=0 decklinkBuffered=4
|
|
||||||
```
|
- warning when DeckLink late/dropped-frame counters increase
|
||||||
|
- warning when schedule failures increase
|
||||||
|
- error when app/DeckLink output buffering is starved
|
||||||
|
|
||||||
## Startup Sequence
|
## Startup Sequence
|
||||||
|
|
||||||
@@ -371,24 +388,28 @@ Target first-version startup:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
main
|
main
|
||||||
-> parse AppConfig
|
-> load AppConfig through AppConfigProvider
|
||||||
-> initialize COM
|
-> initialize COM
|
||||||
-> DeckLinkOutput discover/select/configure output
|
|
||||||
-> DeckLinkOutput prepare output schedule
|
|
||||||
-> create SystemFrameExchange
|
-> create SystemFrameExchange
|
||||||
-> start RenderThread
|
-> start RenderThread
|
||||||
-> wait for completed frame warmup
|
-> wait for completed frame warmup
|
||||||
-> start DeckLinkOutputThread
|
-> optionally discover/select/configure DeckLink output
|
||||||
-> wait for scheduled depth warmup
|
-> if DeckLink is available:
|
||||||
-> DeckLinkOutput start scheduled playback
|
-> start DeckLinkOutputThread
|
||||||
-> start TelemetryPrinter
|
-> wait for scheduled depth warmup
|
||||||
|
-> DeckLinkOutput start scheduled playback
|
||||||
|
-> if DeckLink is unavailable:
|
||||||
|
-> continue without video output
|
||||||
|
-> start TelemetryHealthMonitor
|
||||||
|
-> start HttpControlServer
|
||||||
-> wait for Enter
|
-> wait for Enter
|
||||||
```
|
```
|
||||||
|
|
||||||
Shutdown:
|
Shutdown:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
stop TelemetryPrinter
|
stop HttpControlServer
|
||||||
|
stop TelemetryHealthMonitor
|
||||||
stop DeckLinkOutputThread
|
stop DeckLinkOutputThread
|
||||||
DeckLinkOutput stop playback
|
DeckLinkOutput stop playback
|
||||||
stop RenderThread
|
stop RenderThread
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Not allowed on the render thread:
|
|||||||
- network/API/OSC handling
|
- network/API/OSC handling
|
||||||
- DeckLink scheduling
|
- DeckLink scheduling
|
||||||
- blocking console logging
|
- blocking console logging
|
||||||
|
- config file discovery or parsing
|
||||||
|
|
||||||
If future GL preparation needs to happen off-thread, use an explicit shared-context GL prepare thread. Do not smuggle non-render work back into the cadence loop.
|
If future GL preparation needs to happen off-thread, use an explicit shared-context GL prepare thread. Do not smuggle non-render work back into the cadence loop.
|
||||||
|
|
||||||
@@ -127,6 +128,10 @@ Preferred boundaries:
|
|||||||
- system-memory frame exchange
|
- system-memory frame exchange
|
||||||
- DeckLink output scheduling
|
- DeckLink output scheduling
|
||||||
- telemetry
|
- telemetry
|
||||||
|
- local control/API edge
|
||||||
|
- config loading
|
||||||
|
- JSON presentation/serialization
|
||||||
|
- logging
|
||||||
|
|
||||||
If a file starts coordinating multiple subsystems and doing detailed work for each of them, split it before it becomes the new old app.
|
If a file starts coordinating multiple subsystems and doing detailed work for each of them, split it before it becomes the new old app.
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ void TestHelpers()
|
|||||||
|
|
||||||
const double duration = FrameDurationMillisecondsFromRateString("50");
|
const double duration = FrameDurationMillisecondsFromRateString("50");
|
||||||
Expect(duration > 19.9 && duration < 20.1, "frame duration parses numeric rate");
|
Expect(duration > 19.9 && duration < 20.1, "frame duration parses numeric rate");
|
||||||
|
|
||||||
|
const std::filesystem::path configPath = FindConfigFile();
|
||||||
|
Expect(!configPath.empty(), "default config is discoverable from test working directory");
|
||||||
|
Expect(configPath.filename() == "runtime-host.json", "default config discovery returns runtime-host.json");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user