diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp index 19bdebd..bf2e273 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp @@ -1,47 +1,3 @@ -/* -LICENSE-START- - ** Copyright (c) 2012 Blackmagic Design - ** - ** Permission is hereby granted, free of charge, to any person or organization - ** obtaining a copy of the software and accompanying documentation (the - ** "Software") to use, reproduce, display, distribute, sub-license, execute, - ** and transmit the Software, and to prepare derivative works of the Software, - ** and to permit third-parties to whom the Software is furnished to do so, in - ** accordance with: - ** - ** (1) if the Software is obtained from Blackmagic Design, the End User License - ** Agreement for the Software Development Kit ("EULA") available at - ** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or - ** - ** (2) if the Software is obtained from any third party, such licensing terms - ** as notified by that third party, - ** - ** and all subject to the following: - ** - ** (3) the copyright notices in the Software and this entire statement, - ** including the above license grant, this restriction and the following - ** disclaimer, must be included in all copies of the Software, in whole or in - ** part, and all derivative works of the Software, unless such copies or - ** derivative works are solely in the form of machine-executable object code - ** generated by a source language processor. - ** - ** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - ** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT - ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - ** DEALINGS IN THE SOFTWARE. - ** - ** A copy of the Software is available free of charge at - ** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA. - ** - ** -LICENSE-END- - */ -// -// LoopThroughWithOpenGLCompositing.cpp -// LoopThroughWithOpenGLCompositing -// - #include "stdafx.h" #include "resource.h" #include "OpenGLComposite.h" diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.h b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.h index 1a188c5..d00d47e 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.h +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.h @@ -1,47 +1,3 @@ -/* -LICENSE-START- - ** Copyright (c) 2012 Blackmagic Design - ** - ** Permission is hereby granted, free of charge, to any person or organization - ** obtaining a copy of the software and accompanying documentation (the - ** "Software") to use, reproduce, display, distribute, sub-license, execute, - ** and transmit the Software, and to prepare derivative works of the Software, - ** and to permit third-parties to whom the Software is furnished to do so, in - ** accordance with: - ** - ** (1) if the Software is obtained from Blackmagic Design, the End User License - ** Agreement for the Software Development Kit ("EULA") available at - ** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or - ** - ** (2) if the Software is obtained from any third party, such licensing terms - ** as notified by that third party, - ** - ** and all subject to the following: - ** - ** (3) the copyright notices in the Software and this entire statement, - ** including the above license grant, this restriction and the following - ** disclaimer, must be included in all copies of the Software, in whole or in - ** part, and all derivative works of the Software, unless such copies or - ** derivative works are solely in the form of machine-executable object code - ** generated by a source language processor. - ** - ** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - ** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT - ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - ** DEALINGS IN THE SOFTWARE. - ** - ** A copy of the Software is available free of charge at - ** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA. - ** - ** -LICENSE-END- - */ -// -// LoopThroughWithOpenGLCompositing.h -// LoopThroughWithOpenGLCompositing -// - #pragma once #include "resource.h" diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp index 4e7aa48..0259c5e 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp @@ -712,58 +712,6 @@ RuntimeHost::RuntimeHost() { } -bool RuntimeHost::Initialize(std::string& error) -{ - try - { - std::lock_guard lock(mMutex); - - if (!ResolvePaths(error)) - return false; - if (!LoadConfig(error)) - return false; - mShaderRoot = mRepoRoot / mConfig.shaderLibrary; - if (!LoadPersistentState(error)) - return false; - if (!ScanShaderPackages(error)) - return false; - NormalizePersistentLayerIdsLocked(); - - for (LayerPersistentState& layer : mPersistentState.layers) - { - auto shaderIt = mPackagesById.find(layer.shaderId); - if (shaderIt != mPackagesById.end()) - EnsureLayerDefaultsLocked(layer, shaderIt->second); - } - - if (mPersistentState.layers.empty() && !mPackageOrder.empty()) - { - LayerPersistentState layer; - layer.id = GenerateLayerId(); - layer.shaderId = mPackageOrder.front(); - layer.bypass = false; - EnsureLayerDefaultsLocked(layer, mPackagesById[layer.shaderId]); - mPersistentState.layers.push_back(layer); - } - - mServerPort = mConfig.serverPort; - mAutoReloadEnabled = mConfig.autoReload; - mReloadRequested = true; - mCompileMessage = "Waiting for shader compile."; - return true; - } - catch (const std::exception& exception) - { - error = std::string("RuntimeHost::Initialize exception: ") + exception.what(); - return false; - } - catch (...) - { - error = "RuntimeHost::Initialize threw a non-standard exception."; - return false; - } -} - bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error) { try @@ -1491,118 +1439,6 @@ bool RuntimeHost::BuildLayerPassFragmentShaderSources(const std::string& layerId } } -std::vector RuntimeHost::GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const -{ - std::lock_guard lock(mMutex); - std::vector states; - BuildLayerRenderStatesLocked(outputWidth, outputHeight, states); - return states; -} - -bool RuntimeHost::TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector& states) const -{ - std::unique_lock lock(mMutex, std::try_to_lock); - if (!lock.owns_lock()) - return false; - - states.clear(); - BuildLayerRenderStatesLocked(outputWidth, outputHeight, states); - return true; -} - -bool RuntimeHost::TryRefreshCachedLayerStates(std::vector& states) const -{ - std::unique_lock lock(mMutex, std::try_to_lock); - if (!lock.owns_lock()) - return false; - - for (RuntimeRenderState& state : states) - { - const auto layerIt = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(), - [&state](const LayerPersistentState& layer) { return layer.id == state.layerId; }); - if (layerIt == mPersistentState.layers.end()) - continue; - - state.bypass = layerIt->bypass ? 1.0 : 0.0; - state.parameterValues.clear(); - for (const ShaderParameterDefinition& definition : state.parameterDefinitions) - { - ShaderParameterValue value = DefaultValueForDefinition(definition); - auto valueIt = layerIt->parameterValues.find(definition.id); - if (valueIt != layerIt->parameterValues.end()) - value = valueIt->second; - state.parameterValues[definition.id] = value; - } - } - - return true; -} - -void RuntimeHost::RefreshDynamicRenderStateFields(std::vector& states) const -{ - const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot(); - const double timeSeconds = std::chrono::duration_cast>(std::chrono::steady_clock::now() - mStartTime).count(); - const double frameCount = static_cast(mFrameCounter.load(std::memory_order_relaxed)); - - for (RuntimeRenderState& state : states) - { - state.timeSeconds = timeSeconds; - state.utcTimeSeconds = clock.utcTimeSeconds; - state.utcOffsetSeconds = clock.utcOffsetSeconds; - state.startupRandom = mStartupRandom; - state.frameCount = frameCount; - } -} - -void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector& states) const -{ - const HealthTelemetry::SignalStatusSnapshot signalStatus = mHealthTelemetry.GetSignalStatusSnapshot(); - - for (const LayerPersistentState& layer : mPersistentState.layers) - { - auto shaderIt = mPackagesById.find(layer.shaderId); - if (shaderIt == mPackagesById.end()) - continue; - - RuntimeRenderState state; - state.layerId = layer.id; - state.shaderId = layer.shaderId; - state.shaderName = shaderIt->second.displayName; - state.mixAmount = 1.0; - state.bypass = layer.bypass ? 1.0 : 0.0; - state.inputWidth = signalStatus.width; - state.inputHeight = signalStatus.height; - state.outputWidth = outputWidth; - state.outputHeight = outputHeight; - state.parameterDefinitions = shaderIt->second.parameters; - state.textureAssets = shaderIt->second.textureAssets; - state.fontAssets = shaderIt->second.fontAssets; - state.isTemporal = shaderIt->second.temporal.enabled; - state.temporalHistorySource = shaderIt->second.temporal.historySource; - state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength; - state.effectiveTemporalHistoryLength = shaderIt->second.temporal.effectiveHistoryLength; - state.feedback = shaderIt->second.feedback; - - for (const ShaderParameterDefinition& definition : shaderIt->second.parameters) - { - ShaderParameterValue value = DefaultValueForDefinition(definition); - auto valueIt = layer.parameterValues.find(definition.id); - if (valueIt != layer.parameterValues.end()) - value = valueIt->second; - state.parameterValues[definition.id] = value; - } - - states.push_back(state); - } - - RefreshDynamicRenderStateFields(states); -} - -std::string RuntimeHost::BuildStateJson() const -{ - return SerializeJson(BuildStateValue(), true); -} - void RuntimeHost::SetServerPort(unsigned short port) { std::lock_guard lock(mMutex); @@ -2019,121 +1855,6 @@ bool RuntimeHost::ResolvePaths(std::string& error) return true; } -JsonValue RuntimeHost::BuildStateValue() const -{ - const HealthTelemetry::Snapshot telemetrySnapshot = mHealthTelemetry.GetSnapshot(); - std::lock_guard lock(mMutex); - - JsonValue root = JsonValue::MakeObject(); - - JsonValue app = JsonValue::MakeObject(); - app.set("serverPort", JsonValue(static_cast(mServerPort))); - app.set("oscPort", JsonValue(static_cast(mConfig.oscPort))); - app.set("oscBindAddress", JsonValue(mConfig.oscBindAddress)); - app.set("oscSmoothing", JsonValue(mConfig.oscSmoothing)); - app.set("autoReload", JsonValue(mAutoReloadEnabled)); - app.set("maxTemporalHistoryFrames", JsonValue(static_cast(mConfig.maxTemporalHistoryFrames))); - app.set("previewFps", JsonValue(static_cast(mConfig.previewFps))); - app.set("enableExternalKeying", JsonValue(mConfig.enableExternalKeying)); - app.set("inputVideoFormat", JsonValue(mConfig.inputVideoFormat)); - app.set("inputFrameRate", JsonValue(mConfig.inputFrameRate)); - app.set("outputVideoFormat", JsonValue(mConfig.outputVideoFormat)); - app.set("outputFrameRate", JsonValue(mConfig.outputFrameRate)); - root.set("app", app); - - JsonValue runtime = JsonValue::MakeObject(); - runtime.set("layerCount", JsonValue(static_cast(mPersistentState.layers.size()))); - runtime.set("compileSucceeded", JsonValue(mCompileSucceeded)); - runtime.set("compileMessage", JsonValue(mCompileMessage)); - root.set("runtime", runtime); - - JsonValue video = JsonValue::MakeObject(); - 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(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(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(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(); - for (const ShaderPackageStatus& status : mPackageStatuses) - { - JsonValue shader = JsonValue::MakeObject(); - shader.set("id", JsonValue(status.id)); - shader.set("name", JsonValue(status.displayName)); - shader.set("description", JsonValue(status.description)); - shader.set("category", JsonValue(status.category)); - shader.set("available", JsonValue(status.available)); - if (!status.available) - shader.set("error", JsonValue(status.error)); - - auto shaderIt = mPackagesById.find(status.id); - if (status.available && shaderIt != mPackagesById.end() && shaderIt->second.temporal.enabled) - { - JsonValue temporal = JsonValue::MakeObject(); - temporal.set("enabled", JsonValue(true)); - temporal.set("historySource", JsonValue(TemporalHistorySourceToString(shaderIt->second.temporal.historySource))); - temporal.set("requestedHistoryLength", JsonValue(static_cast(shaderIt->second.temporal.requestedHistoryLength))); - temporal.set("effectiveHistoryLength", JsonValue(static_cast(shaderIt->second.temporal.effectiveHistoryLength))); - shader.set("temporal", temporal); - } - if (status.available && shaderIt != mPackagesById.end() && shaderIt->second.feedback.enabled) - { - JsonValue feedback = JsonValue::MakeObject(); - feedback.set("enabled", JsonValue(true)); - feedback.set("writePass", JsonValue(shaderIt->second.feedback.writePassId)); - shader.set("feedback", feedback); - } - shaderLibrary.pushBack(shader); - } - root.set("shaders", shaderLibrary); - - JsonValue stackPresets = JsonValue::MakeArray(); - for (const std::string& presetName : GetStackPresetNamesLocked()) - stackPresets.pushBack(JsonValue(presetName)); - root.set("stackPresets", stackPresets); - - root.set("layers", SerializeLayerStackLocked()); - - return root; -} - JsonValue RuntimeHost::SerializeLayerStackLocked() const { JsonValue layers = JsonValue::MakeArray(); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h index 2223110..e5351d7 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h @@ -13,13 +13,14 @@ #include #include +class RuntimeStore; +class RuntimeSnapshotProvider; + class RuntimeHost { public: RuntimeHost(); - bool Initialize(std::string& error); - bool PollFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error); bool ManualReloadRequested(); void ClearReloadRequest(); @@ -57,11 +58,6 @@ public: const HealthTelemetry& GetHealthTelemetry() const { return mHealthTelemetry; } bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector& passSources, std::string& error); - std::vector GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const; - bool TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector& states) const; - bool TryRefreshCachedLayerStates(std::vector& states) const; - void RefreshDynamicRenderStateFields(std::vector& states) const; - std::string BuildStateJson() const; uint64_t GetRenderStateVersion() const { return mRenderStateVersion.load(std::memory_order_relaxed); } uint64_t GetParameterStateVersion() const { return mParameterStateVersion.load(std::memory_order_relaxed); } @@ -124,8 +120,6 @@ private: std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const; bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const; bool ResolvePaths(std::string& error); - void BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector& states) const; - JsonValue BuildStateValue() const; JsonValue SerializeLayerStackLocked() const; bool DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector& layers, std::string& error); void NormalizePersistentLayerIdsLocked(); @@ -140,6 +134,8 @@ private: void MarkParameterStateDirtyLocked(); private: + friend class RuntimeStore; + friend class RuntimeSnapshotProvider; HealthTelemetry mHealthTelemetry; mutable std::mutex mMutex; AppConfig mConfig; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp index 559277a..e9d5bca 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp @@ -1,5 +1,9 @@ #include "RuntimeSnapshotProvider.h" +#include "RuntimeClock.h" + +#include +#include #include RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeHost& runtimeHost) : @@ -28,7 +32,7 @@ RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const RuntimeRenderFrameContext RuntimeSnapshotProvider::GetFrameContext() const { std::vector stateScratch(1); - mRuntimeHost.RefreshDynamicRenderStateFields(stateScratch); + RefreshDynamicRenderStateFields(stateScratch); RuntimeRenderFrameContext frameContext; const RuntimeRenderState& state = stateScratch.front(); @@ -59,7 +63,7 @@ RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsig RuntimeRenderStateSnapshot snapshot; snapshot.outputWidth = outputWidth; snapshot.outputHeight = outputHeight; - snapshot.states = mRuntimeHost.GetLayerRenderStates(outputWidth, outputHeight); + snapshot.states = GetLayerRenderStates(outputWidth, outputHeight); const RuntimeSnapshotVersions versionsAfter = GetVersions(); if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion && @@ -76,7 +80,7 @@ bool RuntimeSnapshotProvider::TryGetRenderStateSnapshot(unsigned outputWidth, un const RuntimeSnapshotVersions versionsBefore = GetVersions(); std::vector states; - if (!mRuntimeHost.TryGetLayerRenderStates(outputWidth, outputHeight, states)) + if (!TryGetLayerRenderStates(outputWidth, outputHeight, states)) return false; const RuntimeSnapshotVersions versionsAfter = GetVersions(); @@ -96,7 +100,7 @@ bool RuntimeSnapshotProvider::TryGetRenderStateSnapshot(unsigned outputWidth, un bool RuntimeSnapshotProvider::TryRefreshSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const { const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion; - if (!mRuntimeHost.TryRefreshCachedLayerStates(snapshot.states)) + if (!TryRefreshCachedLayerStates(snapshot.states)) return false; const RuntimeSnapshotVersions versions = GetVersions(); @@ -126,35 +130,37 @@ void RuntimeSnapshotProvider::ApplyFrameContext(RuntimeRenderStateSnapshot& snap std::vector RuntimeSnapshotProvider::GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const { - return GetRenderStateSnapshot(outputWidth, outputHeight).states; + std::lock_guard lock(mRuntimeHost.mMutex); + std::vector states; + BuildLayerRenderStatesLocked(outputWidth, outputHeight, states); + return states; } bool RuntimeSnapshotProvider::TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector& states) const { - RuntimeRenderStateSnapshot snapshot; - if (!TryGetRenderStateSnapshot(outputWidth, outputHeight, snapshot)) + std::unique_lock lock(mRuntimeHost.mMutex, std::try_to_lock); + if (!lock.owns_lock()) return false; - states = std::move(snapshot.states); + states.clear(); + BuildLayerRenderStatesLocked(outputWidth, outputHeight, states); return true; } bool RuntimeSnapshotProvider::TryRefreshCachedLayerStates(std::vector& states) const { - RuntimeRenderStateSnapshot snapshot; - snapshot.versions.renderStateVersion = mRuntimeHost.GetRenderStateVersion(); - snapshot.versions.parameterStateVersion = mRuntimeHost.GetParameterStateVersion(); - snapshot.states = states; - if (!TryRefreshSnapshotParameters(snapshot)) + std::unique_lock lock(mRuntimeHost.mMutex, std::try_to_lock); + if (!lock.owns_lock()) return false; - states = std::move(snapshot.states); + RefreshCachedLayerStatesLocked(states); return true; } void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector& states) const { - ApplyFrameContext(states, GetFrameContext()); + std::lock_guard lock(mRuntimeHost.mMutex); + RefreshDynamicRenderStateFieldsLocked(states); } uint64_t RuntimeSnapshotProvider::GetRenderStateVersion() const @@ -166,3 +172,85 @@ uint64_t RuntimeSnapshotProvider::GetParameterStateVersion() const { return GetVersions().parameterStateVersion; } + +void RuntimeSnapshotProvider::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector& states) const +{ + const HealthTelemetry::SignalStatusSnapshot signalStatus = mRuntimeHost.mHealthTelemetry.GetSignalStatusSnapshot(); + + for (const RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) + { + auto shaderIt = mRuntimeHost.mPackagesById.find(layer.shaderId); + if (shaderIt == mRuntimeHost.mPackagesById.end()) + continue; + + RuntimeRenderState state; + state.layerId = layer.id; + state.shaderId = layer.shaderId; + state.shaderName = shaderIt->second.displayName; + state.mixAmount = 1.0; + state.bypass = layer.bypass ? 1.0 : 0.0; + state.inputWidth = signalStatus.width; + state.inputHeight = signalStatus.height; + state.outputWidth = outputWidth; + state.outputHeight = outputHeight; + state.parameterDefinitions = shaderIt->second.parameters; + state.textureAssets = shaderIt->second.textureAssets; + state.fontAssets = shaderIt->second.fontAssets; + state.isTemporal = shaderIt->second.temporal.enabled; + state.temporalHistorySource = shaderIt->second.temporal.historySource; + state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength; + state.effectiveTemporalHistoryLength = shaderIt->second.temporal.effectiveHistoryLength; + state.feedback = shaderIt->second.feedback; + + for (const ShaderParameterDefinition& definition : shaderIt->second.parameters) + { + ShaderParameterValue value = mRuntimeHost.DefaultValueForDefinition(definition); + auto valueIt = layer.parameterValues.find(definition.id); + if (valueIt != layer.parameterValues.end()) + value = valueIt->second; + state.parameterValues[definition.id] = value; + } + + states.push_back(state); + } + + RefreshDynamicRenderStateFieldsLocked(states); +} + +void RuntimeSnapshotProvider::RefreshCachedLayerStatesLocked(std::vector& states) const +{ + for (RuntimeRenderState& state : states) + { + const auto layerIt = std::find_if(mRuntimeHost.mPersistentState.layers.begin(), mRuntimeHost.mPersistentState.layers.end(), + [&state](const RuntimeHost::LayerPersistentState& layer) { return layer.id == state.layerId; }); + if (layerIt == mRuntimeHost.mPersistentState.layers.end()) + continue; + + state.bypass = layerIt->bypass ? 1.0 : 0.0; + state.parameterValues.clear(); + for (const ShaderParameterDefinition& definition : state.parameterDefinitions) + { + ShaderParameterValue value = mRuntimeHost.DefaultValueForDefinition(definition); + auto valueIt = layerIt->parameterValues.find(definition.id); + if (valueIt != layerIt->parameterValues.end()) + value = valueIt->second; + state.parameterValues[definition.id] = value; + } + } +} + +void RuntimeSnapshotProvider::RefreshDynamicRenderStateFieldsLocked(std::vector& states) const +{ + const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot(); + const double timeSeconds = std::chrono::duration_cast>(std::chrono::steady_clock::now() - mRuntimeHost.mStartTime).count(); + const double frameCount = static_cast(mRuntimeHost.mFrameCounter.load(std::memory_order_relaxed)); + + for (RuntimeRenderState& state : states) + { + state.timeSeconds = timeSeconds; + state.utcTimeSeconds = clock.utcTimeSeconds; + state.utcOffsetSeconds = clock.utcOffsetSeconds; + state.startupRandom = mRuntimeHost.mStartupRandom; + state.frameCount = frameCount; + } +} diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h index 252cf42..b6cb7c2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h @@ -54,5 +54,9 @@ public: uint64_t GetParameterStateVersion() const; private: + void BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector& states) const; + void RefreshCachedLayerStatesLocked(std::vector& states) const; + void RefreshDynamicRenderStateFieldsLocked(std::vector& states) const; + RuntimeHost& mRuntimeHost; }; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp index 909129f..0b7c8f8 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp @@ -7,12 +7,59 @@ RuntimeStore::RuntimeStore(RuntimeHost& runtimeHost) : bool RuntimeStore::InitializeStore(std::string& error) { - return mRuntimeHost.Initialize(error); + try + { + std::lock_guard lock(mRuntimeHost.mMutex); + + if (!mRuntimeHost.ResolvePaths(error)) + return false; + if (!mRuntimeHost.LoadConfig(error)) + return false; + mRuntimeHost.mShaderRoot = mRuntimeHost.mRepoRoot / mRuntimeHost.mConfig.shaderLibrary; + if (!mRuntimeHost.LoadPersistentState(error)) + return false; + if (!mRuntimeHost.ScanShaderPackages(error)) + return false; + mRuntimeHost.NormalizePersistentLayerIdsLocked(); + + for (RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) + { + auto shaderIt = mRuntimeHost.mPackagesById.find(layer.shaderId); + if (shaderIt != mRuntimeHost.mPackagesById.end()) + mRuntimeHost.EnsureLayerDefaultsLocked(layer, shaderIt->second); + } + + if (mRuntimeHost.mPersistentState.layers.empty() && !mRuntimeHost.mPackageOrder.empty()) + { + RuntimeHost::LayerPersistentState layer; + layer.id = mRuntimeHost.GenerateLayerId(); + layer.shaderId = mRuntimeHost.mPackageOrder.front(); + layer.bypass = false; + mRuntimeHost.EnsureLayerDefaultsLocked(layer, mRuntimeHost.mPackagesById[layer.shaderId]); + mRuntimeHost.mPersistentState.layers.push_back(layer); + } + + mRuntimeHost.mServerPort = mRuntimeHost.mConfig.serverPort; + mRuntimeHost.mAutoReloadEnabled = mRuntimeHost.mConfig.autoReload; + mRuntimeHost.mReloadRequested = true; + mRuntimeHost.mCompileMessage = "Waiting for shader compile."; + return true; + } + catch (const std::exception& exception) + { + error = std::string("RuntimeStore::InitializeStore exception: ") + exception.what(); + return false; + } + catch (...) + { + error = "RuntimeStore::InitializeStore threw a non-standard exception."; + return false; + } } std::string RuntimeStore::BuildPersistentStateJson() const { - return mRuntimeHost.BuildStateJson(); + return SerializeJson(BuildRuntimeStateValue(), true); } bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& error) @@ -159,3 +206,122 @@ void RuntimeStore::ClearReloadRequest() { mRuntimeHost.ClearReloadRequest(); } + +JsonValue RuntimeStore::BuildRuntimeStateValue() const +{ + const HealthTelemetry::Snapshot telemetrySnapshot = mRuntimeHost.mHealthTelemetry.GetSnapshot(); + std::lock_guard lock(mRuntimeHost.mMutex); + + JsonValue root = JsonValue::MakeObject(); + + JsonValue app = JsonValue::MakeObject(); + app.set("serverPort", JsonValue(static_cast(mRuntimeHost.mServerPort))); + app.set("oscPort", JsonValue(static_cast(mRuntimeHost.mConfig.oscPort))); + app.set("oscBindAddress", JsonValue(mRuntimeHost.mConfig.oscBindAddress)); + app.set("oscSmoothing", JsonValue(mRuntimeHost.mConfig.oscSmoothing)); + app.set("autoReload", JsonValue(mRuntimeHost.mAutoReloadEnabled)); + app.set("maxTemporalHistoryFrames", JsonValue(static_cast(mRuntimeHost.mConfig.maxTemporalHistoryFrames))); + app.set("previewFps", JsonValue(static_cast(mRuntimeHost.mConfig.previewFps))); + app.set("enableExternalKeying", JsonValue(mRuntimeHost.mConfig.enableExternalKeying)); + app.set("inputVideoFormat", JsonValue(mRuntimeHost.mConfig.inputVideoFormat)); + app.set("inputFrameRate", JsonValue(mRuntimeHost.mConfig.inputFrameRate)); + app.set("outputVideoFormat", JsonValue(mRuntimeHost.mConfig.outputVideoFormat)); + app.set("outputFrameRate", JsonValue(mRuntimeHost.mConfig.outputFrameRate)); + root.set("app", app); + + JsonValue runtime = JsonValue::MakeObject(); + runtime.set("layerCount", JsonValue(static_cast(mRuntimeHost.mPersistentState.layers.size()))); + runtime.set("compileSucceeded", JsonValue(mRuntimeHost.mCompileSucceeded)); + runtime.set("compileMessage", JsonValue(mRuntimeHost.mCompileMessage)); + root.set("runtime", runtime); + + JsonValue video = JsonValue::MakeObject(); + 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(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(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(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(); + for (const ShaderPackageStatus& status : mRuntimeHost.mPackageStatuses) + { + JsonValue shader = JsonValue::MakeObject(); + shader.set("id", JsonValue(status.id)); + shader.set("name", JsonValue(status.displayName)); + shader.set("description", JsonValue(status.description)); + shader.set("category", JsonValue(status.category)); + shader.set("available", JsonValue(status.available)); + if (!status.available) + shader.set("error", JsonValue(status.error)); + + auto shaderIt = mRuntimeHost.mPackagesById.find(status.id); + if (status.available && shaderIt != mRuntimeHost.mPackagesById.end() && shaderIt->second.temporal.enabled) + { + JsonValue temporal = JsonValue::MakeObject(); + temporal.set("enabled", JsonValue(true)); + temporal.set("historySource", JsonValue(mRuntimeHost.TemporalHistorySourceToString(shaderIt->second.temporal.historySource))); + temporal.set("requestedHistoryLength", JsonValue(static_cast(shaderIt->second.temporal.requestedHistoryLength))); + temporal.set("effectiveHistoryLength", JsonValue(static_cast(shaderIt->second.temporal.effectiveHistoryLength))); + shader.set("temporal", temporal); + } + if (status.available && shaderIt != mRuntimeHost.mPackagesById.end() && shaderIt->second.feedback.enabled) + { + JsonValue feedback = JsonValue::MakeObject(); + feedback.set("enabled", JsonValue(true)); + feedback.set("writePass", JsonValue(shaderIt->second.feedback.writePassId)); + shader.set("feedback", feedback); + } + shaderLibrary.pushBack(shader); + } + root.set("shaders", shaderLibrary); + + JsonValue stackPresets = JsonValue::MakeArray(); + for (const std::string& presetName : mRuntimeHost.GetStackPresetNamesLocked()) + stackPresets.pushBack(JsonValue(presetName)); + root.set("stackPresets", stackPresets); + + root.set("layers", SerializeLayerStack()); + return root; +} + +JsonValue RuntimeStore::SerializeLayerStack() const +{ + return mRuntimeHost.SerializeLayerStackLocked(); +} diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h index 8599f12..3495f00 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h @@ -46,5 +46,8 @@ public: void ClearReloadRequest(); private: + JsonValue BuildRuntimeStateValue() const; + JsonValue SerializeLayerStack() const; + RuntimeHost& mRuntimeHost; }; diff --git a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md index 4400839..716ea71 100644 --- a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md +++ b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md @@ -115,8 +115,11 @@ The codebase now has an initial Phase 1 compatibility split in place: These are still compatibility seams, not a completed subsystem extraction. Most of them continue to delegate heavily to `RuntimeHost`, `OpenGLComposite`, `DeckLinkSession`, and the existing bridge/pipeline classes. Their purpose is to give later Phase 1 work real code boundaries that can be expanded in parallel: - store-facing UI/runtime control calls in `OpenGLCompositeRuntimeControls.cpp` now route through `RuntimeStore` +- runtime startup for path resolution, config load, persistent state load, and shader package scan now initializes through `RuntimeStore` +- runtime/UI state JSON composition now lives in `RuntimeStore` instead of `RuntimeHost` - mutation and reload policy now routes through `RuntimeCoordinator` - render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider` +- render-state assembly, cached parameter refresh, and frame-context application now live in `RuntimeSnapshotProvider` instead of `RuntimeHost` public APIs - 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