4 Commits

Author SHA1 Message Date
Aiden
7f0f60c0e3 ore untangling
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m38s
CI / Windows Release Package (push) Successful in 2m36s
2026-05-10 23:31:45 +10:00
Aiden
739231d5a1 Phase 1 clean-up and separation of concerns
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m34s
CI / Windows Release Package (push) Successful in 2m42s
2026-05-10 23:21:13 +10:00
Aiden
3629227aa9 intial phase 1 subsytem split
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m31s
CI / Windows Release Package (push) Successful in 2m32s
2026-05-10 23:03:15 +10:00
Aiden
618831d578 Phase 1 goals
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m26s
CI / Windows Release Package (push) Successful in 2m32s
2026-05-10 22:36:34 +10:00
33 changed files with 2442 additions and 533 deletions

View File

@@ -34,6 +34,8 @@ set(APP_SOURCES
"${APP_DIR}/videoio/decklink/DeckLinkAPI_i.c"
"${APP_DIR}/control/ControlServer.cpp"
"${APP_DIR}/control/ControlServer.h"
"${APP_DIR}/control/ControlServices.cpp"
"${APP_DIR}/control/ControlServices.h"
"${APP_DIR}/control/OscServer.cpp"
"${APP_DIR}/control/OscServer.h"
"${APP_DIR}/control/RuntimeControlBridge.cpp"
@@ -60,6 +62,8 @@ set(APP_SOURCES
"${APP_DIR}/gl/OpenGLComposite.cpp"
"${APP_DIR}/gl/OpenGLComposite.h"
"${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp"
"${APP_DIR}/gl/RenderEngine.cpp"
"${APP_DIR}/gl/RenderEngine.h"
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.cpp"
"${APP_DIR}/gl/pipeline/OpenGLRenderPass.h"
"${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.cpp"
@@ -98,12 +102,18 @@ set(APP_SOURCES
"${APP_DIR}/resource.h"
"${APP_DIR}/runtime/RuntimeHost.cpp"
"${APP_DIR}/runtime/RuntimeHost.h"
"${APP_DIR}/runtime/HealthTelemetry.cpp"
"${APP_DIR}/runtime/HealthTelemetry.h"
"${APP_DIR}/runtime/RuntimeSnapshotProvider.cpp"
"${APP_DIR}/runtime/RuntimeSnapshotProvider.h"
"${APP_DIR}/runtime/RuntimeClock.cpp"
"${APP_DIR}/runtime/RuntimeClock.h"
"${APP_DIR}/runtime/RuntimeJson.cpp"
"${APP_DIR}/runtime/RuntimeJson.h"
"${APP_DIR}/runtime/RuntimeParameterUtils.cpp"
"${APP_DIR}/runtime/RuntimeParameterUtils.h"
"${APP_DIR}/runtime/RuntimeStore.cpp"
"${APP_DIR}/runtime/RuntimeStore.h"
"${APP_DIR}/shader/ShaderCompiler.cpp"
"${APP_DIR}/shader/ShaderCompiler.h"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
@@ -114,6 +124,8 @@ set(APP_SOURCES
"${APP_DIR}/targetver.h"
"${APP_DIR}/videoio/VideoIOFormat.cpp"
"${APP_DIR}/videoio/VideoIOFormat.h"
"${APP_DIR}/videoio/VideoBackend.cpp"
"${APP_DIR}/videoio/VideoBackend.h"
"${APP_DIR}/videoio/VideoIOTypes.h"
"${APP_DIR}/videoio/VideoPlayoutScheduler.cpp"
"${APP_DIR}/videoio/VideoPlayoutScheduler.h"

View File

@@ -89,7 +89,7 @@
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -99,7 +99,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<TargetMachine>MachineX86</TargetMachine>
@@ -111,7 +111,7 @@
</Midl>
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -121,7 +121,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<TargetMachine>MachineX64</TargetMachine>
@@ -131,7 +131,7 @@
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
@@ -141,7 +141,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
@@ -156,7 +156,7 @@
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>.;control;gl;gl\pipeline;gl\renderer;gl\shader;platform;runtime;shader;videoio;videoio\decklink;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
@@ -166,7 +166,7 @@
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Windowscodecs.lib;Ole32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>opengl32.lib;Glu32.lib;Ws2_32.lib;Crypt32.lib;Advapi32.lib;Gdiplus.lib;Ole32.lib;Windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
@@ -175,18 +175,32 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="control\ControlServer.cpp" />
<ClCompile Include="control\ControlServices.cpp" />
<ClCompile Include="control\OscServer.cpp" />
<ClCompile Include="control\RuntimeControlBridge.cpp" />
<ClCompile Include="control\RuntimeServices.cpp" />
<ClCompile Include="gl\renderer\GLExtensions.cpp" />
<ClCompile Include="gl\shader\GlobalParamsBuffer.cpp" />
<ClCompile Include="gl\shader\GlShaderSources.cpp" />
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp" />
<ClCompile Include="gl\OpenGLComposite.cpp" />
<ClCompile Include="gl\OpenGLCompositeRuntimeControls.cpp" />
<ClCompile Include="gl\RenderEngine.cpp" />
<ClCompile Include="gl\pipeline\OpenGLRenderPass.cpp" />
<ClCompile Include="gl\pipeline\OpenGLRenderPipeline.cpp" />
<ClCompile Include="gl\pipeline\ShaderFeedbackBuffers.cpp" />
<ClCompile Include="gl\renderer\OpenGLRenderer.cpp" />
<ClCompile Include="gl\renderer\RenderTargetPool.cpp" />
<ClCompile Include="gl\pipeline\OpenGLVideoIOBridge.cpp" />
<ClCompile Include="gl\shader\OpenGLShaderPrograms.cpp" />
<ClCompile Include="gl\pipeline\PngScreenshotWriter.cpp" />
<ClCompile Include="gl\shader\ShaderProgramCompiler.cpp" />
<ClCompile Include="gl\shader\ShaderBuildQueue.cpp" />
<ClCompile Include="gl\shader\ShaderTextureBindings.cpp" />
<ClCompile Include="gl\shader\TextRasterizer.cpp" />
<ClCompile Include="gl\shader\TextureAssetLoader.cpp" />
<ClCompile Include="gl\pipeline\TemporalHistoryBuffers.cpp" />
<ClCompile Include="gl\pipeline\OpenGLVideoIOBridge.cpp" />
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
@@ -194,34 +208,74 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="videoio\decklink\DeckLinkAPI_i.c" />
<ClCompile Include="control\RuntimeServices.cpp" />
<ClCompile Include="videoio\decklink\DeckLinkDisplayMode.cpp" />
<ClCompile Include="videoio\decklink\DeckLinkFrameTransfer.cpp" />
<ClCompile Include="videoio\decklink\DeckLinkSession.cpp" />
<ClCompile Include="videoio\decklink\DeckLinkVideoIOFormat.cpp" />
<ClCompile Include="runtime\HealthTelemetry.cpp" />
<ClCompile Include="runtime\RuntimeClock.cpp" />
<ClCompile Include="runtime\RuntimeHost.cpp" />
<ClCompile Include="runtime\RuntimeJson.cpp" />
<ClCompile Include="runtime\RuntimeParameterUtils.cpp" />
<ClCompile Include="runtime\RuntimeSnapshotProvider.cpp" />
<ClCompile Include="runtime\RuntimeStore.cpp" />
<ClCompile Include="shader\ShaderCompiler.cpp" />
<ClCompile Include="shader\ShaderPackageRegistry.cpp" />
<ClCompile Include="videoio\VideoBackend.cpp" />
<ClCompile Include="videoio\VideoIOFormat.cpp" />
<ClCompile Include="videoio\VideoPlayoutScheduler.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="control\ControlServer.h" />
<ClInclude Include="control\ControlServices.h" />
<ClInclude Include="control\OscServer.h" />
<ClInclude Include="control\RuntimeControlBridge.h" />
<ClInclude Include="control\RuntimeServices.h" />
<ClInclude Include="gl\renderer\GLExtensions.h" />
<ClInclude Include="gl\shader\GlobalParamsBuffer.h" />
<ClInclude Include="gl\renderer\GlRenderConstants.h" />
<ClInclude Include="gl\renderer\GlScopedObjects.h" />
<ClInclude Include="gl\shader\GlShaderSources.h" />
<ClInclude Include="LoopThroughWithOpenGLCompositing.h" />
<ClInclude Include="gl\OpenGLComposite.h" />
<ClInclude Include="gl\RenderEngine.h" />
<ClInclude Include="gl\pipeline\OpenGLRenderPass.h" />
<ClInclude Include="gl\pipeline\OpenGLRenderPipeline.h" />
<ClInclude Include="gl\pipeline\RenderPassDescriptor.h" />
<ClInclude Include="gl\pipeline\ShaderFeedbackBuffers.h" />
<ClInclude Include="gl\renderer\OpenGLRenderer.h" />
<ClInclude Include="gl\renderer\RenderTargetPool.h" />
<ClInclude Include="gl\pipeline\OpenGLVideoIOBridge.h" />
<ClInclude Include="gl\shader\OpenGLShaderPrograms.h" />
<ClInclude Include="gl\pipeline\PngScreenshotWriter.h" />
<ClInclude Include="gl\shader\ShaderProgramCompiler.h" />
<ClInclude Include="gl\shader\ShaderBuildQueue.h" />
<ClInclude Include="gl\shader\ShaderTextureBindings.h" />
<ClInclude Include="gl\shader\Std140Buffer.h" />
<ClInclude Include="gl\shader\TextRasterizer.h" />
<ClInclude Include="gl\shader\TextureAssetLoader.h" />
<ClInclude Include="gl\pipeline\TemporalHistoryBuffers.h" />
<ClInclude Include="gl\pipeline\OpenGLVideoIOBridge.h" />
<ClInclude Include="platform\NativeHandles.h" />
<ClInclude Include="platform\NativeSockets.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="runtime\HealthTelemetry.h" />
<ClInclude Include="runtime\RuntimeClock.h" />
<ClInclude Include="runtime\RuntimeHost.h" />
<ClInclude Include="runtime\RuntimeJson.h" />
<ClInclude Include="runtime\RuntimeParameterUtils.h" />
<ClInclude Include="runtime\RuntimeSnapshotProvider.h" />
<ClInclude Include="runtime\RuntimeStore.h" />
<ClInclude Include="shader\ShaderCompiler.h" />
<ClInclude Include="shader\ShaderPackageRegistry.h" />
<ClInclude Include="shader\ShaderTypes.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="control\RuntimeServices.h" />
<ClInclude Include="videoio\decklink\DeckLinkAPI_h.h" />
<ClInclude Include="videoio\decklink\DeckLinkDisplayMode.h" />
<ClInclude Include="videoio\decklink\DeckLinkFrameTransfer.h" />
<ClInclude Include="videoio\decklink\DeckLinkSession.h" />
<ClInclude Include="videoio\decklink\DeckLinkVideoIOFormat.h" />
<ClInclude Include="runtime\RuntimeClock.h" />
<ClInclude Include="videoio\VideoBackend.h" />
<ClInclude Include="videoio\VideoIOFormat.h" />
<ClInclude Include="videoio\VideoIOTypes.h" />
<ClInclude Include="videoio\VideoPlayoutScheduler.h" />

View File

@@ -18,21 +18,48 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="control\ControlServer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="control\ControlServices.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="control\OscServer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="control\RuntimeControlBridge.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\renderer\GLExtensions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\shader\GlobalParamsBuffer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\shader\GlShaderSources.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LoopThroughWithOpenGLCompositing.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\OpenGLComposite.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\OpenGLCompositeRuntimeControls.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\RenderEngine.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\pipeline\OpenGLRenderPass.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\pipeline\OpenGLRenderPipeline.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\pipeline\ShaderFeedbackBuffers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\renderer\OpenGLRenderer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -45,9 +72,21 @@
<ClCompile Include="gl\pipeline\PngScreenshotWriter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\shader\ShaderProgramCompiler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\shader\ShaderBuildQueue.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\shader\ShaderTextureBindings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\shader\TextRasterizer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\shader\TextureAssetLoader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gl\pipeline\TemporalHistoryBuffers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -63,15 +102,48 @@
<ClCompile Include="control\RuntimeServices.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\decklink\DeckLinkDisplayMode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\decklink\DeckLinkFrameTransfer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\decklink\DeckLinkSession.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\decklink\DeckLinkVideoIOFormat.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runtime\HealthTelemetry.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runtime\RuntimeClock.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runtime\RuntimeHost.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runtime\RuntimeJson.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runtime\RuntimeParameterUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runtime\RuntimeSnapshotProvider.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runtime\RuntimeStore.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="shader\ShaderCompiler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="shader\ShaderPackageRegistry.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\VideoBackend.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="videoio\VideoIOFormat.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -80,15 +152,42 @@
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="control\ControlServer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="control\ControlServices.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="control\OscServer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="control\RuntimeControlBridge.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\renderer\GLExtensions.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\GlobalParamsBuffer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\renderer\GlRenderConstants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\renderer\GlScopedObjects.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\GlShaderSources.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="LoopThroughWithOpenGLCompositing.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\OpenGLComposite.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\RenderEngine.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\OpenGLRenderPass.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -98,6 +197,9 @@
<ClInclude Include="gl\pipeline\RenderPassDescriptor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\ShaderFeedbackBuffers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\renderer\OpenGLRenderer.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -110,18 +212,66 @@
<ClInclude Include="gl\pipeline\PngScreenshotWriter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\ShaderProgramCompiler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\ShaderBuildQueue.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\ShaderTextureBindings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\Std140Buffer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\TextRasterizer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\shader\TextureAssetLoader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\TemporalHistoryBuffers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gl\pipeline\OpenGLVideoIOBridge.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="platform\NativeHandles.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="platform\NativeSockets.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runtime\HealthTelemetry.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runtime\RuntimeHost.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runtime\RuntimeJson.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runtime\RuntimeParameterUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runtime\RuntimeSnapshotProvider.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runtime\RuntimeStore.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shader\ShaderCompiler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shader\ShaderPackageRegistry.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="shader\ShaderTypes.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -137,6 +287,18 @@
<ClInclude Include="videoio\decklink\DeckLinkVideoIOFormat.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="videoio\decklink\DeckLinkAPI_h.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="videoio\decklink\DeckLinkDisplayMode.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="videoio\decklink\DeckLinkFrameTransfer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="videoio\VideoBackend.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runtime\RuntimeClock.h">
<Filter>Header Files</Filter>
</ClInclude>

View File

@@ -0,0 +1,247 @@
#include "ControlServices.h"
#include "ControlServer.h"
#include "OscServer.h"
#include "RuntimeControlBridge.h"
#include "RuntimeHost.h"
#include <windows.h>
ControlServices::ControlServices() :
mControlServer(std::make_unique<ControlServer>()),
mOscServer(std::make_unique<OscServer>()),
mPollRunning(false),
mRegistryChanged(false),
mReloadRequested(false),
mPollFailed(false)
{
}
ControlServices::~ControlServices()
{
Stop();
}
bool ControlServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
{
Stop();
if (!StartControlServicesBoundary(composite, runtimeHost, *this, *mControlServer, *mOscServer, error))
{
Stop();
return false;
}
return true;
}
void ControlServices::BeginPolling(RuntimeHost& runtimeHost)
{
StartPolling(runtimeHost);
}
void ControlServices::Stop()
{
StopPolling();
if (mOscServer)
mOscServer->Stop();
if (mControlServer)
mControlServer->Stop();
}
void ControlServices::BroadcastState()
{
if (mControlServer)
mControlServer->BroadcastState();
}
void ControlServices::RequestBroadcastState()
{
if (mControlServer)
mControlServer->RequestBroadcastState();
}
bool ControlServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
{
(void)error;
PendingOscUpdate update;
update.layerKey = layerKey;
update.parameterKey = parameterKey;
update.valueJson = valueJson;
const std::string routeKey = layerKey + "\n" + parameterKey;
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
mPendingOscUpdates[routeKey] = std::move(update);
}
return true;
}
bool ControlServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
{
appliedUpdates.clear();
std::map<std::string, PendingOscUpdate> pending;
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
if (mPendingOscUpdates.empty())
return true;
pending.swap(mPendingOscUpdates);
}
for (const auto& entry : pending)
{
JsonValue targetValue;
std::string parseError;
if (!ParseJson(entry.second.valueJson, targetValue, parseError))
{
OutputDebugStringA(("OSC queued value parse failed: " + parseError + "\n").c_str());
continue;
}
AppliedOscUpdate appliedUpdate;
appliedUpdate.routeKey = entry.first;
appliedUpdate.layerKey = entry.second.layerKey;
appliedUpdate.parameterKey = entry.second.parameterKey;
appliedUpdate.targetValue = targetValue;
appliedUpdates.push_back(std::move(appliedUpdate));
}
(void)error;
return true;
}
bool ControlServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
{
(void)error;
PendingOscCommit commit;
commit.routeKey = routeKey;
commit.layerKey = layerKey;
commit.parameterKey = parameterKey;
commit.value = value;
commit.generation = generation;
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
mPendingOscCommits[routeKey] = std::move(commit);
}
return true;
}
void ControlServices::ClearOscState()
{
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
mPendingOscUpdates.clear();
}
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
mPendingOscCommits.clear();
}
{
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
mCompletedOscCommits.clear();
}
}
void ControlServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
{
completedCommits.clear();
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
if (mCompletedOscCommits.empty())
return;
completedCommits.swap(mCompletedOscCommits);
}
RuntimePollEvents ControlServices::ConsumePollEvents()
{
RuntimePollEvents events;
events.registryChanged = mRegistryChanged.exchange(false);
events.reloadRequested = mReloadRequested.exchange(false);
events.failed = mPollFailed.exchange(false);
if (events.failed)
{
std::lock_guard<std::mutex> lock(mPollErrorMutex);
events.error = mPollError;
}
return events;
}
void ControlServices::StartPolling(RuntimeHost& runtimeHost)
{
if (mPollRunning.exchange(true))
return;
mPollThread = std::thread([this, &runtimeHost]() { PollLoop(runtimeHost); });
}
void ControlServices::StopPolling()
{
if (!mPollRunning.exchange(false))
return;
if (mPollThread.joinable())
mPollThread.join();
}
void ControlServices::PollLoop(RuntimeHost& runtimeHost)
{
while (mPollRunning)
{
std::map<std::string, PendingOscCommit> pendingCommits;
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
pendingCommits.swap(mPendingOscCommits);
}
for (const auto& entry : pendingCommits)
{
std::string commitError;
if (runtimeHost.UpdateLayerParameterByControlKey(
entry.second.layerKey,
entry.second.parameterKey,
entry.second.value,
false,
commitError))
{
CompletedOscCommit completedCommit;
completedCommit.routeKey = entry.second.routeKey;
completedCommit.generation = entry.second.generation;
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
mCompletedOscCommits.push_back(std::move(completedCommit));
}
else if (!commitError.empty())
{
OutputDebugStringA(("OSC commit failed: " + commitError + "\n").c_str());
}
}
bool registryChanged = false;
bool reloadRequested = false;
std::string runtimeError;
if (!runtimeHost.PollFileChanges(registryChanged, reloadRequested, runtimeError))
{
{
std::lock_guard<std::mutex> lock(mPollErrorMutex);
mPollError = runtimeError;
}
mPollFailed = true;
}
else
{
if (registryChanged)
mRegistryChanged = true;
if (reloadRequested)
mReloadRequested = true;
}
for (int i = 0; i < 25 && mPollRunning; ++i)
Sleep(10);
}
}

