Health
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m35s
CI / Windows Release Package (push) Successful in 2m46s

This commit is contained in:
Aiden
2026-05-11 00:08:12 +10:00
parent a24cdc0630
commit 34c145e80b
6 changed files with 244 additions and 200 deletions

View File

@@ -202,7 +202,7 @@ void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage)
if (!statusMessage.empty()) if (!statusMessage.empty())
mVideoBackend->SetStatusMessage(statusMessage); mVideoBackend->SetStatusMessage(statusMessage);
mRuntimeHost->SetVideoIOStatus( mRuntimeHost->GetHealthTelemetry().ReportVideoIOStatus(
"decklink", "decklink",
mVideoBackend->OutputModelName(), mVideoBackend->OutputModelName(),
mVideoBackend->SupportsInternalKeying(), mVideoBackend->SupportsInternalKeying(),

View File

@@ -1,43 +1,141 @@
#include "stdafx.h" #include "stdafx.h"
#include "HealthTelemetry.h" #include "HealthTelemetry.h"
#include "RuntimeHost.h"
HealthTelemetry::HealthTelemetry(RuntimeHost& runtimeHost) :
mRuntimeHost(runtimeHost)
{
}
void HealthTelemetry::ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) void HealthTelemetry::ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{ {
mRuntimeHost.WriteSignalStatus(hasSignal, width, height, modeName); std::lock_guard<std::mutex> lock(mMutex);
mSignalStatus.hasSignal = hasSignal;
mSignalStatus.width = width;
mSignalStatus.height = height;
mSignalStatus.modeName = modeName;
} }
bool HealthTelemetry::TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) bool HealthTelemetry::TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{ {
return mRuntimeHost.TryWriteSignalStatus(hasSignal, width, height, modeName); std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false;
mSignalStatus.hasSignal = hasSignal;
mSignalStatus.width = width;
mSignalStatus.height = height;
mSignalStatus.modeName = modeName;
return true;
}
void HealthTelemetry::ReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
{
std::lock_guard<std::mutex> lock(mMutex);
mVideoIOStatus.backendName = backendName;
mVideoIOStatus.modelName = modelName;
mVideoIOStatus.supportsInternalKeying = supportsInternalKeying;
mVideoIOStatus.supportsExternalKeying = supportsExternalKeying;
mVideoIOStatus.keyerInterfaceAvailable = keyerInterfaceAvailable;
mVideoIOStatus.externalKeyingRequested = externalKeyingRequested;
mVideoIOStatus.externalKeyingActive = externalKeyingActive;
mVideoIOStatus.statusMessage = statusMessage;
}
bool HealthTelemetry::TryReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
{
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false;
mVideoIOStatus.backendName = backendName;
mVideoIOStatus.modelName = modelName;
mVideoIOStatus.supportsInternalKeying = supportsInternalKeying;
mVideoIOStatus.supportsExternalKeying = supportsExternalKeying;
mVideoIOStatus.keyerInterfaceAvailable = keyerInterfaceAvailable;
mVideoIOStatus.externalKeyingRequested = externalKeyingRequested;
mVideoIOStatus.externalKeyingActive = externalKeyingActive;
mVideoIOStatus.statusMessage = statusMessage;
return true;
} }
void HealthTelemetry::RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds) void HealthTelemetry::RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{ {
mRuntimeHost.WritePerformanceStats(frameBudgetMilliseconds, renderMilliseconds); std::lock_guard<std::mutex> lock(mMutex);
mPerformance.frameBudgetMilliseconds = std::max(frameBudgetMilliseconds, 0.0);
mPerformance.renderMilliseconds = std::max(renderMilliseconds, 0.0);
if (mPerformance.smoothedRenderMilliseconds <= 0.0)
mPerformance.smoothedRenderMilliseconds = mPerformance.renderMilliseconds;
else
mPerformance.smoothedRenderMilliseconds = mPerformance.smoothedRenderMilliseconds * 0.9 + mPerformance.renderMilliseconds * 0.1;
} }
bool HealthTelemetry::TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds) bool HealthTelemetry::TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{ {
return mRuntimeHost.TryWritePerformanceStats(frameBudgetMilliseconds, renderMilliseconds); std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false;
mPerformance.frameBudgetMilliseconds = std::max(frameBudgetMilliseconds, 0.0);
mPerformance.renderMilliseconds = std::max(renderMilliseconds, 0.0);
if (mPerformance.smoothedRenderMilliseconds <= 0.0)
mPerformance.smoothedRenderMilliseconds = mPerformance.renderMilliseconds;
else
mPerformance.smoothedRenderMilliseconds = mPerformance.smoothedRenderMilliseconds * 0.9 + mPerformance.renderMilliseconds * 0.1;
return true;
} }
void HealthTelemetry::RecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, void HealthTelemetry::RecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount) double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{ {
mRuntimeHost.WriteFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds, std::lock_guard<std::mutex> lock(mMutex);
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount); mPerformance.completionIntervalMilliseconds = std::max(completionIntervalMilliseconds, 0.0);
mPerformance.smoothedCompletionIntervalMilliseconds = std::max(smoothedCompletionIntervalMilliseconds, 0.0);
mPerformance.maxCompletionIntervalMilliseconds = std::max(maxCompletionIntervalMilliseconds, 0.0);
mPerformance.lateFrameCount = lateFrameCount;
mPerformance.droppedFrameCount = droppedFrameCount;
mPerformance.flushedFrameCount = flushedFrameCount;
} }
bool HealthTelemetry::TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, bool HealthTelemetry::TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount) double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{ {
return mRuntimeHost.TryWriteFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds, std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount); if (!lock.owns_lock())
return false;
mPerformance.completionIntervalMilliseconds = std::max(completionIntervalMilliseconds, 0.0);
mPerformance.smoothedCompletionIntervalMilliseconds = std::max(smoothedCompletionIntervalMilliseconds, 0.0);
mPerformance.maxCompletionIntervalMilliseconds = std::max(maxCompletionIntervalMilliseconds, 0.0);
mPerformance.lateFrameCount = lateFrameCount;
mPerformance.droppedFrameCount = droppedFrameCount;
mPerformance.flushedFrameCount = flushedFrameCount;
return true;
}
HealthTelemetry::SignalStatusSnapshot HealthTelemetry::GetSignalStatusSnapshot() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mSignalStatus;
}
HealthTelemetry::VideoIOStatusSnapshot HealthTelemetry::GetVideoIOStatusSnapshot() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mVideoIOStatus;
}
HealthTelemetry::PerformanceSnapshot HealthTelemetry::GetPerformanceSnapshot() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mPerformance;
}
HealthTelemetry::Snapshot HealthTelemetry::GetSnapshot() const
{
std::lock_guard<std::mutex> lock(mMutex);
Snapshot snapshot;
snapshot.signal = mSignalStatus;
snapshot.videoIO = mVideoIOStatus;
snapshot.performance = mPerformance;
return snapshot;
} }

