diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index 7c7a08f..1b19758 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -53,12 +53,17 @@ #include #include +#include #include OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC), mDeckLink(std::make_unique()), - mRenderer(std::make_unique()) + mRenderer(std::make_unique()), + mRuntimePollRunning(false), + mRuntimeRegistryChanged(false), + mRuntimeReloadRequested(false), + mRuntimePollFailed(false) { InitializeCriticalSection(&pMutex); mRuntimeHost = std::make_unique(); @@ -79,6 +84,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : OpenGLComposite::~OpenGLComposite() { + StopRuntimePolling(); mDeckLink->ReleaseResources(); mRenderer->DestroyResources(); if (mOscServer) @@ -284,6 +290,7 @@ bool OpenGLComposite::InitOpenGLState() } broadcastRuntimeState(); + StartRuntimePolling(); return true; } @@ -305,6 +312,8 @@ bool OpenGLComposite::Start() bool OpenGLComposite::Stop() { + StopRuntimePolling(); + if (mOscServer) mOscServer->Stop(); @@ -351,10 +360,17 @@ bool OpenGLComposite::ReloadShader() void OpenGLComposite::renderEffect() { - PollRuntimeChanges(); + ProcessRuntimePollResults(); const bool hasInputSource = mDeckLink->HasInputSource(); - const std::vector layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight()) : std::vector(); + std::vector layerStates; + if (mRuntimeHost) + { + if (mRuntimeHost->TryGetLayerRenderStates(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), layerStates)) + mCachedLayerRenderStates = layerStates; + else + layerStates = mCachedLayerRenderStates; + } const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; mRenderPass->Render( hasInputSource, @@ -370,25 +386,72 @@ void OpenGLComposite::renderEffect() }); } -bool OpenGLComposite::PollRuntimeChanges() +void OpenGLComposite::StartRuntimePolling() +{ + if (!mRuntimeHost || mRuntimePollRunning.exchange(true)) + return; + + mRuntimePollThread = std::thread([this]() { RuntimePollLoop(); }); +} + +void OpenGLComposite::StopRuntimePolling() +{ + if (!mRuntimePollRunning.exchange(false)) + return; + + if (mRuntimePollThread.joinable()) + mRuntimePollThread.join(); +} + +void OpenGLComposite::RuntimePollLoop() +{ + while (mRuntimePollRunning) + { + bool registryChanged = false; + bool reloadRequested = false; + std::string runtimeError; + if (!mRuntimeHost->PollFileChanges(registryChanged, reloadRequested, runtimeError)) + { + { + std::lock_guard lock(mRuntimePollErrorMutex); + mRuntimePollError = runtimeError; + } + mRuntimePollFailed = true; + } + else + { + if (registryChanged) + mRuntimeRegistryChanged = true; + if (reloadRequested) + mRuntimeReloadRequested = true; + } + + for (int i = 0; i < 25 && mRuntimePollRunning; ++i) + Sleep(10); + } +} + +bool OpenGLComposite::ProcessRuntimePollResults() { if (!mRuntimeHost) return true; - bool registryChanged = false; - bool reloadRequested = false; - std::string runtimeError; - if (!mRuntimeHost->PollFileChanges(registryChanged, reloadRequested, runtimeError)) + if (mRuntimePollFailed.exchange(false)) { + std::string runtimeError; + { + std::lock_guard lock(mRuntimePollErrorMutex); + runtimeError = mRuntimePollError; + } mRuntimeHost->SetCompileStatus(false, runtimeError); broadcastRuntimeState(); return false; } - if (registryChanged) + if (mRuntimeRegistryChanged.exchange(false)) broadcastRuntimeState(); - if (!reloadRequested) + if (!mRuntimeReloadRequested.exchange(false)) return true; char compilerErrorMessage[1024] = {}; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h index 923fbda..5586758 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h @@ -60,6 +60,9 @@ #include #include #include +#include +#include +#include #include #include @@ -119,10 +122,21 @@ private: std::unique_ptr mShaderPrograms; std::unique_ptr mControlServer; std::unique_ptr mOscServer; + std::thread mRuntimePollThread; + std::atomic mRuntimePollRunning; + std::atomic mRuntimeRegistryChanged; + std::atomic mRuntimeReloadRequested; + std::atomic mRuntimePollFailed; + std::mutex mRuntimePollErrorMutex; + std::string mRuntimePollError; + std::vector mCachedLayerRenderStates; bool InitOpenGLState(); void renderEffect(); - bool PollRuntimeChanges(); + void StartRuntimePolling(); + void StopRuntimePolling(); + void RuntimePollLoop(); + bool ProcessRuntimePollResults(); void broadcastRuntimeState(); void resetTemporalHistoryState(); }; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp index 45e7fe1..7b67b50 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp @@ -52,7 +52,7 @@ void OpenGLDeckLinkBridge::RecordFramePacing(BMDOutputFrameCompletionResult comp else if (completionResult == bmdOutputFrameFlushed) ++mFlushedFrameCount; - mRuntimeHost.SetFramePacingStats( + mRuntimeHost.TrySetFramePacingStats( mCompletionIntervalMilliseconds, mSmoothedCompletionIntervalMilliseconds, mMaxCompletionIntervalMilliseconds, @@ -64,7 +64,7 @@ void OpenGLDeckLinkBridge::RecordFramePacing(BMDOutputFrameCompletionResult comp void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource) { mDeckLink.SetInputSourceMissing(hasNoInputSource); - mRuntimeHost.SetSignalStatus(!hasNoInputSource, mDeckLink.InputFrameWidth(), mDeckLink.InputFrameHeight(), mDeckLink.InputDisplayModeName()); + mRuntimeHost.TrySetSignalStatus(!hasNoInputSource, mDeckLink.InputFrameWidth(), mDeckLink.InputFrameHeight(), mDeckLink.InputDisplayModeName()); if (!mDeckLink.HasInputSource()) return; // don't transfer texture when there's no input @@ -147,8 +147,8 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF const auto renderEndTime = std::chrono::steady_clock::now(); const double frameBudgetMilliseconds = mDeckLink.FrameBudgetMilliseconds(); const double renderMilliseconds = std::chrono::duration_cast>(renderEndTime - renderStartTime).count(); - mRuntimeHost.SetPerformanceStats(frameBudgetMilliseconds, renderMilliseconds); - mRuntimeHost.AdvanceFrame(); + mRuntimeHost.TrySetPerformanceStats(frameBudgetMilliseconds, renderMilliseconds); + mRuntimeHost.TryAdvanceFrame(); IDeckLinkVideoBuffer* outputVideoFrameBuffer; if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK) diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp index 156b82a..d30c562 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp @@ -1155,6 +1155,21 @@ void RuntimeHost::SetCompileStatus(bool succeeded, const std::string& message) void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) { std::lock_guard lock(mMutex); + SetSignalStatusLocked(hasSignal, width, height, modeName); +} + +bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) +{ + std::unique_lock lock(mMutex, std::try_to_lock); + if (!lock.owns_lock()) + return false; + + SetSignalStatusLocked(hasSignal, width, height, modeName); + return true; +} + +void RuntimeHost::SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) +{ mHasSignal = hasSignal; mSignalWidth = width; mSignalHeight = height; @@ -1177,6 +1192,21 @@ void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool sup void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds) { std::lock_guard lock(mMutex); + SetPerformanceStatsLocked(frameBudgetMilliseconds, renderMilliseconds); +} + +bool RuntimeHost::TrySetPerformanceStats(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) @@ -1189,6 +1219,25 @@ void RuntimeHost::SetFramePacingStats(double completionIntervalMilliseconds, dou 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::TrySetFramePacingStats(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); @@ -1203,6 +1252,16 @@ void RuntimeHost::AdvanceFrame() ++mFrameCounter; } +bool RuntimeHost::TryAdvanceFrame() +{ + std::unique_lock lock(mMutex, std::try_to_lock); + if (!lock.owns_lock()) + return false; + + ++mFrameCounter; + return true; +} + bool RuntimeHost::BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error) { try @@ -1245,7 +1304,23 @@ std::vector RuntimeHost::GetLayerRenderStates(unsigned outpu { 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; +} + +void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector& states) const +{ for (const LayerPersistentState& layer : mPersistentState.layers) { auto shaderIt = mPackagesById.find(layer.shaderId); @@ -1282,8 +1357,6 @@ std::vector RuntimeHost::GetLayerRenderStates(unsigned outpu states.push_back(state); } - - return states; } std::string RuntimeHost::BuildStateJson() const diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h index 145ed57..3f6636f 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h @@ -35,15 +35,21 @@ public: 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, bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage); void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds); + bool TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds); void SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, 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(); bool BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error); std::vector GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const; + bool TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector& states) const; std::string BuildStateJson() const; const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; } @@ -111,6 +117,7 @@ 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); @@ -122,6 +129,10 @@ private: LayerPersistentState* FindLayerById(const std::string& layerId); const LayerPersistentState* FindLayerById(const std::string& layerId) const; std::string GenerateLayerId(); + void SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName); + void SetPerformanceStatsLocked(double frameBudgetMilliseconds, double renderMilliseconds); + void SetFramePacingStatsLocked(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, + double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount); private: mutable std::mutex mMutex;