View File

@@ -0,0 +1,95 @@
#pragma once
#include "RuntimeJson.h"
#include "ShaderTypes.h"
#include <atomic>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
class ControlServer;
class OpenGLComposite;
class OscServer;
class RuntimeHost;
struct RuntimePollEvents
{
bool registryChanged = false;
bool reloadRequested = false;
bool failed = false;
std::string error;
};
class ControlServices
{
public:
struct AppliedOscUpdate
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue targetValue;
};
struct CompletedOscCommit
{
std::string routeKey;
uint64_t generation = 0;
};
ControlServices();
~ControlServices();
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
void BeginPolling(RuntimeHost& runtimeHost);
void Stop();
void BroadcastState();
void RequestBroadcastState();
bool QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
void ClearOscState();
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
RuntimePollEvents ConsumePollEvents();
private:
struct PendingOscUpdate
{
std::string layerKey;
std::string parameterKey;
std::string valueJson;
};
struct PendingOscCommit
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue value;
uint64_t generation = 0;
};
void StartPolling(RuntimeHost& runtimeHost);
void StopPolling();
void PollLoop(RuntimeHost& runtimeHost);
std::unique_ptr<ControlServer> mControlServer;
std::unique_ptr<OscServer> mOscServer;
std::thread mPollThread;
std::atomic<bool> mPollRunning;
std::atomic<bool> mRegistryChanged;
std::atomic<bool> mReloadRequested;
std::atomic<bool> mPollFailed;
std::mutex mPollErrorMutex;
std::string mPollError;
std::mutex mPendingOscMutex;
std::map<std::string, PendingOscUpdate> mPendingOscUpdates;
std::mutex mPendingOscCommitMutex;
std::map<std::string, PendingOscCommit> mPendingOscCommits;
std::mutex mCompletedOscCommitMutex;
std::vector<CompletedOscCommit> mCompletedOscCommits;
};

View File

@@ -1,15 +1,15 @@
#include "RuntimeControlBridge.h"
#include "ControlServices.h"
#include "ControlServer.h"
#include "OpenGLComposite.h"
#include "OscServer.h"
#include "RuntimeHost.h"
#include "RuntimeServices.h"
bool StartRuntimeControlServices(
bool StartControlServicesBoundary(
OpenGLComposite& composite,
RuntimeHost& runtimeHost,
RuntimeServices& runtimeServices,
ControlServices& controlServices,
ControlServer& controlServer,
OscServer& oscServer,
std::string& error)
@@ -43,8 +43,8 @@ bool StartRuntimeControlServices(
runtimeHost.SetServerPort(controlServer.GetPort());
OscServer::Callbacks oscCallbacks;
oscCallbacks.updateParameter = [&runtimeServices](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
return runtimeServices.QueueOscUpdate(layerKey, parameterKey, valueJson, actionError);
oscCallbacks.updateParameter = [&controlServices](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
return controlServices.QueueOscUpdate(layerKey, parameterKey, valueJson, actionError);
};
if (runtimeHost.GetOscPort() > 0 && !oscServer.Start(runtimeHost.GetOscBindAddress(), runtimeHost.GetOscPort(), oscCallbacks, error))
return false;

View File

@@ -3,15 +3,15 @@
#include <string>
class ControlServer;
class ControlServices;
class OpenGLComposite;
class OscServer;
class RuntimeHost;
class RuntimeServices;
bool StartRuntimeControlServices(
bool StartControlServicesBoundary(
OpenGLComposite& composite,
RuntimeHost& runtimeHost,
RuntimeServices& runtimeServices,
ControlServices& controlServices,
ControlServer& controlServer,
OscServer& oscServer,
std::string& error);

View File

@@ -1,18 +1,7 @@
#include "RuntimeServices.h"
#include "ControlServer.h"
#include "OscServer.h"
#include "RuntimeControlBridge.h"
#include "RuntimeHost.h"
#include <windows.h>
RuntimeServices::RuntimeServices() :
mControlServer(std::make_unique<ControlServer>()),
mOscServer(std::make_unique<OscServer>()),
mPollRunning(false),
mRegistryChanged(false),
mReloadRequested(false),
mPollFailed(false)
mControlServices(std::make_unique<ControlServices>())
{
}
@@ -23,225 +12,72 @@ RuntimeServices::~RuntimeServices()
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
{
Stop();
if (!StartRuntimeControlServices(composite, runtimeHost, *this, *mControlServer, *mOscServer, error))
{
Stop();
return false;
}
return true;
return mControlServices && mControlServices->Start(composite, runtimeHost, error);
}
void RuntimeServices::BeginPolling(RuntimeHost& runtimeHost)
{
StartPolling(runtimeHost);
if (mControlServices)
mControlServices->BeginPolling(runtimeHost);
}
void RuntimeServices::Stop()
{
StopPolling();
if (mOscServer)
mOscServer->Stop();
if (mControlServer)
mControlServer->Stop();
if (mControlServices)
mControlServices->Stop();
}
void RuntimeServices::BroadcastState()
{
if (mControlServer)
mControlServer->BroadcastState();
if (mControlServices)
mControlServices->BroadcastState();
}
void RuntimeServices::RequestBroadcastState()
{
if (mControlServer)
mControlServer->RequestBroadcastState();
if (mControlServices)
mControlServices->RequestBroadcastState();
}
bool RuntimeServices::QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
{
(void)error;
PendingOscUpdate update;
update.layerKey = layerKey;
update.parameterKey = parameterKey;
update.valueJson = valueJson;
const std::string routeKey = layerKey + "\n" + parameterKey;
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
mPendingOscUpdates[routeKey] = std::move(update);
}
return true;
return mControlServices && mControlServices->QueueOscUpdate(layerKey, parameterKey, valueJson, error);
}
bool RuntimeServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error)
{
appliedUpdates.clear();
std::map<std::string, PendingOscUpdate> pending;
if (!mControlServices)
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
if (mPendingOscUpdates.empty())
return true;
pending.swap(mPendingOscUpdates);
appliedUpdates.clear();
return true;
}
for (const auto& entry : pending)
{
JsonValue targetValue;
std::string parseError;
if (!ParseJson(entry.second.valueJson, targetValue, parseError))
{
OutputDebugStringA(("OSC queued value parse failed: " + parseError + "\n").c_str());
continue;
}
AppliedOscUpdate appliedUpdate;
appliedUpdate.routeKey = entry.first;
appliedUpdate.layerKey = entry.second.layerKey;
appliedUpdate.parameterKey = entry.second.parameterKey;
appliedUpdate.targetValue = targetValue;
appliedUpdates.push_back(std::move(appliedUpdate));
}
(void)error;
return true;
return mControlServices->ApplyPendingOscUpdates(appliedUpdates, error);
}
bool RuntimeServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
{
(void)error;
PendingOscCommit commit;
commit.routeKey = routeKey;
commit.layerKey = layerKey;
commit.parameterKey = parameterKey;
commit.value = value;
commit.generation = generation;
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
mPendingOscCommits[routeKey] = std::move(commit);
}
return true;
return mControlServices && mControlServices->QueueOscCommit(routeKey, layerKey, parameterKey, value, generation, error);
}
void RuntimeServices::ClearOscState()
{
{
std::lock_guard<std::mutex> lock(mPendingOscMutex);
mPendingOscUpdates.clear();
}
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
mPendingOscCommits.clear();
}
{
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
mCompletedOscCommits.clear();
}
if (mControlServices)
mControlServices->ClearOscState();
}
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
{
completedCommits.clear();
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
if (mCompletedOscCommits.empty())
if (!mControlServices)
{
completedCommits.clear();
return;
}
completedCommits.swap(mCompletedOscCommits);
mControlServices->ConsumeCompletedOscCommits(completedCommits);
}
RuntimePollEvents RuntimeServices::ConsumePollEvents()
{
RuntimePollEvents events;
events.registryChanged = mRegistryChanged.exchange(false);
events.reloadRequested = mReloadRequested.exchange(false);
events.failed = mPollFailed.exchange(false);
if (events.failed)
{
std::lock_guard<std::mutex> lock(mPollErrorMutex);
events.error = mPollError;
}
return events;
}
void RuntimeServices::StartPolling(RuntimeHost& runtimeHost)
{
if (mPollRunning.exchange(true))
return;
mPollThread = std::thread([this, &runtimeHost]() { PollLoop(runtimeHost); });
}
void RuntimeServices::StopPolling()
{
if (!mPollRunning.exchange(false))
return;
if (mPollThread.joinable())
mPollThread.join();
}
void RuntimeServices::PollLoop(RuntimeHost& runtimeHost)
{
while (mPollRunning)
{
std::map<std::string, PendingOscCommit> pendingCommits;
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
pendingCommits.swap(mPendingOscCommits);
}
for (const auto& entry : pendingCommits)
{
std::string commitError;
if (runtimeHost.UpdateLayerParameterByControlKey(
entry.second.layerKey,
entry.second.parameterKey,
entry.second.value,
false,
commitError))
{
CompletedOscCommit completedCommit;
completedCommit.routeKey = entry.second.routeKey;
completedCommit.generation = entry.second.generation;
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
mCompletedOscCommits.push_back(std::move(completedCommit));
}
else if (!commitError.empty())
{
OutputDebugStringA(("OSC commit failed: " + commitError + "\n").c_str());
}
}
bool registryChanged = false;
bool reloadRequested = false;
std::string runtimeError;
if (!runtimeHost.PollFileChanges(registryChanged, reloadRequested, runtimeError))
{
{
std::lock_guard<std::mutex> lock(mPollErrorMutex);
mPollError = runtimeError;
}
mPollFailed = true;
}
else
{
if (registryChanged)
mRegistryChanged = true;
if (reloadRequested)
mReloadRequested = true;
}
for (int i = 0; i < 25 && mPollRunning; ++i)
Sleep(10);
}
return mControlServices ? mControlServices->ConsumePollEvents() : RuntimePollEvents{};
}

