pass 1
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m39s
CI / Windows Release Package (push) Successful in 2m45s

This commit is contained in:
Aiden
2026-05-11 01:12:24 +10:00
parent 53e78890a8
commit c4883d3413
16 changed files with 618 additions and 303 deletions

View File

@@ -9,10 +9,7 @@
ControlServices::ControlServices() :
mControlServer(std::make_unique<ControlServer>()),
mOscServer(std::make_unique<OscServer>()),
mPollRunning(false),
mRegistryChanged(false),
mReloadRequested(false),
mPollFailed(false)
mPollRunning(false)
{
}
@@ -34,9 +31,9 @@ bool ControlServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeSto
return true;
}
void ControlServices::BeginPolling(RuntimeStore& runtimeStore)
void ControlServices::BeginPolling(RuntimeCoordinator& runtimeCoordinator)
{
StartPolling(runtimeStore);
StartPolling(runtimeCoordinator);
}
void ControlServices::Stop()
@@ -158,28 +155,23 @@ void ControlServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>
completedCommits.swap(mCompletedOscCommits);
}
RuntimePollEvents ControlServices::ConsumePollEvents()
void ControlServices::ConsumeRuntimeCoordinatorResults(std::vector<RuntimeCoordinatorServiceResult>& results)
{
RuntimePollEvents events;
events.registryChanged = mRegistryChanged.exchange(false);
events.reloadRequested = mReloadRequested.exchange(false);
events.failed = mPollFailed.exchange(false);
results.clear();
if (events.failed)
{
std::lock_guard<std::mutex> lock(mPollErrorMutex);
events.error = mPollError;
}
std::lock_guard<std::mutex> lock(mRuntimeCoordinatorResultMutex);
if (mRuntimeCoordinatorResults.empty())
return;
return events;
results.swap(mRuntimeCoordinatorResults);
}
void ControlServices::StartPolling(RuntimeStore& runtimeStore)
void ControlServices::StartPolling(RuntimeCoordinator& runtimeCoordinator)
{
if (mPollRunning.exchange(true))
return;
mPollThread = std::thread([this, &runtimeStore]() { PollLoop(runtimeStore); });
mPollThread = std::thread([this, &runtimeCoordinator]() { PollLoop(runtimeCoordinator); });
}
void ControlServices::StopPolling()
@@ -191,7 +183,7 @@ void ControlServices::StopPolling()
mPollThread.join();
}
void ControlServices::PollLoop(RuntimeStore& runtimeStore)
void ControlServices::PollLoop(RuntimeCoordinator& runtimeCoordinator)
{
while (mPollRunning)
{
@@ -202,46 +194,41 @@ void ControlServices::PollLoop(RuntimeStore& runtimeStore)
}
for (const auto& entry : pendingCommits)
{
std::string commitError;
if (runtimeStore.SetStoredParameterValueByControlKey(
const RuntimeCoordinatorResult result = runtimeCoordinator.CommitOscParameterByControlKey(
entry.second.layerKey,
entry.second.parameterKey,
entry.second.value,
false,
commitError))
entry.second.value);
if (result.accepted)
{
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));
QueueRuntimeCoordinatorResult(result);
}
else if (!commitError.empty())
else if (!result.errorMessage.empty())
{
OutputDebugStringA(("OSC commit failed: " + commitError + "\n").c_str());
OutputDebugStringA(("OSC commit failed: " + result.errorMessage + "\n").c_str());
}
}
bool registryChanged = false;
bool reloadRequested = false;
std::string runtimeError;
if (!runtimeStore.PollStoredFileChanges(registryChanged, reloadRequested, runtimeError))
{
{
std::lock_guard<std::mutex> lock(mPollErrorMutex);
mPollError = runtimeError;
}
mPollFailed = true;
}
else
{
if (registryChanged)
mRegistryChanged = true;
if (reloadRequested)
mReloadRequested = true;
}
const RuntimeCoordinatorResult pollResult = runtimeCoordinator.PollRuntimeStoreChanges(registryChanged);
if (pollResult.runtimeStateBroadcastRequired || pollResult.shaderBuildRequested || pollResult.compileStatusChanged)
QueueRuntimeCoordinatorResult(pollResult, pollResult.compileStatusChanged && !pollResult.compileStatusSucceeded && !pollResult.compileStatusMessage.empty());
for (int i = 0; i < 25 && mPollRunning; ++i)
Sleep(10);
}
}
void ControlServices::QueueRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, bool failed)
{
RuntimeCoordinatorServiceResult serviceResult;
serviceResult.result = result;
serviceResult.failed = failed;
std::lock_guard<std::mutex> lock(mRuntimeCoordinatorResultMutex);
mRuntimeCoordinatorResults.push_back(std::move(serviceResult));
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "RuntimeJson.h"
#include "RuntimeCoordinator.h"
#include "ShaderTypes.h"
#include <atomic>
@@ -16,12 +17,10 @@ class OpenGLComposite;
class OscServer;
class RuntimeStore;
struct RuntimePollEvents
struct RuntimeCoordinatorServiceResult
{
bool registryChanged = false;
bool reloadRequested = false;
RuntimeCoordinatorResult result;
bool failed = false;
std::string error;
};
class ControlServices
@@ -45,7 +44,7 @@ public:
~ControlServices();
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
void BeginPolling(RuntimeStore& runtimeStore);
void BeginPolling(RuntimeCoordinator& runtimeCoordinator);
void Stop();
void BroadcastState();
void RequestBroadcastState();
@@ -54,7 +53,7 @@ public:
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();
void ConsumeRuntimeCoordinatorResults(std::vector<RuntimeCoordinatorServiceResult>& results);
private:
struct PendingOscUpdate
@@ -73,19 +72,17 @@ private:
uint64_t generation = 0;
};
void StartPolling(RuntimeStore& runtimeStore);
void StartPolling(RuntimeCoordinator& runtimeCoordinator);
void StopPolling();
void PollLoop(RuntimeStore& runtimeStore);
void PollLoop(RuntimeCoordinator& runtimeCoordinator);
void QueueRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, bool failed = false);
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 mRuntimeCoordinatorResultMutex;
std::vector<RuntimeCoordinatorServiceResult> mRuntimeCoordinatorResults;
std::mutex mPendingOscMutex;
std::map<std::string, PendingOscUpdate> mPendingOscUpdates;
std::mutex mPendingOscCommitMutex;

View File