View File

@@ -1,21 +1,67 @@
#pragma once #pragma once
#include <mutex>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
class RuntimeHost; // Phase 1 compatibility seam for status and timing reporting. HealthTelemetry
// now owns the current operational status snapshot directly, so callers can
// Phase 1 compatibility seam for status and timing reporting. The current // target it without flowing through RuntimeHost-owned backing fields.
// implementation still writes through RuntimeHost, but callers can now target
// HealthTelemetry as the home for operational visibility work.
class HealthTelemetry class HealthTelemetry
{ {
public: public:
explicit HealthTelemetry(RuntimeHost& runtimeHost); struct SignalStatusSnapshot
{
bool hasSignal = false;
unsigned width = 0;
unsigned height = 0;
std::string modeName;
};
struct VideoIOStatusSnapshot
{
std::string backendName = "decklink";
std::string modelName;
bool supportsInternalKeying = false;
bool supportsExternalKeying = false;
bool keyerInterfaceAvailable = false;
bool externalKeyingRequested = false;
bool externalKeyingActive = false;
std::string statusMessage;
};
struct PerformanceSnapshot
{
double frameBudgetMilliseconds = 0.0;
double renderMilliseconds = 0.0;
double smoothedRenderMilliseconds = 0.0;
double completionIntervalMilliseconds = 0.0;
double smoothedCompletionIntervalMilliseconds = 0.0;
double maxCompletionIntervalMilliseconds = 0.0;
uint64_t lateFrameCount = 0;
uint64_t droppedFrameCount = 0;
uint64_t flushedFrameCount = 0;
};
struct Snapshot
{
SignalStatusSnapshot signal;
VideoIOStatusSnapshot videoIO;
PerformanceSnapshot performance;
};
HealthTelemetry() = default;
void ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); void ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
bool TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); bool TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void ReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
bool TryReportVideoIOStatus(const std::string& backendName, const std::string& modelName,
bool supportsInternalKeying, bool supportsExternalKeying, bool keyerInterfaceAvailable,
bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
void RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds); void RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
bool TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds); bool TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
@@ -24,6 +70,14 @@ public:
bool TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, bool TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount); double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
SignalStatusSnapshot GetSignalStatusSnapshot() const;
VideoIOStatusSnapshot GetVideoIOStatusSnapshot() const;
PerformanceSnapshot GetPerformanceSnapshot() const;
Snapshot GetSnapshot() const;
private: private:
RuntimeHost& mRuntimeHost; mutable std::mutex mMutex;
SignalStatusSnapshot mSignalStatus;
VideoIOStatusSnapshot mVideoIOStatus;
PerformanceSnapshot mPerformance;
}; };

