From e00e2574ed593b2f8ee913edd4acc20320510853 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Mon, 11 May 2026 17:06:42 +1000 Subject: [PATCH] render engine updates --- CMakeLists.txt | 3 + .../control/RuntimeServiceLiveBridge.cpp | 17 +-- .../control/RuntimeServiceLiveBridge.h | 11 +- .../gl/OpenGLComposite.cpp | 43 +++---- .../gl/RenderEngine.cpp | 118 ++++------------- .../gl/RenderEngine.h | 27 ++-- .../gl/RenderFrameState.h | 31 +++++ .../gl/RenderFrameStateResolver.cpp | 119 ++++++++++++++++++ .../gl/RenderFrameStateResolver.h | 40 ++++++ ..._LIVE_STATE_SERVICE_COORDINATION_DESIGN.md | 22 ++-- 10 files changed, 265 insertions(+), 166 deletions(-) create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameState.h create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e2b13a..d9424a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,9 @@ set(APP_SOURCES "${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp" "${APP_DIR}/gl/RenderEngine.cpp" "${APP_DIR}/gl/RenderEngine.h" + "${APP_DIR}/gl/RenderFrameState.h" + "${APP_DIR}/gl/RenderFrameStateResolver.cpp" + "${APP_DIR}/gl/RenderFrameStateResolver.h" "${APP_DIR}/gl/RuntimeUpdateController.cpp" "${APP_DIR}/gl/RuntimeUpdateController.h" "${APP_DIR}/gl/pipeline/OpenGLRenderPass.cpp" diff --git a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServiceLiveBridge.cpp b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServiceLiveBridge.cpp index 93ccc0e..6c9dffa 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServiceLiveBridge.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServiceLiveBridge.cpp @@ -56,25 +56,16 @@ void QueueServiceCommitRequests( } } -bool RuntimeServiceLiveBridge::PrepareLiveRenderLayerStates( +bool RuntimeServiceLiveBridge::PrepareLiveRenderFrameState( RuntimeServices& runtimeServices, RenderEngine& renderEngine, - bool useCommittedLayerStates, - unsigned renderWidth, - unsigned renderHeight, - double oscSmoothing, - std::vector& layerStates) + const RenderFrameInput& input, + RenderFrameState& frameState) { DrainServiceEvents(runtimeServices, renderEngine); std::vector commitRequests; - const bool resolved = renderEngine.ResolveRenderLayerStates( - useCommittedLayerStates, - renderWidth, - renderHeight, - oscSmoothing, - &commitRequests, - layerStates); + const bool resolved = renderEngine.ResolveRenderFrameState(input, &commitRequests, frameState); QueueServiceCommitRequests(runtimeServices, commitRequests); return resolved; diff --git a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServiceLiveBridge.h b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServiceLiveBridge.h index b93e0f8..983bf84 100644 --- a/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServiceLiveBridge.h +++ b/apps/LoopThroughWithOpenGLCompositing/control/RuntimeServiceLiveBridge.h @@ -1,6 +1,6 @@ #pragma once -#include "ShaderTypes.h" +#include "RenderFrameState.h" #include @@ -10,12 +10,9 @@ class RuntimeServices; class RuntimeServiceLiveBridge { public: - static bool PrepareLiveRenderLayerStates( + static bool PrepareLiveRenderFrameState( RuntimeServices& runtimeServices, RenderEngine& renderEngine, - bool useCommittedLayerStates, - unsigned renderWidth, - unsigned renderHeight, - double oscSmoothing, - std::vector& layerStates); + const RenderFrameInput& input, + RenderFrameState& frameState); }; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index 856f781..030a0bf 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -294,39 +294,32 @@ void OpenGLComposite::renderEffect() if (mRuntimeUpdateController) mRuntimeUpdateController->ProcessRuntimeWork(); - const bool hasInputSource = mVideoBackend->HasInputSource(); - std::vector layerStates; - const double smoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0; + RenderFrameInput frameInput; + frameInput.useCommittedLayerStates = mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates(); + frameInput.hasInputSource = mVideoBackend->HasInputSource(); + frameInput.renderWidth = mVideoBackend->InputFrameWidth(); + frameInput.renderHeight = mVideoBackend->InputFrameHeight(); + frameInput.inputFrameWidth = mVideoBackend->InputFrameWidth(); + frameInput.inputFrameHeight = mVideoBackend->InputFrameHeight(); + frameInput.captureTextureWidth = mVideoBackend->CaptureTextureWidth(); + frameInput.inputPixelFormat = mVideoBackend->InputPixelFormat(); + frameInput.historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0; + frameInput.oscSmoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0; + + RenderFrameState frameState; if (mRuntimeServices) { - RuntimeServiceLiveBridge::PrepareLiveRenderLayerStates( + RuntimeServiceLiveBridge::PrepareLiveRenderFrameState( *mRuntimeServices, *mRenderEngine, - mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates(), - mVideoBackend->InputFrameWidth(), - mVideoBackend->InputFrameHeight(), - smoothing, - layerStates); + frameInput, + frameState); } else { - mRenderEngine->ResolveRenderLayerStates( - mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates(), - mVideoBackend->InputFrameWidth(), - mVideoBackend->InputFrameHeight(), - smoothing, - nullptr, - layerStates); + mRenderEngine->ResolveRenderFrameState(frameInput, nullptr, frameState); } - const unsigned historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0; - mRenderEngine->RenderLayerStack( - hasInputSource, - layerStates, - mVideoBackend->InputFrameWidth(), - mVideoBackend->InputFrameHeight(), - mVideoBackend->CaptureTextureWidth(), - mVideoBackend->InputPixelFormat(), - historyCap); + mRenderEngine->RenderPreparedFrame(frameState); } void OpenGLComposite::ProcessScreenshotRequest() diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp index 6562d35..672918a 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp @@ -6,11 +6,6 @@ #include -namespace -{ -constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150); -} - RenderEngine::RenderEngine( RuntimeSnapshotProvider& runtimeSnapshotProvider, HealthTelemetry& healthTelemetry, @@ -24,10 +19,10 @@ RenderEngine::RenderEngine( mRenderPass(mRenderer), mRenderPipeline(mRenderer, runtimeSnapshotProvider, healthTelemetry, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)), mShaderPrograms(mRenderer, runtimeSnapshotProvider), - mRuntimeSnapshotProvider(runtimeSnapshotProvider), mMutex(mutex), mHdc(hdc), - mHglrc(hglrc) + mHglrc(hglrc), + mFrameStateResolver(runtimeSnapshotProvider) { } @@ -86,11 +81,7 @@ bool RenderEngine::ApplyPreparedShaderBuild( if (!CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage)) return false; - mCachedLayerRenderStates = mShaderPrograms.CommittedLayerStates(); - mCachedRenderStateVersion = preparedBuild.renderSnapshot.versions.renderStateVersion; - mCachedParameterStateVersion = preparedBuild.renderSnapshot.versions.parameterStateVersion; - mCachedRenderStateWidth = preparedBuild.renderSnapshot.outputWidth; - mCachedRenderStateHeight = preparedBuild.renderSnapshot.outputHeight; + mFrameStateResolver.StoreCommittedSnapshot(preparedBuild.renderSnapshot, mShaderPrograms.CommittedLayerStates()); ResetTemporalHistoryState(); if (!preserveFeedbackState) ResetShaderFeedbackState(); @@ -248,92 +239,37 @@ bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, return rendered; } -bool RenderEngine::ResolveRenderLayerStates( - bool useCommittedLayerStates, - unsigned renderWidth, - unsigned renderHeight, - double oscSmoothing, +bool RenderEngine::ResolveRenderFrameState( + const RenderFrameInput& input, std::vector* commitRequests, - std::vector& layerStates) + RenderFrameState& frameState) { - layerStates.clear(); - if (useCommittedLayerStates) + std::vector liveCommitRequests; + const bool resolved = mFrameStateResolver.Resolve( + input, + mShaderPrograms.CommittedLayerStates(), + mRuntimeLiveState, + commitRequests ? &liveCommitRequests : nullptr, + frameState); + + if (commitRequests) { - layerStates = ComposeRenderLayerStates(mShaderPrograms.CommittedLayerStates(), false, oscSmoothing, commitRequests); - mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); - return true; + for (const RuntimeLiveOscCommitRequest& request : liveCommitRequests) + commitRequests->push_back({ request.routeKey, request.layerKey, request.parameterKey, request.value, request.generation }); } - - const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions(); - const bool renderStateCacheValid = - !mCachedLayerRenderStates.empty() && - mCachedRenderStateVersion == versions.renderStateVersion && - mCachedRenderStateWidth == renderWidth && - mCachedRenderStateHeight == renderHeight; - - if (renderStateCacheValid) - { - RuntimeRenderStateSnapshot renderSnapshot; - renderSnapshot.outputWidth = renderWidth; - renderSnapshot.outputHeight = renderHeight; - renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion; - renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion; - renderSnapshot.states = mCachedLayerRenderStates; - - renderSnapshot.states = ComposeRenderLayerStates(renderSnapshot.states, true, oscSmoothing, commitRequests); - if (mCachedParameterStateVersion != versions.parameterStateVersion && - mRuntimeSnapshotProvider.TryRefreshPublishedSnapshotParameters(renderSnapshot)) - { - mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; - renderSnapshot.states = ComposeRenderLayerStates(renderSnapshot.states, true, oscSmoothing, commitRequests); - } - - mCachedLayerRenderStates = renderSnapshot.states; - layerStates = renderSnapshot.states; - mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); - return true; - } - - RuntimeRenderStateSnapshot renderSnapshot; - if (mRuntimeSnapshotProvider.TryPublishRenderStateSnapshot(renderWidth, renderHeight, renderSnapshot)) - { - mCachedLayerRenderStates = renderSnapshot.states; - mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion; - mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; - mCachedRenderStateWidth = renderSnapshot.outputWidth; - mCachedRenderStateHeight = renderSnapshot.outputHeight; - mCachedLayerRenderStates = ComposeRenderLayerStates(mCachedLayerRenderStates, true, oscSmoothing, commitRequests); - layerStates = mCachedLayerRenderStates; - return true; - } - - layerStates = ComposeRenderLayerStates(mCachedLayerRenderStates, true, oscSmoothing, commitRequests); - mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); - return !layerStates.empty(); + return resolved; } -std::vector RenderEngine::ComposeRenderLayerStates( - const std::vector& baseStates, - bool allowCommit, - double smoothing, - std::vector* commitRequests) +void RenderEngine::RenderPreparedFrame(const RenderFrameState& frameState) { - RenderStateCompositionInput input; - input.baseLayerStates = &baseStates; - input.liveState = &mRuntimeLiveState; - input.allowLiveCommits = allowCommit; - input.collectLiveCommitRequests = commitRequests != nullptr; - input.liveSmoothing = smoothing; - input.liveCommitDelay = kOscOverlayCommitDelay; - input.now = std::chrono::steady_clock::now(); - const RenderStateCompositionResult result = mRenderStateComposer.BuildFrameState(input); - - if (!commitRequests) - return result.layerStates; - - for (const RuntimeLiveOscCommitRequest& request : result.commitRequests) - commitRequests->push_back({ request.routeKey, request.layerKey, request.parameterKey, request.value, request.generation }); - return result.layerStates; + RenderLayerStack( + frameState.hasInputSource, + frameState.layerStates, + frameState.inputFrameWidth, + frameState.inputFrameHeight, + frameState.captureTextureWidth, + frameState.inputPixelFormat, + frameState.historyCap); } void RenderEngine::RenderLayerStack( diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h index 451e59a..abb4519 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h @@ -4,7 +4,8 @@ #include "OpenGLRenderPipeline.h" #include "OpenGLRenderer.h" #include "OpenGLShaderPrograms.h" -#include "RenderStateComposer.h" +#include "RenderFrameState.h" +#include "RenderFrameStateResolver.h" #include "HealthTelemetry.h" #include "RuntimeCoordinator.h" #include "RuntimeSnapshotProvider.h" @@ -104,13 +105,11 @@ public: bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight); bool TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState); bool RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame); - bool ResolveRenderLayerStates( - bool useCommittedLayerStates, - unsigned renderWidth, - unsigned renderHeight, - double oscSmoothing, + bool ResolveRenderFrameState( + const RenderFrameInput& input, std::vector* commitRequests, - std::vector& layerStates); + RenderFrameState& frameState); + void RenderPreparedFrame(const RenderFrameState& frameState); void RenderLayerStack( bool hasInputSource, const std::vector& layerStates, @@ -127,23 +126,11 @@ private: OpenGLRenderPass mRenderPass; OpenGLRenderPipeline mRenderPipeline; OpenGLShaderPrograms mShaderPrograms; - RuntimeSnapshotProvider& mRuntimeSnapshotProvider; CRITICAL_SECTION& mMutex; HDC mHdc; HGLRC mHglrc; - std::vector ComposeRenderLayerStates( - const std::vector& baseStates, - bool allowCommit, - double smoothing, - std::vector* commitRequests); - - std::vector mCachedLayerRenderStates; - uint64_t mCachedRenderStateVersion = 0; - uint64_t mCachedParameterStateVersion = 0; - unsigned mCachedRenderStateWidth = 0; - unsigned mCachedRenderStateHeight = 0; std::chrono::steady_clock::time_point mLastPreviewPresentTime; - RenderStateComposer mRenderStateComposer; + RenderFrameStateResolver mFrameStateResolver; RuntimeLiveState mRuntimeLiveState; }; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameState.h b/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameState.h new file mode 100644 index 0000000..f497ac3 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameState.h @@ -0,0 +1,31 @@ +#pragma once + +#include "ShaderTypes.h" +#include "VideoIOTypes.h" + +#include + +struct RenderFrameInput +{ + bool useCommittedLayerStates = false; + bool hasInputSource = false; + unsigned renderWidth = 0; + unsigned renderHeight = 0; + unsigned inputFrameWidth = 0; + unsigned inputFrameHeight = 0; + unsigned captureTextureWidth = 0; + VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8; + unsigned historyCap = 0; + double oscSmoothing = 0.0; +}; + +struct RenderFrameState +{ + bool hasInputSource = false; + unsigned inputFrameWidth = 0; + unsigned inputFrameHeight = 0; + unsigned captureTextureWidth = 0; + VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8; + unsigned historyCap = 0; + std::vector layerStates; +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.cpp new file mode 100644 index 0000000..8f253bc --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.cpp @@ -0,0 +1,119 @@ +#include "RenderFrameStateResolver.h" + +#include + +namespace +{ +constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150); +} + +RenderFrameStateResolver::RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider) : + mRuntimeSnapshotProvider(runtimeSnapshotProvider) +{ +} + +void RenderFrameStateResolver::StoreCommittedSnapshot( + const RuntimeRenderStateSnapshot& snapshot, + const std::vector& committedLayerStates) +{ + mCachedLayerRenderStates = committedLayerStates; + mCachedRenderStateVersion = snapshot.versions.renderStateVersion; + mCachedParameterStateVersion = snapshot.versions.parameterStateVersion; + mCachedRenderStateWidth = snapshot.outputWidth; + mCachedRenderStateHeight = snapshot.outputHeight; +} + +bool RenderFrameStateResolver::Resolve( + const RenderFrameInput& input, + const std::vector& committedLayerStates, + RuntimeLiveState& liveState, + std::vector* commitRequests, + RenderFrameState& frameState) +{ + frameState.hasInputSource = input.hasInputSource; + frameState.inputFrameWidth = input.inputFrameWidth; + frameState.inputFrameHeight = input.inputFrameHeight; + frameState.captureTextureWidth = input.captureTextureWidth; + frameState.inputPixelFormat = input.inputPixelFormat; + frameState.historyCap = input.historyCap; + frameState.layerStates.clear(); + + if (input.useCommittedLayerStates) + { + frameState.layerStates = ComposeLayerStates(committedLayerStates, liveState, false, input.oscSmoothing, commitRequests); + mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates); + return true; + } + + const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions(); + const bool renderStateCacheValid = + !mCachedLayerRenderStates.empty() && + mCachedRenderStateVersion == versions.renderStateVersion && + mCachedRenderStateWidth == input.renderWidth && + mCachedRenderStateHeight == input.renderHeight; + + if (renderStateCacheValid) + { + RuntimeRenderStateSnapshot renderSnapshot; + renderSnapshot.outputWidth = input.renderWidth; + renderSnapshot.outputHeight = input.renderHeight; + renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion; + renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion; + renderSnapshot.states = mCachedLayerRenderStates; + + renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests); + if (mCachedParameterStateVersion != versions.parameterStateVersion && + mRuntimeSnapshotProvider.TryRefreshPublishedSnapshotParameters(renderSnapshot)) + { + mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; + renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests); + } + + mCachedLayerRenderStates = renderSnapshot.states; + frameState.layerStates = renderSnapshot.states; + mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates); + return true; + } + + RuntimeRenderStateSnapshot renderSnapshot; + if (mRuntimeSnapshotProvider.TryPublishRenderStateSnapshot(input.renderWidth, input.renderHeight, renderSnapshot)) + { + mCachedLayerRenderStates = renderSnapshot.states; + mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion; + mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; + mCachedRenderStateWidth = renderSnapshot.outputWidth; + mCachedRenderStateHeight = renderSnapshot.outputHeight; + mCachedLayerRenderStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests); + frameState.layerStates = mCachedLayerRenderStates; + return true; + } + + frameState.layerStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests); + mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates); + return !frameState.layerStates.empty(); +} + +std::vector RenderFrameStateResolver::ComposeLayerStates( + const std::vector& baseStates, + RuntimeLiveState& liveState, + bool allowCommit, + double smoothing, + std::vector* commitRequests) const +{ + RenderStateCompositionInput input; + input.baseLayerStates = &baseStates; + input.liveState = &liveState; + input.allowLiveCommits = allowCommit; + input.collectLiveCommitRequests = commitRequests != nullptr; + input.liveSmoothing = smoothing; + input.liveCommitDelay = kOscOverlayCommitDelay; + input.now = std::chrono::steady_clock::now(); + const RenderStateCompositionResult result = mRenderStateComposer.BuildFrameState(input); + + if (commitRequests) + { + for (const RuntimeLiveOscCommitRequest& request : result.commitRequests) + commitRequests->push_back(request); + } + return result.layerStates; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.h b/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.h new file mode 100644 index 0000000..f77ea1f --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderFrameStateResolver.h @@ -0,0 +1,40 @@ +#pragma once + +#include "RenderFrameState.h" +#include "RenderStateComposer.h" +#include "RuntimeSnapshotProvider.h" + +#include +#include + +class RenderFrameStateResolver +{ +public: + explicit RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider); + + void StoreCommittedSnapshot( + const RuntimeRenderStateSnapshot& snapshot, + const std::vector& committedLayerStates); + bool Resolve( + const RenderFrameInput& input, + const std::vector& committedLayerStates, + RuntimeLiveState& liveState, + std::vector* commitRequests, + RenderFrameState& frameState); + +private: + std::vector ComposeLayerStates( + const std::vector& baseStates, + RuntimeLiveState& liveState, + bool allowCommit, + double smoothing, + std::vector* commitRequests) const; + + RuntimeSnapshotProvider& mRuntimeSnapshotProvider; + RenderStateComposer mRenderStateComposer; + std::vector mCachedLayerRenderStates; + uint64_t mCachedRenderStateVersion = 0; + uint64_t mCachedParameterStateVersion = 0; + unsigned mCachedRenderStateWidth = 0; + unsigned mCachedRenderStateHeight = 0; +}; diff --git a/docs/PHASE_3_LIVE_STATE_SERVICE_COORDINATION_DESIGN.md b/docs/PHASE_3_LIVE_STATE_SERVICE_COORDINATION_DESIGN.md index c251e97..2914ac2 100644 --- a/docs/PHASE_3_LIVE_STATE_SERVICE_COORDINATION_DESIGN.md +++ b/docs/PHASE_3_LIVE_STATE_SERVICE_COORDINATION_DESIGN.md @@ -7,8 +7,8 @@ Phase 1 split runtime responsibilities into named subsystems. Phase 2 added the ## Status - Phase 3 design package: proposed. -- Phase 3 implementation: initial parallel implementation batch integrated. -- Current alignment: the repo now has the live-state/composer building blocks and a service bridge. `OpenGLComposite::renderEffect()` still remains the app-level frame entrypoint, but the service drain, layer-state resolution, and OSC commit handoff now sit behind a named bridge helper. +- Phase 3 implementation: exit criteria satisfied for the current architecture. +- Current alignment: the repo now has the live-state/composer building blocks, a service bridge, and a named frame-state handoff. `OpenGLComposite::renderEffect()` still remains the app-level frame entrypoint, but the service drain, layer-state resolution, and OSC commit handoff now sit behind named helpers and frame-state data. Current footholds: @@ -17,7 +17,9 @@ Current footholds: - `RuntimeSnapshotProvider` publishes render snapshots from `RenderSnapshotBuilder`. - `RuntimeLiveState` owns transient OSC overlay bookkeeping and commit-settlement policy. - `RenderStateComposer` exists as the first pure composition boundary for combining base layer state with live overlays. -- `RenderEngine` still owns snapshot cache selection and final render-layer resolution, but live overlay value composition now delegates to `RenderStateComposer` and `RuntimeLiveState`. +- `RenderFrameInput` / `RenderFrameState` now provide a named frame-facing handoff model for preparing layer state and render inputs before drawing. +- `RenderFrameStateResolver` now owns snapshot cache selection, parameter refresh decisions, and final frame-state resolution before drawing. +- `RenderEngine` owns GL/render resources and delegates frame-state preparation to the resolver. - `ControlServices` owns OSC ingress, pending OSC updates, completed OSC commit notifications, and service start/stop. - `RuntimeServiceLiveBridge` translates service OSC queues into render live-state updates and queues settled overlay commit requests. - `RuntimeEventDispatcher` now routes accepted mutations, reloads, snapshots, shader build events, backend observations, and health observations. @@ -68,7 +70,7 @@ Those are later phases. Phase 3 is about making state and service coordination c 2. asks `RuntimeServiceLiveBridge` to prepare live render layer states 3. asks `RenderEngine` to draw the layer stack -The bridge now owns service queue draining, live automation settlement, committed/live state selection, and OSC commit handoff. `RenderEngine` still owns snapshot cache selection and dynamic render-field refresh, which is the remaining boundary to clarify before Phase 4. +The bridge now owns service queue draining, live automation settlement, committed/live state selection, and OSC commit handoff. `RenderFrameStateResolver` owns snapshot cache selection, parameter refresh decisions, and dynamic render-field refresh before handing a prepared frame state to `RenderEngine`. ## Target State Model @@ -150,7 +152,7 @@ Non-responsibilities: | pending OSC updates drained by `OpenGLComposite` | `OscValueReceived` -> live-state overlay update handler | Phase 2 already has the event type; Phase 3 decides whether transient overlay updates enter the app dispatcher or a source-local bridge. | | render asks for overlay commit requests | `OscOverlaySettled` or direct coordinator command plus event publication | Commit request creation should leave `renderEffect()` and live near the live-state owner. | | completed OSC commits drained by `OpenGLComposite` | `RuntimeMutationAccepted` / completion event -> live-state commit completion | Completed commit routing should be event-driven or owned by live-state service bridge. | -| `RenderEngine::ResolveRenderLayerStates(...)` | `RenderStateComposer::BuildFrameState(...)` | Keep final state composition testable without GL. | +| `RenderFrameStateResolver::Resolve(...)` | `RenderStateComposer::BuildFrameState(...)` | Keep final state composition testable without GL. | | direct persistence writes from store mutations | `RuntimePersistenceRequested` as the durable write trigger | Background writer lands later; Phase 3 should make request boundaries clear. | | runtime-state broadcast side effects | `RuntimeStateBroadcastRequested` plus optional completed/failed observations | Keep broadcast delivery in services and presentation ownership in runtime presentation. | @@ -246,7 +248,7 @@ This keeps Phase 6 smaller: the background snapshot writer can subscribe to pers Introduce `RuntimeLiveState`, `RenderStateComposer`, or an equivalent pair of classes. -Start by moving pure data operations out of `RenderEngine::ResolveRenderLayerStates(...)` without changing behavior. +Start by moving pure data operations out of frame rendering without changing behavior. Status: started. `runtime/live/RuntimeLiveState` and `runtime/live/RenderStateComposer` now exist, are included in the build, and have a focused `RuntimeLiveStateTests` target. @@ -291,7 +293,7 @@ void OpenGLComposite::renderEffect() The exact names can change. The goal is that render effect no longer manually drains services, settles overlay commits, and resolves layer values. -Status: started. `OpenGLComposite::renderEffect()` still drives frame timing, video dimensions, and drawing, but the service-drain, resolve, and commit-handoff path has moved behind `RuntimeServiceLiveBridge::PrepareLiveRenderLayerStates(...)`. +Status: started. `OpenGLComposite::renderEffect()` still drives frame timing, video dimensions, and drawing, but the service-drain, resolve, and commit-handoff path has moved behind `RuntimeServiceLiveBridge::PrepareLiveRenderFrameState(...)` and a named `RenderFrameInput` / `RenderFrameState` handoff. ### Step 5. Add Persistence Boundary Tests @@ -339,7 +341,7 @@ The current groundwork is intended to let these lanes proceed in parallel with l | Lane | Primary files | Goal | | --- | --- | --- | | A. Live-state behavior | `runtime/live/RuntimeLiveState.*`, `tests/RuntimeLiveStateTests.cpp` | Finish stale completion tests, smoothing edge cases, trigger behavior, and overlay settle policy. | -| B. Render-state composition | `runtime/live/RenderStateComposer.*`, `gl/RenderEngine.*` | Move more of `RenderEngine::ResolveRenderLayerStates(...)` value composition behind the pure composer while keeping GL calls in `RenderEngine`. | +| B. Render-state composition | `runtime/live/RenderStateComposer.*`, `gl/RenderFrameStateResolver.*`, `gl/RenderEngine.*` | Keep value composition and frame-state selection outside GL drawing while keeping GL calls in `RenderEngine`. | | C. Service bridge | `control/RuntimeServices.*`, `control/ControlServices.*`, possible new bridge class | Stop `OpenGLComposite::renderEffect()` from draining OSC update/completion queues directly. | | D. App-frame orchestration | `gl/OpenGLComposite.*`, `gl/RuntimeUpdateController.*` | Replace render-effect glue with a narrow frame-state preparation call and commit-request handoff. | | E. Persistence boundary | `runtime/coordination/RuntimeCoordinator.*`, `runtime/store/*`, event tests | Keep persistence request publication explicit and prepare for a later background writer without changing storage behavior yet. | @@ -348,13 +350,13 @@ The current groundwork is intended to let these lanes proceed in parallel with l Phase 3 can be considered complete once the project can say: -- [x] final render-state composition has a named, testable owner outside `OpenGLComposite` (live value composition is covered by `RenderStateComposer`; full snapshot/cache selection still remains in `RenderEngine`) +- [x] final render-state composition has named owners outside `OpenGLComposite` (`RenderStateComposer` covers live value composition; `RenderFrameStateResolver` covers snapshot/cache selection and frame-state resolution) - [x] transient OSC overlay state has a named owner and tests - [x] overlay commit requests and completions no longer require `OpenGLComposite` to drain service queues directly - [x] `RenderEngine` is closer to GL/render resource ownership and less responsible for value composition - [x] `RuntimeStore` remains durable-state focused and does not gain live overlay responsibilities - [x] persistence requests are explicit event outcomes for persisted mutations -- [ ] Phase 4 can define a render-thread input contract around immutable or near-immutable frame state +- [x] Phase 4 can define a render-thread input contract around immutable or near-immutable frame state ## Open Questions