From ba4643dfa3c49315e7f795d993614a84b9db1150 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Mon, 11 May 2026 00:38:49 +1000 Subject: [PATCH] further phase 1 --- .../control/ControlServices.cpp | 12 +- .../control/ControlServices.h | 6 +- .../control/RuntimeServices.cpp | 4 +- .../control/RuntimeServices.h | 2 +- .../gl/OpenGLComposite.cpp | 106 +++------ .../gl/OpenGLComposite.h | 3 - .../gl/RenderEngine.cpp | 47 ++++ .../gl/RenderEngine.h | 16 ++ .../runtime/RuntimeCoordinator.cpp | 25 ++ .../runtime/RuntimeCoordinator.h | 4 + .../runtime/RuntimeHost.cpp | 220 ------------------ .../runtime/RuntimeHost.h | 12 - .../runtime/RuntimeSnapshotProvider.cpp | 62 ++++- .../runtime/RuntimeStore.cpp | 144 +++++++++++- .../runtime/RuntimeStore.h | 1 + .../videoio/VideoBackend.cpp | 25 ++ .../videoio/VideoBackend.h | 2 + 17 files changed, 359 insertions(+), 332 deletions(-) diff --git a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp index ffe341d..1cd2ff2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp @@ -35,9 +35,9 @@ bool ControlServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost return true; } -void ControlServices::BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) +void ControlServices::BeginPolling(RuntimeStore& runtimeStore) { - StartPolling(runtimeHost, runtimeStore); + StartPolling(runtimeStore); } void ControlServices::Stop() @@ -175,12 +175,12 @@ RuntimePollEvents ControlServices::ConsumePollEvents() return events; } -void ControlServices::StartPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) +void ControlServices::StartPolling(RuntimeStore& runtimeStore) { if (mPollRunning.exchange(true)) return; - mPollThread = std::thread([this, &runtimeHost, &runtimeStore]() { PollLoop(runtimeHost, runtimeStore); }); + mPollThread = std::thread([this, &runtimeStore]() { PollLoop(runtimeStore); }); } void ControlServices::StopPolling() @@ -192,7 +192,7 @@ void ControlServices::StopPolling() mPollThread.join(); } -void ControlServices::PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) +void ControlServices::PollLoop(RuntimeStore& runtimeStore) { while (mPollRunning) { @@ -226,7 +226,7 @@ void ControlServices::PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeSt bool registryChanged = false; bool reloadRequested = false; std::string runtimeError; - if (!runtimeHost.PollFileChanges(registryChanged, reloadRequested, runtimeError)) + if (!runtimeStore.PollStoredFileChanges(registryChanged, reloadRequested, runtimeError)) { { std::lock_guard lock(mPollErrorMutex); diff --git a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h index 0a20c92..348cb71 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h +++ b/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h @@ -46,7 +46,7 @@ public: ~ControlServices(); bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error); - void BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); + void BeginPolling(RuntimeStore& runtimeStore); void Stop(); void BroadcastState(); void RequestBroadcastState(); @@ -74,9 +74,9 @@ private: uint64_t generation = 0; }; - void StartPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); + void StartPolling(RuntimeStore& runtimeStore); void StopPolling(); - void PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); + void PollLoop(RuntimeStore& runtimeStore); std::unique_ptr mControlServer; std::unique_ptr mOscServer; diff --git a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp index de418d7..ecd41d1 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp @@ -17,10 +17,10 @@ bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost return mControlServices && mControlServices->Start(composite, runtimeHost, error); } -void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) +void RuntimeServices::BeginPolling(RuntimeStore& runtimeStore) { if (mControlServices) - mControlServices->BeginPolling(runtimeHost, runtimeStore); + mControlServices->BeginPolling(runtimeStore); } void RuntimeServices::Stop() diff --git a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.h b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.h index c2094f9..cefa79f 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.h +++ b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.h @@ -18,7 +18,7 @@ public: ~RuntimeServices(); bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error); - void BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); + void BeginPolling(RuntimeStore& runtimeStore); void Stop(); void BroadcastState(); void RequestBroadcastState(); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index d13c236..fb8457e 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -28,7 +28,6 @@ namespace OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC), - mUseCommittedLayerStates(false), mScreenshotRequested(false) { InitializeCriticalSection(&pMutex); @@ -121,9 +120,11 @@ bool OpenGLComposite::InitVideoIO() goto error; } - PublishVideoIOStatus(mVideoBackend->OutputModelName().empty() - ? "DeckLink output device selected." - : ("Selected output device: " + mVideoBackend->OutputModelName())); + mVideoBackend->PublishStatus( + mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), + mVideoBackend->OutputModelName().empty() + ? "DeckLink output device selected." + : ("Selected output device: " + mVideoBackend->OutputModelName())); // Resize window to match output video frame, but scale large formats down by half for viewing. if (mVideoBackend->OutputFrameWidth() < 1920) @@ -135,21 +136,17 @@ bool OpenGLComposite::InitVideoIO() { goto error; } - if (!mVideoBackend->HasInputDevice() && mRuntimeHost) - { - mRuntimeHost->GetHealthTelemetry().ReportSignalStatus( - false, - mVideoBackend->InputFrameWidth(), - mVideoBackend->InputFrameHeight(), - mVideoBackend->InputDisplayModeName()); - } + if (!mVideoBackend->HasInputDevice()) + mVideoBackend->ReportNoInputDeviceSignalStatus(); if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason)) { goto error; } - PublishVideoIOStatus(mVideoBackend->StatusMessage()); + mVideoBackend->PublishStatus( + mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), + mVideoBackend->StatusMessage()); return true; @@ -194,25 +191,6 @@ void OpenGLComposite::resizeWindow(int width, int height) } } -void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage) -{ - if (!mRuntimeHost) - return; - - if (!statusMessage.empty()) - mVideoBackend->SetStatusMessage(statusMessage); - - mRuntimeHost->GetHealthTelemetry().ReportVideoIOStatus( - "decklink", - mVideoBackend->OutputModelName(), - mVideoBackend->SupportsInternalKeying(), - mVideoBackend->SupportsExternalKeying(), - mVideoBackend->KeyerInterfaceAvailable(), - mRuntimeStore ? mRuntimeStore->IsExternalKeyingConfigured() : false, - mVideoBackend->ExternalKeyingActive(), - mVideoBackend->StatusMessage()); -} - bool OpenGLComposite::InitOpenGLState() { if (! ResolveGLExtensions()) @@ -264,13 +242,12 @@ bool OpenGLComposite::InitOpenGLState() return false; } mRuntimeStore->SetCompileStatus(true, "Shader layers compiled successfully."); - mUseCommittedLayerStates = false; mRenderEngine->ResetTemporalHistoryState(); mRenderEngine->ResetShaderFeedbackState(); broadcastRuntimeState(); - mRuntimeServices->BeginPolling(*mRuntimeHost, *mRuntimeStore); + mRuntimeServices->BeginPolling(*mRuntimeStore); return true; } @@ -287,7 +264,9 @@ bool OpenGLComposite::Stop() const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive(); mVideoBackend->Stop(); if (wasExternalKeyingActive) - PublishVideoIOStatus("External keying has been disabled."); + mVideoBackend->PublishStatus( + mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), + "External keying has been disabled."); return true; } @@ -340,7 +319,7 @@ void OpenGLComposite::renderEffect() std::vector overlayCommitRequests; const double smoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0; mRenderEngine->ResolveRenderLayerStates( - mUseCommittedLayerStates.load(), + mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates(), mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), smoothing, @@ -439,20 +418,20 @@ bool OpenGLComposite::ProcessRuntimePollResults() if (!events.reloadRequested) { - PreparedShaderBuild readyBuild; - if (!mShaderBuildQueue || !mShaderBuildQueue->TryConsumeReadyBuild(readyBuild)) + if (!mShaderBuildQueue || !mRenderEngine) return true; - char compilerErrorMessage[1024] = {}; - if (!mRenderEngine->ApplyPreparedShaderBuild( - readyBuild, + const RenderEngine::PreparedShaderBuildApplyResult buildResult = mRenderEngine->TryApplyReadyShaderBuild( + *mShaderBuildQueue, mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), - mRuntimeCoordinator && mRuntimeCoordinator->PreserveFeedbackOnNextShaderBuild(), - sizeof(compilerErrorMessage), - compilerErrorMessage)) + mRuntimeCoordinator && mRuntimeCoordinator->PreserveFeedbackOnNextShaderBuild()); + if (!buildResult.hadReadyBuild) + return true; + + if (!buildResult.applied) { - ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildFailure(compilerErrorMessage)); + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildFailure(buildResult.errorMessage)); return false; } @@ -487,18 +466,8 @@ bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResu if (result.clearReloadRequest && mRuntimeStore) mRuntimeStore->ClearReloadRequest(); - switch (result.committedStateMode) - { - case RuntimeCoordinatorCommittedStateMode::UseCommittedStates: - mUseCommittedLayerStates = true; - break; - case RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots: - mUseCommittedLayerStates = false; - break; - case RuntimeCoordinatorCommittedStateMode::Unchanged: - default: - break; - } + if (mRuntimeCoordinator) + mRuntimeCoordinator->ApplyCommittedStateMode(result.committedStateMode); if (result.clearTransientOscState) { @@ -508,7 +477,8 @@ bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResu mRuntimeServices->ClearOscState(); } - ApplyRuntimeCoordinatorRenderReset(result.renderResetScope); + if (mRenderEngine) + mRenderEngine->ApplyRuntimeCoordinatorRenderReset(result.renderResetScope); if (result.shaderBuildRequested) RequestShaderBuild(); @@ -519,26 +489,6 @@ bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResu return true; } -void OpenGLComposite::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope) -{ - if (!mRenderEngine) - return; - - switch (resetScope) - { - case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly: - mRenderEngine->ResetTemporalHistoryState(); - break; - case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback: - mRenderEngine->ResetTemporalHistoryState(); - mRenderEngine->ResetShaderFeedbackState(); - break; - case RuntimeCoordinatorRenderResetScope::None: - default: - break; - } -} - void OpenGLComposite::broadcastRuntimeState() { if (mRuntimeServices) diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h index 4b3e2f2..062f259 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h @@ -67,7 +67,6 @@ public: private: void resizeWindow(int width, int height); bool CheckOpenGLExtensions(); - void PublishVideoIOStatus(const std::string& statusMessage); HWND hGLWnd; HDC hGLDC; @@ -82,7 +81,6 @@ private: std::unique_ptr mShaderBuildQueue; std::unique_ptr mRuntimeServices; std::unique_ptr mVideoBackend; - std::atomic mUseCommittedLayerStates; std::atomic mScreenshotRequested; bool InitOpenGLState(); @@ -90,7 +88,6 @@ private: bool ProcessRuntimePollResults(); void RequestShaderBuild(); bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr); - void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope); void ProcessScreenshotRequest(); std::filesystem::path BuildScreenshotPath() const; void broadcastRuntimeState(); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp index e5b77e7..5e265e2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp @@ -1,6 +1,7 @@ #include "RenderEngine.h" #include "RuntimeParameterUtils.h" +#include "ShaderBuildQueue.h" #include @@ -165,6 +166,35 @@ bool RenderEngine::ApplyPreparedShaderBuild( return true; } +RenderEngine::PreparedShaderBuildApplyResult RenderEngine::TryApplyReadyShaderBuild( + ShaderBuildQueue& shaderBuildQueue, + unsigned inputFrameWidth, + unsigned inputFrameHeight, + bool preserveFeedbackState) +{ + PreparedShaderBuildApplyResult result; + PreparedShaderBuild readyBuild; + if (!shaderBuildQueue.TryConsumeReadyBuild(readyBuild)) + return result; + + result.hadReadyBuild = true; + char compilerErrorMessage[1024] = {}; + if (!ApplyPreparedShaderBuild( + readyBuild, + inputFrameWidth, + inputFrameHeight, + preserveFeedbackState, + sizeof(compilerErrorMessage), + compilerErrorMessage)) + { + result.errorMessage = compilerErrorMessage; + return result; + } + + result.applied = true; + return result; +} + const std::vector& RenderEngine::CommittedLayerStates() const { return mShaderPrograms.CommittedLayerStates(); @@ -180,6 +210,23 @@ void RenderEngine::ResetShaderFeedbackState() mShaderPrograms.ResetShaderFeedbackState(); } +void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope) +{ + switch (resetScope) + { + case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly: + ResetTemporalHistoryState(); + break; + case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback: + ResetTemporalHistoryState(); + ResetShaderFeedbackState(); + break; + case RuntimeCoordinatorRenderResetScope::None: + default: + break; + } +} + void RenderEngine::ClearOscOverlayState() { mOscOverlayStates.clear(); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h index e0e43ef..ac0865b 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h @@ -5,6 +5,7 @@ #include "OpenGLRenderer.h" #include "OpenGLShaderPrograms.h" #include "HealthTelemetry.h" +#include "RuntimeCoordinator.h" #include "RuntimeSnapshotProvider.h" #include @@ -16,6 +17,8 @@ #include #include +class ShaderBuildQueue; + class RenderEngine { public: @@ -46,6 +49,13 @@ public: uint64_t generation = 0; }; + struct PreparedShaderBuildApplyResult + { + bool hadReadyBuild = false; + bool applied = false; + std::string errorMessage; + }; + RenderEngine( RuntimeSnapshotProvider& runtimeSnapshotProvider, HealthTelemetry& healthTelemetry, @@ -76,10 +86,16 @@ public: bool preserveFeedbackState, int errorMessageSize, char* errorMessage); + PreparedShaderBuildApplyResult TryApplyReadyShaderBuild( + ShaderBuildQueue& shaderBuildQueue, + unsigned inputFrameWidth, + unsigned inputFrameHeight, + bool preserveFeedbackState); const std::vector& CommittedLayerStates() const; void ResetTemporalHistoryState(); void ResetShaderFeedbackState(); + void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope); void ClearOscOverlayState(); void UpdateOscOverlayState( const std::vector& updates, diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp index 52f273f..913a872 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp @@ -98,6 +98,7 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std: RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error) { mPreserveFeedbackOnNextShaderBuild = false; + mUseCommittedLayerStates = true; RuntimeCoordinatorResult result; result.accepted = true; @@ -111,6 +112,8 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(co RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess() { + mUseCommittedLayerStates = false; + RuntimeCoordinatorResult result; result.accepted = true; result.runtimeStateBroadcastRequired = true; @@ -127,6 +130,27 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest() return BuildQueuedReloadResult(false); } +void RuntimeCoordinator::ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode) +{ + switch (mode) + { + case RuntimeCoordinatorCommittedStateMode::UseCommittedStates: + mUseCommittedLayerStates = true; + break; + case RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots: + mUseCommittedLayerStates = false; + break; + case RuntimeCoordinatorCommittedStateMode::Unchanged: + default: + break; + } +} + +bool RuntimeCoordinator::UseCommittedLayerStates() const +{ + return mUseCommittedLayerStates.load(); +} + bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const { return mPreserveFeedbackOnNextShaderBuild; @@ -151,6 +175,7 @@ RuntimeCoordinatorResult RuntimeCoordinator::ApplyStoreMutation(bool succeeded, RuntimeCoordinatorResult RuntimeCoordinator::BuildQueuedReloadResult(bool preserveFeedbackState) { mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState; + mUseCommittedLayerStates = true; RuntimeCoordinatorResult result; result.accepted = true; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h index 476bed9..bac4d94 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h @@ -2,6 +2,7 @@ #include "RuntimeJson.h" +#include #include #include @@ -58,6 +59,8 @@ public: RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error); RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess(); RuntimeCoordinatorResult HandleRuntimeReloadRequest(); + void ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode); + bool UseCommittedLayerStates() const; bool PreserveFeedbackOnNextShaderBuild() const; private: @@ -67,4 +70,5 @@ private: RuntimeStore& mRuntimeStore; bool mPreserveFeedbackOnNextShaderBuild = false; + std::atomic mUseCommittedLayerStates{ false }; }; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp index f201aba..7b9a64a 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp @@ -3,7 +3,6 @@ #include "RuntimeClock.h" #include "RuntimeParameterUtils.h" -#include "ShaderCompiler.h" #include "ShaderPackageRegistry.h" #include @@ -212,42 +211,6 @@ bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& return false; } -bool TextureAssetsEqual(const std::vector& left, const std::vector& right) -{ - if (left.size() != right.size()) - return false; - - for (std::size_t index = 0; index < left.size(); ++index) - { - if (left[index].id != right[index].id || - left[index].path != right[index].path || - left[index].writeTime != right[index].writeTime) - { - return false; - } - } - - return true; -} - -bool FontAssetsEqual(const std::vector& left, const std::vector& right) -{ - if (left.size() != right.size()) - return false; - - for (std::size_t index = 0; index < left.size(); ++index) - { - if (left[index].id != right[index].id || - left[index].path != right[index].path || - left[index].writeTime != right[index].writeTime) - { - return false; - } - } - - return true; -} - std::string ManifestPathMessage(const std::filesystem::path& manifestPath) { return manifestPath.string(); @@ -712,126 +675,6 @@ RuntimeHost::RuntimeHost() { } -bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error) -{ - try - { - std::lock_guard lock(mMutex); - registryChanged = false; - reloadRequested = false; - - if (!mAutoReloadEnabled) - { - reloadRequested = mReloadRequested; - return true; - } - - const auto now = std::chrono::steady_clock::now(); - if (mLastScanTime != std::chrono::steady_clock::time_point::min() && - std::chrono::duration_cast(now - mLastScanTime).count() < 250) - { - reloadRequested = mReloadRequested; - return true; - } - - mLastScanTime = now; - - std::string scanError; - std::map previousPackages = mPackagesById; - std::vector previousOrder = mPackageOrder; - std::map> previousLayerShaderTimes; - for (const LayerPersistentState& layer : mPersistentState.layers) - { - auto previous = previousPackages.find(layer.shaderId); - if (previous != previousPackages.end()) - previousLayerShaderTimes[layer.id] = std::make_pair(previous->second.shaderWriteTime, previous->second.manifestWriteTime); - } - - if (!ScanShaderPackages(scanError)) - { - error = scanError; - return false; - } - - registryChanged = previousOrder != mPackageOrder; - if (!registryChanged && previousPackages.size() == mPackagesById.size()) - { - for (const auto& item : mPackagesById) - { - auto previous = previousPackages.find(item.first); - if (previous == previousPackages.end()) - { - registryChanged = true; - break; - } - if (previous->second.shaderWriteTime != item.second.shaderWriteTime || - previous->second.manifestWriteTime != item.second.manifestWriteTime || - !TextureAssetsEqual(previous->second.textureAssets, item.second.textureAssets) || - !FontAssetsEqual(previous->second.fontAssets, item.second.fontAssets)) - { - registryChanged = true; - break; - } - } - } - - for (LayerPersistentState& layer : mPersistentState.layers) - { - auto active = mPackagesById.find(layer.shaderId); - auto previous = previousLayerShaderTimes.find(layer.id); - if (active == mPackagesById.end()) - continue; - EnsureLayerDefaultsLocked(layer, active->second); - if (previous != previousLayerShaderTimes.end()) - { - auto previousPackage = previousPackages.find(layer.shaderId); - if (previous->second.first != active->second.shaderWriteTime || - previous->second.second != active->second.manifestWriteTime || - (previousPackage != previousPackages.end() && - (!TextureAssetsEqual(previousPackage->second.textureAssets, active->second.textureAssets) || - !FontAssetsEqual(previousPackage->second.fontAssets, active->second.fontAssets)))) - { - mReloadRequested = true; - } - } - } - - reloadRequested = mReloadRequested; - if (registryChanged || reloadRequested) - MarkRenderStateDirtyLocked(); - return true; - } - catch (const std::exception& exception) - { - error = std::string("RuntimeHost::PollFileChanges exception: ") + exception.what(); - return false; - } - catch (...) - { - error = "RuntimeHost::PollFileChanges threw a non-standard exception."; - return false; - } -} - -bool RuntimeHost::ManualReloadRequested() -{ - std::lock_guard lock(mMutex); - return mReloadRequested; -} - -void RuntimeHost::ClearReloadRequest() -{ - std::lock_guard lock(mMutex); - mReloadRequested = false; -} - -void RuntimeHost::SetCompileStatus(bool succeeded, const std::string& message) -{ - std::lock_guard lock(mMutex); - mCompileSucceeded = succeeded; - mCompileMessage = message; -} - void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) { const HealthTelemetry::SignalStatusSnapshot previousStatus = mHealthTelemetry.GetSignalStatusSnapshot(); @@ -921,69 +764,6 @@ bool RuntimeHost::TrySetFramePacingStats(double completionIntervalMilliseconds, maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount); } -void RuntimeHost::AdvanceFrame() -{ - ++mFrameCounter; -} - -bool RuntimeHost::TryAdvanceFrame() -{ - ++mFrameCounter; - return true; -} - -bool RuntimeHost::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector& passSources, std::string& error) -{ - try - { - ShaderPackage shaderPackage; - { - std::lock_guard lock(mMutex); - const LayerPersistentState* layer = FindLayerById(layerId); - if (!layer) - { - error = "Unknown layer id: " + layerId; - return false; - } - - auto it = mPackagesById.find(layer->shaderId); - if (it == mPackagesById.end()) - { - error = "Unknown shader id: " + layer->shaderId; - return false; - } - shaderPackage = it->second; - } - - ShaderCompiler compiler(mRepoRoot, mWrapperPath, mGeneratedGlslPath, mPatchedGlslPath, mConfig.maxTemporalHistoryFrames); - // Compile every declared pass while the caller remains backend-neutral. - // The GL layer decides how the resulting pass sources are routed. - passSources.clear(); - passSources.reserve(shaderPackage.passes.size()); - for (const ShaderPassDefinition& pass : shaderPackage.passes) - { - ShaderPassBuildSource passSource; - passSource.passId = pass.id; - passSource.inputNames = pass.inputNames; - passSource.outputName = pass.outputName; - if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, passSource.fragmentShaderSource, error)) - return false; - passSources.push_back(std::move(passSource)); - } - return true; - } - catch (const std::exception& exception) - { - error = std::string("RuntimeHost::BuildLayerPassFragmentShaderSources exception: ") + exception.what(); - return false; - } - catch (...) - { - error = "RuntimeHost::BuildLayerPassFragmentShaderSources threw a non-standard exception."; - return false; - } -} - void RuntimeHost::SetServerPort(unsigned short port) { std::lock_guard lock(mMutex); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h index 8141abb..7aa5dc8 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h @@ -20,12 +20,6 @@ class RuntimeHost { public: RuntimeHost(); - - bool PollFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error); - bool ManualReloadRequested(); - void ClearReloadRequest(); - - void SetCompileStatus(bool succeeded, const std::string& message); void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); bool TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); void SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying, @@ -38,15 +32,9 @@ public: double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount); bool TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount); - void AdvanceFrame(); - bool TryAdvanceFrame(); HealthTelemetry& GetHealthTelemetry() { return mHealthTelemetry; } const HealthTelemetry& GetHealthTelemetry() const { return mHealthTelemetry; } - bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector& passSources, std::string& error); - uint64_t GetRenderStateVersion() const { return mRenderStateVersion.load(std::memory_order_relaxed); } - uint64_t GetParameterStateVersion() const { return mParameterStateVersion.load(std::memory_order_relaxed); } - const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; } const std::filesystem::path& GetUiRoot() const { return mUiRoot; } const std::filesystem::path& GetDocsRoot() const { return mDocsRoot; } diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp index e9d5bca..025a916 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp @@ -1,6 +1,7 @@ #include "RuntimeSnapshotProvider.h" #include "RuntimeClock.h" +#include "ShaderCompiler.h" #include #include @@ -13,7 +14,57 @@ RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeHost& runtimeHost) : bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector& passSources, std::string& error) const { - return mRuntimeHost.BuildLayerPassFragmentShaderSources(layerId, passSources, error); + try + { + ShaderPackage shaderPackage; + { + std::lock_guard lock(mRuntimeHost.mMutex); + const RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId); + if (!layer) + { + error = "Unknown layer id: " + layerId; + return false; + } + + auto it = mRuntimeHost.mPackagesById.find(layer->shaderId); + if (it == mRuntimeHost.mPackagesById.end()) + { + error = "Unknown shader id: " + layer->shaderId; + return false; + } + shaderPackage = it->second; + } + + ShaderCompiler compiler( + mRuntimeHost.mRepoRoot, + mRuntimeHost.mWrapperPath, + mRuntimeHost.mGeneratedGlslPath, + mRuntimeHost.mPatchedGlslPath, + mRuntimeHost.mConfig.maxTemporalHistoryFrames); + passSources.clear(); + passSources.reserve(shaderPackage.passes.size()); + for (const ShaderPassDefinition& pass : shaderPackage.passes) + { + ShaderPassBuildSource passSource; + passSource.passId = pass.id; + passSource.inputNames = pass.inputNames; + passSource.outputName = pass.outputName; + if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, passSource.fragmentShaderSource, error)) + return false; + passSources.push_back(std::move(passSource)); + } + return true; + } + catch (const std::exception& exception) + { + error = std::string("RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources exception: ") + exception.what(); + return false; + } + catch (...) + { + error = "RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources threw a non-standard exception."; + return false; + } } unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const @@ -24,8 +75,8 @@ unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const { RuntimeSnapshotVersions versions; - versions.renderStateVersion = mRuntimeHost.GetRenderStateVersion(); - versions.parameterStateVersion = mRuntimeHost.GetParameterStateVersion(); + versions.renderStateVersion = mRuntimeHost.mRenderStateVersion.load(std::memory_order_relaxed); + versions.parameterStateVersion = mRuntimeHost.mParameterStateVersion.load(std::memory_order_relaxed); return versions; } @@ -46,12 +97,13 @@ RuntimeRenderFrameContext RuntimeSnapshotProvider::GetFrameContext() const void RuntimeSnapshotProvider::AdvanceFrame() { - mRuntimeHost.AdvanceFrame(); + ++mRuntimeHost.mFrameCounter; } bool RuntimeSnapshotProvider::TryAdvanceFrame() { - return mRuntimeHost.TryAdvanceFrame(); + ++mRuntimeHost.mFrameCounter; + return true; } RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp index 5bb7358..541f0b4 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp @@ -30,6 +30,42 @@ bool MatchesControlKey(const std::string& candidate, const std::string& key) { return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key); } + +bool TextureAssetsEqual(const std::vector& left, const std::vector& right) +{ + if (left.size() != right.size()) + return false; + + for (std::size_t index = 0; index < left.size(); ++index) + { + if (left[index].id != right[index].id || + left[index].path != right[index].path || + left[index].writeTime != right[index].writeTime) + { + return false; + } + } + + return true; +} + +bool FontAssetsEqual(const std::vector& left, const std::vector& right) +{ + if (left.size() != right.size()) + return false; + + for (std::size_t index = 0; index < left.size(); ++index) + { + if (left[index].id != right[index].id || + left[index].path != right[index].path || + left[index].writeTime != right[index].writeTime) + { + return false; + } + } + + return true; +} } RuntimeStore::RuntimeStore(RuntimeHost& runtimeHost) : @@ -94,6 +130,107 @@ std::string RuntimeStore::BuildPersistentStateJson() const return SerializeJson(BuildRuntimeStateValue(), true); } +bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error) +{ + try + { + std::lock_guard lock(mRuntimeHost.mMutex); + registryChanged = false; + reloadRequested = false; + + if (!mRuntimeHost.mAutoReloadEnabled) + { + reloadRequested = mRuntimeHost.mReloadRequested; + return true; + } + + const auto now = std::chrono::steady_clock::now(); + if (mRuntimeHost.mLastScanTime != std::chrono::steady_clock::time_point::min() && + std::chrono::duration_cast(now - mRuntimeHost.mLastScanTime).count() < 250) + { + reloadRequested = mRuntimeHost.mReloadRequested; + return true; + } + + mRuntimeHost.mLastScanTime = now; + + std::string scanError; + std::map previousPackages = mRuntimeHost.mPackagesById; + std::vector previousOrder = mRuntimeHost.mPackageOrder; + std::map> previousLayerShaderTimes; + for (const RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) + { + auto previous = previousPackages.find(layer.shaderId); + if (previous != previousPackages.end()) + previousLayerShaderTimes[layer.id] = std::make_pair(previous->second.shaderWriteTime, previous->second.manifestWriteTime); + } + + if (!mRuntimeHost.ScanShaderPackages(scanError)) + { + error = scanError; + return false; + } + + registryChanged = previousOrder != mRuntimeHost.mPackageOrder; + if (!registryChanged && previousPackages.size() == mRuntimeHost.mPackagesById.size()) + { + for (const auto& item : mRuntimeHost.mPackagesById) + { + auto previous = previousPackages.find(item.first); + if (previous == previousPackages.end()) + { + registryChanged = true; + break; + } + if (previous->second.shaderWriteTime != item.second.shaderWriteTime || + previous->second.manifestWriteTime != item.second.manifestWriteTime || + !TextureAssetsEqual(previous->second.textureAssets, item.second.textureAssets) || + !FontAssetsEqual(previous->second.fontAssets, item.second.fontAssets)) + { + registryChanged = true; + break; + } + } + } + + for (RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers) + { + auto active = mRuntimeHost.mPackagesById.find(layer.shaderId); + auto previous = previousLayerShaderTimes.find(layer.id); + if (active == mRuntimeHost.mPackagesById.end()) + continue; + mRuntimeHost.EnsureLayerDefaultsLocked(layer, active->second); + if (previous != previousLayerShaderTimes.end()) + { + auto previousPackage = previousPackages.find(layer.shaderId); + if (previous->second.first != active->second.shaderWriteTime || + previous->second.second != active->second.manifestWriteTime || + (previousPackage != previousPackages.end() && + (!TextureAssetsEqual(previousPackage->second.textureAssets, active->second.textureAssets) || + !FontAssetsEqual(previousPackage->second.fontAssets, active->second.fontAssets)))) + { + mRuntimeHost.mReloadRequested = true; + } + } + } + + reloadRequested = mRuntimeHost.mReloadRequested; + if (registryChanged || reloadRequested) + mRuntimeHost.MarkRenderStateDirtyLocked(); + return true; + } + catch (const std::exception& exception) + { + error = std::string("RuntimeStore::PollStoredFileChanges exception: ") + exception.what(); + return false; + } + catch (...) + { + error = "RuntimeStore::PollStoredFileChanges threw a non-standard exception."; + return false; + } +} + bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& error) { std::lock_guard lock(mRuntimeHost.mMutex); @@ -465,12 +602,15 @@ const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message) { - mRuntimeHost.SetCompileStatus(succeeded, message); + std::lock_guard lock(mRuntimeHost.mMutex); + mRuntimeHost.mCompileSucceeded = succeeded; + mRuntimeHost.mCompileMessage = message; } void RuntimeStore::ClearReloadRequest() { - mRuntimeHost.ClearReloadRequest(); + std::lock_guard lock(mRuntimeHost.mMutex); + mRuntimeHost.mReloadRequested = false; } JsonValue RuntimeStore::BuildRuntimeStateValue() const diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h index 215c3ce..6756a82 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h @@ -12,6 +12,7 @@ public: bool InitializeStore(std::string& error); std::string BuildPersistentStateJson() const; + bool PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error); bool CreateStoredLayer(const std::string& shaderId, std::string& error); bool DeleteStoredLayer(const std::string& layerId, std::string& error); diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp index d2f517f..ae5722e 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp @@ -177,6 +177,31 @@ void VideoBackend::SetStatusMessage(const std::string& message) mVideoIODevice->SetStatusMessage(message); } +void VideoBackend::PublishStatus(bool externalKeyingConfigured, const std::string& statusMessage) +{ + if (!statusMessage.empty()) + SetStatusMessage(statusMessage); + + mHealthTelemetry.ReportVideoIOStatus( + "decklink", + OutputModelName(), + SupportsInternalKeying(), + SupportsExternalKeying(), + KeyerInterfaceAvailable(), + externalKeyingConfigured, + ExternalKeyingActive(), + StatusMessage()); +} + +void VideoBackend::ReportNoInputDeviceSignalStatus() +{ + mHealthTelemetry.ReportSignalStatus( + false, + InputFrameWidth(), + InputFrameHeight(), + InputDisplayModeName()); +} + void VideoBackend::HandleInputFrame(const VideoIOFrame& frame) { const VideoIOState& state = mVideoIODevice->State(); diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h index 0484875..5dd0e59 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h @@ -50,6 +50,8 @@ public: bool ExternalKeyingActive() const; const std::string& StatusMessage() const; void SetStatusMessage(const std::string& message); + void PublishStatus(bool externalKeyingConfigured, const std::string& statusMessage = std::string()); + void ReportNoInputDeviceSignalStatus(); private: void HandleInputFrame(const VideoIOFrame& frame);