From 41075bbc610c9b763962821502e7f1addb776cf7 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Sun, 10 May 2026 23:53:27 +1000 Subject: [PATCH] more seperation --- CMakeLists.txt | 2 + .../LoopThroughWithOpenGLCompositing.sln | 28 -- .../LoopThroughWithOpenGLCompositing.vcxproj | 299 ---------------- ...roughWithOpenGLCompositing.vcxproj.filters | 338 ------------------ .../gl/OpenGLComposite.cpp | 227 +++++------- .../gl/OpenGLComposite.h | 12 +- .../gl/OpenGLCompositeRuntimeControls.cpp | 89 ++--- .../gl/RenderEngine.cpp | 139 ++++++- .../gl/RenderEngine.h | 30 +- .../gl/pipeline/OpenGLRenderPipeline.cpp | 13 +- .../gl/pipeline/OpenGLRenderPipeline.h | 9 +- .../gl/pipeline/OpenGLVideoIOBridge.cpp | 64 +--- .../gl/pipeline/OpenGLVideoIOBridge.h | 24 +- .../gl/shader/OpenGLShaderPrograms.cpp | 15 +- .../gl/shader/OpenGLShaderPrograms.h | 4 +- .../gl/shader/ShaderProgramCompiler.cpp | 10 +- .../gl/shader/ShaderProgramCompiler.h | 6 +- .../runtime/RuntimeCoordinator.cpp | 173 +++++++++ .../runtime/RuntimeCoordinator.h | 70 ++++ .../runtime/RuntimeSnapshotProvider.cpp | 15 + .../runtime/RuntimeSnapshotProvider.h | 3 + .../videoio/VideoBackend.cpp | 71 +++- .../videoio/VideoBackend.h | 14 + .../videoio/decklink/DeckLinkSession.cpp | 103 +++--- .../videoio/decklink/DeckLinkSession.h | 6 + 25 files changed, 733 insertions(+), 1031 deletions(-) delete mode 100644 apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.sln delete mode 100644 apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj delete mode 100644 apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters create mode 100644 apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a50320..8e17a13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,8 @@ set(APP_SOURCES "${APP_DIR}/runtime/RuntimeHost.h" "${APP_DIR}/runtime/HealthTelemetry.cpp" "${APP_DIR}/runtime/HealthTelemetry.h" + "${APP_DIR}/runtime/RuntimeCoordinator.cpp" + "${APP_DIR}/runtime/RuntimeCoordinator.h" "${APP_DIR}/runtime/RuntimeSnapshotProvider.cpp" "${APP_DIR}/runtime/RuntimeSnapshotProvider.h" "${APP_DIR}/runtime/RuntimeClock.cpp" diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.sln b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.sln deleted file mode 100644 index e4f57c3..0000000 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.21005.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LoopThroughWithOpenGLCompositing", "LoopThroughWithOpenGLCompositing.vcxproj", "{92C79085-CA51-4008-95DB-5403D2E19885}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {92C79085-CA51-4008-95DB-5403D2E19885}.Debug|Win32.ActiveCfg = Debug|Win32 - {92C79085-CA51-4008-95DB-5403D2E19885}.Debug|Win32.Build.0 = Debug|Win32 - {92C79085-CA51-4008-95DB-5403D2E19885}.Debug|x64.ActiveCfg = Debug|x64 - {92C79085-CA51-4008-95DB-5403D2E19885}.Debug|x64.Build.0 = Debug|x64 - {92C79085-CA51-4008-95DB-5403D2E19885}.Release|Win32.ActiveCfg = Release|Win32 - {92C79085-CA51-4008-95DB-5403D2E19885}.Release|Win32.Build.0 = Release|Win32 - {92C79085-CA51-4008-95DB-5403D2E19885}.Release|x64.ActiveCfg = Release|x64 - {92C79085-CA51-4008-95DB-5403D2E19885}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj deleted file mode 100644 index e4e4868..0000000 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj +++ /dev/null @@ -1,299 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {92C79085-CA51-4008-95DB-5403D2E19885} - LoopThroughWithOpenGLCompositing - Win32Proj - 10.0.26100.0 - - - - Application - v143 - MultiByte - true - - - Application - v143 - MultiByte - - - Application - v143 - MultiByte - true - - - Application - v143 - MultiByte - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>12.0.21005.1 - - - $(SolutionDir)$(Configuration)\ - $(Configuration)\ - true - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - true - - - $(SolutionDir)$(Configuration)\ - $(Configuration)\ - false - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - false - - - - Disabled - .;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebugDLL - - Level3 - EditAndContinue - stdcpp17 - - - opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies) - true - Windows - MachineX86 - - - - - X64 - - - Disabled - .;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebugDLL - - Level3 - ProgramDatabase - stdcpp17 - - - opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies) - true - Windows - MachineX64 - - - - - MaxSpeed - true - .;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - MultiThreadedDLL - true - - Level3 - ProgramDatabase - stdcpp17 - - - opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies) - true - Windows - true - true - MachineX86 - - - - - X64 - - - MaxSpeed - true - .;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - MultiThreadedDLL - true - - Level3 - ProgramDatabase - stdcpp17 - - - opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies) - true - Windows - true - true - MachineX64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - Create - Create - Create - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters deleted file mode 100644 index b745b94..0000000 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters +++ /dev/null @@ -1,338 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav - - - {1eab21d6-58f8-49e0-929b-8a4482e04756} - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - DeckLink API - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Resource Files - - - Resource Files - - - - - Resource Files - - - - - DeckLink API - - - - - Resource Files - - - diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index d24f81e..bcdad27 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -101,9 +101,10 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : mRuntimeHost = std::make_unique(); mRuntimeStore = std::make_unique(*mRuntimeHost); mRuntimeSnapshotProvider = std::make_unique(*mRuntimeHost); + mRuntimeCoordinator = std::make_unique(*mRuntimeStore); mRenderEngine = std::make_unique( - *mRuntimeHost, *mRuntimeSnapshotProvider, + mRuntimeHost->GetHealthTelemetry(), pMutex, hGLDC, hGLRC, @@ -202,7 +203,11 @@ bool OpenGLComposite::InitVideoIO() } if (!mVideoBackend->HasInputDevice() && mRuntimeHost) { - mRuntimeHost->SetSignalStatus(false, mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), mVideoBackend->InputDisplayModeName()); + mRuntimeHost->GetHealthTelemetry().ReportSignalStatus( + false, + mVideoBackend->InputFrameWidth(), + mVideoBackend->InputFrameHeight(), + mVideoBackend->InputDisplayModeName()); } if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason)) @@ -227,27 +232,15 @@ void OpenGLComposite::paintGL(bool force) { if (IsIconic(hGLWnd)) return; - - const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetConfiguredPreviewFps() : 30u; - if (previewFps == 0) - return; - - const auto now = std::chrono::steady_clock::now(); - const auto minimumInterval = std::chrono::microseconds(1000000 / (previewFps == 0 ? 1u : previewFps)); - if (mLastPreviewPresentTime != std::chrono::steady_clock::time_point() && - now - mLastPreviewPresentTime < minimumInterval) - { - return; - } } - if (!mRenderEngine->TryPresentToWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight())) + const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetConfiguredPreviewFps() : 30u; + if (!mRenderEngine->TryPresentPreview(force, previewFps, mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight())) { ValidateRect(hGLWnd, NULL); return; } - mLastPreviewPresentTime = std::chrono::steady_clock::now(); ValidateRect(hGLWnd, NULL); } @@ -336,7 +329,7 @@ bool OpenGLComposite::InitOpenGLState() MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK); return false; } - mCachedLayerRenderStates = mRenderEngine->CommittedLayerStates(); + mRuntimeStore->SetCompileStatus(true, "Shader layers compiled successfully."); mUseCommittedLayerStates = false; mRenderEngine->ResetTemporalHistoryState(); @@ -367,15 +360,8 @@ bool OpenGLComposite::Stop() bool OpenGLComposite::ReloadShader(bool preserveFeedbackState) { - mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState; - if (mRuntimeHost) - { - mRuntimeStore->SetCompileStatus(true, "Shader rebuild queued."); - mRuntimeStore->ClearReloadRequest(); - } - RequestShaderBuild(); - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RequestShaderReload(preserveFeedbackState)); } bool OpenGLComposite::RequestScreenshot(std::string& error) @@ -442,7 +428,7 @@ void OpenGLComposite::renderEffect() const auto applyOscOverlays = [&](std::vector& states, bool allowCommit) { - if (states.empty() || mOscOverlayStates.empty() || !mRuntimeHost) + if (states.empty() || mOscOverlayStates.empty()) return; const double smoothing = ClampOscAlpha(mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0); @@ -576,66 +562,12 @@ void OpenGLComposite::renderEffect() const bool hasInputSource = mVideoBackend->HasInputSource(); std::vector layerStates; - if (mUseCommittedLayerStates) - { - layerStates = mRenderEngine->CommittedLayerStates(); - applyOscOverlays(layerStates, false); - if (mRuntimeSnapshotProvider) - mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates); - } - else if (mRuntimeSnapshotProvider) - { - const unsigned renderWidth = mVideoBackend->InputFrameWidth(); - const unsigned renderHeight = mVideoBackend->InputFrameHeight(); - const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider->GetVersions(); - const bool renderStateCacheValid = - !mCachedLayerRenderStates.empty() && - mCachedRenderStateVersion == versions.renderStateVersion && - mCachedRenderStateWidth == renderWidth && - mCachedRenderStateHeight == renderHeight; - - if (renderStateCacheValid) - { - RuntimeRenderStateSnapshot renderSnapshot; - renderSnapshot.outputWidth = renderWidth; - renderSnapshot.outputHeight = renderHeight; - renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion; - renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion; - renderSnapshot.states = mCachedLayerRenderStates; - - applyOscOverlays(renderSnapshot.states, true); - if (mCachedParameterStateVersion != versions.parameterStateVersion && - mRuntimeSnapshotProvider->TryRefreshSnapshotParameters(renderSnapshot)) - { - mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; - applyOscOverlays(renderSnapshot.states, true); - } - - mCachedLayerRenderStates = renderSnapshot.states; - layerStates = renderSnapshot.states; - mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates); - } - else - { - RuntimeRenderStateSnapshot renderSnapshot; - if (mRuntimeSnapshotProvider->TryGetRenderStateSnapshot(renderWidth, renderHeight, renderSnapshot)) - { - mCachedLayerRenderStates = renderSnapshot.states; - mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion; - mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; - mCachedRenderStateWidth = renderSnapshot.outputWidth; - mCachedRenderStateHeight = renderSnapshot.outputHeight; - applyOscOverlays(mCachedLayerRenderStates, true); - layerStates = mCachedLayerRenderStates; - } - else - { - applyOscOverlays(mCachedLayerRenderStates, true); - layerStates = mCachedLayerRenderStates; - mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates); - } - } - } + mRenderEngine->ResolveRenderLayerStates( + mUseCommittedLayerStates.load(), + mVideoBackend->InputFrameWidth(), + mVideoBackend->InputFrameHeight(), + applyOscOverlays, + layerStates); const unsigned historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0; mRenderEngine->RenderLayerStack( hasInputSource, @@ -657,20 +589,9 @@ void OpenGLComposite::ProcessScreenshotRequest() if (width == 0 || height == 0) return; - std::vector bottomUpPixels; - if (!mRenderEngine->ReadOutputFrameRgba(width, height, bottomUpPixels)) + std::vector topDownPixels; + if (!mRenderEngine->CaptureOutputFrameRgbaTopDown(width, height, topDownPixels)) return; - std::vector topDownPixels(bottomUpPixels.size()); - - const std::size_t rowBytes = static_cast(width) * 4; - for (unsigned y = 0; y < height; ++y) - { - const unsigned sourceY = height - 1 - y; - std::copy( - bottomUpPixels.begin() + static_cast(sourceY * rowBytes), - bottomUpPixels.begin() + static_cast((sourceY + 1) * rowBytes), - topDownPixels.begin() + static_cast(y * rowBytes)); - } try { @@ -707,14 +628,13 @@ std::filesystem::path OpenGLComposite::BuildScreenshotPath() const bool OpenGLComposite::ProcessRuntimePollResults() { - if (!mRuntimeHost || !mRuntimeServices) + if (!mRuntimeServices) return true; const RuntimePollEvents events = mRuntimeServices->ConsumePollEvents(); if (events.failed) { - mRuntimeStore->SetCompileStatus(false, events.error); - broadcastRuntimeState(); + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandleRuntimePollFailure(events.error)); return false; } @@ -728,29 +648,23 @@ bool OpenGLComposite::ProcessRuntimePollResults() return true; char compilerErrorMessage[1024] = {}; - if (!mRenderEngine->CommitPreparedLayerPrograms(readyBuild, mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage)) + if (!mRenderEngine->ApplyPreparedShaderBuild( + readyBuild, + mVideoBackend->InputFrameWidth(), + mVideoBackend->InputFrameHeight(), + mRuntimeCoordinator && mRuntimeCoordinator->PreserveFeedbackOnNextShaderBuild(), + sizeof(compilerErrorMessage), + compilerErrorMessage)) { - mRuntimeStore->SetCompileStatus(false, compilerErrorMessage); - mUseCommittedLayerStates = true; - mPreserveFeedbackOnNextShaderBuild = false; - broadcastRuntimeState(); + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildFailure(compilerErrorMessage)); return false; } - mUseCommittedLayerStates = false; - mCachedLayerRenderStates = mRenderEngine->CommittedLayerStates(); - mRenderEngine->ResetTemporalHistoryState(); - if (!mPreserveFeedbackOnNextShaderBuild) - mRenderEngine->ResetShaderFeedbackState(); - mPreserveFeedbackOnNextShaderBuild = false; - broadcastRuntimeState(); + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandlePreparedShaderBuildSuccess()); return true; } - mRuntimeStore->SetCompileStatus(true, "Shader rebuild queued."); - mPreserveFeedbackOnNextShaderBuild = false; - RequestShaderBuild(); - broadcastRuntimeState(); + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandleRuntimeReloadRequest()); return true; } @@ -759,24 +673,81 @@ void OpenGLComposite::RequestShaderBuild() if (!mShaderBuildQueue || !mVideoBackend) return; - mUseCommittedLayerStates = true; - if (mRuntimeHost) - mRuntimeStore->ClearReloadRequest(); mShaderBuildQueue->RequestBuild(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight()); } +bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error) +{ + if (!result.accepted) + { + if (error) + *error = result.errorMessage; + return false; + } + + if (result.compileStatusChanged && mRuntimeStore) + mRuntimeStore->SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage); + + if (result.clearReloadRequest && mRuntimeStore) + mRuntimeStore->ClearReloadRequest(); + + switch (result.committedStateMode) + { + case RuntimeCoordinatorCommittedStateMode::UseCommittedStates: + mUseCommittedLayerStates = true; + break; + case RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots: + mUseCommittedLayerStates = false; + break; + case RuntimeCoordinatorCommittedStateMode::Unchanged: + default: + break; + } + + if (result.clearTransientOscState) + { + mOscOverlayStates.clear(); + if (mRuntimeServices) + mRuntimeServices->ClearOscState(); + } + + ApplyRuntimeCoordinatorRenderReset(result.renderResetScope); + + if (result.shaderBuildRequested) + RequestShaderBuild(); + + if (result.runtimeStateBroadcastRequired) + broadcastRuntimeState(); + + 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() { if (mRuntimeServices) mRuntimeServices->BroadcastState(); } -void OpenGLComposite::resetTemporalHistoryState() -{ - mRenderEngine->ResetTemporalHistoryState(); - mRenderEngine->ResetShaderFeedbackState(); -} - bool OpenGLComposite::CheckOpenGLExtensions() { return true; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h index a50c5a8..a12bdac 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h @@ -12,6 +12,7 @@ #include #include "GLExtensions.h" +#include "RuntimeCoordinator.h" #include "RuntimeHost.h" #include "RuntimeSnapshotProvider.h" #include "RuntimeStore.h" @@ -91,30 +92,25 @@ private: std::unique_ptr mRuntimeHost; std::unique_ptr mRuntimeStore; + std::unique_ptr mRuntimeCoordinator; std::unique_ptr mRuntimeSnapshotProvider; std::unique_ptr mRenderEngine; std::unique_ptr mShaderBuildQueue; std::unique_ptr mRuntimeServices; std::unique_ptr mVideoBackend; - std::vector mCachedLayerRenderStates; - uint64_t mCachedRenderStateVersion = 0; - uint64_t mCachedParameterStateVersion = 0; - unsigned mCachedRenderStateWidth = 0; - unsigned mCachedRenderStateHeight = 0; std::map mOscOverlayStates; std::atomic mUseCommittedLayerStates; std::atomic mScreenshotRequested; - std::chrono::steady_clock::time_point mLastPreviewPresentTime; - bool mPreserveFeedbackOnNextShaderBuild = false; bool InitOpenGLState(); void renderEffect(); bool ProcessRuntimePollResults(); void RequestShaderBuild(); + bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr); + void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope); void ProcessScreenshotRequest(); std::filesystem::path BuildScreenshotPath() const; void broadcastRuntimeState(); - void resetTemporalHistoryState(); }; #endif // __OPENGL_COMPOSITE_H__ diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp index 970c710..6a1b804 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLCompositeRuntimeControls.cpp @@ -38,62 +38,38 @@ std::string OpenGLComposite::GetOscAddress() const bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error) { - if (!mRuntimeStore->CreateStoredLayer(shaderId, error)) - return false; - - ReloadShader(true); - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->AddLayer(shaderId), &error); } bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error) { - if (!mRuntimeStore->DeleteStoredLayer(layerId, error)) - return false; - - ReloadShader(true); - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RemoveLayer(layerId), &error); } bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error) { - if (!mRuntimeStore->MoveStoredLayer(layerId, direction, error)) - return false; - - ReloadShader(true); - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayer(layerId, direction), &error); } bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error) { - if (!mRuntimeStore->MoveStoredLayerToIndex(layerId, targetIndex, error)) - return false; - - ReloadShader(true); - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayerToIndex(layerId, targetIndex), &error); } bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error) { - if (!mRuntimeStore->SetStoredLayerBypassState(layerId, bypassed, error)) - return false; - - ReloadShader(); - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerBypass(layerId, bypassed), &error); } bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error) { - if (!mRuntimeStore->SetStoredLayerShaderSelection(layerId, shaderId, error)) - return false; - - ReloadShader(); - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerShader(layerId, shaderId), &error); } bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error) @@ -102,11 +78,8 @@ bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const if (!ParseJson(valueJson, parsedValue, error)) return false; - if (!mRuntimeStore->SetStoredParameterValue(layerId, parameterId, parsedValue, error)) - return false; - - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameter(layerId, parameterId, parsedValue), &error); } bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error) @@ -115,42 +88,24 @@ bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& la if (!ParseJson(valueJson, parsedValue, error)) return false; - if (!mRuntimeStore->SetStoredParameterValueByControlKey(layerKey, parameterKey, parsedValue, error)) - return false; - - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue), &error); } bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error) { - if (!mRuntimeStore->ResetStoredLayerParameterValues(layerId, error)) - return false; - - mOscOverlayStates.clear(); - if (mRuntimeServices) - mRuntimeServices->ClearOscState(); - resetTemporalHistoryState(); - - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->ResetLayerParameters(layerId), &error); } bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error) { - if (!mRuntimeStore->SaveStackPresetSnapshot(presetName, error)) - return false; - - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SaveStackPreset(presetName), &error); } bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error) { - if (!mRuntimeStore->LoadStackPresetSnapshot(presetName, error)) - return false; - - ReloadShader(); - broadcastRuntimeState(); - return true; + return mRuntimeCoordinator && + ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->LoadStackPreset(presetName), &error); } diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp index 0e836d4..948a56a 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp @@ -2,9 +2,12 @@ #include +#include +#include + RenderEngine::RenderEngine( - RuntimeHost& runtimeHost, RuntimeSnapshotProvider& runtimeSnapshotProvider, + HealthTelemetry& healthTelemetry, CRITICAL_SECTION& mutex, HDC hdc, HGLRC hglrc, @@ -13,8 +16,9 @@ RenderEngine::RenderEngine( PreviewPaintCallback previewPaint) : mRenderer(), mRenderPass(mRenderer), - mRenderPipeline(mRenderer, runtimeHost, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)), - mShaderPrograms(mRenderer, runtimeHost, runtimeSnapshotProvider), + mRenderPipeline(mRenderer, runtimeSnapshotProvider, healthTelemetry, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)), + mShaderPrograms(mRenderer, runtimeSnapshotProvider), + mRuntimeSnapshotProvider(runtimeSnapshotProvider), mMutex(mutex), mHdc(hdc), mHglrc(hglrc) @@ -65,6 +69,28 @@ bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& prepar return mShaderPrograms.CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage); } +bool RenderEngine::ApplyPreparedShaderBuild( + const PreparedShaderBuild& preparedBuild, + unsigned inputFrameWidth, + unsigned inputFrameHeight, + bool preserveFeedbackState, + int errorMessageSize, + char* errorMessage) +{ + if (!CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage)) + return false; + + mCachedLayerRenderStates = mShaderPrograms.CommittedLayerStates(); + mCachedRenderStateVersion = preparedBuild.renderSnapshot.versions.renderStateVersion; + mCachedParameterStateVersion = preparedBuild.renderSnapshot.versions.parameterStateVersion; + mCachedRenderStateWidth = preparedBuild.renderSnapshot.outputWidth; + mCachedRenderStateHeight = preparedBuild.renderSnapshot.outputHeight; + ResetTemporalHistoryState(); + if (!preserveFeedbackState) + ResetShaderFeedbackState(); + return true; +} + const std::vector& RenderEngine::CommittedLayerStates() const { return mShaderPrograms.CommittedLayerStates(); @@ -85,12 +111,27 @@ void RenderEngine::ResizeView(int width, int height) mRenderer.ResizeView(width, height); } -bool RenderEngine::TryPresentToWindow(unsigned outputFrameWidth, unsigned outputFrameHeight) +bool RenderEngine::TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight) { + if (!force) + { + if (previewFps == 0) + return false; + + const auto now = std::chrono::steady_clock::now(); + const auto minimumInterval = std::chrono::microseconds(1000000 / (previewFps == 0 ? 1u : previewFps)); + if (mLastPreviewPresentTime != std::chrono::steady_clock::time_point() && + now - mLastPreviewPresentTime < minimumInterval) + { + return false; + } + } + if (!TryEnterCriticalSection(&mMutex)) return false; mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight); + mLastPreviewPresentTime = std::chrono::steady_clock::now(); LeaveCriticalSection(&mMutex); return true; } @@ -133,6 +174,76 @@ bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, return rendered; } +bool RenderEngine::ResolveRenderLayerStates( + bool useCommittedLayerStates, + unsigned renderWidth, + unsigned renderHeight, + OverlayApplier overlayApplier, + std::vector& layerStates) +{ + layerStates.clear(); + if (useCommittedLayerStates) + { + layerStates = mShaderPrograms.CommittedLayerStates(); + if (overlayApplier) + overlayApplier(layerStates, false); + mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); + return true; + } + + const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions(); + const bool renderStateCacheValid = + !mCachedLayerRenderStates.empty() && + mCachedRenderStateVersion == versions.renderStateVersion && + mCachedRenderStateWidth == renderWidth && + mCachedRenderStateHeight == renderHeight; + + if (renderStateCacheValid) + { + RuntimeRenderStateSnapshot renderSnapshot; + renderSnapshot.outputWidth = renderWidth; + renderSnapshot.outputHeight = renderHeight; + renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion; + renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion; + renderSnapshot.states = mCachedLayerRenderStates; + + if (overlayApplier) + overlayApplier(renderSnapshot.states, true); + if (mCachedParameterStateVersion != versions.parameterStateVersion && + mRuntimeSnapshotProvider.TryRefreshSnapshotParameters(renderSnapshot)) + { + mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; + if (overlayApplier) + overlayApplier(renderSnapshot.states, true); + } + + mCachedLayerRenderStates = renderSnapshot.states; + layerStates = renderSnapshot.states; + mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); + return true; + } + + RuntimeRenderStateSnapshot renderSnapshot; + if (mRuntimeSnapshotProvider.TryGetRenderStateSnapshot(renderWidth, renderHeight, renderSnapshot)) + { + mCachedLayerRenderStates = renderSnapshot.states; + mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion; + mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; + mCachedRenderStateWidth = renderSnapshot.outputWidth; + mCachedRenderStateHeight = renderSnapshot.outputHeight; + if (overlayApplier) + overlayApplier(mCachedLayerRenderStates, true); + layerStates = mCachedLayerRenderStates; + return true; + } + + if (overlayApplier) + overlayApplier(mCachedLayerRenderStates, true); + layerStates = mCachedLayerRenderStates; + mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); + return !layerStates.empty(); +} + void RenderEngine::RenderLayerStack( bool hasInputSource, const std::vector& layerStates, @@ -178,3 +289,23 @@ bool RenderEngine::ReadOutputFrameRgba(unsigned width, unsigned height, std::vec LeaveCriticalSection(&mMutex); return true; } + +bool RenderEngine::CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector& topDownPixels) +{ + std::vector bottomUpPixels; + if (!ReadOutputFrameRgba(width, height, bottomUpPixels)) + return false; + + topDownPixels.resize(bottomUpPixels.size()); + const std::size_t rowBytes = static_cast(width) * 4; + for (unsigned y = 0; y < height; ++y) + { + const unsigned sourceY = height - 1 - y; + std::copy( + bottomUpPixels.begin() + static_cast(sourceY * rowBytes), + bottomUpPixels.begin() + static_cast((sourceY + 1) * rowBytes), + topDownPixels.begin() + static_cast(y * rowBytes)); + } + + return true; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h index f9007f3..f6b0c4b 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h @@ -4,11 +4,13 @@ #include "OpenGLRenderPipeline.h" #include "OpenGLRenderer.h" #include "OpenGLShaderPrograms.h" -#include "RuntimeHost.h" +#include "HealthTelemetry.h" #include "RuntimeSnapshotProvider.h" #include +#include +#include #include #include #include @@ -19,10 +21,11 @@ public: using RenderEffectCallback = std::function; using ScreenshotCallback = std::function; using PreviewPaintCallback = std::function; + using OverlayApplier = std::function& states, bool allowCommit)>; RenderEngine( - RuntimeHost& runtimeHost, RuntimeSnapshotProvider& runtimeSnapshotProvider, + HealthTelemetry& healthTelemetry, CRITICAL_SECTION& mutex, HDC hdc, HGLRC hglrc, @@ -43,14 +46,27 @@ public: std::string& error); bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); + bool ApplyPreparedShaderBuild( + const PreparedShaderBuild& preparedBuild, + unsigned inputFrameWidth, + unsigned inputFrameHeight, + bool preserveFeedbackState, + int errorMessageSize, + char* errorMessage); const std::vector& CommittedLayerStates() const; void ResetTemporalHistoryState(); void ResetShaderFeedbackState(); void ResizeView(int width, int height); - bool TryPresentToWindow(unsigned outputFrameWidth, unsigned outputFrameHeight); + bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight); bool TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState); bool RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame); + bool ResolveRenderLayerStates( + bool useCommittedLayerStates, + unsigned renderWidth, + unsigned renderHeight, + OverlayApplier overlayApplier, + std::vector& layerStates); void RenderLayerStack( bool hasInputSource, const std::vector& layerStates, @@ -60,13 +76,21 @@ public: VideoIOPixelFormat inputPixelFormat, unsigned historyCap); bool ReadOutputFrameRgba(unsigned width, unsigned height, std::vector& bottomUpPixels); + bool CaptureOutputFrameRgbaTopDown(unsigned width, unsigned height, std::vector& topDownPixels); private: OpenGLRenderer mRenderer; OpenGLRenderPass mRenderPass; OpenGLRenderPipeline mRenderPipeline; OpenGLShaderPrograms mShaderPrograms; + RuntimeSnapshotProvider& mRuntimeSnapshotProvider; CRITICAL_SECTION& mMutex; HDC mHdc; HGLRC mHglrc; + std::vector mCachedLayerRenderStates; + uint64_t mCachedRenderStateVersion = 0; + uint64_t mCachedParameterStateVersion = 0; + unsigned mCachedRenderStateWidth = 0; + unsigned mCachedRenderStateHeight = 0; + std::chrono::steady_clock::time_point mLastPreviewPresentTime; }; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.cpp index 7328f9d..3eb5061 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.cpp @@ -1,7 +1,8 @@ #include "OpenGLRenderPipeline.h" +#include "HealthTelemetry.h" #include "OpenGLRenderer.h" -#include "RuntimeHost.h" +#include "RuntimeSnapshotProvider.h" #include "VideoIOFormat.h" #include @@ -11,12 +12,14 @@ OpenGLRenderPipeline::OpenGLRenderPipeline( OpenGLRenderer& renderer, - RuntimeHost& runtimeHost, + RuntimeSnapshotProvider& runtimeSnapshotProvider, + HealthTelemetry& healthTelemetry, RenderEffectCallback renderEffect, OutputReadyCallback outputReady, PaintCallback paint) : mRenderer(renderer), - mRuntimeHost(runtimeHost), + mRuntimeSnapshotProvider(runtimeSnapshotProvider), + mHealthTelemetry(healthTelemetry), mRenderEffect(renderEffect), mOutputReady(outputReady), mPaint(paint) @@ -47,8 +50,8 @@ bool OpenGLRenderPipeline::RenderFrame(const RenderPipelineFrameContext& context const auto renderEndTime = std::chrono::steady_clock::now(); const double renderMilliseconds = std::chrono::duration_cast>(renderEndTime - renderStartTime).count(); - mRuntimeHost.GetHealthTelemetry().TryRecordPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds); - mRuntimeHost.TryAdvanceFrame(); + mHealthTelemetry.TryRecordPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds); + mRuntimeSnapshotProvider.TryAdvanceFrame(); ReadOutputFrame(state, outputFrame); if (mPaint) diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.h b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.h index 070c541..4fc6cce 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPipeline.h @@ -8,7 +8,8 @@ #include class OpenGLRenderer; -class RuntimeHost; +class HealthTelemetry; +class RuntimeSnapshotProvider; struct RenderPipelineFrameContext { @@ -25,7 +26,8 @@ public: OpenGLRenderPipeline( OpenGLRenderer& renderer, - RuntimeHost& runtimeHost, + RuntimeSnapshotProvider& runtimeSnapshotProvider, + HealthTelemetry& healthTelemetry, RenderEffectCallback renderEffect, OutputReadyCallback outputReady, PaintCallback paint); @@ -53,7 +55,8 @@ private: void ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame); OpenGLRenderer& mRenderer; - RuntimeHost& mRuntimeHost; + RuntimeSnapshotProvider& mRuntimeSnapshotProvider; + HealthTelemetry& mHealthTelemetry; RenderEffectCallback mRenderEffect; OutputReadyCallback mOutputReady; PaintCallback mPaint; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp index c819d08..ccba9a2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp @@ -1,81 +1,25 @@ #include "OpenGLVideoIOBridge.h" -#include "HealthTelemetry.h" #include "RenderEngine.h" -#include - -OpenGLVideoIOBridge::OpenGLVideoIOBridge( - VideoIODevice& videoIO, - RenderEngine& renderEngine, - HealthTelemetry& healthTelemetry) : - mVideoIO(videoIO), - mRenderEngine(renderEngine), - mHealthTelemetry(healthTelemetry) +OpenGLVideoIOBridge::OpenGLVideoIOBridge(RenderEngine& renderEngine) : + mRenderEngine(renderEngine) { } -void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionResult) +void OpenGLVideoIOBridge::UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state) { - 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 == VideoIOCompletionResult::DisplayedLate) - ++mLateFrameCount; - else if (completionResult == VideoIOCompletionResult::Dropped) - ++mDroppedFrameCount; - else if (completionResult == VideoIOCompletionResult::Flushed) - ++mFlushedFrameCount; - - mHealthTelemetry.TryRecordFramePacingStats( - mCompletionIntervalMilliseconds, - mSmoothedCompletionIntervalMilliseconds, - mMaxCompletionIntervalMilliseconds, - mLateFrameCount, - mDroppedFrameCount, - mFlushedFrameCount); -} - -void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame) -{ - const VideoIOState& state = mVideoIO.State(); - mHealthTelemetry.TryReportSignalStatus(!inputFrame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName); - if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr) return; // don't transfer texture when there's no input mRenderEngine.TryUploadInputFrame(inputFrame, state); } -void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& completion) +void OpenGLVideoIOBridge::RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame) { - RecordFramePacing(completion.result); - - VideoIOOutputFrame outputFrame; - if (!mVideoIO.BeginOutputFrame(outputFrame)) - return; - const VideoIOState& state = mVideoIO.State(); RenderPipelineFrameContext frameContext; frameContext.videoState = state; frameContext.completion = completion; mRenderEngine.RenderOutputFrame(frameContext, outputFrame); - - mVideoIO.EndOutputFrame(outputFrame); - - mVideoIO.AccountForCompletionResult(completion.result); - - // Schedule the next frame for playout after the GL bridge is released so - // input uploads are not blocked by non-GL output bookkeeping. - mVideoIO.ScheduleOutputFrame(outputFrame); } diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h index 9f1db55..94525c9 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h @@ -2,34 +2,16 @@ #include "OpenGLRenderPipeline.h" -#include -#include - -class HealthTelemetry; class RenderEngine; class OpenGLVideoIOBridge { public: - OpenGLVideoIOBridge( - VideoIODevice& videoIO, - RenderEngine& renderEngine, - HealthTelemetry& healthTelemetry); + explicit OpenGLVideoIOBridge(RenderEngine& renderEngine); - void VideoFrameArrived(const VideoIOFrame& inputFrame); - void PlayoutFrameCompleted(const VideoIOCompletion& completion); + void UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state); + void RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame); private: - void RecordFramePacing(VideoIOCompletionResult completionResult); - - VideoIODevice& mVideoIO; RenderEngine& mRenderEngine; - HealthTelemetry& mHealthTelemetry; - 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/gl/shader/OpenGLShaderPrograms.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp index 894ecfe..31525e4 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp @@ -29,12 +29,11 @@ std::size_t RequiredTemporaryRenderTargets(const std::vector& layerStates = renderSnapshot.states; std::string temporalError; - const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames(); + const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames(); if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError)) { CopyErrorMessage(temporalError, errorMessageSize, errorMessage); @@ -92,9 +91,6 @@ bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsign mRenderer.ReplaceLayerPrograms(newPrograms); mCommittedLayerStates = renderSnapshot.states; - mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully."); - mRuntimeHost.ClearReloadRequest(); - return true; } @@ -107,7 +103,7 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild } std::string temporalError; - const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames(); + const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames(); if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.renderSnapshot.states, historyCap, temporalError)) { CopyErrorMessage(temporalError, errorMessageSize, errorMessage); @@ -155,9 +151,6 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild mRenderer.ReplaceLayerPrograms(newPrograms); mCommittedLayerStates = preparedBuild.renderSnapshot.states; - mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully."); - mRuntimeHost.ClearReloadRequest(); - return true; } diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.h b/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.h index 20b1f16..b1458da 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.h @@ -2,7 +2,6 @@ #include "GlobalParamsBuffer.h" #include "OpenGLRenderer.h" -#include "RuntimeHost.h" #include "RuntimeSnapshotProvider.h" #include "ShaderBuildQueue.h" #include "ShaderTypes.h" @@ -16,7 +15,7 @@ class OpenGLShaderPrograms public: using LayerProgram = OpenGLRenderer::LayerProgram; - OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, RuntimeSnapshotProvider& runtimeSnapshotProvider); + OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider); bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); @@ -33,7 +32,6 @@ public: private: OpenGLRenderer& mRenderer; - RuntimeHost& mRuntimeHost; RuntimeSnapshotProvider& mRuntimeSnapshotProvider; ShaderTextureBindings mTextureBindings; GlobalParamsBuffer mGlobalParamsBuffer; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderProgramCompiler.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderProgramCompiler.cpp index 9d80e97..0be6d62 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderProgramCompiler.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderProgramCompiler.cpp @@ -19,9 +19,9 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er } } -ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings) : +ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings) : mRenderer(renderer), - mRuntimeHost(runtimeHost), + mRuntimeSnapshotProvider(runtimeSnapshotProvider), mTextureBindings(textureBindings) { } @@ -31,7 +31,7 @@ bool ShaderProgramCompiler::CompileLayerProgram(const RuntimeRenderState& state, std::vector passSources; std::string loadError; - if (!mRuntimeHost.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError)) + if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError)) { CopyErrorMessage(loadError, errorMessageSize, errorMessage); return false; @@ -117,7 +117,7 @@ bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState passProgram.passId = passSource.passId; passProgram.inputNames = passSource.inputNames; passProgram.outputName = passSource.outputName; - passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeHost.GetMaxTemporalHistoryFrames()); + passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames()); passProgram.textureBindings.swap(textureBindings); passProgram.textBindings.swap(textBindings); @@ -125,7 +125,7 @@ bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState if (globalParamsIndex != GL_INVALID_INDEX) glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint); - const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames(); + const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames(); glUseProgram(newProgram.get()); mTextureBindings.AssignLayerSamplerUniforms(newProgram.get(), state, passProgram, historyCap); glUseProgram(0); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderProgramCompiler.h b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderProgramCompiler.h index db2e38f..82e05af 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderProgramCompiler.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/shader/ShaderProgramCompiler.h @@ -1,7 +1,7 @@ #pragma once #include "OpenGLRenderer.h" -#include "RuntimeHost.h" +#include "RuntimeSnapshotProvider.h" #include "ShaderTextureBindings.h" #include @@ -13,7 +13,7 @@ public: using LayerProgram = OpenGLRenderer::LayerProgram; using PassProgram = OpenGLRenderer::LayerProgram::PassProgram; - ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings); + ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings); bool CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage); bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage); @@ -22,6 +22,6 @@ public: private: OpenGLRenderer& mRenderer; - RuntimeHost& mRuntimeHost; + RuntimeSnapshotProvider& mRuntimeSnapshotProvider; ShaderTextureBindings& mTextureBindings; }; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp new file mode 100644 index 0000000..52f273f --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp @@ -0,0 +1,173 @@ +#include "RuntimeCoordinator.h" + +#include "RuntimeStore.h" + +RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore) : + mRuntimeStore(runtimeStore) +{ +} + +RuntimeCoordinatorResult RuntimeCoordinator::AddLayer(const std::string& shaderId) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.CreateStoredLayer(shaderId, error), error, true, true); +} + +RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& layerId) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true); +} + +RuntimeCoordinatorResult RuntimeCoordinator::MoveLayer(const std::string& layerId, int direction) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.MoveStoredLayer(layerId, direction, error), error, true, true); +} + +RuntimeCoordinatorResult RuntimeCoordinator::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.MoveStoredLayerToIndex(layerId, targetIndex, error), error, true, true); +} + +RuntimeCoordinatorResult RuntimeCoordinator::SetLayerBypass(const std::string& layerId, bool bypassed) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.SetStoredLayerBypassState(layerId, bypassed, error), error, true, false); +} + +RuntimeCoordinatorResult RuntimeCoordinator::SetLayerShader(const std::string& layerId, const std::string& shaderId) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.SetStoredLayerShaderSelection(layerId, shaderId, error), error, true, false); +} + +RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(layerId, parameterId, newValue, error), error, false, false); +} + +RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValueByControlKey(layerKey, parameterKey, newValue, error), error, false, false); +} + +RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::string& layerId) +{ + std::string error; + RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.ResetStoredLayerParameterValues(layerId, error), error, false, false); + if (!result.accepted) + return result; + + result.clearTransientOscState = true; + result.renderResetScope = RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback; + return result; +} + +RuntimeCoordinatorResult RuntimeCoordinator::SaveStackPreset(const std::string& presetName) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.SaveStackPresetSnapshot(presetName, error), error, false, false); +} + +RuntimeCoordinatorResult RuntimeCoordinator::LoadStackPreset(const std::string& presetName) +{ + std::string error; + return ApplyStoreMutation(mRuntimeStore.LoadStackPresetSnapshot(presetName, error), error, true, false); +} + +RuntimeCoordinatorResult RuntimeCoordinator::RequestShaderReload(bool preserveFeedbackState) +{ + return BuildQueuedReloadResult(preserveFeedbackState); +} + +RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std::string& error) +{ + RuntimeCoordinatorResult result; + result.accepted = true; + result.runtimeStateBroadcastRequired = true; + result.compileStatusChanged = true; + result.compileStatusSucceeded = false; + result.compileStatusMessage = error; + return result; +} + +RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error) +{ + mPreserveFeedbackOnNextShaderBuild = false; + + RuntimeCoordinatorResult result; + result.accepted = true; + result.runtimeStateBroadcastRequired = true; + result.compileStatusChanged = true; + result.compileStatusSucceeded = false; + result.compileStatusMessage = error; + result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates; + return result; +} + +RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess() +{ + RuntimeCoordinatorResult result; + result.accepted = true; + result.runtimeStateBroadcastRequired = true; + result.compileStatusChanged = true; + result.compileStatusSucceeded = true; + result.compileStatusMessage = "Shader layers compiled successfully."; + result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseLiveSnapshots; + mPreserveFeedbackOnNextShaderBuild = false; + return result; +} + +RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest() +{ + return BuildQueuedReloadResult(false); +} + +bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const +{ + return mPreserveFeedbackOnNextShaderBuild; +} + +RuntimeCoordinatorResult RuntimeCoordinator::ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState) +{ + if (!succeeded) + { + RuntimeCoordinatorResult result; + result.accepted = false; + result.errorMessage = errorMessage; + return result; + } + + if (reloadRequired) + return BuildQueuedReloadResult(preserveFeedbackState); + + return BuildAcceptedNoReloadResult(); +} + +RuntimeCoordinatorResult RuntimeCoordinator::BuildQueuedReloadResult(bool preserveFeedbackState) +{ + mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState; + + RuntimeCoordinatorResult result; + result.accepted = true; + result.runtimeStateBroadcastRequired = true; + result.shaderBuildRequested = true; + result.compileStatusChanged = true; + result.compileStatusSucceeded = true; + result.compileStatusMessage = "Shader rebuild queued."; + result.clearReloadRequest = true; + result.committedStateMode = RuntimeCoordinatorCommittedStateMode::UseCommittedStates; + return result; +} + +RuntimeCoordinatorResult RuntimeCoordinator::BuildAcceptedNoReloadResult() const +{ + RuntimeCoordinatorResult result; + result.accepted = true; + result.runtimeStateBroadcastRequired = true; + return result; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h new file mode 100644 index 0000000..476bed9 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h @@ -0,0 +1,70 @@ +#pragma once + +#include "RuntimeJson.h" + +#include +#include + +class RuntimeStore; + +enum class RuntimeCoordinatorCommittedStateMode +{ + Unchanged, + UseCommittedStates, + UseLiveSnapshots +}; + +enum class RuntimeCoordinatorRenderResetScope +{ + None, + TemporalHistoryOnly, + TemporalHistoryAndFeedback +}; + +struct RuntimeCoordinatorResult +{ + bool accepted = false; + bool runtimeStateBroadcastRequired = false; + bool shaderBuildRequested = false; + bool clearTransientOscState = false; + bool compileStatusChanged = false; + bool compileStatusSucceeded = false; + bool clearReloadRequest = false; + RuntimeCoordinatorCommittedStateMode committedStateMode = RuntimeCoordinatorCommittedStateMode::Unchanged; + RuntimeCoordinatorRenderResetScope renderResetScope = RuntimeCoordinatorRenderResetScope::None; + std::string compileStatusMessage; + std::string errorMessage; +}; + +class RuntimeCoordinator +{ +public: + explicit RuntimeCoordinator(RuntimeStore& runtimeStore); + + RuntimeCoordinatorResult AddLayer(const std::string& shaderId); + RuntimeCoordinatorResult RemoveLayer(const std::string& layerId); + RuntimeCoordinatorResult MoveLayer(const std::string& layerId, int direction); + RuntimeCoordinatorResult MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex); + RuntimeCoordinatorResult SetLayerBypass(const std::string& layerId, bool bypassed); + RuntimeCoordinatorResult SetLayerShader(const std::string& layerId, const std::string& shaderId); + RuntimeCoordinatorResult UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue); + RuntimeCoordinatorResult UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue); + RuntimeCoordinatorResult ResetLayerParameters(const std::string& layerId); + RuntimeCoordinatorResult SaveStackPreset(const std::string& presetName); + RuntimeCoordinatorResult LoadStackPreset(const std::string& presetName); + + RuntimeCoordinatorResult RequestShaderReload(bool preserveFeedbackState = false); + RuntimeCoordinatorResult HandleRuntimePollFailure(const std::string& error); + RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error); + RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess(); + RuntimeCoordinatorResult HandleRuntimeReloadRequest(); + bool PreserveFeedbackOnNextShaderBuild() const; + +private: + RuntimeCoordinatorResult ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState); + RuntimeCoordinatorResult BuildQueuedReloadResult(bool preserveFeedbackState); + RuntimeCoordinatorResult BuildAcceptedNoReloadResult() const; + + RuntimeStore& mRuntimeStore; + bool mPreserveFeedbackOnNextShaderBuild = false; +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp index e4d590d..559277a 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp @@ -12,6 +12,11 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str return mRuntimeHost.BuildLayerPassFragmentShaderSources(layerId, passSources, error); } +unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const +{ + return mRuntimeHost.GetMaxTemporalHistoryFrames(); +} + RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const { RuntimeSnapshotVersions versions; @@ -35,6 +40,16 @@ RuntimeRenderFrameContext RuntimeSnapshotProvider::GetFrameContext() const return frameContext; } +void RuntimeSnapshotProvider::AdvanceFrame() +{ + mRuntimeHost.AdvanceFrame(); +} + +bool RuntimeSnapshotProvider::TryAdvanceFrame() +{ + return mRuntimeHost.TryAdvanceFrame(); +} + RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const { for (;;) diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h index d791bfa..252cf42 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h @@ -35,8 +35,11 @@ public: explicit RuntimeSnapshotProvider(RuntimeHost& runtimeHost); bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector& passSources, std::string& error) const; + unsigned GetMaxTemporalHistoryFrames() const; RuntimeSnapshotVersions GetVersions() const; RuntimeRenderFrameContext GetFrameContext() const; + void AdvanceFrame(); + bool TryAdvanceFrame(); RuntimeRenderStateSnapshot GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const; bool TryGetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const; bool TryRefreshSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const; diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp index 8b0a3ec..d2f517f 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp @@ -2,12 +2,15 @@ #include "DeckLinkSession.h" #include "OpenGLVideoIOBridge.h" -#include "RenderEngine.h" #include "HealthTelemetry.h" +#include "RenderEngine.h" + +#include VideoBackend::VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry) : + mHealthTelemetry(healthTelemetry), mVideoIODevice(std::make_unique()), - mBridge(std::make_unique(*mVideoIODevice, renderEngine, healthTelemetry)) + mBridge(std::make_unique(renderEngine)) { } @@ -35,7 +38,7 @@ bool VideoBackend::SelectPreferredFormats(const VideoFormatSelection& videoModes bool VideoBackend::ConfigureInput(const VideoFormat& inputVideoMode, std::string& error) { return mVideoIODevice->ConfigureInput( - [this](const VideoIOFrame& frame) { mBridge->VideoFrameArrived(frame); }, + [this](const VideoIOFrame& frame) { HandleInputFrame(frame); }, inputVideoMode, error); } @@ -43,7 +46,7 @@ bool VideoBackend::ConfigureInput(const VideoFormat& inputVideoMode, std::string bool VideoBackend::ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) { return mVideoIODevice->ConfigureOutput( - [this](const VideoIOCompletion& completion) { mBridge->PlayoutFrameCompleted(completion); }, + [this](const VideoIOCompletion& completion) { HandleOutputFrameCompletion(completion); }, outputVideoMode, externalKeyingEnabled, error); @@ -173,3 +176,63 @@ void VideoBackend::SetStatusMessage(const std::string& message) { mVideoIODevice->SetStatusMessage(message); } + +void VideoBackend::HandleInputFrame(const VideoIOFrame& frame) +{ + const VideoIOState& state = mVideoIODevice->State(); + mHealthTelemetry.TryReportSignalStatus(!frame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName); + + if (mBridge) + mBridge->UploadInputFrame(frame, state); +} + +void VideoBackend::HandleOutputFrameCompletion(const VideoIOCompletion& completion) +{ + RecordFramePacing(completion.result); + + VideoIOOutputFrame outputFrame; + if (!BeginOutputFrame(outputFrame)) + return; + + const VideoIOState& state = mVideoIODevice->State(); + if (mBridge) + mBridge->RenderScheduledFrame(state, completion, outputFrame); + + EndOutputFrame(outputFrame); + AccountForCompletionResult(completion.result); + + // Schedule the next frame after render work is complete so device-side + // bookkeeping stays with the backend seam and the bridge stays render-only. + ScheduleOutputFrame(outputFrame); +} + +void VideoBackend::RecordFramePacing(VideoIOCompletionResult 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 == VideoIOCompletionResult::DisplayedLate) + ++mLateFrameCount; + else if (completionResult == VideoIOCompletionResult::Dropped) + ++mDroppedFrameCount; + else if (completionResult == VideoIOCompletionResult::Flushed) + ++mFlushedFrameCount; + + mHealthTelemetry.TryRecordFramePacingStats( + mCompletionIntervalMilliseconds, + mSmoothedCompletionIntervalMilliseconds, + mMaxCompletionIntervalMilliseconds, + mLateFrameCount, + mDroppedFrameCount, + mFlushedFrameCount); +} diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h index d5a1702..0484875 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h @@ -2,6 +2,8 @@ #include "VideoIOTypes.h" +#include +#include #include #include @@ -50,6 +52,18 @@ public: void SetStatusMessage(const std::string& message); private: + void HandleInputFrame(const VideoIOFrame& frame); + void HandleOutputFrameCompletion(const VideoIOCompletion& completion); + void RecordFramePacing(VideoIOCompletionResult completionResult); + + HealthTelemetry& mHealthTelemetry; std::unique_ptr mVideoIODevice; std::unique_ptr mBridge; + 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/videoio/decklink/DeckLinkSession.cpp b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.cpp index 4c8e4fc..2a29432 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.cpp @@ -417,11 +417,21 @@ double DeckLinkSession::FrameBudgetMilliseconds() const return mScheduler.FrameBudgetMilliseconds(); } -bool DeckLinkSession::BeginOutputFrame(VideoIOOutputFrame& frame) +bool DeckLinkSession::AcquireNextOutputVideoFrame(CComPtr& outputVideoFrame) { - CComPtr outputVideoFrame = outputVideoFrameQueue.front(); + if (outputVideoFrameQueue.empty()) + return false; + + outputVideoFrame = outputVideoFrameQueue.front(); outputVideoFrameQueue.push_back(outputVideoFrame); outputVideoFrameQueue.pop_front(); + return outputVideoFrame != nullptr; +} + +bool DeckLinkSession::PopulateOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame, VideoIOOutputFrame& frame) +{ + if (outputVideoFrame == nullptr) + return false; CComPtr outputVideoFrameBuffer; if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK) @@ -438,11 +448,44 @@ bool DeckLinkSession::BeginOutputFrame(VideoIOOutputFrame& frame) frame.width = mState.outputFrameSize.width; frame.height = mState.outputFrameSize.height; frame.pixelFormat = mState.outputPixelFormat; - frame.nativeFrame = outputVideoFrame.p; + frame.nativeFrame = outputVideoFrame; frame.nativeBuffer = outputVideoFrameBuffer.Detach(); return true; } +bool DeckLinkSession::ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame) +{ + const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime(); + return outputVideoFrame != nullptr && + output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale) == S_OK; +} + +bool DeckLinkSession::ScheduleBlackFrame(IDeckLinkMutableVideoFrame* outputVideoFrame) +{ + if (outputVideoFrame == nullptr) + return false; + + CComPtr outputVideoFrameBuffer; + if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK) + return false; + + if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK) + return false; + + void* pFrame = nullptr; + outputVideoFrameBuffer->GetBytes((void**)&pFrame); + memset(pFrame, 0, outputVideoFrame->GetRowBytes() * mState.outputFrameSize.height); + + outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite); + return ScheduleFrame(outputVideoFrame); +} + +bool DeckLinkSession::BeginOutputFrame(VideoIOOutputFrame& frame) +{ + CComPtr outputVideoFrame; + return AcquireNextOutputVideoFrame(outputVideoFrame) && PopulateOutputFrame(outputVideoFrame, frame); +} + void DeckLinkSession::EndOutputFrame(VideoIOOutputFrame& frame) { IDeckLinkVideoBuffer* outputVideoFrameBuffer = static_cast(frame.nativeBuffer); @@ -463,11 +506,7 @@ void DeckLinkSession::AccountForCompletionResult(VideoIOCompletionResult complet bool DeckLinkSession::ScheduleOutputFrame(const VideoIOOutputFrame& frame) { IDeckLinkMutableVideoFrame* outputVideoFrame = static_cast(frame.nativeFrame); - const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime(); - if (outputVideoFrame == nullptr || output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale) != S_OK) - return false; - - return true; + return ScheduleFrame(outputVideoFrame); } bool DeckLinkSession::Start() @@ -486,31 +525,13 @@ bool DeckLinkSession::Start() for (unsigned i = 0; i < kPrerollFrameCount; i++) { - CComPtr outputVideoFrame = outputVideoFrameQueue.front(); - outputVideoFrameQueue.push_back(outputVideoFrame); - outputVideoFrameQueue.pop_front(); - - CComPtr outputVideoFrameBuffer; - if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK) + CComPtr outputVideoFrame; + if (!AcquireNextOutputVideoFrame(outputVideoFrame)) { - MessageBoxA(NULL, "Could not query the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR); + MessageBoxA(NULL, "Could not acquire a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR); return false; } - - if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK) - { - MessageBoxA(NULL, "Could not write to the preroll output frame buffer.", "DeckLink start failed", MB_OK | MB_ICONERROR); - return false; - } - - void* pFrame = nullptr; - outputVideoFrameBuffer->GetBytes((void**)&pFrame); - memset(pFrame, 0, outputVideoFrame->GetRowBytes() * mState.outputFrameSize.height); - - outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite); - - const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime(); - if (output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale) != S_OK) + if (!ScheduleBlackFrame(outputVideoFrame)) { MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR); return false; @@ -599,23 +620,23 @@ void DeckLinkSession::HandlePlayoutFrameCompleted(IDeckLinkVideoFrame*, BMDOutpu return; VideoIOCompletion completion; + completion.result = TranslateCompletionResult(completionResult); + mOutputFrameCallback(completion); +} + +VideoIOCompletionResult DeckLinkSession::TranslateCompletionResult(BMDOutputFrameCompletionResult completionResult) +{ switch (completionResult) { case bmdOutputFrameDisplayedLate: - completion.result = VideoIOCompletionResult::DisplayedLate; - break; + return VideoIOCompletionResult::DisplayedLate; case bmdOutputFrameDropped: - completion.result = VideoIOCompletionResult::Dropped; - break; + return VideoIOCompletionResult::Dropped; case bmdOutputFrameFlushed: - completion.result = VideoIOCompletionResult::Flushed; - break; + return VideoIOCompletionResult::Flushed; case bmdOutputFrameCompleted: - completion.result = VideoIOCompletionResult::Completed; - break; + return VideoIOCompletionResult::Completed; default: - completion.result = VideoIOCompletionResult::Unknown; - break; + return VideoIOCompletionResult::Unknown; } - mOutputFrameCallback(completion); } diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.h b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.h index 211914d..5d695e8 100644 --- a/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.h +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/decklink/DeckLinkSession.h @@ -66,6 +66,12 @@ public: void HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult); private: + bool AcquireNextOutputVideoFrame(CComPtr& outputVideoFrame); + bool PopulateOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame, VideoIOOutputFrame& frame); + bool ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame); + bool ScheduleBlackFrame(IDeckLinkMutableVideoFrame* outputVideoFrame); + static VideoIOCompletionResult TranslateCompletionResult(BMDOutputFrameCompletionResult completionResult); + CComPtr captureDelegate; CComPtr playoutDelegate; CComPtr input;