Pacing problems
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:31:48 +10:00
parent 2317a80ce5
commit d2ac369fdc
5 changed files with 178 additions and 17 deletions

View File

@@ -53,12 +53,17 @@
#include <memory>
#include <string>
#include <thread>
#include <vector>
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC),
mDeckLink(std::make_unique<DeckLinkSession>()),
mRenderer(std::make_unique<OpenGLRenderer>())
mRenderer(std::make_unique<OpenGLRenderer>()),
mRuntimePollRunning(false),
mRuntimeRegistryChanged(false),
mRuntimeReloadRequested(false),
mRuntimePollFailed(false)
{
InitializeCriticalSection(&pMutex);
mRuntimeHost = std::make_unique<RuntimeHost>();
@@ -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<RuntimeRenderState> layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight()) : std::vector<RuntimeRenderState>();
std::vector<RuntimeRenderState> 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<std::mutex> 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<std::mutex> 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] = {};

View File

@@ -60,6 +60,9 @@
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <deque>
@@ -119,10 +122,21 @@ private:
std::unique_ptr<OpenGLShaderPrograms> mShaderPrograms;
std::unique_ptr<ControlServer> mControlServer;
std::unique_ptr<OscServer> mOscServer;
std::thread mRuntimePollThread;
std::atomic<bool> mRuntimePollRunning;
std::atomic<bool> mRuntimeRegistryChanged;
std::atomic<bool> mRuntimeReloadRequested;
std::atomic<bool> mRuntimePollFailed;
std::mutex mRuntimePollErrorMutex;
std::string mRuntimePollError;
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
bool InitOpenGLState();
void renderEffect();
bool PollRuntimeChanges();
void StartRuntimePolling();
void StopRuntimePolling();
void RuntimePollLoop();
bool ProcessRuntimePollResults();
void broadcastRuntimeState();
void resetTemporalHistoryState();
};

View File

@@ -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<std::chrono::duration<double, std::milli>>(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)

View File

@@ -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<std::mutex> lock(mMutex);
SetSignalStatusLocked(hasSignal, width, height, modeName);
}
bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
std::unique_lock<std::mutex> 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<std::mutex> lock(mMutex);
SetPerformanceStatsLocked(frameBudgetMilliseconds, renderMilliseconds);
}
bool RuntimeHost::TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<RuntimeRenderState> RuntimeHost::GetLayerRenderStates(unsigned outpu
{
std::lock_guard<std::mutex> lock(mMutex);
std::vector<RuntimeRenderState> states;
BuildLayerRenderStatesLocked(outputWidth, outputHeight, states);
return states;
}
bool RuntimeHost::TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
std::unique_lock<std::mutex> 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<RuntimeRenderState>& states) const
{
for (const LayerPersistentState& layer : mPersistentState.layers)
{
auto shaderIt = mPackagesById.find(layer.shaderId);
@@ -1282,8 +1357,6 @@ std::vector<RuntimeRenderState> RuntimeHost::GetLayerRenderStates(unsigned outpu
states.push_back(state);
}
return states;
}
std::string RuntimeHost::BuildStateJson() const

View File

@@ -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<RuntimeRenderState> GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const;
bool TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& 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<RuntimeRenderState>& states) const;
JsonValue BuildStateValue() const;
JsonValue SerializeLayerStackLocked() const;
bool DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector<LayerPersistentState>& 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;