View File

@@ -1,44 +1,17 @@
#pragma once
#include "RuntimeJson.h"
#include "ShaderTypes.h"
#include "ControlServices.h"
#include <atomic>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
class ControlServer;
class OpenGLComposite;
class OscServer;
class RuntimeHost;
struct RuntimePollEvents
{
bool registryChanged = false;
bool reloadRequested = false;
bool failed = false;
std::string error;
};
class RuntimeServices
{
public:
struct AppliedOscUpdate
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue targetValue;
};
struct CompletedOscCommit
{
std::string routeKey;
uint64_t generation = 0;
};
using AppliedOscUpdate = ControlServices::AppliedOscUpdate;
using CompletedOscCommit = ControlServices::CompletedOscCommit;
RuntimeServices();
~RuntimeServices();
@@ -56,39 +29,5 @@ public:
RuntimePollEvents ConsumePollEvents();
private:
struct PendingOscUpdate
{
std::string layerKey;
std::string parameterKey;
std::string valueJson;
};
struct PendingOscCommit
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue value;
uint64_t generation = 0;
};
void StartPolling(RuntimeHost& runtimeHost);
void StopPolling();
void PollLoop(RuntimeHost& runtimeHost);
std::unique_ptr<ControlServer> mControlServer;
std::unique_ptr<OscServer> mOscServer;
std::thread mPollThread;
std::atomic<bool> mPollRunning;
std::atomic<bool> mRegistryChanged;
std::atomic<bool> mReloadRequested;
std::atomic<bool> mPollFailed;
std::mutex mPollErrorMutex;
std::string mPollError;
std::mutex mPendingOscMutex;
std::map<std::string, PendingOscUpdate> mPendingOscUpdates;
std::mutex mPendingOscCommitMutex;
std::map<std::string, PendingOscCommit> mPendingOscCommits;
std::mutex mCompletedOscCommitMutex;
std::vector<CompletedOscCommit> mCompletedOscCommits;
std::unique_ptr<ControlServices> mControlServices;
};

View File

