From 2317a80ce52b705eb0c3252ae165140fb3691f11 Mon Sep 17 00:00:00 2001 From: Aiden Date: Wed, 6 May 2026 11:23:40 +1000 Subject: [PATCH] stutter fix --- .../gl/OpenGLDeckLinkBridge.cpp | 39 +++++++++++++++++-- .../gl/OpenGLDeckLinkBridge.h | 11 ++++++ .../runtime/RuntimeHost.cpp | 24 ++++++++++++ .../runtime/RuntimeHost.h | 8 ++++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp index 124935b..45e7fe1 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp @@ -30,6 +30,37 @@ OpenGLDeckLinkBridge::OpenGLDeckLinkBridge( { } +void OpenGLDeckLinkBridge::RecordFramePacing(BMDOutputFrameCompletionResult completionResult) +{ + const auto now = std::chrono::steady_clock::now(); + if (mLastPlayoutCompletionTime != std::chrono::steady_clock::time_point()) + { + mCompletionIntervalMilliseconds = std::chrono::duration_cast>(now - mLastPlayoutCompletionTime).count(); + if (mSmoothedCompletionIntervalMilliseconds <= 0.0) + mSmoothedCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds; + else + mSmoothedCompletionIntervalMilliseconds = mSmoothedCompletionIntervalMilliseconds * 0.9 + mCompletionIntervalMilliseconds * 0.1; + if (mCompletionIntervalMilliseconds > mMaxCompletionIntervalMilliseconds) + mMaxCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds; + } + mLastPlayoutCompletionTime = now; + + if (completionResult == bmdOutputFrameDisplayedLate) + ++mLateFrameCount; + else if (completionResult == bmdOutputFrameDropped) + ++mDroppedFrameCount; + else if (completionResult == bmdOutputFrameFlushed) + ++mFlushedFrameCount; + + mRuntimeHost.SetFramePacingStats( + mCompletionIntervalMilliseconds, + mSmoothedCompletionIntervalMilliseconds, + mMaxCompletionIntervalMilliseconds, + mLateFrameCount, + mDroppedFrameCount, + mFlushedFrameCount); +} + void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource) { mDeckLink.SetInputSourceMissing(hasNoInputSource); @@ -90,6 +121,8 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF { (void)completedFrame; + RecordFramePacing(completionResult); + EnterCriticalSection(&mMutex); // Get the first frame from the queue @@ -120,6 +153,7 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF IDeckLinkVideoBuffer* outputVideoFrameBuffer; if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK) { + wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return; } @@ -127,6 +161,7 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK) { outputVideoFrameBuffer->Release(); + wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return; } @@ -136,10 +171,6 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF if (mRenderer.FastTransferAvailable()) { - // Finished sampling the capture texture for this frame. - if (mDeckLink.HasInputSource()) - VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU); - if (!mDeckLink.TransferPlayoutFrame(pFrame, mRenderer.OutputTexture())) OutputDebugStringA("Playback: transferFrame() failed\n"); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.h index 55803e4..4470a99 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.h @@ -4,7 +4,9 @@ #include +#include #include +#include class DeckLinkSession; class OpenGLRenderer; @@ -30,6 +32,8 @@ public: void PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult); private: + void RecordFramePacing(BMDOutputFrameCompletionResult completionResult); + DeckLinkSession& mDeckLink; OpenGLRenderer& mRenderer; RuntimeHost& mRuntimeHost; @@ -38,4 +42,11 @@ private: HGLRC mHglrc; RenderEffectCallback mRenderEffect; PaintCallback mPaint; + std::chrono::steady_clock::time_point mLastPlayoutCompletionTime; + double mCompletionIntervalMilliseconds = 0.0; + double mSmoothedCompletionIntervalMilliseconds = 0.0; + double mMaxCompletionIntervalMilliseconds = 0.0; + uint64_t mLateFrameCount = 0; + uint64_t mDroppedFrameCount = 0; + uint64_t mFlushedFrameCount = 0; }; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp index fdcc916..156b82a 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp @@ -680,6 +680,12 @@ RuntimeHost::RuntimeHost() mFrameBudgetMilliseconds(0.0), mRenderMilliseconds(0.0), mSmoothedRenderMilliseconds(0.0), + mCompletionIntervalMilliseconds(0.0), + mSmoothedCompletionIntervalMilliseconds(0.0), + mMaxCompletionIntervalMilliseconds(0.0), + mLateFrameCount(0), + mDroppedFrameCount(0), + mFlushedFrameCount(0), mServerPort(8080), mAutoReloadEnabled(true), mStartTime(std::chrono::steady_clock::now()), @@ -1179,6 +1185,18 @@ void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double ren mSmoothedRenderMilliseconds = mSmoothedRenderMilliseconds * 0.9 + mRenderMilliseconds * 0.1; } +void RuntimeHost::SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, + double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount) +{ + std::lock_guard lock(mMutex); + mCompletionIntervalMilliseconds = std::max(completionIntervalMilliseconds, 0.0); + mSmoothedCompletionIntervalMilliseconds = std::max(smoothedCompletionIntervalMilliseconds, 0.0); + mMaxCompletionIntervalMilliseconds = std::max(maxCompletionIntervalMilliseconds, 0.0); + mLateFrameCount = lateFrameCount; + mDroppedFrameCount = droppedFrameCount; + mFlushedFrameCount = flushedFrameCount; +} + void RuntimeHost::AdvanceFrame() { std::lock_guard lock(mMutex); @@ -1749,6 +1767,12 @@ JsonValue RuntimeHost::BuildStateValue() const performance.set("renderMs", JsonValue(mRenderMilliseconds)); performance.set("smoothedRenderMs", JsonValue(mSmoothedRenderMilliseconds)); performance.set("budgetUsedPercent", JsonValue(mFrameBudgetMilliseconds > 0.0 ? (mSmoothedRenderMilliseconds / mFrameBudgetMilliseconds) * 100.0 : 0.0)); + performance.set("completionIntervalMs", JsonValue(mCompletionIntervalMilliseconds)); + performance.set("smoothedCompletionIntervalMs", JsonValue(mSmoothedCompletionIntervalMilliseconds)); + performance.set("maxCompletionIntervalMs", JsonValue(mMaxCompletionIntervalMilliseconds)); + performance.set("lateFrameCount", JsonValue(static_cast(mLateFrameCount))); + performance.set("droppedFrameCount", JsonValue(static_cast(mDroppedFrameCount))); + performance.set("flushedFrameCount", JsonValue(static_cast(mFlushedFrameCount))); root.set("performance", performance); JsonValue shaderLibrary = JsonValue::MakeArray(); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h index 853b707..145ed57 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h @@ -38,6 +38,8 @@ public: 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); + void SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, + double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount); void AdvanceFrame(); bool BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error); @@ -148,6 +150,12 @@ private: double mFrameBudgetMilliseconds; double mRenderMilliseconds; double mSmoothedRenderMilliseconds; + double mCompletionIntervalMilliseconds; + double mSmoothedCompletionIntervalMilliseconds; + double mMaxCompletionIntervalMilliseconds; + uint64_t mLateFrameCount; + uint64_t mDroppedFrameCount; + uint64_t mFlushedFrameCount; DeckLinkOutputStatus mDeckLinkOutputStatus; unsigned short mServerPort; bool mAutoReloadEnabled;