@@ -17,10 +17,10 @@ bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeSto
return mControlServices && mControlServices->Start(composite, runtimeStore, error);
}
void RuntimeServices::BeginPolling(RuntimeStore& runtimeStore)
void RuntimeServices::BeginPolling(RuntimeCoordinator& runtimeCoordinator)
{
if (mControlServices)
mControlServices->BeginPolling(runtimeStore);
mControlServices->BeginPolling(runtimeCoordinator);
}
void RuntimeServices::Stop()
@@ -79,7 +79,13 @@ void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>
mControlServices->ConsumeCompletedOscCommits(completedCommits);
}
RuntimePollEvents RuntimeServices::ConsumePollEvents()
void RuntimeServices::ConsumeRuntimeCoordinatorResults(std::vector<RuntimeCoordinatorServiceResult>& results)
{
return mControlServices ? mControlServices->ConsumePollEvents() : RuntimePollEvents{};
if (!mControlServices)
{
results.clear();
return;
}
mControlServices->ConsumeRuntimeCoordinatorResults(results);
}

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <string>
class OpenGLComposite;
class RuntimeCoordinator;
class RuntimeStore;
class RuntimeServices
@@ -17,7 +18,7 @@ public:
~RuntimeServices();
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
void BeginPolling(RuntimeStore& runtimeStore);
void BeginPolling(RuntimeCoordinator& runtimeCoordinator);
void Stop();
void BroadcastState();
void RequestBroadcastState();
@@ -26,7 +27,7 @@ public:
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();
void ConsumeRuntimeCoordinatorResults(std::vector<RuntimeCoordinatorServiceResult>& results);
private:
std::unique_ptr<ControlServices> mControlServices;

View File

@@ -31,20 +31,19 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
mScreenshotRequested(false)
{
InitializeCriticalSection(&pMutex);
mRuntimeHost = std::make_unique<RuntimeHost>();
mRuntimeStore = std::make_unique<RuntimeStore>(*mRuntimeHost);
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(*mRuntimeHost);
mRuntimeStore = std::make_unique<RuntimeStore>();
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(*mRuntimeStore);
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore);
mRenderEngine = std::make_unique<RenderEngine>(
*mRuntimeSnapshotProvider,
mRuntimeHost->GetHealthTelemetry(),
mRuntimeStore->GetHealthTelemetry(),
pMutex,
hGLDC,
hGLRC,
[this]() { renderEffect(); },
[this]() { ProcessScreenshotRequest(); },
[this]() { paintGL(false); });
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeHost->GetHealthTelemetry());
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeStore->GetHealthTelemetry());
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeSnapshotProvider);
mRuntimeServices = std::make_unique<RuntimeServices>();
}
@@ -247,7 +246,7 @@ bool OpenGLComposite::InitOpenGLState()
mRenderEngine->ResetShaderFeedbackState();
broadcastRuntimeState();
mRuntimeServices->BeginPolling(*mRuntimeStore);
mRuntimeServices->BeginPolling(*mRuntimeCoordinator);
return true;
}
@@ -406,17 +405,18 @@ bool OpenGLComposite::ProcessRuntimePollResults()
if (!mRuntimeServices)
return true;
const RuntimePollEvents events = mRuntimeServices->ConsumePollEvents();
if (events.failed)
bool shaderBuildRequested = false;
std::vector<RuntimeCoordinatorServiceResult> serviceResults;
mRuntimeServices->ConsumeRuntimeCoordinatorResults(serviceResults);
for (const RuntimeCoordinatorServiceResult& serviceResult : serviceResults)
{
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandleRuntimePollFailure(events.error));
shaderBuildRequested = shaderBuildRequested || serviceResult.result.shaderBuildRequested;
ApplyRuntimeCoordinatorResult(serviceResult.result);
if (serviceResult.failed)
return false;
}
if (events.registryChanged)
broadcastRuntimeState();
if (!events.reloadRequested)
if (!shaderBuildRequested)
{
if (!mShaderBuildQueue || !mRenderEngine)
return true;
@@ -439,7 +439,6 @@ bool OpenGLComposite::ProcessRuntimePollResults()
return true;
}
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->HandleRuntimeReloadRequest());
return true;
}

View File

@@ -13,7 +13,6 @@
#include "GLExtensions.h"
#include "RuntimeCoordinator.h"
#include "RuntimeHost.h"
#include "RuntimeSnapshotProvider.h"
#include "RuntimeStore.h"
@@ -73,7 +72,6 @@ private:
HGLRC hGLRC;
CRITICAL_SECTION pMutex;
std::unique_ptr<RuntimeHost> mRuntimeHost;
std::unique_ptr<RuntimeStore> mRuntimeStore;
std::unique_ptr<RuntimeCoordinator> mRuntimeCoordinator;
std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;

View File