@@ -1,16 +1,13 @@
#include "DeckLinkDisplayMode.h"
#include "DeckLinkSession.h"
#include "OpenGLComposite.h"
#include "GLExtensions.h"
#include "GlRenderConstants.h"
#include "OpenGLRenderPass.h"
#include "OpenGLRenderPipeline.h"
#include "OpenGLShaderPrograms.h"
#include "OpenGLVideoIOBridge.h"
#include "PngScreenshotWriter.h"
#include "RenderEngine.h"
#include "RuntimeParameterUtils.h"
#include "RuntimeServices.h"
#include "ShaderBuildQueue.h"
#include "VideoBackend.h"
#include <algorithm>
#include <cctype>
@@ -97,30 +94,24 @@ JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC),
mVideoIO(std::make_unique<DeckLinkSession>()),
mRenderer(std::make_unique<OpenGLRenderer>()),
mUseCommittedLayerStates(false),
mScreenshotRequested(false)
{
InitializeCriticalSection(&pMutex);
mRuntimeHost = std::make_unique<RuntimeHost>();
mRenderPipeline = std::make_unique<OpenGLRenderPipeline>(
*mRenderer,
mRuntimeStore = std::make_unique<RuntimeStore>(*mRuntimeHost);
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(*mRuntimeHost);
mRenderEngine = std::make_unique<RenderEngine>(
*mRuntimeHost,
*mRuntimeSnapshotProvider,
pMutex,
hGLDC,
hGLRC,
[this]() { renderEffect(); },
[this]() { ProcessScreenshotRequest(); },
[this]() { paintGL(false); });
mVideoIOBridge = std::make_unique<OpenGLVideoIOBridge>(
*mVideoIO,
*mRenderer,
*mRenderPipeline,
*mRuntimeHost,
pMutex,
hGLDC,
hGLRC);
mRenderPass = std::make_unique<OpenGLRenderPass>(*mRenderer);
mShaderPrograms = std::make_unique<OpenGLShaderPrograms>(*mRenderer, *mRuntimeHost);
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeHost);
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeHost->GetHealthTelemetry());
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeSnapshotProvider);
mRuntimeServices = std::make_unique<RuntimeServices>();
}
@@ -130,8 +121,8 @@ OpenGLComposite::~OpenGLComposite()
mRuntimeServices->Stop();
if (mShaderBuildQueue)
mShaderBuildQueue->Stop();
mVideoIO->ReleaseResources();
mRenderer->DestroyResources();
if (mVideoBackend)
mVideoBackend->ReleaseResources();
DeleteCriticalSection(&pMutex);
}
@@ -146,23 +137,23 @@ bool OpenGLComposite::InitVideoIO()
VideoFormatSelection videoModes;
std::string initFailureReason;
if (mRuntimeHost && mRuntimeHost->GetRepoRoot().empty())
if (mRuntimeStore && mRuntimeStore->GetRuntimeRepositoryRoot().empty())
{
std::string runtimeError;
if (!mRuntimeHost->Initialize(runtimeError))
if (!mRuntimeStore->InitializeStore(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
}
if (mRuntimeHost)
if (mRuntimeStore)
{
if (!ResolveConfiguredVideoFormats(
mRuntimeHost->GetInputVideoFormat(),
mRuntimeHost->GetInputFrameRate(),
mRuntimeHost->GetOutputVideoFormat(),
mRuntimeHost->GetOutputFrameRate(),
mRuntimeStore->GetConfiguredInputVideoFormat(),
mRuntimeStore->GetConfiguredInputFrameRate(),
mRuntimeStore->GetConfiguredOutputVideoFormat(),
mRuntimeStore->GetConfiguredOutputFrameRate(),
videoModes,
initFailureReason))
{
@@ -171,7 +162,7 @@ bool OpenGLComposite::InitVideoIO()
}
}
if (!mVideoIO->DiscoverDevicesAndModes(videoModes, initFailureReason))
if (!mVideoBackend->DiscoverDevicesAndModes(videoModes, initFailureReason))
{
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
? "This application requires the DeckLink drivers installed."
@@ -179,8 +170,8 @@ bool OpenGLComposite::InitVideoIO()
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
return false;
}
const bool outputAlphaRequired = mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled();
if (!mVideoIO->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
const bool outputAlphaRequired = mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured();
if (!mVideoBackend->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
goto error;
if (! CheckOpenGLExtensions())
@@ -195,38 +186,38 @@ bool OpenGLComposite::InitVideoIO()
goto error;
}
PublishVideoIOStatus(mVideoIO->OutputModelName().empty()
PublishVideoIOStatus(mVideoBackend->OutputModelName().empty()
? "DeckLink output device selected."
: ("Selected output device: " + mVideoIO->OutputModelName()));
: ("Selected output device: " + mVideoBackend->OutputModelName()));
// Resize window to match output video frame, but scale large formats down by half for viewing.
if (mVideoIO->OutputFrameWidth() < 1920)
resizeWindow(mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight());
if (mVideoBackend->OutputFrameWidth() < 1920)
resizeWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight());
else
resizeWindow(mVideoIO->OutputFrameWidth() / 2, mVideoIO->OutputFrameHeight() / 2);
resizeWindow(mVideoBackend->OutputFrameWidth() / 2, mVideoBackend->OutputFrameHeight() / 2);
if (!mVideoIO->ConfigureInput([this](const VideoIOFrame& frame) { mVideoIOBridge->VideoFrameArrived(frame); }, videoModes.input, initFailureReason))
if (!mVideoBackend->ConfigureInput(videoModes.input, initFailureReason))
{
goto error;
}
if (!mVideoIO->HasInputDevice() && mRuntimeHost)
if (!mVideoBackend->HasInputDevice() && mRuntimeHost)
{
mRuntimeHost->SetSignalStatus(false, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), mVideoIO->InputDisplayModeName());
mRuntimeHost->SetSignalStatus(false, mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), mVideoBackend->InputDisplayModeName());
}
if (!mVideoIO->ConfigureOutput([this](const VideoIOCompletion& completion) { mVideoIOBridge->PlayoutFrameCompleted(completion); }, videoModes.output, mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled(), initFailureReason))
if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason))
{
goto error;
}
PublishVideoIOStatus(mVideoIO->StatusMessage());
PublishVideoIOStatus(mVideoBackend->StatusMessage());
return true;
error:
if (!initFailureReason.empty())
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
mVideoIO->ReleaseResources();
mVideoBackend->ReleaseResources();
return false;
}
@@ -237,7 +228,7 @@ void OpenGLComposite::paintGL(bool force)
if (IsIconic(hGLWnd))
return;
const unsigned previewFps = mRuntimeHost ? mRuntimeHost->GetPreviewFps() : 30u;
const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetConfiguredPreviewFps() : 30u;
if (previewFps == 0)
return;
@@ -250,23 +241,21 @@ void OpenGLComposite::paintGL(bool force)
}
}
if (!TryEnterCriticalSection(&pMutex))
if (!mRenderEngine->TryPresentToWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight()))
{
ValidateRect(hGLWnd, NULL);
return;
}
mRenderer->PresentToWindow(hGLDC, mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight());
mLastPreviewPresentTime = std::chrono::steady_clock::now();
ValidateRect(hGLWnd, NULL);
LeaveCriticalSection(&pMutex);
}
void OpenGLComposite::resizeGL(WORD width, WORD height)
{
// We don't set the project or model matrices here since the window data is copied directly from
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
mRenderer->ResizeView(width, height);
mRenderEngine->ResizeView(width, height);
}
void OpenGLComposite::resizeWindow(int width, int height)
@@ -284,17 +273,17 @@ void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage)
return;
if (!statusMessage.empty())
mVideoIO->SetStatusMessage(statusMessage);
mVideoBackend->SetStatusMessage(statusMessage);
mRuntimeHost->SetVideoIOStatus(
"decklink",
mVideoIO->OutputModelName(),
mVideoIO->SupportsInternalKeying(),
mVideoIO->SupportsExternalKeying(),
mVideoIO->KeyerInterfaceAvailable(),
mRuntimeHost->ExternalKeyingEnabled(),
mVideoIO->ExternalKeyingActive(),
mVideoIO->StatusMessage());
mVideoBackend->OutputModelName(),
mVideoBackend->SupportsInternalKeying(),
mVideoBackend->SupportsExternalKeying(),
mVideoBackend->KeyerInterfaceAvailable(),
mRuntimeStore ? mRuntimeStore->IsExternalKeyingConfigured() : false,
mVideoBackend->ExternalKeyingActive(),
mVideoBackend->StatusMessage());
}
bool OpenGLComposite::InitOpenGLState()
@@ -303,7 +292,7 @@ bool OpenGLComposite::InitOpenGLState()
return false;
std::string runtimeError;
if (mRuntimeHost->GetRepoRoot().empty() && !mRuntimeHost->Initialize(runtimeError))
if (mRuntimeStore->GetRuntimeRepositoryRoot().empty() && !mRuntimeStore->InitializeStore(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
@@ -317,41 +306,41 @@ bool OpenGLComposite::InitOpenGLState()
// Prepare the runtime shader program generated from the active shader package.
char compilerErrorMessage[1024];
if (!mShaderPrograms->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
if (!mRenderEngine->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
return false;
}
if (!mShaderPrograms->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
if (!mRenderEngine->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
return false;
}
std::string rendererError;
if (!mRenderer->InitializeResources(
mVideoIO->InputFrameWidth(),
mVideoIO->InputFrameHeight(),
mVideoIO->CaptureTextureWidth(),
mVideoIO->OutputFrameWidth(),
mVideoIO->OutputFrameHeight(),
mVideoIO->OutputPackTextureWidth(),
if (!mRenderEngine->InitializeResources(
mVideoBackend->InputFrameWidth(),
mVideoBackend->InputFrameHeight(),
mVideoBackend->CaptureTextureWidth(),
mVideoBackend->OutputFrameWidth(),
mVideoBackend->OutputFrameHeight(),
mVideoBackend->OutputPackTextureWidth(),
rendererError))
{
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
return false;
}
if (!mShaderPrograms->CompileLayerPrograms(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
if (!mRenderEngine->CompileLayerPrograms(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
return false;
}
mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates();
mCachedLayerRenderStates = mRenderEngine->CommittedLayerStates();
mUseCommittedLayerStates = false;
mShaderPrograms->ResetTemporalHistoryState();
mShaderPrograms->ResetShaderFeedbackState();
mRenderEngine->ResetTemporalHistoryState();
mRenderEngine->ResetShaderFeedbackState();
broadcastRuntimeState();
mRuntimeServices->BeginPolling(*mRuntimeHost);
@@ -360,7 +349,7 @@ bool OpenGLComposite::InitOpenGLState()
bool OpenGLComposite::Start()
{
return mVideoIO->Start();
return mVideoBackend->Start();
}
bool OpenGLComposite::Stop()
@@ -368,8 +357,8 @@ bool OpenGLComposite::Stop()
if (mRuntimeServices)
mRuntimeServices->Stop();
const bool wasExternalKeyingActive = mVideoIO->ExternalKeyingActive();
mVideoIO->Stop();
const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive();
mVideoBackend->Stop();
if (wasExternalKeyingActive)
PublishVideoIOStatus("External keying has been disabled.");
@@ -381,8 +370,8 @@ bool OpenGLComposite::ReloadShader(bool preserveFeedbackState)
mPreserveFeedbackOnNextShaderBuild = preserveFeedbackState;
if (mRuntimeHost)
{
mRuntimeHost->SetCompileStatus(true, "Shader rebuild queued.");
mRuntimeHost->ClearReloadRequest();
mRuntimeStore->SetCompileStatus(true, "Shader rebuild queued.");
mRuntimeStore->ClearReloadRequest();
}
RequestShaderBuild();
broadcastRuntimeState();
@@ -456,7 +445,7 @@ void OpenGLComposite::renderEffect()
if (states.empty() || mOscOverlayStates.empty() || !mRuntimeHost)
return;
const double smoothing = ClampOscAlpha(mRuntimeHost->GetOscSmoothing());
const double smoothing = ClampOscAlpha(mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0);
std::vector<std::string> overlayKeysToRemove;
for (auto& item : mOscOverlayStates)
{
@@ -585,48 +574,57 @@ void OpenGLComposite::renderEffect()
mOscOverlayStates.erase(overlayKey);
};
const bool hasInputSource = mVideoIO->HasInputSource();
const bool hasInputSource = mVideoBackend->HasInputSource();
std::vector<RuntimeRenderState> layerStates;
if (mUseCommittedLayerStates)
{
layerStates = mShaderPrograms->CommittedLayerStates();
layerStates = mRenderEngine->CommittedLayerStates();
applyOscOverlays(layerStates, false);
if (mRuntimeHost)
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
if (mRuntimeSnapshotProvider)
mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates);
}
else if (mRuntimeHost)
else if (mRuntimeSnapshotProvider)
{
const unsigned renderWidth = mVideoIO->InputFrameWidth();
const unsigned renderHeight = mVideoIO->InputFrameHeight();
const uint64_t renderStateVersion = mRuntimeHost->GetRenderStateVersion();
const uint64_t parameterStateVersion = mRuntimeHost->GetParameterStateVersion();
const unsigned renderWidth = mVideoBackend->InputFrameWidth();
const unsigned renderHeight = mVideoBackend->InputFrameHeight();
const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider->GetVersions();
const bool renderStateCacheValid =
!mCachedLayerRenderStates.empty() &&
mCachedRenderStateVersion == renderStateVersion &&
mCachedRenderStateVersion == versions.renderStateVersion &&
mCachedRenderStateWidth == renderWidth &&
mCachedRenderStateHeight == renderHeight;
if (renderStateCacheValid)
{
applyOscOverlays(mCachedLayerRenderStates, true);
if (mCachedParameterStateVersion != parameterStateVersion &&
mRuntimeHost->TryRefreshCachedLayerStates(mCachedLayerRenderStates))
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 = parameterStateVersion;
applyOscOverlays(mCachedLayerRenderStates, true);
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
applyOscOverlays(renderSnapshot.states, true);
}
layerStates = mCachedLayerRenderStates;
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
mCachedLayerRenderStates = renderSnapshot.states;
layerStates = renderSnapshot.states;
mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates);
}
else
{
if (mRuntimeHost->TryGetLayerRenderStates(renderWidth, renderHeight, layerStates))
RuntimeRenderStateSnapshot renderSnapshot;
if (mRuntimeSnapshotProvider->TryGetRenderStateSnapshot(renderWidth, renderHeight, renderSnapshot))
{
mCachedLayerRenderStates = layerStates;
mCachedRenderStateVersion = renderStateVersion;
mCachedParameterStateVersion = parameterStateVersion;
mCachedRenderStateWidth = renderWidth;
mCachedRenderStateHeight = renderHeight;
mCachedLayerRenderStates = renderSnapshot.states;
mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion;
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
mCachedRenderStateWidth = renderSnapshot.outputWidth;
mCachedRenderStateHeight = renderSnapshot.outputHeight;
applyOscOverlays(mCachedLayerRenderStates, true);
layerStates = mCachedLayerRenderStates;
}
@@ -634,25 +632,19 @@ void OpenGLComposite::renderEffect()
{
applyOscOverlays(mCachedLayerRenderStates, true);
layerStates = mCachedLayerRenderStates;
mRuntimeHost->RefreshDynamicRenderStateFields(layerStates);
mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates);
}
}
}
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
mRenderPass->Render(
const unsigned historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0;
mRenderEngine->RenderLayerStack(
hasInputSource,
layerStates,
mVideoIO->InputFrameWidth(),
mVideoIO->InputFrameHeight(),
mVideoIO->CaptureTextureWidth(),
mVideoIO->InputPixelFormat(),
historyCap,
[this](const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) {
return mShaderPrograms->UpdateTextBindingTexture(state, textBinding, error);
},
[this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) {
return mShaderPrograms->UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
});
mVideoBackend->InputFrameWidth(),
mVideoBackend->InputFrameHeight(),
mVideoBackend->CaptureTextureWidth(),
mVideoBackend->InputPixelFormat(),
historyCap);
}
void OpenGLComposite::ProcessScreenshotRequest()
@@ -660,21 +652,16 @@ void OpenGLComposite::ProcessScreenshotRequest()
if (!mScreenshotRequested.exchange(false))
return;
const unsigned width = mVideoIO ? mVideoIO->OutputFrameWidth() : 0;
const unsigned height = mVideoIO ? mVideoIO->OutputFrameHeight() : 0;
const unsigned width = mVideoBackend ? mVideoBackend->OutputFrameWidth() : 0;
const unsigned height = mVideoBackend ? mVideoBackend->OutputFrameHeight() : 0;
if (width == 0 || height == 0)
return;
std::vector<unsigned char> bottomUpPixels(static_cast<std::size_t>(width) * height * 4);
std::vector<unsigned char> bottomUpPixels;
if (!mRenderEngine->ReadOutputFrameRgba(width, height, bottomUpPixels))
return;
std::vector<unsigned char> topDownPixels(bottomUpPixels.size());
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->OutputFramebuffer());
glReadBuffer(GL_COLOR_ATTACHMENT0);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data());
glPixelStorei(GL_PACK_ALIGNMENT, 4);
const std::size_t rowBytes = static_cast<std::size_t>(width) * 4;
for (unsigned y = 0; y < height; ++y)
{
@@ -699,8 +686,8 @@ void OpenGLComposite::ProcessScreenshotRequest()
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
{
const std::filesystem::path root = mRuntimeHost && !mRuntimeHost->GetRuntimeRoot().empty()
? mRuntimeHost->GetRuntimeRoot()
const std::filesystem::path root = mRuntimeStore && !mRuntimeStore->GetRuntimeDataRoot().empty()
? mRuntimeStore->GetRuntimeDataRoot()
: std::filesystem::current_path();
const auto now = std::chrono::system_clock::now();
@@ -726,7 +713,7 @@ bool OpenGLComposite::ProcessRuntimePollResults()
const RuntimePollEvents events = mRuntimeServices->ConsumePollEvents();
if (events.failed)
{
mRuntimeHost->SetCompileStatus(false, events.error);
mRuntimeStore->SetCompileStatus(false, events.error);
broadcastRuntimeState();
return false;
}
@@ -741,9 +728,9 @@ bool OpenGLComposite::ProcessRuntimePollResults()
return true;
char compilerErrorMessage[1024] = {};
if (!mShaderPrograms->CommitPreparedLayerPrograms(readyBuild, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
if (!mRenderEngine->CommitPreparedLayerPrograms(readyBuild, mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
{
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
mRuntimeStore->SetCompileStatus(false, compilerErrorMessage);
mUseCommittedLayerStates = true;
mPreserveFeedbackOnNextShaderBuild = false;
broadcastRuntimeState();
@@ -751,16 +738,16 @@ bool OpenGLComposite::ProcessRuntimePollResults()
}
mUseCommittedLayerStates = false;
mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates();
mShaderPrograms->ResetTemporalHistoryState();
mCachedLayerRenderStates = mRenderEngine->CommittedLayerStates();
mRenderEngine->ResetTemporalHistoryState();
if (!mPreserveFeedbackOnNextShaderBuild)
mShaderPrograms->ResetShaderFeedbackState();
mRenderEngine->ResetShaderFeedbackState();
mPreserveFeedbackOnNextShaderBuild = false;
broadcastRuntimeState();
return true;
}
mRuntimeHost->SetCompileStatus(true, "Shader rebuild queued.");
mRuntimeStore->SetCompileStatus(true, "Shader rebuild queued.");
mPreserveFeedbackOnNextShaderBuild = false;
RequestShaderBuild();
broadcastRuntimeState();
@@ -769,13 +756,13 @@ bool OpenGLComposite::ProcessRuntimePollResults()
void OpenGLComposite::RequestShaderBuild()
{
if (!mShaderBuildQueue || !mVideoIO)
if (!mShaderBuildQueue || !mVideoBackend)
return;
mUseCommittedLayerStates = true;
if (mRuntimeHost)
mRuntimeHost->ClearReloadRequest();
mShaderBuildQueue->RequestBuild(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight());
mRuntimeStore->ClearReloadRequest();
mShaderBuildQueue->RequestBuild(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight());
}
void OpenGLComposite::broadcastRuntimeState()
@@ -786,8 +773,8 @@ void OpenGLComposite::broadcastRuntimeState()
void OpenGLComposite::resetTemporalHistoryState()
{
mShaderPrograms->ResetTemporalHistoryState();
mShaderPrograms->ResetShaderFeedbackState();
mRenderEngine->ResetTemporalHistoryState();
mRenderEngine->ResetShaderFeedbackState();
}
bool OpenGLComposite::CheckOpenGLExtensions()

View File

@@ -12,8 +12,9 @@
#include <comutil.h>
#include "GLExtensions.h"
#include "OpenGLRenderer.h"
#include "RuntimeHost.h"
#include "RuntimeSnapshotProvider.h"
#include "RuntimeStore.h"
#include <functional>
#include <atomic>
@@ -25,13 +26,10 @@
#include <deque>
#include <chrono>
class VideoIODevice;
class OpenGLVideoIOBridge;
class OpenGLRenderPass;
class OpenGLRenderPipeline;
class OpenGLShaderPrograms;
class RenderEngine;
class RuntimeServices;
class ShaderBuildQueue;
class VideoBackend;
class OpenGLComposite
@@ -72,7 +70,6 @@ private:
void resizeWindow(int width, int height);
bool CheckOpenGLExtensions();
void PublishVideoIOStatus(const std::string& statusMessage);
using LayerProgram = OpenGLRenderer::LayerProgram;
struct OscOverlayState
{
std::string layerKey;
@@ -92,15 +89,13 @@ private:
HGLRC hGLRC;
CRITICAL_SECTION pMutex;
std::unique_ptr<VideoIODevice> mVideoIO;
std::unique_ptr<OpenGLRenderer> mRenderer;
std::unique_ptr<RuntimeHost> mRuntimeHost;
std::unique_ptr<OpenGLVideoIOBridge> mVideoIOBridge;
std::unique_ptr<OpenGLRenderPass> mRenderPass;
std::unique_ptr<OpenGLRenderPipeline> mRenderPipeline;
std::unique_ptr<OpenGLShaderPrograms> mShaderPrograms;
std::unique_ptr<RuntimeStore> mRuntimeStore;
std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;
std::unique_ptr<RenderEngine> mRenderEngine;
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
std::unique_ptr<RuntimeServices> mRuntimeServices;
std::unique_ptr<VideoBackend> mVideoBackend;
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
uint64_t mCachedRenderStateVersion = 0;
uint64_t mCachedParameterStateVersion = 0;

View File

@@ -3,22 +3,22 @@
std::string OpenGLComposite::GetRuntimeStateJson() const
{
return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}";
return mRuntimeStore ? mRuntimeStore->BuildPersistentStateJson() : "{}";
}
unsigned short OpenGLComposite::GetControlServerPort() const
{
return mRuntimeHost ? mRuntimeHost->GetServerPort() : 0;
return mRuntimeStore ? mRuntimeStore->GetConfiguredControlServerPort() : 0;
}
unsigned short OpenGLComposite::GetOscPort() const
{
return mRuntimeHost ? mRuntimeHost->GetOscPort() : 0;
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscPort() : 0;
}
std::string OpenGLComposite::GetOscBindAddress() const
{
return mRuntimeHost ? mRuntimeHost->GetOscBindAddress() : "127.0.0.1";
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscBindAddress() : "127.0.0.1";
}
std::string OpenGLComposite::GetControlUrl() const
@@ -38,7 +38,7 @@ std::string OpenGLComposite::GetOscAddress() const
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
{
if (!mRuntimeHost->AddLayer(shaderId, error))
if (!mRuntimeStore->CreateStoredLayer(shaderId, error))
return false;
ReloadShader(true);
@@ -48,7 +48,7 @@ bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
{
if (!mRuntimeHost->RemoveLayer(layerId, error))
if (!mRuntimeStore->DeleteStoredLayer(layerId, error))
return false;
ReloadShader(true);
@@ -58,7 +58,7 @@ bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
if (!mRuntimeHost->MoveLayer(layerId, direction, error))
if (!mRuntimeStore->MoveStoredLayer(layerId, direction, error))
return false;
ReloadShader(true);
@@ -68,7 +68,7 @@ bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
if (!mRuntimeHost->MoveLayerToIndex(layerId, targetIndex, error))
if (!mRuntimeStore->MoveStoredLayerToIndex(layerId, targetIndex, error))
return false;
ReloadShader(true);
@@ -78,7 +78,7 @@ bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t t
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
{
if (!mRuntimeHost->SetLayerBypass(layerId, bypassed, error))
if (!mRuntimeStore->SetStoredLayerBypassState(layerId, bypassed, error))
return false;
ReloadShader();
@@ -88,7 +88,7 @@ bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed,
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
{
if (!mRuntimeHost->SetLayerShader(layerId, shaderId, error))
if (!mRuntimeStore->SetStoredLayerShaderSelection(layerId, shaderId, error))
return false;
ReloadShader();
@@ -102,7 +102,7 @@ bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const
if (!ParseJson(valueJson, parsedValue, error))
return false;
if (!mRuntimeHost->UpdateLayerParameter(layerId, parameterId, parsedValue, error))
if (!mRuntimeStore->SetStoredParameterValue(layerId, parameterId, parsedValue, error))
return false;
broadcastRuntimeState();
@@ -115,7 +115,7 @@ bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& la
if (!ParseJson(valueJson, parsedValue, error))
return false;
if (!mRuntimeHost->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue, error))
if (!mRuntimeStore->SetStoredParameterValueByControlKey(layerKey, parameterKey, parsedValue, error))
return false;
broadcastRuntimeState();
@@ -124,7 +124,7 @@ bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& la
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
{
if (!mRuntimeHost->ResetLayerParameters(layerId, error))
if (!mRuntimeStore->ResetStoredLayerParameterValues(layerId, error))
return false;
mOscOverlayStates.clear();
@@ -138,7 +138,7 @@ bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::stri
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
{
if (!mRuntimeHost->SaveStackPreset(presetName, error))
if (!mRuntimeStore->SaveStackPresetSnapshot(presetName, error))
return false;
broadcastRuntimeState();
@@ -147,7 +147,7 @@ bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
{
if (!mRuntimeHost->LoadStackPreset(presetName, error))
if (!mRuntimeStore->LoadStackPresetSnapshot(presetName, error))
return false;
ReloadShader();

View File

@@ -0,0 +1,180 @@
#include "RenderEngine.h"
#include <gl/gl.h>
RenderEngine::RenderEngine(
RuntimeHost& runtimeHost,
RuntimeSnapshotProvider& runtimeSnapshotProvider,
CRITICAL_SECTION& mutex,
HDC hdc,
HGLRC hglrc,
RenderEffectCallback renderEffect,
ScreenshotCallback screenshotReady,
PreviewPaintCallback previewPaint) :
mRenderer(),
mRenderPass(mRenderer),
mRenderPipeline(mRenderer, runtimeHost, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)),
mShaderPrograms(mRenderer, runtimeHost, runtimeSnapshotProvider),
mMutex(mutex),
mHdc(hdc),
mHglrc(hglrc)
{
}
RenderEngine::~RenderEngine()
{
mRenderer.DestroyResources();
}
bool RenderEngine::CompileDecodeShader(int errorMessageSize, char* errorMessage)
{
return mShaderPrograms.CompileDecodeShader(errorMessageSize, errorMessage);
}
bool RenderEngine::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
{
return mShaderPrograms.CompileOutputPackShader(errorMessageSize, errorMessage);
}
bool RenderEngine::InitializeResources(
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
unsigned outputFrameWidth,
unsigned outputFrameHeight,
unsigned outputPackTextureWidth,
std::string& error)
{
return mRenderer.InitializeResources(
inputFrameWidth,
inputFrameHeight,
captureTextureWidth,
outputFrameWidth,
outputFrameHeight,
outputPackTextureWidth,
error);
}
bool RenderEngine::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
return mShaderPrograms.CompileLayerPrograms(inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
}
bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
return mShaderPrograms.CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
}
const std::vector<RuntimeRenderState>& RenderEngine::CommittedLayerStates() const
{
return mShaderPrograms.CommittedLayerStates();
}
void RenderEngine::ResetTemporalHistoryState()
{
mShaderPrograms.ResetTemporalHistoryState();
}
void RenderEngine::ResetShaderFeedbackState()
{
mShaderPrograms.ResetShaderFeedbackState();
}
void RenderEngine::ResizeView(int width, int height)
{
mRenderer.ResizeView(width, height);
}
bool RenderEngine::TryPresentToWindow(unsigned outputFrameWidth, unsigned outputFrameHeight)
{
if (!TryEnterCriticalSection(&mMutex))
return false;
mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight);
LeaveCriticalSection(&mMutex);
return true;
}
bool RenderEngine::TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
{
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
return true;
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
if (!TryEnterCriticalSection(&mMutex))
return false;
wglMakeCurrent(mHdc, mHglrc);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer());
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW);
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
else
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
return true;
}
bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{
EnterCriticalSection(&mMutex);
wglMakeCurrent(mHdc, mHglrc);
const bool rendered = mRenderPipeline.RenderFrame(context, outputFrame);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
return rendered;
}
void RenderEngine::RenderLayerStack(
bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
VideoIOPixelFormat inputPixelFormat,
unsigned historyCap)
{
mRenderPass.Render(
hasInputSource,
layerStates,
inputFrameWidth,
inputFrameHeight,
captureTextureWidth,
inputPixelFormat,
historyCap,
[this](const RuntimeRenderState& state, OpenGLRenderer::LayerProgram::TextBinding& textBinding, std::string& error) {
return mShaderPrograms.UpdateTextBindingTexture(state, textBinding, error);
},
[this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) {
return mShaderPrograms.UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
});
}
bool RenderEngine::ReadOutputFrameRgba(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels)
{
if (width == 0 || height == 0)
return false;
EnterCriticalSection(&mMutex);
wglMakeCurrent(mHdc, mHglrc);
bottomUpPixels.resize(static_cast<std::size_t>(width) * height * 4);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
glReadBuffer(GL_COLOR_ATTACHMENT0);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data());
glPixelStorei(GL_PACK_ALIGNMENT, 4);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
return true;
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include "OpenGLRenderPass.h"
#include "OpenGLRenderPipeline.h"
#include "OpenGLRenderer.h"
#include "OpenGLShaderPrograms.h"
#include "RuntimeHost.h"
#include "RuntimeSnapshotProvider.h"
#include <windows.h>
#include <functional>
#include <string>
#include <vector>
class RenderEngine
{
public:
using RenderEffectCallback = std::function<void()>;
using ScreenshotCallback = std::function<void()>;
using PreviewPaintCallback = std::function<void()>;
RenderEngine(
RuntimeHost& runtimeHost,
RuntimeSnapshotProvider& runtimeSnapshotProvider,
CRITICAL_SECTION& mutex,
HDC hdc,
HGLRC hglrc,
RenderEffectCallback renderEffect,
ScreenshotCallback screenshotReady,
PreviewPaintCallback previewPaint);
~RenderEngine();
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
bool InitializeResources(
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
unsigned outputFrameWidth,
unsigned outputFrameHeight,
unsigned outputPackTextureWidth,
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);
const std::vector<RuntimeRenderState>& CommittedLayerStates() const;
void ResetTemporalHistoryState();
void ResetShaderFeedbackState();
void ResizeView(int width, int height);
bool TryPresentToWindow(unsigned outputFrameWidth, unsigned outputFrameHeight);
bool TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
bool RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
void RenderLayerStack(
bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
VideoIOPixelFormat inputPixelFormat,
unsigned historyCap);
bool ReadOutputFrameRgba(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels);
private:
OpenGLRenderer mRenderer;
OpenGLRenderPass mRenderPass;
OpenGLRenderPipeline mRenderPipeline;
OpenGLShaderPrograms mShaderPrograms;
CRITICAL_SECTION& mMutex;
HDC mHdc;
HGLRC mHglrc;
};

View File

@@ -47,7 +47,7 @@ bool OpenGLRenderPipeline::RenderFrame(const RenderPipelineFrameContext& context
const auto renderEndTime = std::chrono::steady_clock::now();
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
mRuntimeHost.TrySetPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
mRuntimeHost.GetHealthTelemetry().TryRecordPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
mRuntimeHost.TryAdvanceFrame();
ReadOutputFrame(state, outputFrame);

View File

@@ -1,26 +1,17 @@
#include "OpenGLVideoIOBridge.h"
#include "OpenGLRenderer.h"
#include "RuntimeHost.h"
#include "HealthTelemetry.h"
#include "RenderEngine.h"
#include <chrono>
#include <gl/gl.h>
OpenGLVideoIOBridge::OpenGLVideoIOBridge(
VideoIODevice& videoIO,
OpenGLRenderer& renderer,
OpenGLRenderPipeline& renderPipeline,
RuntimeHost& runtimeHost,
CRITICAL_SECTION& mutex,
HDC hdc,
HGLRC hglrc) :
RenderEngine& renderEngine,
HealthTelemetry& healthTelemetry) :
mVideoIO(videoIO),
mRenderer(renderer),
mRenderPipeline(renderPipeline),
mRuntimeHost(runtimeHost),
mMutex(mutex),
mHdc(hdc),
mHglrc(hglrc)
mRenderEngine(renderEngine),
mHealthTelemetry(healthTelemetry)
{
}
@@ -46,7 +37,7 @@ void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionRe
else if (completionResult == VideoIOCompletionResult::Flushed)
++mFlushedFrameCount;
mRuntimeHost.TrySetFramePacingStats(
mHealthTelemetry.TryRecordFramePacingStats(
mCompletionIntervalMilliseconds,
mSmoothedCompletionIntervalMilliseconds,
mMaxCompletionIntervalMilliseconds,
@@ -58,38 +49,12 @@ void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionRe
void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame)
{
const VideoIOState& state = mVideoIO.State();
mRuntimeHost.TrySetSignalStatus(!inputFrame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName);
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
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
// Never let input upload stall the playout/render callback. If the GL bridge
// is busy producing an output frame, skip this upload and use the next input.
if (!TryEnterCriticalSection(&mMutex))
return;
wglMakeCurrent(mHdc, mHglrc); // make OpenGL context current in this thread
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer());
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW);
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
// NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data.
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
else
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
mRenderEngine.TryUploadInputFrame(inputFrame, state);
}
void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& completion)
@@ -104,15 +69,7 @@ void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& complet
frameContext.videoState = state;
frameContext.completion = completion;
EnterCriticalSection(&mMutex);
// make GL context current in this thread
wglMakeCurrent(mHdc, mHglrc);
mRenderPipeline.RenderFrame(frameContext, outputFrame);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
mRenderEngine.RenderOutputFrame(frameContext, outputFrame);
mVideoIO.EndOutputFrame(outputFrame);

View File

@@ -2,24 +2,19 @@
#include "OpenGLRenderPipeline.h"
#include <windows.h>
#include <chrono>
#include <cstdint>
class RuntimeHost;
class HealthTelemetry;
class RenderEngine;
class OpenGLVideoIOBridge
{
public:
OpenGLVideoIOBridge(
VideoIODevice& videoIO,
OpenGLRenderer& renderer,
OpenGLRenderPipeline& renderPipeline,
RuntimeHost& runtimeHost,
CRITICAL_SECTION& mutex,
HDC hdc,
HGLRC hglrc);
RenderEngine& renderEngine,
HealthTelemetry& healthTelemetry);
void VideoFrameArrived(const VideoIOFrame& inputFrame);
void PlayoutFrameCompleted(const VideoIOCompletion& completion);
@@ -28,12 +23,8 @@ private:
void RecordFramePacing(VideoIOCompletionResult completionResult);
VideoIODevice& mVideoIO;
OpenGLRenderer& mRenderer;
OpenGLRenderPipeline& mRenderPipeline;
RuntimeHost& mRuntimeHost;
CRITICAL_SECTION& mMutex;
HDC mHdc;
HGLRC mHglrc;
RenderEngine& mRenderEngine;
HealthTelemetry& mHealthTelemetry;
std::chrono::steady_clock::time_point mLastPlayoutCompletionTime;
double mCompletionIntervalMilliseconds = 0.0;
double mSmoothedCompletionIntervalMilliseconds = 0.0;

View File

@@ -29,9 +29,10 @@ std::size_t RequiredTemporaryRenderTargets(const std::vector<OpenGLRenderer::Lay
}
}
OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost) :
OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, RuntimeSnapshotProvider& runtimeSnapshotProvider) :
mRenderer(renderer),
mRuntimeHost(runtimeHost),
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mGlobalParamsBuffer(renderer),
mCompiler(renderer, runtimeHost, mTextureBindings)
{
@@ -39,7 +40,9 @@ OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost
bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost.GetLayerRenderStates(inputFrameWidth, inputFrameHeight);
const RuntimeRenderStateSnapshot renderSnapshot =
mRuntimeSnapshotProvider.GetRenderStateSnapshot(inputFrameWidth, inputFrameHeight);
const std::vector<RuntimeRenderState>& layerStates = renderSnapshot.states;
std::string temporalError;
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError))
@@ -87,7 +90,7 @@ bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsign
DestroyLayerPrograms();
mRenderer.ReplaceLayerPrograms(newPrograms);
mCommittedLayerStates = layerStates;
mCommittedLayerStates = renderSnapshot.states;
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
mRuntimeHost.ClearReloadRequest();
@@ -105,18 +108,18 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild
std::string temporalError;
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.layerStates, historyCap, temporalError))
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.renderSnapshot.states, historyCap, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.layerStates, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.renderSnapshot.states, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
if (mRenderer.ResourcesInitialized() &&
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.layerStates, inputFrameWidth, inputFrameHeight, temporalError))
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.renderSnapshot.states, inputFrameWidth, inputFrameHeight, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
@@ -150,7 +153,7 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild
DestroyLayerPrograms();
mRenderer.ReplaceLayerPrograms(newPrograms);
mCommittedLayerStates = preparedBuild.layerStates;
mCommittedLayerStates = preparedBuild.renderSnapshot.states;
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
mRuntimeHost.ClearReloadRequest();

