Organisation
Some checks failed
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m42s
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
Aiden
2026-05-11 19:31:06 +10:00
parent d332dceb5b
commit a530325fa1
18 changed files with 29 additions and 25 deletions

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
{
LayeredRenderStateInput input;
input.committedLiveLayerStates = &baseStates;
input.transientAutomationOverlay = &liveState;
input.allowTransientAutomationCommits = allowCommit;
input.collectTransientAutomationCommitRequests = commitRequests != nullptr;
input.transientAutomationSmoothing = smoothing;
input.transientAutomationCommitDelay = 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

@@ -0,0 +1,365 @@
#include "RuntimeUpdateController.h"
#include "RenderEngine.h"
#include "RuntimeEventDispatcher.h"
#include "RuntimeServices.h"
#include "RuntimeStore.h"
#include "ShaderBuildQueue.h"
#include "VideoBackend.h"
#include <variant>
namespace
{
RuntimeCoordinatorRenderResetScope ToRuntimeCoordinatorRenderResetScope(RuntimeEventRenderResetScope scope)
{
switch (scope)
{
case RuntimeEventRenderResetScope::TemporalHistoryOnly:
return RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly;
case RuntimeEventRenderResetScope::TemporalHistoryAndFeedback:
return RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
case RuntimeEventRenderResetScope::None:
default:
return RuntimeCoordinatorRenderResetScope::None;
}
}
}
RuntimeUpdateController::RuntimeUpdateController(
RuntimeStore& runtimeStore,
RuntimeCoordinator& runtimeCoordinator,
RuntimeEventDispatcher& runtimeEventDispatcher,
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
ShaderBuildQueue& shaderBuildQueue,
VideoBackend& videoBackend) :
mRuntimeStore(runtimeStore),
mRuntimeCoordinator(runtimeCoordinator),
mRuntimeEventDispatcher(runtimeEventDispatcher),
mRuntimeServices(runtimeServices),
mRenderEngine(renderEngine),
mShaderBuildQueue(shaderBuildQueue),
mVideoBackend(videoBackend)
{
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimeStateBroadcastRequested,
[this](const RuntimeEvent& event) { HandleRuntimeStateBroadcastRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimeReloadRequested,
[this](const RuntimeEvent& event) { HandleRuntimeReloadRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildRequested,
[this](const RuntimeEvent& event) { HandleShaderBuildRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildPrepared,
[this](const RuntimeEvent& event) { HandleShaderBuildPrepared(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildFailed,
[this](const RuntimeEvent& event) { HandleShaderBuildFailed(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::CompileStatusChanged,
[this](const RuntimeEvent& event) { HandleCompileStatusChanged(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RenderResetRequested,
[this](const RuntimeEvent& event) { HandleRenderResetRequested(event); });
}
bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error)
{
if (!result.accepted)
{
if (error)
*error = result.errorMessage;
return false;
}
if (result.compileStatusChanged)
{
mRuntimeStore.SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage);
++mPendingCoordinatorCompileStatusEvents;
}
if (result.clearReloadRequest)
mRuntimeStore.ClearReloadRequest();
mRuntimeCoordinator.ApplyCommittedStateMode(result.committedStateMode);
switch (result.transientOscInvalidation)
{
case RuntimeCoordinatorTransientOscInvalidation::All:
mRenderEngine.ClearOscOverlayState();
mRuntimeServices.ClearOscState();
break;
case RuntimeCoordinatorTransientOscInvalidation::Layer:
mRenderEngine.ClearOscOverlayStateForLayerKey(result.transientOscLayerKey);
mRuntimeServices.ClearOscStateForLayerKey(result.transientOscLayerKey);
break;
case RuntimeCoordinatorTransientOscInvalidation::None:
default:
break;
}
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(result.renderResetScope);
if (result.renderResetScope != RuntimeCoordinatorRenderResetScope::None)
++mPendingCoordinatorRenderResetEvents;
if (result.shaderBuildRequested)
{
RequestShaderBuild();
++mPendingCoordinatorShaderBuildEvents;
}
if (result.runtimeStateBroadcastRequired)
BroadcastRuntimeState();
return true;
}
bool RuntimeUpdateController::ProcessRuntimeWork()
{
DispatchRuntimeEvents();
return ConsumeReadyShaderBuild(0, true, true);
}
void RuntimeUpdateController::RequestShaderBuild()
{
mShaderBuildQueue.RequestBuild(mVideoBackend.InputFrameWidth(), mVideoBackend.InputFrameHeight());
}
void RuntimeUpdateController::BroadcastRuntimeState()
{
RuntimeStateBroadcastRequestedEvent event;
event.reason = "runtime-state-changed";
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController"))
{
mRuntimeServices.BroadcastState();
return;
}
DispatchRuntimeEvents();
}
void RuntimeUpdateController::HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event)
{
if (event.source == "ControlServices")
return;
mRuntimeServices.BroadcastState();
}
void RuntimeUpdateController::HandleRuntimeReloadRequested(const RuntimeEvent& event)
{
const RuntimeReloadRequestedEvent* payload = std::get_if<RuntimeReloadRequestedEvent>(&event.payload);
if (!payload)
return;
mRuntimeStore.ClearReloadRequest();
}
void RuntimeUpdateController::HandleShaderBuildRequested(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Requested)
return;
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorShaderBuildEvents))
return;
RequestShaderBuild();
}
void RuntimeUpdateController::HandleShaderBuildPrepared(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Prepared)
return;
ConsumeReadyShaderBuild(payload->generation, false, true);
}
void RuntimeUpdateController::HandleShaderBuildFailed(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Failed)
return;
ConsumeReadyShaderBuild(payload->generation, false, false);
}
void RuntimeUpdateController::HandleCompileStatusChanged(const RuntimeEvent& event)
{
const CompileStatusChangedEvent* payload = std::get_if<CompileStatusChangedEvent>(&event.payload);
if (!payload)
return;
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorCompileStatusEvents))
return;
mRuntimeStore.SetCompileStatus(payload->succeeded, payload->message);
}
void RuntimeUpdateController::HandleRenderResetRequested(const RuntimeEvent& event)
{
const RenderResetEvent* payload = std::get_if<RenderResetEvent>(&event.payload);
if (!payload || payload->applied)
return;
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorRenderResetEvents))
return;
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(ToRuntimeCoordinatorRenderResetScope(payload->scope));
}
bool RuntimeUpdateController::ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent)
{
PreparedShaderBuild readyBuild;
const bool consumed = expectedGeneration == 0
? mShaderBuildQueue.TryConsumeReadyBuild(readyBuild)
: mShaderBuildQueue.TryConsumeReadyBuild(expectedGeneration, readyBuild);
if (!consumed)
return true;
const unsigned inputWidth = mVideoBackend.InputFrameWidth();
const unsigned inputHeight = mVideoBackend.InputFrameHeight();
if (!readyBuild.succeeded)
{
if (publishFailureEvent)
{
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Failed,
readyBuild.generation,
inputWidth,
inputHeight,
false,
readyBuild.message);
DispatchRuntimeEvents();
}
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(readyBuild.message));
return false;
}
if (publishPreparedEvent)
{
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Prepared,
readyBuild.generation,
inputWidth,
inputHeight,
true,
readyBuild.message);
DispatchRuntimeEvents();
}
char compilerErrorMessage[1024] = {};
if (!mRenderEngine.ApplyPreparedShaderBuild(
readyBuild,
inputWidth,
inputHeight,
mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild(),
sizeof(compilerErrorMessage),
compilerErrorMessage))
{
const std::string errorMessage = compilerErrorMessage;
if (publishFailureEvent)
{
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Failed,
readyBuild.generation,
inputWidth,
inputHeight,
false,
errorMessage);
DispatchRuntimeEvents();
}
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(errorMessage));
return false;
}
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Applied,
readyBuild.generation,
inputWidth,
inputHeight,
true,
"Shader layers applied successfully.");
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildSuccess());
return true;
}
void RuntimeUpdateController::PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase phase,
uint64_t generation,
unsigned inputWidth,
unsigned inputHeight,
bool succeeded,
const std::string& message)
{
ShaderBuildEvent event;
event.phase = phase;
event.generation = generation;
event.inputWidth = inputWidth;
event.inputHeight = inputHeight;
event.preserveFeedbackState = mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild();
event.succeeded = succeeded;
event.message = message;
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController");
}
bool RuntimeUpdateController::ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions)
{
if (event.source != "RuntimeCoordinator")
return false;
if (pendingSuppressions > 0)
--pendingSuppressions;
return true;
}
RuntimeEventDispatchResult RuntimeUpdateController::DispatchRuntimeEvents(std::size_t maxEvents)
{
RuntimeEventDispatchResult result = mRuntimeEventDispatcher.DispatchPending(maxEvents);
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
HealthTelemetry& telemetry = mRuntimeStore.GetHealthTelemetry();
telemetry.TryRecordRuntimeEventDispatchStats(
result.dispatchedEvents,
result.handlerInvocations,
result.handlerFailures,
result.dispatchDurationMilliseconds);
telemetry.TryRecordRuntimeEventQueueMetrics(
"runtime-events",
queueMetrics.depth,
queueMetrics.capacity,
static_cast<uint64_t>(queueMetrics.droppedCount),
queueMetrics.oldestEventAgeMilliseconds);
PublishRuntimeEventHealthObservations(result);
return result;
}
void RuntimeUpdateController::PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result)
{
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
if (queueMetrics.depth != mLastReportedRuntimeEventQueueDepth ||
queueMetrics.droppedCount != mLastReportedRuntimeEventDroppedCount ||
queueMetrics.coalescedCount != mLastReportedRuntimeEventCoalescedCount)
{
QueueDepthChangedEvent queueDepth;
queueDepth.queueName = "runtime-events";
queueDepth.depth = queueMetrics.depth;
queueDepth.capacity = queueMetrics.capacity;
queueDepth.droppedCount = queueMetrics.droppedCount;
queueDepth.coalescedCount = queueMetrics.coalescedCount;
mRuntimeEventDispatcher.PublishPayload(queueDepth, "HealthTelemetry");
mLastReportedRuntimeEventQueueDepth = queueMetrics.depth;
mLastReportedRuntimeEventDroppedCount = queueMetrics.droppedCount;
mLastReportedRuntimeEventCoalescedCount = queueMetrics.coalescedCount;
}
if (result.handlerInvocations == 0 && result.handlerFailures == 0)
return;
TimingSampleRecordedEvent timing;
timing.subsystem = "RuntimeEventDispatcher";
timing.metric = "dispatchDuration";
timing.value = result.dispatchDurationMilliseconds;
timing.unit = "ms";
mRuntimeEventDispatcher.PublishPayload(timing, "HealthTelemetry");
}

