render engine updates
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Windows Release Package (push) Has been cancelled
CI / Native Windows Build And Tests (push) Has been cancelled

This commit is contained in:
Aiden
2026-05-11 17:06:42 +10:00
parent e459155d51
commit e00e2574ed
10 changed files with 265 additions and 166 deletions

View File

@@ -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"

View File

@@ -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<RuntimeRenderState>& layerStates)
const RenderFrameInput& input,
RenderFrameState& frameState)
{
DrainServiceEvents(runtimeServices, renderEngine);
std::vector<RenderEngine::OscOverlayCommitRequest> 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;

View File

@@ -1,6 +1,6 @@
#pragma once
#include "ShaderTypes.h"
#include "RenderFrameState.h"
#include <vector>
@@ -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<RuntimeRenderState>& layerStates);
const RenderFrameInput& input,
RenderFrameState& frameState);
};

View File

@@ -294,39 +294,32 @@ void OpenGLComposite::renderEffect()
if (mRuntimeUpdateController)
mRuntimeUpdateController->ProcessRuntimeWork();
const bool hasInputSource = mVideoBackend->HasInputSource();
std::vector<RuntimeRenderState> 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()

View File

@@ -6,11 +6,6 @@
#include <algorithm>
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<OscOverlayCommitRequest>* commitRequests,
std::vector<RuntimeRenderState>& layerStates)
RenderFrameState& frameState)
{
layerStates.clear();
if (useCommittedLayerStates)
std::vector<RuntimeLiveOscCommitRequest> 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;
}
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();
}
std::vector<RuntimeRenderState> RenderEngine::ComposeRenderLayerStates(
const std::vector<RuntimeRenderState>& baseStates,
bool allowCommit,
double smoothing,
std::vector<OscOverlayCommitRequest>* commitRequests)
{
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)
for (const RuntimeLiveOscCommitRequest& request : liveCommitRequests)
commitRequests->push_back({ request.routeKey, request.layerKey, request.parameterKey, request.value, request.generation });
return result.layerStates;
}
return resolved;
}
void RenderEngine::RenderPreparedFrame(const RenderFrameState& frameState)
{
RenderLayerStack(
frameState.hasInputSource,
frameState.layerStates,
frameState.inputFrameWidth,
frameState.inputFrameHeight,
frameState.captureTextureWidth,
frameState.inputPixelFormat,
frameState.historyCap);
}
void RenderEngine::RenderLayerStack(

View File

@@ -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<OscOverlayCommitRequest>* commitRequests,
std::vector<RuntimeRenderState>& layerStates);
RenderFrameState& frameState);
void RenderPreparedFrame(const RenderFrameState& frameState);
void RenderLayerStack(
bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates,
@@ -127,23 +126,11 @@ private:
OpenGLRenderPass mRenderPass;
OpenGLRenderPipeline mRenderPipeline;
OpenGLShaderPrograms mShaderPrograms;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
CRITICAL_SECTION& mMutex;
HDC mHdc;
HGLRC mHglrc;
std::vector<RuntimeRenderState> ComposeRenderLayerStates(
const std::vector<RuntimeRenderState>& baseStates,
bool allowCommit,
double smoothing,
std::vector<OscOverlayCommitRequest>* commitRequests);
std::vector<RuntimeRenderState> 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;
};

View File

@@ -0,0 +1,31 @@
#pragma once
#include "ShaderTypes.h"
#include "VideoIOTypes.h"
#include <vector>
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<RuntimeRenderState> layerStates;
};

View File

@@ -0,0 +1,119 @@
#include "RenderFrameStateResolver.h"
#include <chrono>
namespace
{
constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150);
}
RenderFrameStateResolver::RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider) :
mRuntimeSnapshotProvider(runtimeSnapshotProvider)
{
}
void RenderFrameStateResolver::StoreCommittedSnapshot(
const RuntimeRenderStateSnapshot& snapshot,
const std::vector<RuntimeRenderState>& 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<RuntimeRenderState>& committedLayerStates,
RuntimeLiveState& liveState,
std::vector<RuntimeLiveOscCommitRequest>* 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<RuntimeRenderState> RenderFrameStateResolver::ComposeLayerStates(
const std::vector<RuntimeRenderState>& baseStates,
RuntimeLiveState& liveState,
bool allowCommit,
double smoothing,
std::vector<RuntimeLiveOscCommitRequest>* 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;
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include "RenderFrameState.h"
#include "RenderStateComposer.h"
#include "RuntimeSnapshotProvider.h"
#include <cstdint>
#include <vector>
class RenderFrameStateResolver
{
public:
explicit RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider);
void StoreCommittedSnapshot(
const RuntimeRenderStateSnapshot& snapshot,
const std::vector<RuntimeRenderState>& committedLayerStates);
bool Resolve(
const RenderFrameInput& input,
const std::vector<RuntimeRenderState>& committedLayerStates,
RuntimeLiveState& liveState,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
RenderFrameState& frameState);
private:
std::vector<RuntimeRenderState> ComposeLayerStates(
const std::vector<RuntimeRenderState>& baseStates,
RuntimeLiveState& liveState,
bool allowCommit,
double smoothing,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
RenderStateComposer mRenderStateComposer;
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
uint64_t mCachedRenderStateVersion = 0;
uint64_t mCachedParameterStateVersion = 0;
unsigned mCachedRenderStateWidth = 0;
unsigned mCachedRenderStateHeight = 0;
};

View File

@@ -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