stutter fix
Some checks failed
CI / Native Windows Build And Tests (push) Has been cancelled
CI / React UI Build (push) Has been cancelled
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-06 11:23:40 +10:00
parent 3cb8d3cfad
commit 2317a80ce5
4 changed files with 78 additions and 4 deletions

View File

@@ -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<std::chrono::duration<double, std::milli>>(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) void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
{ {
mDeckLink.SetInputSourceMissing(hasNoInputSource); mDeckLink.SetInputSourceMissing(hasNoInputSource);
@@ -90,6 +121,8 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF
{ {
(void)completedFrame; (void)completedFrame;
RecordFramePacing(completionResult);
EnterCriticalSection(&mMutex); EnterCriticalSection(&mMutex);
// Get the first frame from the queue // Get the first frame from the queue
@@ -120,6 +153,7 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF
IDeckLinkVideoBuffer* outputVideoFrameBuffer; IDeckLinkVideoBuffer* outputVideoFrameBuffer;
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK) if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
{ {
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex); LeaveCriticalSection(&mMutex);
return; return;
} }
@@ -127,6 +161,7 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK) if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
{ {
outputVideoFrameBuffer->Release(); outputVideoFrameBuffer->Release();
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex); LeaveCriticalSection(&mMutex);
return; return;
} }
@@ -136,10 +171,6 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF
if (mRenderer.FastTransferAvailable()) if (mRenderer.FastTransferAvailable())
{ {
// Finished sampling the capture texture for this frame.
if (mDeckLink.HasInputSource())
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
if (!mDeckLink.TransferPlayoutFrame(pFrame, mRenderer.OutputTexture())) if (!mDeckLink.TransferPlayoutFrame(pFrame, mRenderer.OutputTexture()))
OutputDebugStringA("Playback: transferFrame() failed\n"); OutputDebugStringA("Playback: transferFrame() failed\n");

View File

@@ -4,7 +4,9 @@
#include <windows.h> #include <windows.h>
#include <chrono>
#include <functional> #include <functional>
#include <cstdint>
class DeckLinkSession; class DeckLinkSession;
class OpenGLRenderer; class OpenGLRenderer;
@@ -30,6 +32,8 @@ public:
void PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult); void PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult);
private: private:
void RecordFramePacing(BMDOutputFrameCompletionResult completionResult);
DeckLinkSession& mDeckLink; DeckLinkSession& mDeckLink;
OpenGLRenderer& mRenderer; OpenGLRenderer& mRenderer;
RuntimeHost& mRuntimeHost; RuntimeHost& mRuntimeHost;
@@ -38,4 +42,11 @@ private:
HGLRC mHglrc; HGLRC mHglrc;
RenderEffectCallback mRenderEffect; RenderEffectCallback mRenderEffect;
PaintCallback mPaint; 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;
}; };

View File

@@ -680,6 +680,12 @@ RuntimeHost::RuntimeHost()
mFrameBudgetMilliseconds(0.0), mFrameBudgetMilliseconds(0.0),
mRenderMilliseconds(0.0), mRenderMilliseconds(0.0),
mSmoothedRenderMilliseconds(0.0), mSmoothedRenderMilliseconds(0.0),
mCompletionIntervalMilliseconds(0.0),
mSmoothedCompletionIntervalMilliseconds(0.0),
mMaxCompletionIntervalMilliseconds(0.0),
mLateFrameCount(0),
mDroppedFrameCount(0),
mFlushedFrameCount(0),
mServerPort(8080), mServerPort(8080),
mAutoReloadEnabled(true), mAutoReloadEnabled(true),
mStartTime(std::chrono::steady_clock::now()), mStartTime(std::chrono::steady_clock::now()),
@@ -1179,6 +1185,18 @@ void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double ren
mSmoothedRenderMilliseconds = mSmoothedRenderMilliseconds * 0.9 + mRenderMilliseconds * 0.1; 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<std::mutex> 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() void RuntimeHost::AdvanceFrame()
{ {
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);
@@ -1749,6 +1767,12 @@ JsonValue RuntimeHost::BuildStateValue() const
performance.set("renderMs", JsonValue(mRenderMilliseconds)); performance.set("renderMs", JsonValue(mRenderMilliseconds));
performance.set("smoothedRenderMs", JsonValue(mSmoothedRenderMilliseconds)); performance.set("smoothedRenderMs", JsonValue(mSmoothedRenderMilliseconds));
performance.set("budgetUsedPercent", JsonValue(mFrameBudgetMilliseconds > 0.0 ? (mSmoothedRenderMilliseconds / mFrameBudgetMilliseconds) * 100.0 : 0.0)); 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<double>(mLateFrameCount)));
performance.set("droppedFrameCount", JsonValue(static_cast<double>(mDroppedFrameCount)));
performance.set("flushedFrameCount", JsonValue(static_cast<double>(mFlushedFrameCount)));
root.set("performance", performance); root.set("performance", performance);
JsonValue shaderLibrary = JsonValue::MakeArray(); JsonValue shaderLibrary = JsonValue::MakeArray();

View File

@@ -38,6 +38,8 @@ public:
void SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying, void SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage); bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds); 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(); void AdvanceFrame();
bool BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error); bool BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error);
@@ -148,6 +150,12 @@ private:
double mFrameBudgetMilliseconds; double mFrameBudgetMilliseconds;
double mRenderMilliseconds; double mRenderMilliseconds;
double mSmoothedRenderMilliseconds; double mSmoothedRenderMilliseconds;
double mCompletionIntervalMilliseconds;
double mSmoothedCompletionIntervalMilliseconds;
double mMaxCompletionIntervalMilliseconds;
uint64_t mLateFrameCount;
uint64_t mDroppedFrameCount;
uint64_t mFlushedFrameCount;
DeckLinkOutputStatus mDeckLinkOutputStatus; DeckLinkOutputStatus mDeckLinkOutputStatus;
unsigned short mServerPort; unsigned short mServerPort;
bool mAutoReloadEnabled; bool mAutoReloadEnabled;