View File

@@ -0,0 +1,69 @@
#pragma once
#include "RuntimeCoordinator.h"
#include "RuntimeEventPayloads.h"
#include <cstddef>
#include <cstdint>
#include <string>
class RenderEngine;
struct RuntimeEvent;
struct RuntimeEventDispatchResult;
class RuntimeEventDispatcher;
class RuntimeServices;
class RuntimeStore;
class ShaderBuildQueue;
class VideoBackend;
class RuntimeUpdateController
{
public:
RuntimeUpdateController(
RuntimeStore& runtimeStore,
RuntimeCoordinator& runtimeCoordinator,
RuntimeEventDispatcher& runtimeEventDispatcher,
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
ShaderBuildQueue& shaderBuildQueue,
VideoBackend& videoBackend);
bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr);
bool ProcessRuntimeWork();
void RequestShaderBuild();
void BroadcastRuntimeState();
private:
void HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event);
void HandleRuntimeReloadRequested(const RuntimeEvent& event);
void HandleShaderBuildRequested(const RuntimeEvent& event);
void HandleShaderBuildPrepared(const RuntimeEvent& event);
void HandleShaderBuildFailed(const RuntimeEvent& event);
void HandleCompileStatusChanged(const RuntimeEvent& event);
void HandleRenderResetRequested(const RuntimeEvent& event);
bool ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent);
void PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase phase,
uint64_t generation,
unsigned inputWidth,
unsigned inputHeight,
bool succeeded,
const std::string& message);
bool ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions);
RuntimeEventDispatchResult DispatchRuntimeEvents(std::size_t maxEvents = 0);
void PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result);
RuntimeStore& mRuntimeStore;
RuntimeCoordinator& mRuntimeCoordinator;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
RuntimeServices& mRuntimeServices;
RenderEngine& mRenderEngine;
ShaderBuildQueue& mShaderBuildQueue;
VideoBackend& mVideoBackend;
std::size_t mPendingCoordinatorShaderBuildEvents = 0;
std::size_t mPendingCoordinatorCompileStatusEvents = 0;
std::size_t mPendingCoordinatorRenderResetEvents = 0;
std::size_t mLastReportedRuntimeEventQueueDepth = static_cast<std::size_t>(-1);
std::size_t mLastReportedRuntimeEventDroppedCount = static_cast<std::size_t>(-1);
std::size_t mLastReportedRuntimeEventCoalescedCount = static_cast<std::size_t>(-1);
};