diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index 5e8c023..ce5ea6a 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -202,7 +202,7 @@ void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage) if (!statusMessage.empty()) mVideoBackend->SetStatusMessage(statusMessage); - mRuntimeHost->SetVideoIOStatus( + mRuntimeHost->GetHealthTelemetry().ReportVideoIOStatus( "decklink", mVideoBackend->OutputModelName(), mVideoBackend->SupportsInternalKeying(), diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/HealthTelemetry.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/HealthTelemetry.cpp index 0d41d2b..77cbc85 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/HealthTelemetry.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/HealthTelemetry.cpp @@ -1,43 +1,141 @@ #include "stdafx.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) { - mRuntimeHost.WriteSignalStatus(hasSignal, width, height, modeName); + std::lock_guard 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) { - return mRuntimeHost.TryWriteSignalStatus(hasSignal, width, height, modeName); + std::unique_lock 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 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 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) { - mRuntimeHost.WritePerformanceStats(frameBudgetMilliseconds, renderMilliseconds); + std::lock_guard 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) { - return mRuntimeHost.TryWritePerformanceStats(frameBudgetMilliseconds, renderMilliseconds); + std::unique_lock 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, double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount) { - mRuntimeHost.WriteFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds, - maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount); + std::lock_guard lock(mMutex); + 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, double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount) { - return mRuntimeHost.TryWriteFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds, - maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount); + std::unique_lock lock(mMutex, std::try_to_lock); + 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 lock(mMutex); + return mSignalStatus; +} + +HealthTelemetry::VideoIOStatusSnapshot HealthTelemetry::GetVideoIOStatusSnapshot() const +{ + std::lock_guard lock(mMutex); + return mVideoIOStatus; +} + +HealthTelemetry::PerformanceSnapshot HealthTelemetry::GetPerformanceSnapshot() const +{ + std::lock_guard lock(mMutex); + return mPerformance; +} + +HealthTelemetry::Snapshot HealthTelemetry::GetSnapshot() const +{ + std::lock_guard lock(mMutex); + + Snapshot snapshot; + snapshot.signal = mSignalStatus; + snapshot.videoIO = mVideoIOStatus; + snapshot.performance = mPerformance; + return snapshot; } diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/HealthTelemetry.h b/apps/LoopThroughWithOpenGLCompositing/runtime/HealthTelemetry.h index 1420697..325237a 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/HealthTelemetry.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/HealthTelemetry.h @@ -1,21 +1,67 @@ #pragma once +#include #include #include -class RuntimeHost; - -// Phase 1 compatibility seam for status and timing reporting. The current -// implementation still writes through RuntimeHost, but callers can now target -// HealthTelemetry as the home for operational visibility work. +// Phase 1 compatibility seam for status and timing reporting. HealthTelemetry +// now owns the current operational status snapshot directly, so callers can +// target it without flowing through RuntimeHost-owned backing fields. class HealthTelemetry { 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); 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); bool TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds); @@ -24,6 +70,14 @@ public: bool TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount); + SignalStatusSnapshot GetSignalStatusSnapshot() const; + VideoIOStatusSnapshot GetVideoIOStatusSnapshot() const; + PerformanceSnapshot GetPerformanceSnapshot() const; + Snapshot GetSnapshot() const; + private: - RuntimeHost& mRuntimeHost; + mutable std::mutex mMutex; + SignalStatusSnapshot mSignalStatus; + VideoIOStatusSnapshot mVideoIOStatus; + PerformanceSnapshot mPerformance; }; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp index 50d0383..4e7aa48 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp @@ -699,22 +699,10 @@ bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& sha } RuntimeHost::RuntimeHost() - : mHealthTelemetry(*this), + : mHealthTelemetry(), mReloadRequested(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()), - mLateFrameCount(0), - mDroppedFrameCount(0), - mFlushedFrameCount(0), mServerPort(8080), mAutoReloadEnabled(true), 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) { + const HealthTelemetry::SignalStatusSnapshot previousStatus = mHealthTelemetry.GetSignalStatusSnapshot(); mHealthTelemetry.ReportSignalStatus(hasSignal, width, height, modeName); + + if (previousStatus.hasSignal != hasSignal || + previousStatus.width != width || + previousStatus.height != height || + previousStatus.modeName != modeName) + { + std::lock_guard lock(mMutex); + MarkRenderStateDirtyLocked(); + } } bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) { - return mHealthTelemetry.TryReportSignalStatus(hasSignal, width, height, modeName); -} - -void RuntimeHost::WriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) -{ - std::lock_guard lock(mMutex); - SetSignalStatusLocked(hasSignal, width, height, modeName); -} - -bool RuntimeHost::TryWriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) -{ - std::unique_lock lock(mMutex, std::try_to_lock); - if (!lock.owns_lock()) + const HealthTelemetry::SignalStatusSnapshot previousStatus = mHealthTelemetry.GetSignalStatusSnapshot(); + if (!mHealthTelemetry.TryReportSignalStatus(hasSignal, width, height, modeName)) return false; - SetSignalStatusLocked(hasSignal, width, height, modeName); - return true; -} - -void RuntimeHost::SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) -{ - const bool changed = mHasSignal != hasSignal || - mSignalWidth != width || - mSignalHeight != height || - mSignalModeName != modeName; - mHasSignal = hasSignal; - mSignalWidth = width; - mSignalHeight = height; - mSignalModeName = modeName; - if (changed) + if (previousStatus.hasSignal != hasSignal || + previousStatus.width != width || + previousStatus.height != height || + previousStatus.modeName != modeName) + { + std::lock_guard lock(mMutex); MarkRenderStateDirtyLocked(); + } + + return true; } 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, bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage) { - std::lock_guard lock(mMutex); - mDeckLinkOutputStatus.backendName = backendName; - mDeckLinkOutputStatus.modelName = modelName; - mDeckLinkOutputStatus.supportsInternalKeying = supportsInternalKeying; - mDeckLinkOutputStatus.supportsExternalKeying = supportsExternalKeying; - mDeckLinkOutputStatus.keyerInterfaceAvailable = keyerInterfaceAvailable; - mDeckLinkOutputStatus.externalKeyingRequested = externalKeyingRequested; - mDeckLinkOutputStatus.externalKeyingActive = externalKeyingActive; - mDeckLinkOutputStatus.statusMessage = statusMessage; + mHealthTelemetry.ReportVideoIOStatus( + backendName, + modelName, + supportsInternalKeying, + supportsExternalKeying, + keyerInterfaceAvailable, + externalKeyingRequested, + externalKeyingActive, + statusMessage); } void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds) @@ -1433,32 +1414,6 @@ bool RuntimeHost::TrySetPerformanceStats(double frameBudgetMilliseconds, double return mHealthTelemetry.TryRecordPerformanceStats(frameBudgetMilliseconds, renderMilliseconds); } -void RuntimeHost::WritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds) -{ - std::lock_guard lock(mMutex); - SetPerformanceStatsLocked(frameBudgetMilliseconds, renderMilliseconds); -} - -bool RuntimeHost::TryWritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds) -{ - std::unique_lock 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, double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount) { @@ -1473,37 +1428,6 @@ bool RuntimeHost::TrySetFramePacingStats(double completionIntervalMilliseconds, 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 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 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() { ++mFrameCounter; @@ -1632,6 +1556,8 @@ void RuntimeHost::RefreshDynamicRenderStateFields(std::vector& states) const { + const HealthTelemetry::SignalStatusSnapshot signalStatus = mHealthTelemetry.GetSignalStatusSnapshot(); + for (const LayerPersistentState& layer : mPersistentState.layers) { auto shaderIt = mPackagesById.find(layer.shaderId); @@ -1644,8 +1570,8 @@ void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned ou state.shaderName = shaderIt->second.displayName; state.mixAmount = 1.0; state.bypass = layer.bypass ? 1.0 : 0.0; - state.inputWidth = mSignalWidth; - state.inputHeight = mSignalHeight; + state.inputWidth = signalStatus.width; + state.inputHeight = signalStatus.height; state.outputWidth = outputWidth; state.outputHeight = outputHeight; state.parameterDefinitions = shaderIt->second.parameters; @@ -2095,6 +2021,7 @@ bool RuntimeHost::ResolvePaths(std::string& error) JsonValue RuntimeHost::BuildStateValue() const { + const HealthTelemetry::Snapshot telemetrySnapshot = mHealthTelemetry.GetSnapshot(); std::lock_guard lock(mMutex); JsonValue root = JsonValue::MakeObject(); @@ -2121,44 +2048,47 @@ JsonValue RuntimeHost::BuildStateValue() const root.set("runtime", runtime); JsonValue video = JsonValue::MakeObject(); - video.set("hasSignal", JsonValue(mHasSignal)); - video.set("width", JsonValue(static_cast(mSignalWidth))); - video.set("height", JsonValue(static_cast(mSignalHeight))); - video.set("modeName", JsonValue(mSignalModeName)); + video.set("hasSignal", JsonValue(telemetrySnapshot.signal.hasSignal)); + video.set("width", JsonValue(static_cast(telemetrySnapshot.signal.width))); + video.set("height", JsonValue(static_cast(telemetrySnapshot.signal.height))); + video.set("modeName", JsonValue(telemetrySnapshot.signal.modeName)); root.set("video", video); JsonValue deckLink = JsonValue::MakeObject(); - deckLink.set("modelName", JsonValue(mDeckLinkOutputStatus.modelName)); - deckLink.set("supportsInternalKeying", JsonValue(mDeckLinkOutputStatus.supportsInternalKeying)); - deckLink.set("supportsExternalKeying", JsonValue(mDeckLinkOutputStatus.supportsExternalKeying)); - deckLink.set("keyerInterfaceAvailable", JsonValue(mDeckLinkOutputStatus.keyerInterfaceAvailable)); - deckLink.set("externalKeyingRequested", JsonValue(mDeckLinkOutputStatus.externalKeyingRequested)); - deckLink.set("externalKeyingActive", JsonValue(mDeckLinkOutputStatus.externalKeyingActive)); - deckLink.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage)); + deckLink.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName)); + deckLink.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying)); + deckLink.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying)); + deckLink.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable)); + deckLink.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested)); + deckLink.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive)); + deckLink.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage)); root.set("decklink", deckLink); JsonValue videoIO = JsonValue::MakeObject(); - videoIO.set("backend", JsonValue(mDeckLinkOutputStatus.backendName)); - videoIO.set("modelName", JsonValue(mDeckLinkOutputStatus.modelName)); - videoIO.set("supportsInternalKeying", JsonValue(mDeckLinkOutputStatus.supportsInternalKeying)); - videoIO.set("supportsExternalKeying", JsonValue(mDeckLinkOutputStatus.supportsExternalKeying)); - videoIO.set("keyerInterfaceAvailable", JsonValue(mDeckLinkOutputStatus.keyerInterfaceAvailable)); - videoIO.set("externalKeyingRequested", JsonValue(mDeckLinkOutputStatus.externalKeyingRequested)); - videoIO.set("externalKeyingActive", JsonValue(mDeckLinkOutputStatus.externalKeyingActive)); - videoIO.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage)); + videoIO.set("backend", JsonValue(telemetrySnapshot.videoIO.backendName)); + videoIO.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName)); + videoIO.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying)); + videoIO.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying)); + videoIO.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable)); + videoIO.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested)); + videoIO.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive)); + videoIO.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage)); root.set("videoIO", videoIO); JsonValue performance = JsonValue::MakeObject(); - performance.set("frameBudgetMs", JsonValue(mFrameBudgetMilliseconds)); - performance.set("renderMs", JsonValue(mRenderMilliseconds)); - performance.set("smoothedRenderMs", JsonValue(mSmoothedRenderMilliseconds)); - performance.set("budgetUsedPercent", JsonValue(mFrameBudgetMilliseconds > 0.0 ? (mSmoothedRenderMilliseconds / mFrameBudgetMilliseconds) * 100.0 : 0.0)); - performance.set("completionIntervalMs", JsonValue(mCompletionIntervalMilliseconds)); - performance.set("smoothedCompletionIntervalMs", JsonValue(mSmoothedCompletionIntervalMilliseconds)); - performance.set("maxCompletionIntervalMs", JsonValue(mMaxCompletionIntervalMilliseconds)); - performance.set("lateFrameCount", JsonValue(static_cast(mLateFrameCount))); - performance.set("droppedFrameCount", JsonValue(static_cast(mDroppedFrameCount))); - performance.set("flushedFrameCount", JsonValue(static_cast(mFlushedFrameCount))); + performance.set("frameBudgetMs", JsonValue(telemetrySnapshot.performance.frameBudgetMilliseconds)); + performance.set("renderMs", JsonValue(telemetrySnapshot.performance.renderMilliseconds)); + performance.set("smoothedRenderMs", JsonValue(telemetrySnapshot.performance.smoothedRenderMilliseconds)); + performance.set("budgetUsedPercent", JsonValue( + telemetrySnapshot.performance.frameBudgetMilliseconds > 0.0 + ? (telemetrySnapshot.performance.smoothedRenderMilliseconds / telemetrySnapshot.performance.frameBudgetMilliseconds) * 100.0 + : 0.0)); + performance.set("completionIntervalMs", JsonValue(telemetrySnapshot.performance.completionIntervalMilliseconds)); + performance.set("smoothedCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.smoothedCompletionIntervalMilliseconds)); + performance.set("maxCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.maxCompletionIntervalMilliseconds)); + performance.set("lateFrameCount", JsonValue(static_cast(telemetrySnapshot.performance.lateFrameCount))); + performance.set("droppedFrameCount", JsonValue(static_cast(telemetrySnapshot.performance.droppedFrameCount))); + performance.set("flushedFrameCount", JsonValue(static_cast(telemetrySnapshot.performance.flushedFrameCount))); root.set("performance", performance); JsonValue shaderLibrary = JsonValue::MakeArray(); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h index c1837b1..2223110 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h @@ -101,18 +101,6 @@ private: 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 { std::string id; @@ -148,23 +136,10 @@ private: LayerPersistentState* FindLayerById(const std::string& layerId); const LayerPersistentState* FindLayerById(const std::string& layerId) const; 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 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: - friend class HealthTelemetry; HealthTelemetry mHealthTelemetry; mutable std::mutex mMutex; AppConfig mConfig; @@ -186,21 +161,7 @@ private: bool mReloadRequested; bool mCompileSucceeded; 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; - uint64_t mLateFrameCount; - uint64_t mDroppedFrameCount; - uint64_t mFlushedFrameCount; - DeckLinkOutputStatus mDeckLinkOutputStatus; unsigned short mServerPort; bool mAutoReloadEnabled; std::chrono::steady_clock::time_point mStartTime; diff --git a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md index c0e0ca4..4400839 100644 --- a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md +++ b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md @@ -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` - service ingress and polling coordination now route through `ControlServices` - 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` - 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