View File

@@ -3,6 +3,7 @@
#include "GlobalParamsBuffer.h"
#include "OpenGLRenderer.h"
#include "RuntimeHost.h"
#include "RuntimeSnapshotProvider.h"
#include "ShaderBuildQueue.h"
#include "ShaderTypes.h"
#include "ShaderProgramCompiler.h"
@@ -15,7 +16,7 @@ class OpenGLShaderPrograms
public:
using LayerProgram = OpenGLRenderer::LayerProgram;
OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost);
OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, 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,6 +34,7 @@ public:
private:
OpenGLRenderer& mRenderer;
RuntimeHost& mRuntimeHost;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
ShaderTextureBindings mTextureBindings;
GlobalParamsBuffer mGlobalParamsBuffer;
ShaderProgramCompiler mCompiler;

View File

@@ -1,7 +1,5 @@
#include "ShaderBuildQueue.h"
#include "RuntimeHost.h"
#include <chrono>
#include <utility>
@@ -10,8 +8,8 @@ namespace
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
}
ShaderBuildQueue::ShaderBuildQueue(RuntimeHost& runtimeHost) :
mRuntimeHost(runtimeHost),
ShaderBuildQueue::ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider) :
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mWorkerThread([this]() { WorkerLoop(); })
{
}
@@ -113,14 +111,14 @@ PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned output
{
PreparedShaderBuild build;
build.generation = generation;
build.layerStates = mRuntimeHost.GetLayerRenderStates(outputWidth, outputHeight);
build.layers.reserve(build.layerStates.size());
build.renderSnapshot = mRuntimeSnapshotProvider.GetRenderStateSnapshot(outputWidth, outputHeight);
build.layers.reserve(build.renderSnapshot.states.size());
for (const RuntimeRenderState& state : build.layerStates)
for (const RuntimeRenderState& state : build.renderSnapshot.states)
{
PreparedLayerShader layer;
layer.state = state;
if (!mRuntimeHost.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
{
build.succeeded = false;
return build;

View File

@@ -1,5 +1,6 @@
#pragma once
#include "RuntimeSnapshotProvider.h"
#include "ShaderTypes.h"
#include <condition_variable>
@@ -9,8 +10,6 @@
#include <thread>
#include <vector>
class RuntimeHost;
struct PreparedLayerShader
{
RuntimeRenderState state;
@@ -22,14 +21,14 @@ struct PreparedShaderBuild
uint64_t generation = 0;
bool succeeded = false;
std::string message;
std::vector<RuntimeRenderState> layerStates;
RuntimeRenderStateSnapshot renderSnapshot;
std::vector<PreparedLayerShader> layers;
};
class ShaderBuildQueue
{
public:
explicit ShaderBuildQueue(RuntimeHost& runtimeHost);
explicit ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider);
~ShaderBuildQueue();
ShaderBuildQueue(const ShaderBuildQueue&) = delete;
@@ -43,7 +42,7 @@ private:
void WorkerLoop();
PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight);
RuntimeHost& mRuntimeHost;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
std::thread mWorkerThread;
std::mutex mMutex;
std::condition_variable mCondition;

View File

@@ -0,0 +1,43 @@
#include "stdafx.h"
#include "HealthTelemetry.h"
#include "RuntimeHost.h"
HealthTelemetry::HealthTelemetry(RuntimeHost& runtimeHost) :
mRuntimeHost(runtimeHost)
{
}
void HealthTelemetry::ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
mRuntimeHost.WriteSignalStatus(hasSignal, width, height, modeName);
}
bool HealthTelemetry::TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
return mRuntimeHost.TryWriteSignalStatus(hasSignal, width, height, modeName);
}
void HealthTelemetry::RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
mRuntimeHost.WritePerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
}
bool HealthTelemetry::TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
return mRuntimeHost.TryWritePerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
}
void HealthTelemetry::RecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
mRuntimeHost.WriteFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
}
bool HealthTelemetry::TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
return mRuntimeHost.TryWriteFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
#include <string>
class RuntimeHost;
// Phase 1 compatibility seam for status and timing reporting. The current
// implementation still writes through RuntimeHost, but callers can now target
// HealthTelemetry as the home for operational visibility work.
class HealthTelemetry
{
public:
explicit HealthTelemetry(RuntimeHost& runtimeHost);
void ReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
bool TryReportSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void RecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
bool TryRecordPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
void RecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
bool TryRecordFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
private:
RuntimeHost& mRuntimeHost;
};