View File

@@ -699,22 +699,10 @@ bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& sha
} }
RuntimeHost::RuntimeHost() RuntimeHost::RuntimeHost()
: mHealthTelemetry(*this), : mHealthTelemetry(),
mReloadRequested(false), mReloadRequested(false),
mCompileSucceeded(false), mCompileSucceeded(false),
mHasSignal(false),
mSignalWidth(0),
mSignalHeight(0),
mFrameBudgetMilliseconds(0.0),
mRenderMilliseconds(0.0),
mSmoothedRenderMilliseconds(0.0),
mCompletionIntervalMilliseconds(0.0),
mSmoothedCompletionIntervalMilliseconds(0.0),
mMaxCompletionIntervalMilliseconds(0.0),
mStartupRandom(GenerateStartupRandom()), mStartupRandom(GenerateStartupRandom()),
mLateFrameCount(0),
mDroppedFrameCount(0),
mFlushedFrameCount(0),
mServerPort(8080), mServerPort(8080),
mAutoReloadEnabled(true), mAutoReloadEnabled(true),
mStartTime(std::chrono::steady_clock::now()), mStartTime(std::chrono::steady_clock::now()),
@@ -1353,42 +1341,35 @@ void RuntimeHost::SetCompileStatus(bool succeeded, const std::string& message)
void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{ {
const HealthTelemetry::SignalStatusSnapshot previousStatus = mHealthTelemetry.GetSignalStatusSnapshot();
mHealthTelemetry.ReportSignalStatus(hasSignal, width, height, modeName); mHealthTelemetry.ReportSignalStatus(hasSignal, width, height, modeName);
if (previousStatus.hasSignal != hasSignal ||
previousStatus.width != width ||
previousStatus.height != height ||
previousStatus.modeName != modeName)
{
std::lock_guard<std::mutex> lock(mMutex);
MarkRenderStateDirtyLocked();
}
} }
bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{ {
return mHealthTelemetry.TryReportSignalStatus(hasSignal, width, height, modeName); const HealthTelemetry::SignalStatusSnapshot previousStatus = mHealthTelemetry.GetSignalStatusSnapshot();
} if (!mHealthTelemetry.TryReportSignalStatus(hasSignal, width, height, modeName))
void RuntimeHost::WriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
std::lock_guard<std::mutex> lock(mMutex);
SetSignalStatusLocked(hasSignal, width, height, modeName);
}
bool RuntimeHost::TryWriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false; return false;
SetSignalStatusLocked(hasSignal, width, height, modeName); if (previousStatus.hasSignal != hasSignal ||
return true; previousStatus.width != width ||
} previousStatus.height != height ||
previousStatus.modeName != modeName)
void RuntimeHost::SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) {
{ std::lock_guard<std::mutex> lock(mMutex);
const bool changed = mHasSignal != hasSignal ||
mSignalWidth != width ||
mSignalHeight != height ||
mSignalModeName != modeName;
mHasSignal = hasSignal;
mSignalWidth = width;
mSignalHeight = height;
mSignalModeName = modeName;
if (changed)
MarkRenderStateDirtyLocked(); MarkRenderStateDirtyLocked();
}
return true;
} }
void RuntimeHost::MarkRenderStateDirtyLocked() void RuntimeHost::MarkRenderStateDirtyLocked()
@@ -1412,15 +1393,15 @@ void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool sup
void RuntimeHost::SetVideoIOStatus(const std::string& backendName, const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying, void RuntimeHost::SetVideoIOStatus(const std::string& backendName, const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage) bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
{ {
std::lock_guard<std::mutex> lock(mMutex); mHealthTelemetry.ReportVideoIOStatus(
mDeckLinkOutputStatus.backendName = backendName; backendName,
mDeckLinkOutputStatus.modelName = modelName; modelName,
mDeckLinkOutputStatus.supportsInternalKeying = supportsInternalKeying; supportsInternalKeying,
mDeckLinkOutputStatus.supportsExternalKeying = supportsExternalKeying; supportsExternalKeying,
mDeckLinkOutputStatus.keyerInterfaceAvailable = keyerInterfaceAvailable; keyerInterfaceAvailable,
mDeckLinkOutputStatus.externalKeyingRequested = externalKeyingRequested; externalKeyingRequested,
mDeckLinkOutputStatus.externalKeyingActive = externalKeyingActive; externalKeyingActive,
mDeckLinkOutputStatus.statusMessage = statusMessage; statusMessage);
} }
void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds) void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
@@ -1433,32 +1414,6 @@ bool RuntimeHost::TrySetPerformanceStats(double frameBudgetMilliseconds, double
return mHealthTelemetry.TryRecordPerformanceStats(frameBudgetMilliseconds, renderMilliseconds); return mHealthTelemetry.TryRecordPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
} }
void RuntimeHost::WritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
std::lock_guard<std::mutex> lock(mMutex);
SetPerformanceStatsLocked(frameBudgetMilliseconds, renderMilliseconds);
}
bool RuntimeHost::TryWritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false;
SetPerformanceStatsLocked(frameBudgetMilliseconds, renderMilliseconds);
return true;
}
void RuntimeHost::SetPerformanceStatsLocked(double frameBudgetMilliseconds, double renderMilliseconds)
{
mFrameBudgetMilliseconds = std::max(frameBudgetMilliseconds, 0.0);
mRenderMilliseconds = std::max(renderMilliseconds, 0.0);
if (mSmoothedRenderMilliseconds <= 0.0)
mSmoothedRenderMilliseconds = mRenderMilliseconds;
else
mSmoothedRenderMilliseconds = mSmoothedRenderMilliseconds * 0.9 + mRenderMilliseconds * 0.1;
}
void RuntimeHost::SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, void RuntimeHost::SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount) double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{ {
@@ -1473,37 +1428,6 @@ bool RuntimeHost::TrySetFramePacingStats(double completionIntervalMilliseconds,
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount); maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
} }
void RuntimeHost::WriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
std::lock_guard<std::mutex> lock(mMutex);
SetFramePacingStatsLocked(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
}
bool RuntimeHost::TryWriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false;
SetFramePacingStatsLocked(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
return true;
}
void RuntimeHost::SetFramePacingStatsLocked(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
mCompletionIntervalMilliseconds = std::max(completionIntervalMilliseconds, 0.0);
mSmoothedCompletionIntervalMilliseconds = std::max(smoothedCompletionIntervalMilliseconds, 0.0);
mMaxCompletionIntervalMilliseconds = std::max(maxCompletionIntervalMilliseconds, 0.0);
mLateFrameCount = lateFrameCount;
mDroppedFrameCount = droppedFrameCount;
mFlushedFrameCount = flushedFrameCount;
}
void RuntimeHost::AdvanceFrame() void RuntimeHost::AdvanceFrame()
{ {
++mFrameCounter; ++mFrameCounter;
@@ -1632,6 +1556,8 @@ void RuntimeHost::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState
void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{ {
const HealthTelemetry::SignalStatusSnapshot signalStatus = mHealthTelemetry.GetSignalStatusSnapshot();
for (const LayerPersistentState& layer : mPersistentState.layers) for (const LayerPersistentState& layer : mPersistentState.layers)
{ {
auto shaderIt = mPackagesById.find(layer.shaderId); auto shaderIt = mPackagesById.find(layer.shaderId);
@@ -1644,8 +1570,8 @@ void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned ou
state.shaderName = shaderIt->second.displayName; state.shaderName = shaderIt->second.displayName;
state.mixAmount = 1.0; state.mixAmount = 1.0;
state.bypass = layer.bypass ? 1.0 : 0.0; state.bypass = layer.bypass ? 1.0 : 0.0;
state.inputWidth = mSignalWidth; state.inputWidth = signalStatus.width;
state.inputHeight = mSignalHeight; state.inputHeight = signalStatus.height;
state.outputWidth = outputWidth; state.outputWidth = outputWidth;
state.outputHeight = outputHeight; state.outputHeight = outputHeight;
state.parameterDefinitions = shaderIt->second.parameters; state.parameterDefinitions = shaderIt->second.parameters;
@@ -2095,6 +2021,7 @@ bool RuntimeHost::ResolvePaths(std::string& error)
JsonValue RuntimeHost::BuildStateValue() const JsonValue RuntimeHost::BuildStateValue() const
{ {
const HealthTelemetry::Snapshot telemetrySnapshot = mHealthTelemetry.GetSnapshot();
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);
JsonValue root = JsonValue::MakeObject(); JsonValue root = JsonValue::MakeObject();
@@ -2121,44 +2048,47 @@ JsonValue RuntimeHost::BuildStateValue() const
root.set("runtime", runtime); root.set("runtime", runtime);
JsonValue video = JsonValue::MakeObject(); JsonValue video = JsonValue::MakeObject();
video.set("hasSignal", JsonValue(mHasSignal)); video.set("hasSignal", JsonValue(telemetrySnapshot.signal.hasSignal));
video.set("width", JsonValue(static_cast<double>(mSignalWidth))); video.set("width", JsonValue(static_cast<double>(telemetrySnapshot.signal.width)));
video.set("height", JsonValue(static_cast<double>(mSignalHeight))); video.set("height", JsonValue(static_cast<double>(telemetrySnapshot.signal.height)));
video.set("modeName", JsonValue(mSignalModeName)); video.set("modeName", JsonValue(telemetrySnapshot.signal.modeName));
root.set("video", video); root.set("video", video);
JsonValue deckLink = JsonValue::MakeObject(); JsonValue deckLink = JsonValue::MakeObject();
deckLink.set("modelName", JsonValue(mDeckLinkOutputStatus.modelName)); deckLink.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
deckLink.set("supportsInternalKeying", JsonValue(mDeckLinkOutputStatus.supportsInternalKeying)); deckLink.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
deckLink.set("supportsExternalKeying", JsonValue(mDeckLinkOutputStatus.supportsExternalKeying)); deckLink.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
deckLink.set("keyerInterfaceAvailable", JsonValue(mDeckLinkOutputStatus.keyerInterfaceAvailable)); deckLink.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
deckLink.set("externalKeyingRequested", JsonValue(mDeckLinkOutputStatus.externalKeyingRequested)); deckLink.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
deckLink.set("externalKeyingActive", JsonValue(mDeckLinkOutputStatus.externalKeyingActive)); deckLink.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
deckLink.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage)); deckLink.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
root.set("decklink", deckLink); root.set("decklink", deckLink);
JsonValue videoIO = JsonValue::MakeObject(); JsonValue videoIO = JsonValue::MakeObject();
videoIO.set("backend", JsonValue(mDeckLinkOutputStatus.backendName)); videoIO.set("backend", JsonValue(telemetrySnapshot.videoIO.backendName));
videoIO.set("modelName", JsonValue(mDeckLinkOutputStatus.modelName)); videoIO.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
videoIO.set("supportsInternalKeying", JsonValue(mDeckLinkOutputStatus.supportsInternalKeying)); videoIO.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
videoIO.set("supportsExternalKeying", JsonValue(mDeckLinkOutputStatus.supportsExternalKeying)); videoIO.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
videoIO.set("keyerInterfaceAvailable", JsonValue(mDeckLinkOutputStatus.keyerInterfaceAvailable)); videoIO.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
videoIO.set("externalKeyingRequested", JsonValue(mDeckLinkOutputStatus.externalKeyingRequested)); videoIO.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
videoIO.set("externalKeyingActive", JsonValue(mDeckLinkOutputStatus.externalKeyingActive)); videoIO.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
videoIO.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage)); videoIO.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
root.set("videoIO", videoIO); root.set("videoIO", videoIO);
JsonValue performance = JsonValue::MakeObject(); JsonValue performance = JsonValue::MakeObject();
performance.set("frameBudgetMs", JsonValue(mFrameBudgetMilliseconds)); performance.set("frameBudgetMs", JsonValue(telemetrySnapshot.performance.frameBudgetMilliseconds));
performance.set("renderMs", JsonValue(mRenderMilliseconds)); performance.set("renderMs", JsonValue(telemetrySnapshot.performance.renderMilliseconds));
performance.set("smoothedRenderMs", JsonValue(mSmoothedRenderMilliseconds)); performance.set("smoothedRenderMs", JsonValue(telemetrySnapshot.performance.smoothedRenderMilliseconds));
performance.set("budgetUsedPercent", JsonValue(mFrameBudgetMilliseconds > 0.0 ? (mSmoothedRenderMilliseconds / mFrameBudgetMilliseconds) * 100.0 : 0.0)); performance.set("budgetUsedPercent", JsonValue(
performance.set("completionIntervalMs", JsonValue(mCompletionIntervalMilliseconds)); telemetrySnapshot.performance.frameBudgetMilliseconds > 0.0
performance.set("smoothedCompletionIntervalMs", JsonValue(mSmoothedCompletionIntervalMilliseconds)); ? (telemetrySnapshot.performance.smoothedRenderMilliseconds / telemetrySnapshot.performance.frameBudgetMilliseconds) * 100.0
performance.set("maxCompletionIntervalMs", JsonValue(mMaxCompletionIntervalMilliseconds)); : 0.0));
performance.set("lateFrameCount", JsonValue(static_cast<double>(mLateFrameCount))); performance.set("completionIntervalMs", JsonValue(telemetrySnapshot.performance.completionIntervalMilliseconds));
performance.set("droppedFrameCount", JsonValue(static_cast<double>(mDroppedFrameCount))); performance.set("smoothedCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.smoothedCompletionIntervalMilliseconds));
performance.set("flushedFrameCount", JsonValue(static_cast<double>(mFlushedFrameCount))); performance.set("maxCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.maxCompletionIntervalMilliseconds));
performance.set("lateFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.lateFrameCount)));
performance.set("droppedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.droppedFrameCount)));
performance.set("flushedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.flushedFrameCount)));
root.set("performance", performance); root.set("performance", performance);
JsonValue shaderLibrary = JsonValue::MakeArray(); JsonValue shaderLibrary = JsonValue::MakeArray();

View File

@@ -101,18 +101,6 @@ private:
std::string outputFrameRate = "59.94"; std::string outputFrameRate = "59.94";
}; };
struct DeckLinkOutputStatus
{
std::string backendName = "decklink";
std::string modelName;
bool supportsInternalKeying = false;
bool supportsExternalKeying = false;
bool keyerInterfaceAvailable = false;
bool externalKeyingRequested = false;
bool externalKeyingActive = false;
std::string statusMessage;
};
struct LayerPersistentState struct LayerPersistentState
{ {
std::string id; std::string id;
@@ -148,23 +136,10 @@ private:
LayerPersistentState* FindLayerById(const std::string& layerId); LayerPersistentState* FindLayerById(const std::string& layerId);
const LayerPersistentState* FindLayerById(const std::string& layerId) const; const LayerPersistentState* FindLayerById(const std::string& layerId) const;
std::string GenerateLayerId(); std::string GenerateLayerId();
void WriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
bool TryWriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void MarkRenderStateDirtyLocked(); void MarkRenderStateDirtyLocked();
void MarkParameterStateDirtyLocked(); void MarkParameterStateDirtyLocked();
void WritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
bool TryWritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
void SetPerformanceStatsLocked(double frameBudgetMilliseconds, double renderMilliseconds);
void WriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
bool TryWriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
void SetFramePacingStatsLocked(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
private: private:
friend class HealthTelemetry;
HealthTelemetry mHealthTelemetry; HealthTelemetry mHealthTelemetry;
mutable std::mutex mMutex; mutable std::mutex mMutex;
AppConfig mConfig; AppConfig mConfig;
@@ -186,21 +161,7 @@ private:
bool mReloadRequested; bool mReloadRequested;
bool mCompileSucceeded; bool mCompileSucceeded;
std::string mCompileMessage; std::string mCompileMessage;
bool mHasSignal;
unsigned mSignalWidth;
unsigned mSignalHeight;
std::string mSignalModeName;
double mFrameBudgetMilliseconds;
double mRenderMilliseconds;
double mSmoothedRenderMilliseconds;
double mCompletionIntervalMilliseconds;
double mSmoothedCompletionIntervalMilliseconds;
double mMaxCompletionIntervalMilliseconds;
double mStartupRandom; double mStartupRandom;
uint64_t mLateFrameCount;
uint64_t mDroppedFrameCount;
uint64_t mFlushedFrameCount;
DeckLinkOutputStatus mDeckLinkOutputStatus;
unsigned short mServerPort; unsigned short mServerPort;
bool mAutoReloadEnabled; bool mAutoReloadEnabled;
std::chrono::steady_clock::time_point mStartTime; std::chrono::steady_clock::time_point mStartTime;

View File

@@ -119,6 +119,7 @@ These are still compatibility seams, not a completed subsystem extraction. Most
- render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider` - render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider`
- service ingress and polling coordination now route through `ControlServices` - service ingress and polling coordination now route through `ControlServices`
- timing and status writes now route through `HealthTelemetry` - timing and status writes now route through `HealthTelemetry`
- `HealthTelemetry` now owns the live signal, video-I/O, and performance snapshots directly instead of `RuntimeHost` keeping those backing fields
- render-side frame advancement and render-performance reporting now flow through `RuntimeSnapshotProvider` and `HealthTelemetry` instead of directly through `RuntimeHost` - render-side frame advancement and render-performance reporting now flow through `RuntimeSnapshotProvider` and `HealthTelemetry` instead of directly through `RuntimeHost`
- live OSC overlay state and smoothing/commit decisions now live under `RenderEngine` instead of `OpenGLComposite` - live OSC overlay state and smoothing/commit decisions now live under `RenderEngine` instead of `OpenGLComposite`
- `OpenGLComposite` now owns a `RenderEngine` seam for renderer, pipeline, render-pass, and shader-program responsibilities - `OpenGLComposite` now owns a `RenderEngine` seam for renderer, pipeline, render-pass, and shader-program responsibilities