further phase 1
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m39s
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
Aiden
2026-05-11 00:38:49 +10:00
parent 27dbb55f7b
commit ba4643dfa3
17 changed files with 359 additions and 332 deletions

View File

@@ -35,9 +35,9 @@ bool ControlServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost
return true; return true;
} }
void ControlServices::BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) void ControlServices::BeginPolling(RuntimeStore& runtimeStore)
{ {
StartPolling(runtimeHost, runtimeStore); StartPolling(runtimeStore);
} }
void ControlServices::Stop() void ControlServices::Stop()
@@ -175,12 +175,12 @@ RuntimePollEvents ControlServices::ConsumePollEvents()
return events; return events;
} }
void ControlServices::StartPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) void ControlServices::StartPolling(RuntimeStore& runtimeStore)
{ {
if (mPollRunning.exchange(true)) if (mPollRunning.exchange(true))
return; return;
mPollThread = std::thread([this, &runtimeHost, &runtimeStore]() { PollLoop(runtimeHost, runtimeStore); }); mPollThread = std::thread([this, &runtimeStore]() { PollLoop(runtimeStore); });
} }
void ControlServices::StopPolling() void ControlServices::StopPolling()
@@ -192,7 +192,7 @@ void ControlServices::StopPolling()
mPollThread.join(); mPollThread.join();
} }
void ControlServices::PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) void ControlServices::PollLoop(RuntimeStore& runtimeStore)
{ {
while (mPollRunning) while (mPollRunning)
{ {
@@ -226,7 +226,7 @@ void ControlServices::PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeSt
bool registryChanged = false; bool registryChanged = false;
bool reloadRequested = false; bool reloadRequested = false;
std::string runtimeError; std::string runtimeError;
if (!runtimeHost.PollFileChanges(registryChanged, reloadRequested, runtimeError)) if (!runtimeStore.PollStoredFileChanges(registryChanged, reloadRequested, runtimeError))
{ {
{ {
std::lock_guard<std::mutex> lock(mPollErrorMutex); std::lock_guard<std::mutex> lock(mPollErrorMutex);

View File

@@ -46,7 +46,7 @@ public:
~ControlServices(); ~ControlServices();
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error); bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
void BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); void BeginPolling(RuntimeStore& runtimeStore);
void Stop(); void Stop();
void BroadcastState(); void BroadcastState();
void RequestBroadcastState(); void RequestBroadcastState();
@@ -74,9 +74,9 @@ private:
uint64_t generation = 0; uint64_t generation = 0;
}; };
void StartPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); void StartPolling(RuntimeStore& runtimeStore);
void StopPolling(); void StopPolling();
void PollLoop(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); void PollLoop(RuntimeStore& runtimeStore);
std::unique_ptr<ControlServer> mControlServer; std::unique_ptr<ControlServer> mControlServer;
std::unique_ptr<OscServer> mOscServer; std::unique_ptr<OscServer> mOscServer;

View File

@@ -17,10 +17,10 @@ bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost
return mControlServices && mControlServices->Start(composite, runtimeHost, error); return mControlServices && mControlServices->Start(composite, runtimeHost, error);
} }
void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore) void RuntimeServices::BeginPolling(RuntimeStore& runtimeStore)
{ {
if (mControlServices) if (mControlServices)
mControlServices->BeginPolling(runtimeHost, runtimeStore); mControlServices->BeginPolling(runtimeStore);
} }
void RuntimeServices::Stop() void RuntimeServices::Stop()

View File

@@ -18,7 +18,7 @@ public:
~RuntimeServices(); ~RuntimeServices();
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error); bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
void BeginPolling(RuntimeHost& runtimeHost, RuntimeStore& runtimeStore); void BeginPolling(RuntimeStore& runtimeStore);
void Stop(); void Stop();
void BroadcastState(); void BroadcastState();
void RequestBroadcastState(); void RequestBroadcastState();

View File