View File

@@ -699,7 +699,8 @@ bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& sha
}
RuntimeHost::RuntimeHost()
: mReloadRequested(false),
: mHealthTelemetry(*this),
mReloadRequested(false),
mCompileSucceeded(false),
mHasSignal(false),
mSignalWidth(0),
@@ -1351,12 +1352,22 @@ void RuntimeHost::SetCompileStatus(bool succeeded, const std::string& message)
}
void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
mHealthTelemetry.ReportSignalStatus(hasSignal, width, height, modeName);
}
bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
return mHealthTelemetry.TryReportSignalStatus(hasSignal, width, height, modeName);
}
void RuntimeHost::WriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
std::lock_guard<std::mutex> lock(mMutex);
SetSignalStatusLocked(hasSignal, width, height, modeName);
}
bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
bool RuntimeHost::TryWriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
{
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
if (!lock.owns_lock())
@@ -1413,12 +1424,22 @@ void RuntimeHost::SetVideoIOStatus(const std::string& backendName, const std::st
}
void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
mHealthTelemetry.RecordPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
}
bool RuntimeHost::TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
return mHealthTelemetry.TryRecordPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
}
void RuntimeHost::WritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
std::lock_guard<std::mutex> lock(mMutex);
SetPerformanceStatsLocked(frameBudgetMilliseconds, renderMilliseconds);
}
bool RuntimeHost::TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
bool RuntimeHost::TryWritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
{
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
if (!lock.owns_lock())
@@ -1440,13 +1461,27 @@ void RuntimeHost::SetPerformanceStatsLocked(double frameBudgetMilliseconds, doub
void RuntimeHost::SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
mHealthTelemetry.RecordFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
}
bool RuntimeHost::TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
return mHealthTelemetry.TryRecordFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
}
void RuntimeHost::WriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
std::lock_guard<std::mutex> lock(mMutex);
SetFramePacingStatsLocked(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
}
bool RuntimeHost::TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
bool RuntimeHost::TryWriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
{
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);

View File

@@ -1,5 +1,6 @@
#pragma once
#include "HealthTelemetry.h"
#include "RuntimeJson.h"
#include "ShaderTypes.h"
@@ -52,6 +53,8 @@ public:
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
void AdvanceFrame();
bool TryAdvanceFrame();
HealthTelemetry& GetHealthTelemetry() { return mHealthTelemetry; }
const HealthTelemetry& GetHealthTelemetry() const { return mHealthTelemetry; }
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error);
std::vector<RuntimeRenderState> GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const;
@@ -145,14 +148,24 @@ private:
LayerPersistentState* FindLayerById(const std::string& layerId);
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
std::string GenerateLayerId();
void WriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
bool TryWriteSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void MarkRenderStateDirtyLocked();
void MarkParameterStateDirtyLocked();
void WritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
bool TryWritePerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
void SetPerformanceStatsLocked(double frameBudgetMilliseconds, double renderMilliseconds);
void WriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
bool TryWriteFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
void SetFramePacingStatsLocked(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
private:
friend class HealthTelemetry;
HealthTelemetry mHealthTelemetry;
mutable std::mutex mMutex;
AppConfig mConfig;
PersistentState mPersistentState;

View File

@@ -0,0 +1,153 @@
#include "RuntimeSnapshotProvider.h"
#include <utility>
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeHost& runtimeHost) :
mRuntimeHost(runtimeHost)
{
}
bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
{
return mRuntimeHost.BuildLayerPassFragmentShaderSources(layerId, passSources, error);
}
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
{
RuntimeSnapshotVersions versions;
versions.renderStateVersion = mRuntimeHost.GetRenderStateVersion();
versions.parameterStateVersion = mRuntimeHost.GetParameterStateVersion();
return versions;
}
RuntimeRenderFrameContext RuntimeSnapshotProvider::GetFrameContext() const
{
std::vector<RuntimeRenderState> stateScratch(1);
mRuntimeHost.RefreshDynamicRenderStateFields(stateScratch);
RuntimeRenderFrameContext frameContext;
const RuntimeRenderState& state = stateScratch.front();
frameContext.timeSeconds = state.timeSeconds;
frameContext.utcTimeSeconds = state.utcTimeSeconds;
frameContext.utcOffsetSeconds = state.utcOffsetSeconds;
frameContext.startupRandom = state.startupRandom;
frameContext.frameCount = state.frameCount;
return frameContext;
}
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
{
for (;;)
{
const RuntimeSnapshotVersions versionsBefore = GetVersions();
RuntimeRenderStateSnapshot snapshot;
snapshot.outputWidth = outputWidth;
snapshot.outputHeight = outputHeight;
snapshot.states = mRuntimeHost.GetLayerRenderStates(outputWidth, outputHeight);
const RuntimeSnapshotVersions versionsAfter = GetVersions();
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
versionsBefore.parameterStateVersion == versionsAfter.parameterStateVersion)
{
snapshot.versions = versionsAfter;
return snapshot;
}
}
}
bool RuntimeSnapshotProvider::TryGetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const
{
const RuntimeSnapshotVersions versionsBefore = GetVersions();
std::vector<RuntimeRenderState> states;
if (!mRuntimeHost.TryGetLayerRenderStates(outputWidth, outputHeight, states))
return false;
const RuntimeSnapshotVersions versionsAfter = GetVersions();
if (versionsBefore.renderStateVersion != versionsAfter.renderStateVersion ||
versionsBefore.parameterStateVersion != versionsAfter.parameterStateVersion)
{
return false;
}
snapshot.outputWidth = outputWidth;
snapshot.outputHeight = outputHeight;
snapshot.versions = versionsAfter;
snapshot.states = std::move(states);
return true;
}
bool RuntimeSnapshotProvider::TryRefreshSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
{
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
if (!mRuntimeHost.TryRefreshCachedLayerStates(snapshot.states))
return false;
const RuntimeSnapshotVersions versions = GetVersions();
if (versions.renderStateVersion != expectedRenderStateVersion)
return false;
snapshot.versions = versions;
return true;
}
void RuntimeSnapshotProvider::ApplyFrameContext(std::vector<RuntimeRenderState>& states, const RuntimeRenderFrameContext& frameContext) const
{
for (RuntimeRenderState& state : states)
{
state.timeSeconds = frameContext.timeSeconds;
state.utcTimeSeconds = frameContext.utcTimeSeconds;
state.utcOffsetSeconds = frameContext.utcOffsetSeconds;
state.startupRandom = frameContext.startupRandom;
state.frameCount = frameContext.frameCount;
}
}
void RuntimeSnapshotProvider::ApplyFrameContext(RuntimeRenderStateSnapshot& snapshot, const RuntimeRenderFrameContext& frameContext) const
{
ApplyFrameContext(snapshot.states, frameContext);
}
std::vector<RuntimeRenderState> RuntimeSnapshotProvider::GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const
{
return GetRenderStateSnapshot(outputWidth, outputHeight).states;
}
bool RuntimeSnapshotProvider::TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
RuntimeRenderStateSnapshot snapshot;
if (!TryGetRenderStateSnapshot(outputWidth, outputHeight, snapshot))
return false;
states = std::move(snapshot.states);
return true;
}
bool RuntimeSnapshotProvider::TryRefreshCachedLayerStates(std::vector<RuntimeRenderState>& states) const
{
RuntimeRenderStateSnapshot snapshot;
snapshot.versions.renderStateVersion = mRuntimeHost.GetRenderStateVersion();
snapshot.versions.parameterStateVersion = mRuntimeHost.GetParameterStateVersion();
snapshot.states = states;
if (!TryRefreshSnapshotParameters(snapshot))
return false;
states = std::move(snapshot.states);
return true;
}
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
{
ApplyFrameContext(states, GetFrameContext());
}
uint64_t RuntimeSnapshotProvider::GetRenderStateVersion() const
{
return GetVersions().renderStateVersion;
}
uint64_t RuntimeSnapshotProvider::GetParameterStateVersion() const
{
return GetVersions().parameterStateVersion;
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include "RuntimeHost.h"
#include <cstdint>
#include <string>
#include <vector>
struct RuntimeSnapshotVersions
{
uint64_t renderStateVersion = 0;
uint64_t parameterStateVersion = 0;
};
struct RuntimeRenderFrameContext
{
double timeSeconds = 0.0;
double utcTimeSeconds = 0.0;
double utcOffsetSeconds = 0.0;
double startupRandom = 0.0;
double frameCount = 0.0;
};
struct RuntimeRenderStateSnapshot
{
RuntimeSnapshotVersions versions;
unsigned outputWidth = 0;
unsigned outputHeight = 0;
std::vector<RuntimeRenderState> states;
};
class RuntimeSnapshotProvider
{
public:
explicit RuntimeSnapshotProvider(RuntimeHost& runtimeHost);
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
RuntimeSnapshotVersions GetVersions() const;
RuntimeRenderFrameContext GetFrameContext() const;
RuntimeRenderStateSnapshot GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const;
bool TryGetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, RuntimeRenderStateSnapshot& snapshot) const;
bool TryRefreshSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const;
void ApplyFrameContext(std::vector<RuntimeRenderState>& states, const RuntimeRenderFrameContext& frameContext) const;
void ApplyFrameContext(RuntimeRenderStateSnapshot& snapshot, const RuntimeRenderFrameContext& frameContext) const;
std::vector<RuntimeRenderState> GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const;
bool TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
bool TryRefreshCachedLayerStates(std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
uint64_t GetRenderStateVersion() const;
uint64_t GetParameterStateVersion() const;
private:
RuntimeHost& mRuntimeHost;
};

View File

@@ -0,0 +1,161 @@
#include "RuntimeStore.h"
RuntimeStore::RuntimeStore(RuntimeHost& runtimeHost) :
mRuntimeHost(runtimeHost)
{
}
bool RuntimeStore::InitializeStore(std::string& error)
{
return mRuntimeHost.Initialize(error);
}
std::string RuntimeStore::BuildPersistentStateJson() const
{
return mRuntimeHost.BuildStateJson();
}
bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& error)
{
return mRuntimeHost.AddLayer(shaderId, error);
}
bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& error)
{
return mRuntimeHost.RemoveLayer(layerId, error);
}
bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, std::string& error)
{
return mRuntimeHost.MoveLayer(layerId, direction, error);
}
bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
return mRuntimeHost.MoveLayerToIndex(layerId, targetIndex, error);
}
bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
{
return mRuntimeHost.SetLayerBypass(layerId, bypassed, error);
}
bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error)
{
return mRuntimeHost.SetLayerShader(layerId, shaderId, error);
}
bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error)
{
return mRuntimeHost.UpdateLayerParameter(layerId, parameterId, newValue, error);
}
bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error)
{
return mRuntimeHost.UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, error);
}
bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error)
{
return mRuntimeHost.UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, persistState, error);
}
bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error)
{
return mRuntimeHost.ResetLayerParameters(layerId, error);
}
bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const
{
return mRuntimeHost.SaveStackPreset(presetName, error);
}
bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error)
{
return mRuntimeHost.LoadStackPreset(presetName, error);
}
const std::filesystem::path& RuntimeStore::GetRuntimeRepositoryRoot() const
{
return mRuntimeHost.GetRepoRoot();
}
const std::filesystem::path& RuntimeStore::GetRuntimeUiRoot() const
{
return mRuntimeHost.GetUiRoot();
}
const std::filesystem::path& RuntimeStore::GetRuntimeDocsRoot() const
{
return mRuntimeHost.GetDocsRoot();
}
const std::filesystem::path& RuntimeStore::GetRuntimeDataRoot() const
{
return mRuntimeHost.GetRuntimeRoot();
}
unsigned short RuntimeStore::GetConfiguredControlServerPort() const
{
return mRuntimeHost.GetServerPort();
}
unsigned short RuntimeStore::GetConfiguredOscPort() const
{
return mRuntimeHost.GetOscPort();
}
const std::string& RuntimeStore::GetConfiguredOscBindAddress() const
{
return mRuntimeHost.GetOscBindAddress();
}
double RuntimeStore::GetConfiguredOscSmoothing() const
{
return mRuntimeHost.GetOscSmoothing();
}
unsigned RuntimeStore::GetConfiguredMaxTemporalHistoryFrames() const
{
return mRuntimeHost.GetMaxTemporalHistoryFrames();
}
unsigned RuntimeStore::GetConfiguredPreviewFps() const
{
return mRuntimeHost.GetPreviewFps();
}
bool RuntimeStore::IsExternalKeyingConfigured() const
{
return mRuntimeHost.ExternalKeyingEnabled();
}
const std::string& RuntimeStore::GetConfiguredInputVideoFormat() const
{
return mRuntimeHost.GetInputVideoFormat();
}
const std::string& RuntimeStore::GetConfiguredInputFrameRate() const
{
return mRuntimeHost.GetInputFrameRate();
}
const std::string& RuntimeStore::GetConfiguredOutputVideoFormat() const
{
return mRuntimeHost.GetOutputVideoFormat();
}
const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const
{
return mRuntimeHost.GetOutputFrameRate();
}
void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message)
{
mRuntimeHost.SetCompileStatus(succeeded, message);
}
void RuntimeStore::ClearReloadRequest()
{
mRuntimeHost.ClearReloadRequest();
}