@@ -2,6 +2,9 @@
#include "RuntimeStore.h"
#include <algorithm>
#include <chrono>
RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore) :
mRuntimeStore(runtimeStore)
{
@@ -9,55 +12,110 @@ RuntimeCoordinator::RuntimeCoordinator(RuntimeStore& runtimeStore) :
RuntimeCoordinatorResult RuntimeCoordinator::AddLayer(const std::string& shaderId)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateShaderExists(shaderId, error))
return ApplyStoreMutation(false, error, false, false);
return ApplyStoreMutation(mRuntimeStore.CreateStoredLayer(shaderId, error), error, true, true);
}
RuntimeCoordinatorResult RuntimeCoordinator::RemoveLayer(const std::string& layerId)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateLayerExists(layerId, error))
return ApplyStoreMutation(false, error, false, false);
return ApplyStoreMutation(mRuntimeStore.DeleteStoredLayer(layerId, error), error, true, true);
}
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayer(const std::string& layerId, int direction)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
bool shouldMove = false;
if (!ResolveLayerMove(layerId, direction, shouldMove, error))
return ApplyStoreMutation(false, error, false, false);
if (!shouldMove)
return BuildAcceptedNoReloadResult();
return ApplyStoreMutation(mRuntimeStore.MoveStoredLayer(layerId, direction, error), error, true, true);
}
RuntimeCoordinatorResult RuntimeCoordinator::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
bool shouldMove = false;
if (!ResolveLayerMoveToIndex(layerId, targetIndex, shouldMove, error))
return ApplyStoreMutation(false, error, false, false);
if (!shouldMove)
return BuildAcceptedNoReloadResult();
return ApplyStoreMutation(mRuntimeStore.MoveStoredLayerToIndex(layerId, targetIndex, error), error, true, true);
}
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerBypass(const std::string& layerId, bool bypassed)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateLayerExists(layerId, error))
return ApplyStoreMutation(false, error, false, false);
return ApplyStoreMutation(mRuntimeStore.SetStoredLayerBypassState(layerId, bypassed, error), error, true, false);
}
RuntimeCoordinatorResult RuntimeCoordinator::SetLayerShader(const std::string& layerId, const std::string& shaderId)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateLayerExists(layerId, error) || !ValidateShaderExists(shaderId, error))
return ApplyStoreMutation(false, error, false, false);
return ApplyStoreMutation(mRuntimeStore.SetStoredLayerShaderSelection(layerId, shaderId, error), error, true, false);
}
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(layerId, parameterId, newValue, error), error, false, false);
ResolvedParameterMutation mutation;
if (!BuildParameterMutationById(layerId, parameterId, newValue, true, mutation, error))
return ApplyStoreMutation(false, error, false, false);
return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false);
}
RuntimeCoordinatorResult RuntimeCoordinator::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValueByControlKey(layerKey, parameterKey, newValue, error), error, false, false);
ResolvedParameterMutation mutation;
if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, true, mutation, error))
return ApplyStoreMutation(false, error, false, false);
return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false);
}
RuntimeCoordinatorResult RuntimeCoordinator::CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
ResolvedParameterMutation mutation;
if (!BuildParameterMutationByControlKey(layerKey, parameterKey, newValue, false, mutation, error))
return ApplyStoreMutation(false, error, false, false);
return ApplyStoreMutation(mRuntimeStore.SetStoredParameterValue(mutation.layerId, mutation.parameterId, mutation.value, mutation.persistState, error), error, false, false);
}
RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::string& layerId)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidateLayerExists(layerId, error))
return ApplyStoreMutation(false, error, false, false);
RuntimeCoordinatorResult result = ApplyStoreMutation(mRuntimeStore.ResetStoredLayerParameterValues(layerId, error), error, false, false);
if (!result.accepted)
return result;
@@ -69,21 +127,51 @@ RuntimeCoordinatorResult RuntimeCoordinator::ResetLayerParameters(const std::str
RuntimeCoordinatorResult RuntimeCoordinator::SaveStackPreset(const std::string& presetName)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidatePresetName(presetName, error))
return ApplyStoreMutation(false, error, false, false);
return ApplyStoreMutation(mRuntimeStore.SaveStackPresetSnapshot(presetName, error), error, false, false);
}
RuntimeCoordinatorResult RuntimeCoordinator::LoadStackPreset(const std::string& presetName)
{
std::lock_guard<std::mutex> lock(mMutex);
std::string error;
if (!ValidatePresetName(presetName, error))
return ApplyStoreMutation(false, error, false, false);
return ApplyStoreMutation(mRuntimeStore.LoadStackPresetSnapshot(presetName, error), error, true, false);
}
RuntimeCoordinatorResult RuntimeCoordinator::RequestShaderReload(bool preserveFeedbackState)
{
std::lock_guard<std::mutex> lock(mMutex);
return BuildQueuedReloadResult(preserveFeedbackState);
}
RuntimeCoordinatorResult RuntimeCoordinator::PollRuntimeStoreChanges(bool& registryChanged)
{
std::lock_guard<std::mutex> lock(mMutex);
registryChanged = false;
bool reloadRequested = false;
std::string error;
if (!mRuntimeStore.PollStoredFileChanges(registryChanged, reloadRequested, error))
return HandleRuntimePollFailure(error);
if (reloadRequested)
return BuildQueuedReloadResult(false);
if (registryChanged)
return BuildAcceptedNoReloadResult();
RuntimeCoordinatorResult result;
result.accepted = true;
return result;
}
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std::string& error)
{
RuntimeCoordinatorResult result;
@@ -97,6 +185,7 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimePollFailure(const std:
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(const std::string& error)
{
std::lock_guard<std::mutex> lock(mMutex);
mPreserveFeedbackOnNextShaderBuild = false;
mUseCommittedLayerStates = true;
@@ -112,6 +201,7 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildFailure(co
RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess()
{
std::lock_guard<std::mutex> lock(mMutex);
mUseCommittedLayerStates = false;
RuntimeCoordinatorResult result;
@@ -127,11 +217,13 @@ RuntimeCoordinatorResult RuntimeCoordinator::HandlePreparedShaderBuildSuccess()
RuntimeCoordinatorResult RuntimeCoordinator::HandleRuntimeReloadRequest()
{
std::lock_guard<std::mutex> lock(mMutex);
return BuildQueuedReloadResult(false);
}
void RuntimeCoordinator::ApplyCommittedStateMode(RuntimeCoordinatorCommittedStateMode mode)
{
std::lock_guard<std::mutex> lock(mMutex);
switch (mode)
{
case RuntimeCoordinatorCommittedStateMode::UseCommittedStates:
@@ -153,9 +245,158 @@ bool RuntimeCoordinator::UseCommittedLayerStates() const
bool RuntimeCoordinator::PreserveFeedbackOnNextShaderBuild() const
{
std::lock_guard<std::mutex> lock(mMutex);
return mPreserveFeedbackOnNextShaderBuild;
}
bool RuntimeCoordinator::BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
{
std::lock_guard<std::mutex> lock(mRuntimeStore.mRuntimeHost.mMutex);
RuntimeHost::LayerPersistentState* layer = mRuntimeStore.mRuntimeHost.FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
auto shaderIt = mRuntimeStore.mRuntimeHost.mPackagesById.find(layer->shaderId);
if (shaderIt == mRuntimeStore.mRuntimeHost.mPackagesById.end())
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
auto parameterIt = std::find_if(shaderIt->second.parameters.begin(), shaderIt->second.parameters.end(),
[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
if (parameterIt == shaderIt->second.parameters.end())
{
error = "Unknown parameter id: " + parameterId;
return false;
}
return BuildParameterMutationLocked(*layer, *parameterIt, newValue, persistState, mutation, error);
}
bool RuntimeCoordinator::BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
{
std::lock_guard<std::mutex> lock(mRuntimeStore.mRuntimeHost.mMutex);
RuntimeHost::LayerPersistentState* matchedLayer = nullptr;
const ShaderPackage* matchedPackage = nullptr;
std::vector<ShaderParameterDefinition>::const_iterator parameterIt;
if (!mRuntimeStore.TryResolveStoredLayerAndParameterByControlKeyLocked(layerKey, parameterKey, matchedLayer, matchedPackage, parameterIt, error))
return false;
return BuildParameterMutationLocked(*matchedLayer, *parameterIt, newValue, persistState, mutation, error);
}
bool RuntimeCoordinator::BuildParameterMutationLocked(RuntimeHost::LayerPersistentState& layer, const ShaderParameterDefinition& definition, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const
{
mutation.layerId = layer.id;
mutation.parameterId = definition.id;
mutation.persistState = persistState;
if (definition.type == ShaderParameterType::Trigger)
{
const auto existingValue = layer.parameterValues.find(definition.id);
const double previousCount = existingValue == layer.parameterValues.end() || existingValue->second.numberValues.empty()
? 0.0
: existingValue->second.numberValues[0];
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(
std::chrono::steady_clock::now() - mRuntimeStore.mRuntimeHost.mStartTime).count();
mutation.value.numberValues = { previousCount + 1.0, triggerTime };
mutation.persistState = false;
return true;
}
return mRuntimeStore.mRuntimeHost.NormalizeAndValidateValue(definition, newValue, mutation.value, error);
}
bool RuntimeCoordinator::HasLayerLocked(const std::string& layerId) const
{
return mRuntimeStore.mRuntimeHost.FindLayerById(layerId) != nullptr;
}
bool RuntimeCoordinator::HasShaderLocked(const std::string& shaderId) const
{
return mRuntimeStore.mRuntimeHost.mPackagesById.find(shaderId) != mRuntimeStore.mRuntimeHost.mPackagesById.end();
}
bool RuntimeCoordinator::ValidateLayerExists(const std::string& layerId, std::string& error) const
{
std::lock_guard<std::mutex> lock(mRuntimeStore.mRuntimeHost.mMutex);
if (HasLayerLocked(layerId))
return true;
error = "Unknown layer id: " + layerId;
return false;
}
bool RuntimeCoordinator::ValidateShaderExists(const std::string& shaderId, std::string& error) const
{
std::lock_guard<std::mutex> lock(mRuntimeStore.mRuntimeHost.mMutex);
if (HasShaderLocked(shaderId))
return true;
error = "Unknown shader id: " + shaderId;
return false;
}
bool RuntimeCoordinator::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
{
std::lock_guard<std::mutex> lock(mRuntimeStore.mRuntimeHost.mMutex);
const auto& layers = mRuntimeStore.mRuntimeHost.mPersistentState.layers;
auto it = std::find_if(layers.begin(), layers.end(),
[&layerId](const RuntimeHost::LayerPersistentState& layer) { return layer.id == layerId; });
if (it == layers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
const std::ptrdiff_t index = std::distance(layers.begin(), it);
const std::ptrdiff_t newIndex = index + direction;
shouldMove = newIndex >= 0 && newIndex < static_cast<std::ptrdiff_t>(layers.size()) && newIndex != index;
return true;
}
bool RuntimeCoordinator::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
{
std::lock_guard<std::mutex> lock(mRuntimeStore.mRuntimeHost.mMutex);
const auto& layers = mRuntimeStore.mRuntimeHost.mPersistentState.layers;
auto it = std::find_if(layers.begin(), layers.end(),
[&layerId](const RuntimeHost::LayerPersistentState& layer) { return layer.id == layerId; });
if (it == layers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
if (layers.empty())
{
shouldMove = false;
return true;
}
const std::size_t clampedTargetIndex = (std::min)(targetIndex, layers.size() - 1);
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(layers.begin(), it));
shouldMove = sourceIndex != clampedTargetIndex;
return true;
}
bool RuntimeCoordinator::ValidatePresetName(const std::string& presetName, std::string& error) const
{
if (!mRuntimeStore.MakeSafePresetFileStem(presetName).empty())
return true;
error = "Preset name must include at least one letter or number.";
return false;
}
RuntimeCoordinatorResult RuntimeCoordinator::ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState)
{
if (!succeeded)

View File

@@ -1,9 +1,11 @@
#pragma once
#include "RuntimeHost.h"
#include "RuntimeJson.h"
#include <atomic>
#include <cstddef>
#include <mutex>
#include <string>
class RuntimeStore;
@@ -50,11 +52,13 @@ public:
RuntimeCoordinatorResult SetLayerShader(const std::string& layerId, const std::string& shaderId);
RuntimeCoordinatorResult UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue);
RuntimeCoordinatorResult UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
RuntimeCoordinatorResult CommitOscParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue);
RuntimeCoordinatorResult ResetLayerParameters(const std::string& layerId);
RuntimeCoordinatorResult SaveStackPreset(const std::string& presetName);
RuntimeCoordinatorResult LoadStackPreset(const std::string& presetName);
RuntimeCoordinatorResult RequestShaderReload(bool preserveFeedbackState = false);
RuntimeCoordinatorResult PollRuntimeStoreChanges(bool& registryChanged);
RuntimeCoordinatorResult HandleRuntimePollFailure(const std::string& error);
RuntimeCoordinatorResult HandlePreparedShaderBuildFailure(const std::string& error);
RuntimeCoordinatorResult HandlePreparedShaderBuildSuccess();
@@ -64,11 +68,33 @@ public:
bool PreserveFeedbackOnNextShaderBuild() const;
private:
struct ResolvedParameterMutation
{
std::string layerId;
std::string parameterId;
ShaderParameterValue value;
bool persistState = true;
};
bool BuildParameterMutationById(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
bool BuildParameterMutationByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
bool BuildParameterMutationLocked(RuntimeHost::LayerPersistentState& layer, const ShaderParameterDefinition& definition, const JsonValue& newValue,
bool persistState, ResolvedParameterMutation& mutation, std::string& error) const;
bool HasLayerLocked(const std::string& layerId) const;
bool HasShaderLocked(const std::string& shaderId) const;
bool ValidateLayerExists(const std::string& layerId, std::string& error) const;
bool ValidateShaderExists(const std::string& shaderId, std::string& error) const;
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
bool ValidatePresetName(const std::string& presetName, std::string& error) const;
RuntimeCoordinatorResult ApplyStoreMutation(bool succeeded, const std::string& errorMessage, bool reloadRequired, bool preserveFeedbackState);
RuntimeCoordinatorResult BuildQueuedReloadResult(bool preserveFeedbackState);
RuntimeCoordinatorResult BuildAcceptedNoReloadResult() const;
RuntimeStore& mRuntimeStore;
mutable std::mutex mMutex;
bool mPreserveFeedbackOnNextShaderBuild = false;
std::atomic<bool> mUseCommittedLayerStates{ false };
};

View File

@@ -14,8 +14,6 @@
#include <vector>
class RuntimeStore;
class RuntimeSnapshotProvider;
class RuntimeHost
{
public:
@@ -71,7 +69,7 @@ private:
private:
friend class RuntimeStore;
friend class RuntimeSnapshotProvider;
friend class RuntimeCoordinator;
HealthTelemetry mHealthTelemetry;
mutable std::mutex mMutex;
AppConfig mConfig;

View File

@@ -1,14 +1,12 @@
#include "RuntimeSnapshotProvider.h"
#include "RuntimeClock.h"
#include "ShaderCompiler.h"
#include <algorithm>
#include <mutex>
#include <filesystem>
#include <utility>
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeHost& runtimeHost) :
mRuntimeHost(runtimeHost)
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeStore& runtimeStore) :
mRuntimeStore(runtimeStore)
{
}
@@ -17,30 +15,22 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str
try
{
ShaderPackage shaderPackage;
{
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
const RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
if (!mRuntimeStore.CopyShaderPackageForStoredLayer(layerId, shaderPackage, error))
return false;
}
auto it = mRuntimeHost.mPackagesById.find(layer->shaderId);
if (it == mRuntimeHost.mPackagesById.end())
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
shaderPackage = it->second;
}
std::filesystem::path repoRoot;
std::filesystem::path wrapperPath;
std::filesystem::path generatedGlslPath;
std::filesystem::path patchedGlslPath;
unsigned maxTemporalHistoryFrames = 0;
mRuntimeStore.GetShaderCompilerInputs(repoRoot, wrapperPath, generatedGlslPath, patchedGlslPath, maxTemporalHistoryFrames);
ShaderCompiler compiler(
mRuntimeHost.mRepoRoot,
mRuntimeHost.mWrapperPath,
mRuntimeHost.mGeneratedGlslPath,
mRuntimeHost.mPatchedGlslPath,
mRuntimeHost.mConfig.maxTemporalHistoryFrames);
repoRoot,
wrapperPath,
generatedGlslPath,
patchedGlslPath,
maxTemporalHistoryFrames);
passSources.clear();
passSources.reserve(shaderPackage.passes.size());
for (const ShaderPassDefinition& pass : shaderPackage.passes)
@@ -69,20 +59,20 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
{
return mRuntimeHost.mConfig.maxTemporalHistoryFrames;
return mRuntimeStore.GetConfiguredMaxTemporalHistoryFrames();
}
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
{
RuntimeSnapshotVersions versions;
versions.renderStateVersion = mRuntimeHost.mRenderStateVersion.load(std::memory_order_relaxed);
versions.parameterStateVersion = mRuntimeHost.mParameterStateVersion.load(std::memory_order_relaxed);
versions.renderStateVersion = mRuntimeStore.GetRenderStateVersion();
versions.parameterStateVersion = mRuntimeStore.GetParameterStateVersion();
return versions;
}
void RuntimeSnapshotProvider::AdvanceFrame()
{
++mRuntimeHost.mFrameCounter;
mRuntimeStore.AdvanceFrameCounter();
}
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
@@ -94,10 +84,7 @@ RuntimeRenderStateSnapshot RuntimeSnapshotProvider::GetRenderStateSnapshot(unsig
RuntimeRenderStateSnapshot snapshot;
snapshot.outputWidth = outputWidth;
snapshot.outputHeight = outputHeight;
{
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
BuildLayerRenderStatesLocked(outputWidth, outputHeight, snapshot.states);
}
mRuntimeStore.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);
const RuntimeSnapshotVersions versionsAfter = GetVersions();
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
@@ -114,14 +101,9 @@ bool RuntimeSnapshotProvider::TryGetRenderStateSnapshot(unsigned outputWidth, un
const RuntimeSnapshotVersions versionsBefore = GetVersions();
std::vector<RuntimeRenderState> states;
{
std::unique_lock<std::mutex> lock(mRuntimeHost.mMutex, std::try_to_lock);
if (!lock.owns_lock())
if (!mRuntimeStore.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
return false;
BuildLayerRenderStatesLocked(outputWidth, outputHeight, states);
}
const RuntimeSnapshotVersions versionsAfter = GetVersions();
if (versionsBefore.renderStateVersion != versionsAfter.renderStateVersion ||
versionsBefore.parameterStateVersion != versionsAfter.parameterStateVersion)
@@ -139,14 +121,9 @@ bool RuntimeSnapshotProvider::TryGetRenderStateSnapshot(unsigned outputWidth, un
bool RuntimeSnapshotProvider::TryRefreshSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
{
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
{
std::unique_lock<std::mutex> lock(mRuntimeHost.mMutex, std::try_to_lock);
if (!lock.owns_lock())
if (!mRuntimeStore.TryRefreshLayerParameters(snapshot.states))
return false;
RefreshCachedLayerStatesLocked(snapshot.states);
}
const RuntimeSnapshotVersions versions = GetVersions();
if (versions.renderStateVersion != expectedRenderStateVersion)
return false;
@@ -157,88 +134,5 @@ bool RuntimeSnapshotProvider::TryRefreshSnapshotParameters(RuntimeRenderStateSna
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
{
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
RefreshDynamicRenderStateFieldsLocked(states);
}
void RuntimeSnapshotProvider::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
const HealthTelemetry::SignalStatusSnapshot signalStatus = mRuntimeHost.mHealthTelemetry.GetSignalStatusSnapshot();
for (const RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers)
{
auto shaderIt = mRuntimeHost.mPackagesById.find(layer.shaderId);
if (shaderIt == mRuntimeHost.mPackagesById.end())
continue;
RuntimeRenderState state;
state.layerId = layer.id;
state.shaderId = layer.shaderId;
state.shaderName = shaderIt->second.displayName;
state.mixAmount = 1.0;
state.bypass = layer.bypass ? 1.0 : 0.0;
state.inputWidth = signalStatus.width;
state.inputHeight = signalStatus.height;
state.outputWidth = outputWidth;
state.outputHeight = outputHeight;
state.parameterDefinitions = shaderIt->second.parameters;
state.textureAssets = shaderIt->second.textureAssets;
state.fontAssets = shaderIt->second.fontAssets;
state.isTemporal = shaderIt->second.temporal.enabled;
state.temporalHistorySource = shaderIt->second.temporal.historySource;
state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength;
state.effectiveTemporalHistoryLength = shaderIt->second.temporal.effectiveHistoryLength;
state.feedback = shaderIt->second.feedback;
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
{
ShaderParameterValue value = mRuntimeHost.DefaultValueForDefinition(definition);
auto valueIt = layer.parameterValues.find(definition.id);
if (valueIt != layer.parameterValues.end())
value = valueIt->second;
state.parameterValues[definition.id] = value;
}
states.push_back(state);
}
RefreshDynamicRenderStateFieldsLocked(states);
}
void RuntimeSnapshotProvider::RefreshCachedLayerStatesLocked(std::vector<RuntimeRenderState>& states) const
{
for (RuntimeRenderState& state : states)
{
const auto layerIt = std::find_if(mRuntimeHost.mPersistentState.layers.begin(), mRuntimeHost.mPersistentState.layers.end(),
[&state](const RuntimeHost::LayerPersistentState& layer) { return layer.id == state.layerId; });
if (layerIt == mRuntimeHost.mPersistentState.layers.end())
continue;
state.bypass = layerIt->bypass ? 1.0 : 0.0;
state.parameterValues.clear();
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
{
ShaderParameterValue value = mRuntimeHost.DefaultValueForDefinition(definition);
auto valueIt = layerIt->parameterValues.find(definition.id);
if (valueIt != layerIt->parameterValues.end())
value = valueIt->second;
state.parameterValues[definition.id] = value;
}
}
}
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFieldsLocked(std::vector<RuntimeRenderState>& states) const
{
const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot();
const double timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mRuntimeHost.mStartTime).count();
const double frameCount = static_cast<double>(mRuntimeHost.mFrameCounter.load(std::memory_order_relaxed));
for (RuntimeRenderState& state : states)
{
state.timeSeconds = timeSeconds;
state.utcTimeSeconds = clock.utcTimeSeconds;
state.utcOffsetSeconds = clock.utcOffsetSeconds;
state.startupRandom = mRuntimeHost.mStartupRandom;
state.frameCount = frameCount;
}
mRuntimeStore.RefreshDynamicRenderStateFields(states);
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "RuntimeHost.h"
#include "RuntimeStore.h"
#include <cstdint>
#include <string>
@@ -23,7 +23,7 @@ struct RuntimeRenderStateSnapshot
class RuntimeSnapshotProvider
{
public:
explicit RuntimeSnapshotProvider(RuntimeHost& runtimeHost);
explicit RuntimeSnapshotProvider(RuntimeStore& runtimeStore);
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
unsigned GetMaxTemporalHistoryFrames() const;
@@ -35,9 +35,5 @@ public:
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
private:
void BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
void RefreshCachedLayerStatesLocked(std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFieldsLocked(std::vector<RuntimeRenderState>& states) const;
RuntimeHost& mRuntimeHost;
RuntimeStore& mRuntimeStore;
};

View File

@@ -1,9 +1,11 @@
#include "RuntimeStore.h"
#include "RuntimeClock.h"
#include "ShaderPackageRegistry.h"
#include "RuntimeParameterUtils.h"
#include <fstream>
#include <mutex>
#include <random>
#include <sstream>
#include <windows.h>
@@ -129,11 +131,20 @@ bool FontAssetsEqual(const std::vector<ShaderFontAsset>& left, const std::vector
}
}
RuntimeStore::RuntimeStore(RuntimeHost& runtimeHost) :
mRuntimeHost(runtimeHost)
RuntimeStore::RuntimeStore()
{
}
HealthTelemetry& RuntimeStore::GetHealthTelemetry()
{
return mRuntimeHost.GetHealthTelemetry();
}
const HealthTelemetry& RuntimeStore::GetHealthTelemetry() const
{
return mRuntimeHost.GetHealthTelemetry();
}
bool RuntimeStore::InitializeStore(std::string& error)
{
try
@@ -422,7 +433,7 @@ bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, con
return SavePersistentState(error);
}
bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error)
bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, bool persistState, std::string& error)
{
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
@@ -433,71 +444,7 @@ bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std
return false;
}
auto shaderIt = mRuntimeHost.mPackagesById.find(layer->shaderId);
if (shaderIt == mRuntimeHost.mPackagesById.end())
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
const ShaderPackage& shaderPackage = shaderIt->second;
auto parameterIt = std::find_if(shaderPackage.parameters.begin(), shaderPackage.parameters.end(),
[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
if (parameterIt == shaderPackage.parameters.end())
{
error = "Unknown parameter id: " + parameterId;
return false;
}
if (parameterIt->type == ShaderParameterType::Trigger)
{
ShaderParameterValue& value = layer->parameterValues[parameterId];
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mRuntimeHost.mStartTime).count();
value.numberValues = { previousCount + 1.0, triggerTime };
mRuntimeHost.MarkParameterStateDirtyLocked();
return true;
}
ShaderParameterValue normalized;
if (!mRuntimeHost.NormalizeAndValidateValue(*parameterIt, newValue, normalized, error))
return false;
layer->parameterValues[parameterId] = normalized;
mRuntimeHost.MarkParameterStateDirtyLocked();
return SavePersistentState(error);
}
bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error)
{
return SetStoredParameterValueByControlKey(layerKey, parameterKey, newValue, true, error);
}
bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error)
{
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
RuntimeHost::LayerPersistentState* matchedLayer = nullptr;
const ShaderPackage* matchedPackage = nullptr;
std::vector<ShaderParameterDefinition>::const_iterator parameterIt;
if (!TryResolveStoredLayerAndParameterByControlKeyLocked(layerKey, parameterKey, matchedLayer, matchedPackage, parameterIt, error))
return false;
if (parameterIt->type == ShaderParameterType::Trigger)
{
ShaderParameterValue& value = matchedLayer->parameterValues[parameterIt->id];
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mRuntimeHost.mStartTime).count();
value.numberValues = { previousCount + 1.0, triggerTime };
mRuntimeHost.MarkParameterStateDirtyLocked();
return true;
}
ShaderParameterValue normalized;
if (!mRuntimeHost.NormalizeAndValidateValue(*parameterIt, newValue, normalized, error))
return false;
matchedLayer->parameterValues[parameterIt->id] = normalized;
layer->parameterValues[parameterId] = value;
mRuntimeHost.MarkParameterStateDirtyLocked();
return !persistState || SavePersistentState(error);
}
@@ -1194,3 +1141,165 @@ bool RuntimeStore::TryResolveStoredLayerAndParameterByControlKeyLocked(const std
return true;
}
bool RuntimeStore::CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const
{
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
const RuntimeHost::LayerPersistentState* layer = mRuntimeHost.FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
auto it = mRuntimeHost.mPackagesById.find(layer->shaderId);
if (it == mRuntimeHost.mPackagesById.end())
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
shaderPackage = it->second;
return true;
}
void RuntimeStore::GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath,
std::filesystem::path& generatedGlslPath, std::filesystem::path& patchedGlslPath, unsigned& maxTemporalHistoryFrames) const
{
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
repoRoot = mRuntimeHost.mRepoRoot;
wrapperPath = mRuntimeHost.mWrapperPath;
generatedGlslPath = mRuntimeHost.mGeneratedGlslPath;
patchedGlslPath = mRuntimeHost.mPatchedGlslPath;
maxTemporalHistoryFrames = mRuntimeHost.mConfig.maxTemporalHistoryFrames;
}
uint64_t RuntimeStore::GetRenderStateVersion() const
{
return mRuntimeHost.mRenderStateVersion.load(std::memory_order_relaxed);
}
uint64_t RuntimeStore::GetParameterStateVersion() const
{
return mRuntimeHost.mParameterStateVersion.load(std::memory_order_relaxed);
}
void RuntimeStore::AdvanceFrameCounter()
{
++mRuntimeHost.mFrameCounter;
}
void RuntimeStore::BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
BuildLayerRenderStatesLocked(outputWidth, outputHeight, states);
}
bool RuntimeStore::TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
std::unique_lock<std::mutex> lock(mRuntimeHost.mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false;
BuildLayerRenderStatesLocked(outputWidth, outputHeight, states);
return true;
}
bool RuntimeStore::TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const
{
std::unique_lock<std::mutex> lock(mRuntimeHost.mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false;
RefreshLayerParametersLocked(states);
return true;
}
void RuntimeStore::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
{
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
RefreshDynamicRenderStateFieldsLocked(states);
}
void RuntimeStore::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
states.clear();
const HealthTelemetry::SignalStatusSnapshot signalStatus = mRuntimeHost.mHealthTelemetry.GetSignalStatusSnapshot();
for (const RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers)
{
auto shaderIt = mRuntimeHost.mPackagesById.find(layer.shaderId);
if (shaderIt == mRuntimeHost.mPackagesById.end())
continue;
RuntimeRenderState state;
state.layerId = layer.id;
state.shaderId = layer.shaderId;
state.shaderName = shaderIt->second.displayName;
state.mixAmount = 1.0;
state.bypass = layer.bypass ? 1.0 : 0.0;
state.inputWidth = signalStatus.width;
state.inputHeight = signalStatus.height;
state.outputWidth = outputWidth;
state.outputHeight = outputHeight;
state.parameterDefinitions = shaderIt->second.parameters;
state.textureAssets = shaderIt->second.textureAssets;
state.fontAssets = shaderIt->second.fontAssets;
state.isTemporal = shaderIt->second.temporal.enabled;
state.temporalHistorySource = shaderIt->second.temporal.historySource;
state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength;
state.effectiveTemporalHistoryLength = shaderIt->second.temporal.effectiveHistoryLength;
state.feedback = shaderIt->second.feedback;
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
{
ShaderParameterValue value = mRuntimeHost.DefaultValueForDefinition(definition);
auto valueIt = layer.parameterValues.find(definition.id);
if (valueIt != layer.parameterValues.end())
value = valueIt->second;
state.parameterValues[definition.id] = value;
}
states.push_back(state);
}
RefreshDynamicRenderStateFieldsLocked(states);
}
void RuntimeStore::RefreshLayerParametersLocked(std::vector<RuntimeRenderState>& states) const
{
for (RuntimeRenderState& state : states)
{
const auto layerIt = std::find_if(mRuntimeHost.mPersistentState.layers.begin(), mRuntimeHost.mPersistentState.layers.end(),
[&state](const RuntimeHost::LayerPersistentState& layer) { return layer.id == state.layerId; });
if (layerIt == mRuntimeHost.mPersistentState.layers.end())
continue;
state.bypass = layerIt->bypass ? 1.0 : 0.0;
state.parameterValues.clear();
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
{
ShaderParameterValue value = mRuntimeHost.DefaultValueForDefinition(definition);
auto valueIt = layerIt->parameterValues.find(definition.id);
if (valueIt != layerIt->parameterValues.end())
value = valueIt->second;
state.parameterValues[definition.id] = value;
}
}
}
void RuntimeStore::RefreshDynamicRenderStateFieldsLocked(std::vector<RuntimeRenderState>& states) const
{
const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot();
const double timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mRuntimeHost.mStartTime).count();
const double frameCount = static_cast<double>(mRuntimeHost.mFrameCounter.load(std::memory_order_relaxed));
for (RuntimeRenderState& state : states)
{
state.timeSeconds = timeSeconds;
state.utcTimeSeconds = clock.utcTimeSeconds;
state.utcOffsetSeconds = clock.utcOffsetSeconds;
state.startupRandom = mRuntimeHost.mStartupRandom;
state.frameCount = frameCount;
}
}

View File

@@ -2,13 +2,18 @@
#include "RuntimeHost.h"
#include <cstdint>
#include <filesystem>
#include <string>
class RuntimeSnapshotProvider;
class RuntimeStore
{
public:
explicit RuntimeStore(RuntimeHost& runtimeHost);
RuntimeStore();
HealthTelemetry& GetHealthTelemetry();
const HealthTelemetry& GetHealthTelemetry() const;
bool InitializeStore(std::string& error);
std::string BuildPersistentStateJson() const;
@@ -20,9 +25,7 @@ public:
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 SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, 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);
@@ -48,6 +51,8 @@ public:
void ClearReloadRequest();
private:
friend class RuntimeCoordinator;
friend class RuntimeSnapshotProvider;
bool LoadConfig(std::string& error);
bool LoadPersistentState(std::string& error);
bool SavePersistentState(std::string& error) const;
@@ -60,8 +65,21 @@ private:
bool TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey,
RuntimeHost::LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage,
std::vector<ShaderParameterDefinition>::const_iterator& parameterIt, std::string& error) const;
bool CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const;
void GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath,
std::filesystem::path& generatedGlslPath, std::filesystem::path& patchedGlslPath, unsigned& maxTemporalHistoryFrames) const;
uint64_t GetRenderStateVersion() const;
uint64_t GetParameterStateVersion() const;
void AdvanceFrameCounter();
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
bool TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
bool TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
void BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
void RefreshLayerParametersLocked(std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFieldsLocked(std::vector<RuntimeRenderState>& states) const;
JsonValue BuildRuntimeStateValue() const;
JsonValue SerializeLayerStack() const;
RuntimeHost& mRuntimeHost;
mutable RuntimeHost mRuntimeHost;
};

View File

@@ -4,7 +4,7 @@ This note summarizes the main architectural improvements that would make the app
Phase checklist:
- [ ] Define subsystem boundaries and target architecture
- [x] Define subsystem boundaries and target architecture
- [ ] Introduce an internal event model
- [ ] Split `RuntimeHost`
- [ ] Make the render thread the sole GL owner
@@ -13,6 +13,11 @@ Phase checklist:
- [ ] Make DeckLink/backend lifecycle explicit with a state machine
- [ ] Add structured health, telemetry, and operational reporting
Checklist note:
- The checked Phase 1 item means the subsystem vocabulary, dependency direction, state categories, and design package are in place.
- It does not mean the target boundaries are fully extracted in code. The current implementation has Phase 1 compatibility seams, while the full ownership split continues through later phases.
## Timing Review
The recent OSC work removed several control-path stalls, but the app still has a few deeper timing characteristics that matter for live resilience:
@@ -356,6 +361,12 @@ This roadmap is ordered by architectural dependency rather than by “quick wins
Before changing major internals, formalize the target responsibilities for each major part of the app.
Status:
- Design deliverable: complete.
- Compatibility seams in code: partially complete and expanding.
- Target boundary extraction: not complete; remaining work is tracked by later phases, especially the event model, `RuntimeHost` split, render ownership, and persistence work.
Target split:
- `RuntimeStore`
@@ -402,6 +413,10 @@ Suggested deliverables:
- a subsystem design bundle index:
- [docs/subsystems/README.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/README.md)
Current implementation note:
The repo now has concrete classes for the Phase 1 subsystem names and several call paths already route through them. These classes should be treated as migration boundaries, not proof that the target architecture is fully extracted.
### Phase 2. Introduce an internal event model
Once subsystem boundaries are defined, introduce a typed event pipeline between them. This should happen before large state splits so the app has a stable coordination model.

View File

@@ -4,6 +4,15 @@ This document expands Phase 1 of [ARCHITECTURE_RESILIENCE_REVIEW.md](/c:/Users/A
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.
## Status
Phase 1 has two different meanings in this repo, and they should not be collapsed:
- Phase 1 design package: complete.
- Phase 1 target extraction in code: in progress.
The completed design package includes the agreed subsystem names, responsibilities, dependency rules, state categories, and current-to-target migration map. The codebase also has concrete compatibility seams for those subsystems. That is different from saying every target boundary is fully extracted: several subsystems still delegate through compatibility helpers, and later roadmap phases are still responsible for the event model, remaining `RuntimeHost` split, sole-owner render thread, explicit live-state layering, background persistence, backend state machine, and fuller telemetry.
## Why Phase 1 Exists
Today the app works, but too many responsibilities still converge in a few places:
@@ -112,17 +121,20 @@ The codebase now has an initial Phase 1 compatibility split in place:
- [VideoBackend.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h)
- [VideoBackend.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp)
These are still compatibility seams, not a completed subsystem extraction. Most of them continue to delegate heavily to `RuntimeHost`, `OpenGLComposite`, `DeckLinkSession`, and the existing bridge/pipeline classes. Their purpose is to give later Phase 1 work real code boundaries that can be expanded in parallel:
These are still compatibility seams, not a completed subsystem extraction. Some responsibilities have moved behind the new boundaries, while other paths still delegate through compatibility helpers, `OpenGLComposite`, `DeckLinkSession`, and the existing bridge/pipeline classes. Their purpose is to give later work real code boundaries that can be expanded without first inventing the names:
- store-facing UI/runtime control calls in `OpenGLCompositeRuntimeControls.cpp` now route through `RuntimeStore`
- UI/runtime control calls in `OpenGLCompositeRuntimeControls.cpp` now route through `RuntimeCoordinator`
- runtime startup for path resolution, config load, persistent state load, and shader package scan now initializes through `RuntimeStore`
- runtime/UI state JSON composition now lives in `RuntimeStore` instead of `RuntimeHost`
- regular stored layer mutations and stack preset save/load now live in `RuntimeStore` instead of `RuntimeHost` public APIs
- persisted OSC-by-control-key commits now apply through `RuntimeStore`, while `RuntimeHost` no longer exposes the old OSC smoothing/apply helper
- persisted OSC-by-control-key commits now route through `RuntimeCoordinator` before applying store changes
- mutation and reload policy now routes through `RuntimeCoordinator`
- parameter target resolution, value normalization, trigger classification, and move no-op classification now live under `RuntimeCoordinator`
- render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider`
- render-state assembly, cached parameter refresh, and frame-context application now live in `RuntimeSnapshotProvider` instead of `RuntimeHost` public APIs
- `RuntimeSnapshotProvider` now depends on `RuntimeStore` rather than sharing `RuntimeHost` directly
- render-state assembly, cached parameter refresh, and frame-context application now flow through `RuntimeSnapshotProvider` and store-owned snapshot helpers instead of `RuntimeHost` public APIs
- service ingress and polling coordination now route through `ControlServices`
- `ControlServices` now queues coordinator results for OSC commit and file-poll outcomes instead of directly deciding runtime/store policy
- timing and status writes now route through `HealthTelemetry`
- `HealthTelemetry` now owns the live signal, video-I/O, and performance snapshots directly instead of `RuntimeHost` keeping those backing fields
- render-side frame advancement and render-performance reporting now flow through `RuntimeSnapshotProvider` and `HealthTelemetry` instead of directly through `RuntimeHost`
@@ -131,7 +143,15 @@ These are still compatibility seams, not a completed subsystem extraction. Most
- `OpenGLComposite` now owns a `VideoBackend` seam for device/session ownership and callback wiring
- `OpenGLVideoIOBridge` now acts as an explicit compatibility adapter between `VideoBackend` and `RenderEngine`, instead of `OpenGLComposite` directly owning both sides
That means the next parallel Phase 1 work can focus on moving responsibility behind these seams instead of first inventing them.
That means the next extraction work can focus on moving responsibility behind these seams instead of first inventing them.
Remaining extraction work includes:
- replacing the compatibility `RuntimeHost` backing object still owned inside `RuntimeStore`
- removing coordinator friendship/access to store-internal host data
- publishing render snapshots as explicit immutable or near-immutable objects rather than building them from store internals on demand
- moving persistence to an asynchronous writer in a later phase
- replacing polling/shared-object coordination with the planned internal event model
## Subsystem Responsibilities
@@ -646,6 +666,8 @@ Phase 1 can reasonably be considered complete once the project has:
- 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
By that definition, the Phase 1 design deliverable is complete. The implementation should still be described as partially extracted until the compatibility backing objects and cross-subsystem shims are removed.
## Open Questions For Later Phases
These do not block Phase 1, but they should remain visible.

View File

@@ -15,6 +15,12 @@ Parent documents:
- The notes in this directory expand each subsystem boundary without changing the parent Phase 1 design.
- The subsystem notes are meant to be read as design companions, not as independent alternate architectures.
Status note:
- The Phase 1 design package is complete.
- The codebase has compatibility seams for the named subsystems.
- The target subsystem extraction is still in progress, so these notes describe the architecture the implementation is moving toward, not a claim that every boundary is already pure.
## Recommended Reading Order
1. [PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md)
@@ -60,3 +66,5 @@ Phase 1 should leave the project with:
- one agreed current-to-target migration story
Phase 1 does not need to settle every later implementation detail. The subsystem notes intentionally leave some questions open where later phases need room to choose concrete mechanics.
As of the current codebase, those design questions are settled well enough for later work to build against them. Remaining implementation work should be tracked as extraction progress under later phases, especially eventing, the `RuntimeHost` split, render-thread ownership, persistence, backend lifecycle, and telemetry.