@@ -28,7 +28,6 @@ namespace
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC), hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC),
mUseCommittedLayerStates(false),
mScreenshotRequested(false) mScreenshotRequested(false)
{ {
InitializeCriticalSection(&pMutex); InitializeCriticalSection(&pMutex);
@@ -121,9 +120,11 @@ bool OpenGLComposite::InitVideoIO()
goto error; goto error;
} }
PublishVideoIOStatus(mVideoBackend->OutputModelName().empty() mVideoBackend->PublishStatus(
? "DeckLink output device selected." mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
: ("Selected output device: " + mVideoBackend->OutputModelName())); 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. // Resize window to match output video frame, but scale large formats down by half for viewing.
if (mVideoBackend->OutputFrameWidth() < 1920) if (mVideoBackend->OutputFrameWidth() < 1920)
@@ -135,21 +136,17 @@ bool OpenGLComposite::InitVideoIO()
{ {
goto error; goto error;
} }
if (!mVideoBackend->HasInputDevice() && mRuntimeHost) if (!mVideoBackend->HasInputDevice())
{ mVideoBackend->ReportNoInputDeviceSignalStatus();
mRuntimeHost->GetHealthTelemetry().ReportSignalStatus(
false,
mVideoBackend->InputFrameWidth(),
mVideoBackend->InputFrameHeight(),
mVideoBackend->InputDisplayModeName());
}
if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason)) if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason))
{ {
goto error; goto error;
} }
PublishVideoIOStatus(mVideoBackend->StatusMessage()); mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
mVideoBackend->StatusMessage());
return true; 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() bool OpenGLComposite::InitOpenGLState()
{ {
if (! ResolveGLExtensions()) if (! ResolveGLExtensions())
@@ -264,13 +242,12 @@ bool OpenGLComposite::InitOpenGLState()
return false; return false;
} }
mRuntimeStore->SetCompileStatus(true, "Shader layers compiled successfully."); mRuntimeStore->SetCompileStatus(true, "Shader layers compiled successfully.");
mUseCommittedLayerStates = false;
mRenderEngine->ResetTemporalHistoryState(); mRenderEngine->ResetTemporalHistoryState();
mRenderEngine->ResetShaderFeedbackState(); mRenderEngine->ResetShaderFeedbackState();
broadcastRuntimeState(); broadcastRuntimeState();
mRuntimeServices->BeginPolling(*mRuntimeHost, *mRuntimeStore); mRuntimeServices->BeginPolling(*mRuntimeStore);
return true; return true;
} }
@@ -287,7 +264,9 @@ bool OpenGLComposite::Stop()
const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive(); const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive();
mVideoBackend->Stop(); mVideoBackend->Stop();
if (wasExternalKeyingActive) if (wasExternalKeyingActive)
PublishVideoIOStatus("External keying has been disabled."); mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
"External keying has been disabled.");
return true; return true;
} }
@@ -340,7 +319,7 @@ void OpenGLComposite::renderEffect()
std::vector<RenderEngine::OscOverlayCommitRequest> overlayCommitRequests; std::vector<RenderEngine::OscOverlayCommitRequest> overlayCommitRequests;
const double smoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0; const double smoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0;
mRenderEngine->ResolveRenderLayerStates( mRenderEngine->ResolveRenderLayerStates(
mUseCommittedLayerStates.load(), mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates(),
mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameWidth(),
mVideoBackend->InputFrameHeight(), mVideoBackend->InputFrameHeight(),
smoothing, smoothing,
@@ -439,20 +418,20 @@ bool OpenGLComposite::ProcessRuntimePollResults()
if (!events.reloadRequested) if (!events.reloadRequested)
{ {
PreparedShaderBuild readyBuild; if (!mShaderBuildQueue || !mRenderEngine)
if (!mShaderBuildQueue || !mShaderBuildQueue->TryConsumeReadyBuild(readyBuild))
return true; return true;
char compilerErrorMessage[1024] = {}; const RenderEngine::PreparedShaderBuildApplyResult buildResult = mRenderEngine->TryApplyReadyShaderBuild(
if (!mRenderEngine->ApplyPreparedShaderBuild( *mShaderBuildQueue,
readyBuild,
mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameWidth(),
mVideoBackend->InputFrameHeight(), mVideoBackend->InputFrameHeight(),
mRuntimeCoordinator && mRuntimeCoordinator->PreserveFeedbackOnNextShaderBuild(), mRuntimeCoordinator && mRuntimeCoordinator->PreserveFeedbackOnNextShaderBuild());
sizeof(compilerErrorMessage), if (!buildResult.hadReadyBuild)
compilerErrorMessage)) return true;
if (!buildResult.applied)
{ {
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildFailure(compilerErrorMessage)); ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildFailure(buildResult.errorMessage));
return false; return false;
} }
@@ -487,18 +466,8 @@ bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResu
if (result.clearReloadRequest && mRuntimeStore) if (result.clearReloadRequest && mRuntimeStore)
mRuntimeStore->ClearReloadRequest(); mRuntimeStore->ClearReloadRequest();
switch (result.committedStateMode) if (mRuntimeCoordinator)
{ mRuntimeCoordinator->ApplyCommittedStateMode(result.committedStateMode);
case RuntimeCoordinatorCommittedStateMode::UseCommittedStates:
mUseCommittedLayerStates = true;
break;
case RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots:
mUseCommittedLayerStates = false;
break;
case RuntimeCoordinatorCommittedStateMode::Unchanged:
default:
break;
}
if (result.clearTransientOscState) if (result.clearTransientOscState)
{ {
@@ -508,7 +477,8 @@ bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResu
mRuntimeServices->ClearOscState(); mRuntimeServices->ClearOscState();
} }
ApplyRuntimeCoordinatorRenderReset(result.renderResetScope); if (mRenderEngine)
mRenderEngine->ApplyRuntimeCoordinatorRenderReset(result.renderResetScope);
if (result.shaderBuildRequested) if (result.shaderBuildRequested)
RequestShaderBuild(); RequestShaderBuild();
@@ -519,26 +489,6 @@ bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResu
return true; 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() void OpenGLComposite::broadcastRuntimeState()
{ {
if (mRuntimeServices) if (mRuntimeServices)

View File

@@ -67,7 +67,6 @@ public:
private: private:
void resizeWindow(int width, int height); void resizeWindow(int width, int height);
bool CheckOpenGLExtensions(); bool CheckOpenGLExtensions();
void PublishVideoIOStatus(const std::string& statusMessage);
HWND hGLWnd; HWND hGLWnd;
HDC hGLDC; HDC hGLDC;
@@ -82,7 +81,6 @@ private:
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue; std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
std::unique_ptr<RuntimeServices> mRuntimeServices; std::unique_ptr<RuntimeServices> mRuntimeServices;
std::unique_ptr<VideoBackend> mVideoBackend; std::unique_ptr<VideoBackend> mVideoBackend;
std::atomic<bool> mUseCommittedLayerStates;
std::atomic<bool> mScreenshotRequested; std::atomic<bool> mScreenshotRequested;
bool InitOpenGLState(); bool InitOpenGLState();
@@ -90,7 +88,6 @@ private:
bool ProcessRuntimePollResults(); bool ProcessRuntimePollResults();
void RequestShaderBuild(); void RequestShaderBuild();
bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr); bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr);
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
void ProcessScreenshotRequest(); void ProcessScreenshotRequest();
std::filesystem::path BuildScreenshotPath() const; std::filesystem::path BuildScreenshotPath() const;
void broadcastRuntimeState(); void broadcastRuntimeState();

View File

@@ -1,6 +1,7 @@
#include "RenderEngine.h" #include "RenderEngine.h"
#include "RuntimeParameterUtils.h" #include "RuntimeParameterUtils.h"
#include "ShaderBuildQueue.h"
#include <gl/gl.h> #include <gl/gl.h>
@@ -165,6 +166,35 @@ bool RenderEngine::ApplyPreparedShaderBuild(
return true; 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<RuntimeRenderState>& RenderEngine::CommittedLayerStates() const const std::vector<RuntimeRenderState>& RenderEngine::CommittedLayerStates() const
{ {
return mShaderPrograms.CommittedLayerStates(); return mShaderPrograms.CommittedLayerStates();
@@ -180,6 +210,23 @@ void RenderEngine::ResetShaderFeedbackState()
mShaderPrograms.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() void RenderEngine::ClearOscOverlayState()
{ {
mOscOverlayStates.clear(); mOscOverlayStates.clear();

View File

@@ -5,6 +5,7 @@
#include "OpenGLRenderer.h" #include "OpenGLRenderer.h"
#include "OpenGLShaderPrograms.h" #include "OpenGLShaderPrograms.h"
#include "HealthTelemetry.h" #include "HealthTelemetry.h"
#include "RuntimeCoordinator.h"
#include "RuntimeSnapshotProvider.h" #include "RuntimeSnapshotProvider.h"
#include <windows.h> #include <windows.h>
@@ -16,6 +17,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
class ShaderBuildQueue;
class RenderEngine class RenderEngine
{ {
public: public:
@@ -46,6 +49,13 @@ public:
uint64_t generation = 0; uint64_t generation = 0;
}; };
struct PreparedShaderBuildApplyResult
{
bool hadReadyBuild = false;
bool applied = false;
std::string errorMessage;
};
RenderEngine( RenderEngine(
RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry, HealthTelemetry& healthTelemetry,
@@ -76,10 +86,16 @@ public:
bool preserveFeedbackState, bool preserveFeedbackState,
int errorMessageSize, int errorMessageSize,
char* errorMessage); char* errorMessage);
PreparedShaderBuildApplyResult TryApplyReadyShaderBuild(
ShaderBuildQueue& shaderBuildQueue,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
bool preserveFeedbackState);
const std::vector<RuntimeRenderState>& CommittedLayerStates() const; const std::vector<RuntimeRenderState>& CommittedLayerStates() const;
void ResetTemporalHistoryState(); void ResetTemporalHistoryState();
void ResetShaderFeedbackState(); void ResetShaderFeedbackState();
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
void ClearOscOverlayState(); void ClearOscOverlayState();
void UpdateOscOverlayState( void UpdateOscOverlayState(
const std::vector<OscOverlayUpdate>& updates, const std::vector<OscOverlayUpdate>& updates,

View File

@@ -98,6 +98,7 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std:
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error) RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error)
{ {
mPreserveFeedbackOnNextShaderBuild = false; mPreserveFeedbackOnNextShaderBuild = false;
mUseCommittedLayerStates = true;
RuntimeCoordinatorResult result; RuntimeCoordinatorResult result;
result.accepted = true; result.accepted = true;
@@ -111,6 +112,8 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(co
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess() RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess()
{ {
mUseCommittedLayerStates = false;
RuntimeCoordinatorResult result; RuntimeCoordinatorResult result;
result.accepted = true; result.accepted = true;
result.runtimeStateBroadcastRequired = true; result.runtimeStateBroadcastRequired = true;
@@ -127,6 +130,27 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest()
return BuildQueuedReloadResult(false); 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 bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const
{ {
return mPreserveFeedbackOnNextShaderBuild; return mPreserveFeedbackOnNextShaderBuild;
@@ -151,6 +175,7 @@ RuntimeCoordinatorResult RuntimeCoordinator::ApplyStoreMutation(bool succeeded,
RuntimeCoordinatorResult RuntimeCoordinator::BuildQueuedReloadResult(bool preserveFeedbackState) RuntimeCoordinatorResult RuntimeCoordinator::BuildQueuedReloadResult(bool preserveFeedbackState)
{ {
mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState; mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState;
mUseCommittedLayerStates = true;
RuntimeCoordinatorResult result; RuntimeCoordinatorResult result;
result.accepted = true; result.accepted = true;

View File

@@ -2,6 +2,7 @@
#include "RuntimeJson.h" #include "RuntimeJson.h"
#include <atomic>
#include <cstddef> #include <cstddef>
#include <string> #include <string>
@@ -58,6 +59,8 @@ public:
RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error); RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error);
RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess(); RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess();
RuntimeCoordinatorResult HandleRuntimeReloadRequest(); RuntimeCoordinatorResult HandleRuntimeReloadRequest();
void ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode);
bool UseCommittedLayerStates() const;
bool PreserveFeedbackOnNextShaderBuild() const; bool PreserveFeedbackOnNextShaderBuild() const;
private: private:
@@ -67,4 +70,5 @@ private:
RuntimeStore& mRuntimeStore; RuntimeStore& mRuntimeStore;
bool mPreserveFeedbackOnNextShaderBuild = false; bool mPreserveFeedbackOnNextShaderBuild = false;
std::atomic<bool> mUseCommittedLayerStates{ false };
}; };

View File

@@ -3,7 +3,6 @@
#include "RuntimeClock.h" #include "RuntimeClock.h"
#include "RuntimeParameterUtils.h" #include "RuntimeParameterUtils.h"
#include "ShaderCompiler.h"
#include "ShaderPackageRegistry.h" #include "ShaderPackageRegistry.h"
#include <algorithm> #include <algorithm>
@@ -212,42 +211,6 @@ bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType&
return false; return false;
} }
bool TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& 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<ShaderFontAsset>& left, const std::vector<ShaderFontAsset>& 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) std::string ManifestPathMessage(const std::filesystem::path& manifestPath)
{ {
return manifestPath.string(); return manifestPath.string();
@@ -712,126 +675,6 @@ RuntimeHost::RuntimeHost()
{ {
} }
bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error)
{
try
{
std::lock_guard<std::mutex> 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<std::chrono::milliseconds>(now - mLastScanTime).count() < 250)
{
reloadRequested = mReloadRequested;
return true;
}
mLastScanTime = now;
std::string scanError;
std::map<std::string, ShaderPackage> previousPackages = mPackagesById;
std::vector<std::string> previousOrder = mPackageOrder;
std::map<std::string, std::pair<std::filesystem::file_time_type, std::filesystem::file_time_type>> 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<std::mutex> lock(mMutex);
return mReloadRequested;
}
void RuntimeHost::ClearReloadRequest()
{
std::lock_guard<std::mutex> lock(mMutex);
mReloadRequested = false;
}
void RuntimeHost::SetCompileStatus(bool succeeded, const std::string& message)
{
std::lock_guard<std::mutex> lock(mMutex);
mCompileSucceeded = succeeded;
mCompileMessage = message;
}
void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName) void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{ {
const HealthTelemetry::SignalStatusSnapshot previousStatus = mHealthTelemetry.GetSignalStatusSnapshot(); const HealthTelemetry::SignalStatusSnapshot previousStatus = mHealthTelemetry.GetSignalStatusSnapshot();
@@ -921,69 +764,6 @@ bool RuntimeHost::TrySetFramePacingStats(double completionIntervalMilliseconds,
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount); maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
} }
void RuntimeHost::AdvanceFrame()
{
++mFrameCounter;
}
bool RuntimeHost::TryAdvanceFrame()
{
++mFrameCounter;
return true;
}
bool RuntimeHost::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error)
{
try
{
ShaderPackage shaderPackage;
{
std::lock_guard<std::mutex> 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) void RuntimeHost::SetServerPort(unsigned short port)
{ {
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);

View File

@@ -20,12 +20,6 @@ class RuntimeHost
{ {
public: public:
RuntimeHost(); 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); void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
bool TrySetSignalStatus(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, 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); double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
bool TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds, bool TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount); double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
void AdvanceFrame();
bool TryAdvanceFrame();
HealthTelemetry& GetHealthTelemetry() { return mHealthTelemetry; } HealthTelemetry& GetHealthTelemetry() { return mHealthTelemetry; }
const HealthTelemetry& GetHealthTelemetry() const { return mHealthTelemetry; } const HealthTelemetry& GetHealthTelemetry() const { return mHealthTelemetry; }
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& 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& GetRepoRoot() const { return mRepoRoot; }
const std::filesystem::path& GetUiRoot() const { return mUiRoot; } const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
const std::filesystem::path& GetDocsRoot() const { return mDocsRoot; } const std::filesystem::path& GetDocsRoot() const { return mDocsRoot; }

View File

@@ -1,6 +1,7 @@
#include "RuntimeSnapshotProvider.h" #include "RuntimeSnapshotProvider.h"
#include "RuntimeClock.h" #include "RuntimeClock.h"
#include "ShaderCompiler.h"
#include <algorithm> #include <algorithm>
#include <mutex> #include <mutex>
@@ -13,7 +14,57 @@ RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeHost& runtimeHost) :
bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
{ {
return mRuntimeHost.BuildLayerPassFragmentShaderSources(layerId, passSources, error); try
{
ShaderPackage shaderPackage;
{
std::lock_guard<std::mutex> 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 unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
@@ -24,8 +75,8 @@ unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
{ {
RuntimeSnapshotVersions versions; RuntimeSnapshotVersions versions;
versions.renderStateVersion = mRuntimeHost.GetRenderStateVersion(); versions.renderStateVersion = mRuntimeHost.mRenderStateVersion.load(std::memory_order_relaxed);
versions.parameterStateVersion = mRuntimeHost.GetParameterStateVersion(); versions.parameterStateVersion = mRuntimeHost.mParameterStateVersion.load(std::memory_order_relaxed);
return versions; return versions;
} }
@@ -46,12 +97,13 @@ RuntimeRenderFrameContext RuntimeSnapshotProvider::GetFrameContext() const
void RuntimeSnapshotProvider::AdvanceFrame() void RuntimeSnapshotProvider::AdvanceFrame()
{ {
mRuntimeHost.AdvanceFrame(); ++mRuntimeHost.mFrameCounter;
} }
bool RuntimeSnapshotProvider::TryAdvanceFrame() bool RuntimeSnapshotProvider::TryAdvanceFrame()
{ {
return mRuntimeHost.TryAdvanceFrame(); ++mRuntimeHost.mFrameCounter;
return true;
} }
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const

View File

@@ -30,6 +30,42 @@ bool MatchesControlKey(const std::string& candidate, const std::string& key)
{ {
return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key); return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
} }
bool TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& 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<ShaderFontAsset>& left, const std::vector<ShaderFontAsset>& 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) : RuntimeStore::RuntimeStore(RuntimeHost& runtimeHost) :
@@ -94,6 +130,107 @@ std::string RuntimeStore::BuildPersistentStateJson() const
return SerializeJson(BuildRuntimeStateValue(), true); return SerializeJson(BuildRuntimeStateValue(), true);
} }
bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error)
{
try
{
std::lock_guard<std::mutex> 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<std::chrono::milliseconds>(now - mRuntimeHost.mLastScanTime).count() < 250)
{
reloadRequested = mRuntimeHost.mReloadRequested;
return true;
}
mRuntimeHost.mLastScanTime = now;
std::string scanError;
std::map<std::string, ShaderPackage> previousPackages = mRuntimeHost.mPackagesById;
std::vector<std::string> previousOrder = mRuntimeHost.mPackageOrder;
std::map<std::string, std::pair<std::filesystem::file_time_type, std::filesystem::file_time_type>> 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) bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& error)
{ {
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex); std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
@@ -465,12 +602,15 @@ const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const
void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message) void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message)
{ {
mRuntimeHost.SetCompileStatus(succeeded, message); std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
mRuntimeHost.mCompileSucceeded = succeeded;
mRuntimeHost.mCompileMessage = message;
} }
void RuntimeStore::ClearReloadRequest() void RuntimeStore::ClearReloadRequest()
{ {
mRuntimeHost.ClearReloadRequest(); std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
mRuntimeHost.mReloadRequested = false;
} }
JsonValue RuntimeStore::BuildRuntimeStateValue() const JsonValue RuntimeStore::BuildRuntimeStateValue() const

View File

@@ -12,6 +12,7 @@ public:
bool InitializeStore(std::string& error); bool InitializeStore(std::string& error);
std::string BuildPersistentStateJson() const; std::string BuildPersistentStateJson() const;
bool PollStoredFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error);
bool CreateStoredLayer(const std::string& shaderId, std::string& error); bool CreateStoredLayer(const std::string& shaderId, std::string& error);
bool DeleteStoredLayer(const std::string& layerId, std::string& error); bool DeleteStoredLayer(const std::string& layerId, std::string& error);

View File

@@ -177,6 +177,31 @@ void VideoBackend::SetStatusMessage(const std::string& message)
mVideoIODevice->SetStatusMessage(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) void VideoBackend::HandleInputFrame(const VideoIOFrame& frame)
{ {
const VideoIOState& state = mVideoIODevice->State(); const VideoIOState& state = mVideoIODevice->State();

View File

@@ -50,6 +50,8 @@ public:
bool ExternalKeyingActive() const; bool ExternalKeyingActive() const;
const std::string& StatusMessage() const; const std::string& StatusMessage() const;
void SetStatusMessage(const std::string& message); void SetStatusMessage(const std::string& message);
void PublishStatus(bool externalKeyingConfigured, const std::string& statusMessage = std::string());
void ReportNoInputDeviceSignalStatus();
private: private:
void HandleInputFrame(const VideoIOFrame& frame); void HandleInputFrame(const VideoIOFrame& frame);