View File

@@ -0,0 +1,50 @@
#pragma once
#include "RuntimeHost.h"
#include <filesystem>
#include <string>
class RuntimeStore
{
public:
explicit RuntimeStore(RuntimeHost& runtimeHost);
bool InitializeStore(std::string& error);
std::string BuildPersistentStateJson() const;
bool CreateStoredLayer(const std::string& shaderId, std::string& error);
bool DeleteStoredLayer(const std::string& layerId, std::string& error);
bool MoveStoredLayer(const std::string& layerId, int direction, std::string& error);
bool MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
bool SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
bool SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error);
bool SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error);
bool SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error);
bool SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error);
bool ResetStoredLayerParameterValues(const std::string& layerId, std::string& error);
bool SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const;
bool LoadStackPresetSnapshot(const std::string& presetName, std::string& error);
const std::filesystem::path& GetRuntimeRepositoryRoot() const;
const std::filesystem::path& GetRuntimeUiRoot() const;
const std::filesystem::path& GetRuntimeDocsRoot() const;
const std::filesystem::path& GetRuntimeDataRoot() const;
unsigned short GetConfiguredControlServerPort() const;
unsigned short GetConfiguredOscPort() const;
const std::string& GetConfiguredOscBindAddress() const;
double GetConfiguredOscSmoothing() const;
unsigned GetConfiguredMaxTemporalHistoryFrames() const;
unsigned GetConfiguredPreviewFps() const;
bool IsExternalKeyingConfigured() const;
const std::string& GetConfiguredInputVideoFormat() const;
const std::string& GetConfiguredInputFrameRate() const;
const std::string& GetConfiguredOutputVideoFormat() const;
const std::string& GetConfiguredOutputFrameRate() const;
void SetCompileStatus(bool succeeded, const std::string& message);
void ClearReloadRequest();
private:
RuntimeHost& mRuntimeHost;
};

View File

@@ -0,0 +1,175 @@
#include "VideoBackend.h"
#include "DeckLinkSession.h"
#include "OpenGLVideoIOBridge.h"
#include "RenderEngine.h"
#include "HealthTelemetry.h"
VideoBackend::VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry) :
mVideoIODevice(std::make_unique<DeckLinkSession>()),
mBridge(std::make_unique<OpenGLVideoIOBridge>(*mVideoIODevice, renderEngine, healthTelemetry))
{
}
VideoBackend::~VideoBackend()
{
ReleaseResources();
}
void VideoBackend::ReleaseResources()
{
if (mVideoIODevice)
mVideoIODevice->ReleaseResources();
}
bool VideoBackend::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error)
{
return mVideoIODevice->DiscoverDevicesAndModes(videoModes, error);
}
bool VideoBackend::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error)
{
return mVideoIODevice->SelectPreferredFormats(videoModes, outputAlphaRequired, error);
}
bool VideoBackend::ConfigureInput(const VideoFormat& inputVideoMode, std::string& error)
{
return mVideoIODevice->ConfigureInput(
[this](const VideoIOFrame& frame) { mBridge->VideoFrameArrived(frame); },
inputVideoMode,
error);
}
bool VideoBackend::ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
{
return mVideoIODevice->ConfigureOutput(
[this](const VideoIOCompletion& completion) { mBridge->PlayoutFrameCompleted(completion); },
outputVideoMode,
externalKeyingEnabled,
error);
}
bool VideoBackend::Start()
{
return mVideoIODevice->Start();
}
bool VideoBackend::Stop()
{
return mVideoIODevice->Stop();
}
const VideoIOState& VideoBackend::State() const
{
return mVideoIODevice->State();
}
VideoIOState& VideoBackend::MutableState()
{
return mVideoIODevice->MutableState();
}
bool VideoBackend::BeginOutputFrame(VideoIOOutputFrame& frame)
{
return mVideoIODevice->BeginOutputFrame(frame);
}
void VideoBackend::EndOutputFrame(VideoIOOutputFrame& frame)
{
mVideoIODevice->EndOutputFrame(frame);
}
bool VideoBackend::ScheduleOutputFrame(const VideoIOOutputFrame& frame)
{
return mVideoIODevice->ScheduleOutputFrame(frame);
}
void VideoBackend::AccountForCompletionResult(VideoIOCompletionResult result)
{
mVideoIODevice->AccountForCompletionResult(result);
}
bool VideoBackend::HasInputDevice() const
{
return mVideoIODevice->HasInputDevice();
}
bool VideoBackend::HasInputSource() const
{
return mVideoIODevice->HasInputSource();
}
unsigned VideoBackend::InputFrameWidth() const
{
return mVideoIODevice->InputFrameWidth();
}
unsigned VideoBackend::InputFrameHeight() const
{
return mVideoIODevice->InputFrameHeight();
}
unsigned VideoBackend::OutputFrameWidth() const
{
return mVideoIODevice->OutputFrameWidth();
}
unsigned VideoBackend::OutputFrameHeight() const
{
return mVideoIODevice->OutputFrameHeight();
}
unsigned VideoBackend::CaptureTextureWidth() const
{
return mVideoIODevice->CaptureTextureWidth();
}
unsigned VideoBackend::OutputPackTextureWidth() const
{
return mVideoIODevice->OutputPackTextureWidth();
}
VideoIOPixelFormat VideoBackend::InputPixelFormat() const
{
return mVideoIODevice->InputPixelFormat();
}
const std::string& VideoBackend::InputDisplayModeName() const
{
return mVideoIODevice->InputDisplayModeName();
}
const std::string& VideoBackend::OutputModelName() const
{
return mVideoIODevice->OutputModelName();
}
bool VideoBackend::SupportsInternalKeying() const
{
return mVideoIODevice->SupportsInternalKeying();
}
bool VideoBackend::SupportsExternalKeying() const
{
return mVideoIODevice->SupportsExternalKeying();
}
bool VideoBackend::KeyerInterfaceAvailable() const
{
return mVideoIODevice->KeyerInterfaceAvailable();
}
bool VideoBackend::ExternalKeyingActive() const
{
return mVideoIODevice->ExternalKeyingActive();
}
const std::string& VideoBackend::StatusMessage() const
{
return mVideoIODevice->StatusMessage();
}
void VideoBackend::SetStatusMessage(const std::string& message)
{
mVideoIODevice->SetStatusMessage(message);
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include "VideoIOTypes.h"
#include <memory>
#include <string>
class HealthTelemetry;
class OpenGLVideoIOBridge;
class RenderEngine;
class VideoIODevice;
class VideoBackend
{
public:
VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry);
~VideoBackend();
void ReleaseResources();
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error);
bool ConfigureInput(const VideoFormat& inputVideoMode, std::string& error);
bool ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error);
bool Start();
bool Stop();
const VideoIOState& State() const;
VideoIOState& MutableState();
bool BeginOutputFrame(VideoIOOutputFrame& frame);
void EndOutputFrame(VideoIOOutputFrame& frame);
bool ScheduleOutputFrame(const VideoIOOutputFrame& frame);
void AccountForCompletionResult(VideoIOCompletionResult result);
bool HasInputDevice() const;
bool HasInputSource() const;
unsigned InputFrameWidth() const;
unsigned InputFrameHeight() const;
unsigned OutputFrameWidth() const;
unsigned OutputFrameHeight() const;
unsigned CaptureTextureWidth() const;
unsigned OutputPackTextureWidth() const;
VideoIOPixelFormat InputPixelFormat() const;
const std::string& InputDisplayModeName() const;
const std::string& OutputModelName() const;
bool SupportsInternalKeying() const;
bool SupportsExternalKeying() const;
bool KeyerInterfaceAvailable() const;
bool ExternalKeyingActive() const;
const std::string& StatusMessage() const;
void SetStatusMessage(const std::string& message);
private:
std::unique_ptr<VideoIODevice> mVideoIODevice;
std::unique_ptr<OpenGLVideoIOBridge> mBridge;
};

View File

@@ -393,6 +393,8 @@ Suggested deliverables:
- a short architecture diagram
- a responsibility table for each subsystem
- a list of allowed dependencies between subsystems
- a dedicated Phase 1 design note:
- [PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md)
### Phase 2. Introduce an internal event model

View File

@@ -0,0 +1,609 @@
# Phase 1 Design: Subsystem Boundaries and Target Architecture
This document expands Phase 1 of [ARCHITECTURE_RESILIENCE_REVIEW.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/ARCHITECTURE_RESILIENCE_REVIEW.md) into a concrete target design. Its purpose is to define the long-term subsystem split before later phases introduce a full event model, split `RuntimeHost`, and move rendering onto a sole-owner render thread.
The main goal of Phase 1 is not to immediately rewrite the app. It is to establish clear ownership boundaries so later refactors all move toward the same architecture instead of solving local problems in conflicting ways.
## Why Phase 1 Exists
Today the app works, but too many responsibilities still converge in a few places:
- `RuntimeHost` owns persistence, live layer state, shader package access, status reporting, and mutation entrypoints.
- `OpenGLComposite` coordinates runtime setup, render state retrieval, shader rebuild handling, transient OSC overlay behavior, and video backend integration.
- DeckLink callback-driven playout still reaches directly into render-facing work.
- Background services rely on polling and shared mutable state more than explicit subsystem contracts.
Those are exactly the kinds of overlaps that make timing issues, state regressions, and recovery edge cases harder to solve cleanly.
Phase 1 creates a map for where each responsibility should eventually live.
## Design Goals
The target architecture should optimize for:
- live timing isolation
- explicit state ownership
- predictable recovery behavior
- clear boundaries between persistent state and transient live state
- easier testing of non-GL and non-hardware logic
- fewer cross-thread shared mutable objects
- a playout model that can evolve toward producer/consumer scheduling
## Non-Goals
Phase 1 does not itself require:
- replacing every direct call with events immediately
- moving all rendering to a new thread yet
- redesigning the shader contract again
- changing DeckLink behavior in place
- removing all existing classes before replacements exist
This phase is the target design and the dependency rules. Later phases perform the actual extraction.
## Current Pressure Points
The following current code paths are the strongest evidence for the split proposed here:
- `RuntimeHost` is both store and live authority:
- [RuntimeHost.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h:15)
- [RuntimeHost.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp:726)
- `OpenGLComposite` is both app orchestrator and render/runtime coordinator:
- [OpenGLComposite.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp:106)
- [OpenGLComposite.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp:283)
- `RuntimeServices` mixes service orchestration with polling and deferred state work:
- [RuntimeServices.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.h:46)
- [RuntimeServices.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServices.cpp:194)
- Playout is still callback-coupled to render-facing work:
- [OpenGLVideoIOBridge.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp:68)
## Target Subsystems
The long-term architecture should converge on six primary subsystems:
1. `RuntimeStore`
2. `RuntimeCoordinator`
3. `RuntimeSnapshotProvider`
4. `ControlServices`
5. `RenderEngine`
6. `VideoBackend`
7. `HealthTelemetry`
The split below is intentionally sharper than the current code. The point is to make ownership obvious.
## Subsystem Responsibilities
### `RuntimeStore`
`RuntimeStore` owns persisted and operator-authored state.
It is the source of truth for:
- runtime config loaded from disk
- persisted layer stack structure
- persisted parameter values
- stack preset serialization/deserialization
- shader/package metadata that must survive across renders
It should not be responsible for:
- render-thread timing
- GL resource lifetime
- live transient overlays
- hardware callback coordination
- UI/websocket broadcasting policy
Design rules:
- disk I/O belongs here or in its dedicated writer helper
- values here are authoritative for saved state
- writes may be debounced later, but the data model itself belongs here
### `RuntimeCoordinator`
`RuntimeCoordinator` is the mutation and policy layer.
It is responsible for:
- receiving valid mutation requests from controls, services, or automation
- validating requested changes against shader definitions and config rules
- resolving how persisted state, committed live state, and transient overlays should interact
- requesting snapshot publication when state changes affect render
- requesting persistence when stored state changes
It should not be responsible for:
- direct disk serialization details
- direct GL work
- hardware device lifecycle
- polling loops
Design rules:
- all non-render mutations should eventually flow through this layer
- this layer decides whether a change is persisted, transient, or both
- this layer owns state policy, not device policy
### `RuntimeSnapshotProvider`
`RuntimeSnapshotProvider` publishes render-facing snapshots.
It is responsible for:
- building immutable or near-immutable render snapshots
- translating runtime state into render-ready structures
- publishing versioned snapshots
- serving the render side without large mutable shared locks
It should not be responsible for:
- deciding whether a mutation is allowed
- directly applying UI/OSC requests
- persistence
- shader compilation orchestration
Design rules:
- render consumes snapshots, not live mutable store objects
- snapshots should be cheap to read and explicit about version changes
- dynamic frame-only values may still be attached later, but the snapshot shape should stay stable
### `ControlServices`
`ControlServices` is the ingress boundary for non-render control sources.
It is responsible for:
- OSC receive and route resolution
- REST/websocket/control UI ingress
- file-watch or reload request ingress
- translating external inputs into typed internal actions/events
- low-cost buffering/coalescing where appropriate
It should not be responsible for:
- persistence decisions
- render snapshot building
- hardware playout policy
- direct long-lived state ownership beyond ingress-specific queues
Design rules:
- external inputs enter here and are normalized before they touch core state
- service-specific timing concerns stay here unless they affect whole-app policy
- no service should directly mutate render-facing state structures
### `RenderEngine`
`RenderEngine` is the owner of live rendering behavior.
It is responsible for:
- sole ownership of GL work in the target architecture
- shader program lifecycle once compilation outputs are available
- texture upload scheduling
- render-pass execution
- temporal history and shader feedback resources
- transient render-only overlays
- preview production as a subordinate output
- output-frame production for the video backend
It should not be responsible for:
- persistence
- user-facing control normalization
- hardware discovery/configuration
- high-level runtime mutation policy
Design rules:
- render consumes snapshots plus render-local transient state
- render-local state is allowed if it stays render-local
- preview must be treated as best-effort relative to playout
### `VideoBackend`
`VideoBackend` owns input/output device lifecycle and playout policy.
It is responsible for:
- input device configuration and callbacks
- output device configuration and callbacks
- frame scheduling policy
- buffer-pool ownership
- playout headroom policy
- input signal status
- backend state transitions and recovery logic
It should not be responsible for:
- composing frames
- owning GL contexts long-term
- validating shader parameter changes
- persistence
Design rules:
- this subsystem is the consumer of rendered output frames, not the owner of frame composition policy
- it should evolve toward producer/consumer playout rather than callback-driven rendering
- backend state should be explicit and reportable
### `HealthTelemetry`
`HealthTelemetry` owns structured operational visibility.
It is responsible for:
- logging
- warning/error counters
- timing traces
- subsystem health state
- degraded-mode reporting
- operator-visible health summaries
It should not be responsible for:
- deciding core app behavior
- owning render or backend state
- persistence policy
Design rules:
- all major subsystems publish health information here
- health visibility should outlive UI connection state
- modal dialogs should not be the main operational surface
## Target Dependency Rules
The architecture should follow these rules as closely as possible.
Allowed dependency directions:
- `ControlServices -> RuntimeCoordinator`
- `RuntimeCoordinator -> RuntimeStore`
- `RuntimeCoordinator -> RuntimeSnapshotProvider`
- `RuntimeCoordinator -> HealthTelemetry`
- `RuntimeSnapshotProvider -> RuntimeStore`
- `RenderEngine -> RuntimeSnapshotProvider`
- `RenderEngine -> HealthTelemetry`
- `VideoBackend -> RenderEngine`
- `VideoBackend -> HealthTelemetry`
Conditionally allowed during migration:
- `ControlServices -> HealthTelemetry`
- `ControlServices -> RuntimeStore` only through temporary compatibility shims
Not allowed in the target design:
- `RenderEngine -> RuntimeStore`
- `RenderEngine -> ControlServices`
- `VideoBackend -> RuntimeStore`
- `ControlServices -> RenderEngine` for direct mutation
- `RuntimeStore -> RenderEngine`
- `HealthTelemetry -> any subsystem` for control flow
The key principle is:
- store owns durable data
- coordinator owns mutation policy
- snapshot provider owns render-facing state publication
- render owns live GPU execution
- backend owns device timing
- telemetry observes all of them
## State Ownership Model
The app has several different kinds of state, and Phase 1 should name them explicitly.
### Persisted State
Owned by `RuntimeStore`.
Examples:
- layer stack structure
- selected shader ids
- saved parameter values
- runtime host config
- stack presets
### Committed Live State
Owned logically by `RuntimeCoordinator`, stored in the store or a live-state companion depending on future implementation.
Examples:
- current operator-selected parameter values
- current bypass state
- current selected shader for each layer
This is state that should normally survive until explicitly changed and can be persisted if policy says so.
### Transient Live Overlay State
Owned by the subsystem that consumes it, not by the persisted store.
Examples:
- active OSC overlay targets while automation is flowing
- shader feedback buffers
- temporal history textures
- queued input frames
- in-flight preview state
- playout queue state
This is where many current issues come from. The design rule is:
- transient state may influence output
- transient state should not masquerade as persisted truth
### Health and Timing State
Owned by `HealthTelemetry`.
Examples:
- frame pacing stats
- render timing
- late/dropped frame counters
- queue depths
- warning states
## Target Runtime Flow
This section describes the intended long-term flow once later phases are in place.
### Control Mutation Flow
1. OSC/UI/file-watch input enters `ControlServices`.
2. `ControlServices` normalizes it into an internal action or event.
3. `RuntimeCoordinator` validates and classifies the action.
4. If the action changes durable state, `RuntimeStore` is updated.
5. If the action changes render-facing state, `RuntimeSnapshotProvider` publishes a new snapshot.
6. If the action requires persistence, a persistence request is queued.
7. Health/timing observations are emitted separately.
### Render Flow
1. `RenderEngine` consumes the latest published snapshot.
2. `RenderEngine` combines that snapshot with render-local transient state.
3. `RenderEngine` performs uploads, pass execution, feedback/history maintenance, and output production.
4. `RenderEngine` produces:
- preview-ready output
- video-backend-ready output frames
- render timing and warning signals
### Video Output Flow
Target long-term flow:
1. `RenderEngine` produces completed output frames ahead of demand.
2. `VideoBackend` consumes those frames from a bounded queue or ring buffer.
3. Device callbacks only drive dequeue/schedule/accounting behavior.
4. `HealthTelemetry` records queue depth, lateness, underruns, and recovery events.
### Reload / Shader Rebuild Flow
1. file-watch or manual reload enters through `ControlServices`
2. `RuntimeCoordinator` classifies the reload request
3. `RuntimeStore` and shader/package metadata are refreshed if needed
4. `RuntimeSnapshotProvider` republishes affected snapshot state
5. `RenderEngine` rebuilds render-local resources from the new snapshot/build outputs
The important boundary here is that reload is not "a render concern that also touches persistence." It is a coordinated runtime concern with a render-local execution phase.
## Suggested Public Interfaces
These are not final class signatures, but they show the shape the architecture should move toward.
### `RuntimeStore`
Core responsibilities:
- `LoadConfig()`
- `LoadPersistentState()`
- `SavePersistentStateSnapshot(...)`
- `GetStoredLayerStack()`
- `SetStoredLayerStack(...)`
- `GetStackPresetNames()`
- `SaveStackPreset(...)`
- `LoadStackPreset(...)`
### `RuntimeCoordinator`
Core responsibilities:
- `ApplyControlMutation(...)`
- `ApplyAutomationTarget(...)`
- `ResetLayer(...)`
- `RequestReload(...)`
- `CommitOverlayState(...)`
- `PublishSnapshotIfNeeded()`
- `RequestPersistenceIfNeeded()`
### `RuntimeSnapshotProvider`
Core responsibilities:
- `BuildSnapshot(...)`
- `GetLatestSnapshot()`
- `GetSnapshotVersion()`
- `PublishSnapshot(...)`
### `ControlServices`
Core responsibilities:
- `StartOscIngress(...)`
- `StartWebControlIngress(...)`
- `StartFileWatchIngress(...)`
- `EnqueueControlAction(...)`
- `DrainServiceEvents(...)`
### `RenderEngine`
Core responsibilities:
- `StartRenderLoop(...)`
- `ConsumeSnapshot(...)`
- `EnqueueInputFrame(...)`
- `ProduceOutputFrame(...)`
- `ResetRenderLocalState(...)`
- `HandleRebuildOutputs(...)`
### `VideoBackend`
Core responsibilities:
- `ConfigureInput(...)`
- `ConfigureOutput(...)`
- `StartPlayout(...)`
- `StopPlayout(...)`
- `ConsumeRenderedFrame(...)`
- `ReportBackendState(...)`
### `HealthTelemetry`
Core responsibilities:
- `RecordTimingSample(...)`
- `RecordCounterDelta(...)`
- `RaiseWarning(...)`
- `ClearWarning(...)`
- `AppendLogEntry(...)`
- `BuildHealthSnapshot()`
## Mapping From Current Code to Target Subsystems
This is not a one-to-one rename plan. It is a responsibility migration map.
### Current `RuntimeHost`
Should eventually split across:
- `RuntimeStore`
- `RuntimeCoordinator`
- `RuntimeSnapshotProvider`
- parts of `HealthTelemetry`
Likely examples:
- config loading/saving -> `RuntimeStore`
- layer stack mutation validation -> `RuntimeCoordinator`
- render state building/versioning -> `RuntimeSnapshotProvider`
- timing/status setters -> `HealthTelemetry`
### Current `RuntimeServices`
Should eventually become mostly:
- `ControlServices`
- a small service-hosting shell
Likely examples:
- OSC ingress/coalescing -> `ControlServices`
- file-watch ingress -> `ControlServices`
- deferred service coordination now done by polling -> split between `ControlServices` and event-driven coordinator calls
### Current `OpenGLComposite`
Should eventually split across:
- application bootstrap shell
- `RenderEngine`
- orchestration glue that wires subsystems together
Likely examples:
- render-pass facing code -> `RenderEngine`
- app/service/backend bootstrap -> composition root
- runtime mutation API surface -> coordinator-facing adapter, not render owner
### Current `OpenGLVideoIOBridge` and `DeckLinkSession`
Should eventually align more clearly under:
- `VideoBackend`
- `RenderEngine`
Likely examples:
- device callback and scheduling policy -> `VideoBackend`
- GL upload/readback/render work -> `RenderEngine`
## Architectural Guardrails
As later phases begin, these rules should be treated as guardrails.
### 1. No new cross-cutting state should be added to `RuntimeHost`
If a new feature needs durable state, place it conceptually under `RuntimeStore`.
If it needs render-local transient state, place it conceptually under `RenderEngine`.
If it needs timing/status counters, place it conceptually under `HealthTelemetry`.
### 2. Render-local state should stay render-local
Do not push shader feedback, temporal history, preview caches, or playout queues back into the store just to make them easy to reach from other systems.
### 3. Device callbacks should not become a dumping ground for app work
Callback threads should converge toward signaling and queue management, not core rendering, persistence, or control mutation.
### 4. Persistence should not be used as a control synchronization mechanism
Saving state is not how subsystems discover changes. Published snapshots and explicit events should handle that.
### 5. Health reporting should observe, not coordinate
Telemetry systems may record warnings and degraded states, but they should not become the hidden control plane for the app.
## Migration Strategy
Phase 1 is a design phase, but it should support incremental migration.
Recommended order after this document:
1. Introduce names and interfaces before moving logic.
2. Create compatibility adapters around `RuntimeHost` rather than forcing a flag day.
3. Move read-only render snapshot publication out before moving all mutation logic.
4. Move service ingress boundaries out before removing the old polling shell.
5. Isolate timing/health setters from the core store as early as practical.
This keeps progress measurable while reducing rewrite risk.
## Suggested Deliverables for Completing Phase 1
Phase 1 can reasonably be considered complete once the project has:
- this subsystem-boundary design document
- agreed subsystem names and responsibilities
- agreed allowed dependency directions
- explicit state categories: persisted, committed live, transient overlay, health/timing
- a current-to-target responsibility map for `RuntimeHost`, `RuntimeServices`, `OpenGLComposite`, and backend/render bridge code
- a decision that later phases will build against this target rather than inventing new boundaries ad hoc
## Open Questions For Later Phases
These do not block Phase 1, but they should remain visible.
- Should shader package registry ownership live entirely in `RuntimeStore`, or should compile-ready derived registry data move into the snapshot provider?
- Should committed live state be stored directly in `RuntimeStore`, or split into store plus live-session state owned by the coordinator?
- How much of shader build orchestration belongs to `RenderEngine` versus a separate build service?
- At what phase should preview become fully decoupled from playout cadence?
- Should persistence become its own `PersistenceWriter` subsystem in Phase 6, or remain an implementation detail under `RuntimeStore`?
## Short Version
Phase 1 should establish one simple rule for the rest of the refactor:
- durable state lives in the store
- mutation policy lives in the coordinator
- render-facing state is published as snapshots
- external control sources enter through services
- GL work belongs to render
- hardware pacing belongs to the backend
- health visibility belongs to telemetry
If later phases keep to that rule, the architecture will become materially more resilient without needing another round of foundational boundary changes.