diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index bcdad27..5e8c023 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -24,72 +24,6 @@ namespace { -constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150); -constexpr double kOscSmoothingReferenceFps = 60.0; -constexpr double kOscSmoothingMaxStepSeconds = 0.25; - -std::string SimplifyOscControlKey(const std::string& text) -{ - std::string simplified; - for (unsigned char ch : text) - { - if (std::isalnum(ch)) - simplified.push_back(static_cast(std::tolower(ch))); - } - return simplified; -} - -bool MatchesOscControlKey(const std::string& candidate, const std::string& key) -{ - return candidate == key || SimplifyOscControlKey(candidate) == SimplifyOscControlKey(key); -} - -double ClampOscAlpha(double value) -{ - return (std::max)(0.0, (std::min)(1.0, value)); -} - -double ComputeTimeBasedOscAlpha(double smoothing, double deltaSeconds) -{ - const double clampedSmoothing = ClampOscAlpha(smoothing); - if (clampedSmoothing <= 0.0) - return 0.0; - if (clampedSmoothing >= 1.0) - return 1.0; - - const double clampedDeltaSeconds = (std::max)(0.0, (std::min)(kOscSmoothingMaxStepSeconds, deltaSeconds)); - if (clampedDeltaSeconds <= 0.0) - return 0.0; - - const double frameScale = clampedDeltaSeconds * kOscSmoothingReferenceFps; - return ClampOscAlpha(1.0 - std::pow(1.0 - clampedSmoothing, frameScale)); -} - -JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) -{ - switch (definition.type) - { - case ShaderParameterType::Boolean: - return JsonValue(value.booleanValue); - case ShaderParameterType::Enum: - return JsonValue(value.enumValue); - case ShaderParameterType::Text: - return JsonValue(value.textValue); - case ShaderParameterType::Trigger: - case ShaderParameterType::Float: - return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front()); - case ShaderParameterType::Vec2: - case ShaderParameterType::Color: - { - JsonValue array = JsonValue::MakeArray(); - for (double number : value.numberValues) - array.pushBack(JsonValue(number)); - return array; - } - } - - return JsonValue(); -} } OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : @@ -376,7 +310,7 @@ void OpenGLComposite::renderEffect() ProcessRuntimePollResults(); std::vector appliedOscUpdates; std::vector completedOscCommits; - if (mRuntimeHost && mRuntimeServices) + if (mRuntimeServices) { std::string oscError; if (!mRuntimeServices->ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty()) @@ -384,190 +318,52 @@ void OpenGLComposite::renderEffect() mRuntimeServices->ConsumeCompletedOscCommits(completedOscCommits); } - for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits) - { - auto overlayIt = mOscOverlayStates.find(completedCommit.routeKey); - if (overlayIt == mOscOverlayStates.end()) - continue; - - OscOverlayState& overlay = overlayIt->second; - if (overlay.commitQueued && - overlay.pendingCommitGeneration == completedCommit.generation && - overlay.generation == completedCommit.generation) - { - mOscOverlayStates.erase(overlayIt); - } - } - - std::set pendingOscRouteKeys; - const auto oscNow = std::chrono::steady_clock::now(); + std::vector overlayUpdates; + overlayUpdates.reserve(appliedOscUpdates.size()); for (const RuntimeServices::AppliedOscUpdate& update : appliedOscUpdates) { - const std::string routeKey = update.routeKey; - auto overlayIt = mOscOverlayStates.find(routeKey); - if (overlayIt == mOscOverlayStates.end()) - { - OscOverlayState overlay; - overlay.layerKey = update.layerKey; - overlay.parameterKey = update.parameterKey; - overlay.targetValue = update.targetValue; - overlay.lastUpdatedTime = oscNow; - overlay.lastAppliedTime = oscNow; - overlay.generation = 1; - mOscOverlayStates[routeKey] = std::move(overlay); - } - else - { - overlayIt->second.targetValue = update.targetValue; - overlayIt->second.lastUpdatedTime = oscNow; - overlayIt->second.generation += 1; - overlayIt->second.commitQueued = false; - } - pendingOscRouteKeys.insert(routeKey); + overlayUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue }); } - const auto applyOscOverlays = [&](std::vector& states, bool allowCommit) + std::vector overlayCommitCompletions; + overlayCommitCompletions.reserve(completedOscCommits.size()); + for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits) { - if (states.empty() || mOscOverlayStates.empty()) - return; + overlayCommitCompletions.push_back({ completedCommit.routeKey, completedCommit.generation }); + } - const double smoothing = ClampOscAlpha(mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0); - std::vector overlayKeysToRemove; - for (auto& item : mOscOverlayStates) - { - OscOverlayState& overlay = item.second; - auto stateIt = std::find_if(states.begin(), states.end(), - [&overlay](const RuntimeRenderState& state) - { - return MatchesOscControlKey(state.layerId, overlay.layerKey) || - MatchesOscControlKey(state.shaderId, overlay.layerKey) || - MatchesOscControlKey(state.shaderName, overlay.layerKey); - }); - if (stateIt == states.end()) - continue; - - auto definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(), - [&overlay](const ShaderParameterDefinition& definition) - { - return MatchesOscControlKey(definition.id, overlay.parameterKey) || - MatchesOscControlKey(definition.label, overlay.parameterKey); - }); - if (definitionIt == stateIt->parameterDefinitions.end()) - continue; - - if (definitionIt->type == ShaderParameterType::Trigger) - { - if (pendingOscRouteKeys.find(item.first) == pendingOscRouteKeys.end()) - continue; - - ShaderParameterValue& value = stateIt->parameterValues[definitionIt->id]; - const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0]; - const double triggerTime = stateIt->timeSeconds; - value.numberValues = { previousCount + 1.0, triggerTime }; - overlayKeysToRemove.push_back(item.first); - continue; - } - - ShaderParameterValue targetValue; - std::string normalizeError; - if (!NormalizeAndValidateParameterValue(*definitionIt, overlay.targetValue, targetValue, normalizeError)) - continue; - - const bool smoothable = - smoothing > 0.0 && - (definitionIt->type == ShaderParameterType::Float || - definitionIt->type == ShaderParameterType::Vec2 || - definitionIt->type == ShaderParameterType::Color); - if (!smoothable) - { - overlay.currentValue = targetValue; - overlay.hasCurrentValue = true; - stateIt->parameterValues[definitionIt->id] = overlay.currentValue; - if (allowCommit && - !overlay.commitQueued && - oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay && - mRuntimeServices) - { - std::string commitError; - if (mRuntimeServices->QueueOscCommit(item.first, overlay.layerKey, overlay.parameterKey, overlay.targetValue, overlay.generation, commitError)) - { - overlay.pendingCommitGeneration = overlay.generation; - overlay.commitQueued = true; - } - } - continue; - } - - if (!overlay.hasCurrentValue) - { - overlay.currentValue = DefaultValueForDefinition(*definitionIt); - auto currentIt = stateIt->parameterValues.find(definitionIt->id); - if (currentIt != stateIt->parameterValues.end()) - overlay.currentValue = currentIt->second; - overlay.hasCurrentValue = true; - } - - if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size()) - overlay.currentValue.numberValues = targetValue.numberValues; - - double smoothingAlpha = smoothing; - if (overlay.lastAppliedTime != std::chrono::steady_clock::time_point()) - { - const double deltaSeconds = - std::chrono::duration_cast>(oscNow - overlay.lastAppliedTime).count(); - smoothingAlpha = ComputeTimeBasedOscAlpha(smoothing, deltaSeconds); - } - overlay.lastAppliedTime = oscNow; - - ShaderParameterValue nextValue = targetValue; - bool converged = true; - for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index) - { - const double currentNumber = overlay.currentValue.numberValues[index]; - const double targetNumber = targetValue.numberValues[index]; - const double delta = targetNumber - currentNumber; - double nextNumber = currentNumber + delta * smoothingAlpha; - if (std::fabs(delta) <= 0.0005) - nextNumber = targetNumber; - else - converged = false; - nextValue.numberValues[index] = nextNumber; - } - - if (converged) - nextValue.numberValues = targetValue.numberValues; - - overlay.currentValue = nextValue; - overlay.hasCurrentValue = true; - stateIt->parameterValues[definitionIt->id] = overlay.currentValue; - if (allowCommit && - converged && - !overlay.commitQueued && - oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay && - mRuntimeServices) - { - std::string commitError; - JsonValue committedValue = BuildOscCommitValue(*definitionIt, overlay.currentValue); - if (mRuntimeServices->QueueOscCommit(item.first, overlay.layerKey, overlay.parameterKey, committedValue, overlay.generation, commitError)) - { - overlay.pendingCommitGeneration = overlay.generation; - overlay.commitQueued = true; - } - } - } - - for (const std::string& overlayKey : overlayKeysToRemove) - mOscOverlayStates.erase(overlayKey); - }; + if (mRenderEngine) + mRenderEngine->UpdateOscOverlayState(overlayUpdates, overlayCommitCompletions); const bool hasInputSource = mVideoBackend->HasInputSource(); std::vector layerStates; + std::vector overlayCommitRequests; + const double smoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0; mRenderEngine->ResolveRenderLayerStates( mUseCommittedLayerStates.load(), mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), - applyOscOverlays, + smoothing, + &overlayCommitRequests, layerStates); + if (mRuntimeServices) + { + for (const RenderEngine::OscOverlayCommitRequest& commitRequest : overlayCommitRequests) + { + std::string commitError; + if (!mRuntimeServices->QueueOscCommit( + commitRequest.routeKey, + commitRequest.layerKey, + commitRequest.parameterKey, + commitRequest.value, + commitRequest.generation, + commitError) && + !commitError.empty()) + { + OutputDebugStringA(("OSC commit queue failed: " + commitError + "\n").c_str()); + } + } + } const unsigned historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0; mRenderEngine->RenderLayerStack( hasInputSource, @@ -706,7 +502,8 @@ bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResu if (result.clearTransientOscState) { - mOscOverlayStates.clear(); + if (mRenderEngine) + mRenderEngine->ClearOscOverlayState(); if (mRuntimeServices) mRuntimeServices->ClearOscState(); } diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h index a12bdac..4b3e2f2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h @@ -20,12 +20,9 @@ #include #include #include -#include #include #include #include -#include -#include class RenderEngine; class RuntimeServices; @@ -71,19 +68,6 @@ private: void resizeWindow(int width, int height); bool CheckOpenGLExtensions(); void PublishVideoIOStatus(const std::string& statusMessage); - struct OscOverlayState - { - std::string layerKey; - std::string parameterKey; - JsonValue targetValue; - ShaderParameterValue currentValue; - bool hasCurrentValue = false; - std::chrono::steady_clock::time_point lastUpdatedTime; - std::chrono::steady_clock::time_point lastAppliedTime; - uint64_t generation = 0; - uint64_t pendingCommitGeneration = 0; - bool commitQueued = false; - }; HWND hGLWnd; HDC hGLDC; @@ -98,7 +82,6 @@ private: std::unique_ptr mShaderBuildQueue; std::unique_ptr mRuntimeServices; std::unique_ptr mVideoBackend; - std::map mOscOverlayStates; std::atomic mUseCommittedLayerStates; std::atomic mScreenshotRequested; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp index 948a56a..e5b77e7 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp @@ -1,10 +1,84 @@ #include "RenderEngine.h" +#include "RuntimeParameterUtils.h" + #include #include +#include +#include #include +namespace +{ +constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150); +constexpr double kOscSmoothingReferenceFps = 60.0; +constexpr double kOscSmoothingMaxStepSeconds = 0.25; + +std::string SimplifyOscControlKey(const std::string& text) +{ + std::string simplified; + for (unsigned char ch : text) + { + if (std::isalnum(ch)) + simplified.push_back(static_cast(std::tolower(ch))); + } + return simplified; +} + +bool MatchesOscControlKey(const std::string& candidate, const std::string& key) +{ + return candidate == key || SimplifyOscControlKey(candidate) == SimplifyOscControlKey(key); +} + +double ClampOscAlpha(double value) +{ + return (std::max)(0.0, (std::min)(1.0, value)); +} + +double ComputeTimeBasedOscAlpha(double smoothing, double deltaSeconds) +{ + const double clampedSmoothing = ClampOscAlpha(smoothing); + if (clampedSmoothing <= 0.0) + return 0.0; + if (clampedSmoothing >= 1.0) + return 1.0; + + const double clampedDeltaSeconds = (std::max)(0.0, (std::min)(kOscSmoothingMaxStepSeconds, deltaSeconds)); + if (clampedDeltaSeconds <= 0.0) + return 0.0; + + const double frameScale = clampedDeltaSeconds * kOscSmoothingReferenceFps; + return ClampOscAlpha(1.0 - std::pow(1.0 - clampedSmoothing, frameScale)); +} + +JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) +{ + switch (definition.type) + { + case ShaderParameterType::Boolean: + return JsonValue(value.booleanValue); + case ShaderParameterType::Enum: + return JsonValue(value.enumValue); + case ShaderParameterType::Text: + return JsonValue(value.textValue); + case ShaderParameterType::Trigger: + case ShaderParameterType::Float: + return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front()); + case ShaderParameterType::Vec2: + case ShaderParameterType::Color: + { + JsonValue array = JsonValue::MakeArray(); + for (double number : value.numberValues) + array.pushBack(JsonValue(number)); + return array; + } + } + + return JsonValue(); +} +} + RenderEngine::RenderEngine( RuntimeSnapshotProvider& runtimeSnapshotProvider, HealthTelemetry& healthTelemetry, @@ -106,6 +180,55 @@ void RenderEngine::ResetShaderFeedbackState() mShaderPrograms.ResetShaderFeedbackState(); } +void RenderEngine::ClearOscOverlayState() +{ + mOscOverlayStates.clear(); +} + +void RenderEngine::UpdateOscOverlayState( + const std::vector& updates, + const std::vector& completedCommits) +{ + for (const OscOverlayCommitCompletion& completedCommit : completedCommits) + { + auto overlayIt = mOscOverlayStates.find(completedCommit.routeKey); + if (overlayIt == mOscOverlayStates.end()) + continue; + + OscOverlayState& overlay = overlayIt->second; + if (overlay.commitQueued && + overlay.pendingCommitGeneration == completedCommit.generation && + overlay.generation == completedCommit.generation) + { + mOscOverlayStates.erase(overlayIt); + } + } + + const auto now = std::chrono::steady_clock::now(); + for (const OscOverlayUpdate& update : updates) + { + auto overlayIt = mOscOverlayStates.find(update.routeKey); + if (overlayIt == mOscOverlayStates.end()) + { + OscOverlayState overlay; + overlay.layerKey = update.layerKey; + overlay.parameterKey = update.parameterKey; + overlay.targetValue = update.targetValue; + overlay.lastUpdatedTime = now; + overlay.lastAppliedTime = now; + overlay.generation = 1; + mOscOverlayStates[update.routeKey] = std::move(overlay); + } + else + { + overlayIt->second.targetValue = update.targetValue; + overlayIt->second.lastUpdatedTime = now; + overlayIt->second.generation += 1; + overlayIt->second.commitQueued = false; + } + } +} + void RenderEngine::ResizeView(int width, int height) { mRenderer.ResizeView(width, height); @@ -178,15 +301,15 @@ bool RenderEngine::ResolveRenderLayerStates( bool useCommittedLayerStates, unsigned renderWidth, unsigned renderHeight, - OverlayApplier overlayApplier, + double oscSmoothing, + std::vector* commitRequests, std::vector& layerStates) { layerStates.clear(); if (useCommittedLayerStates) { layerStates = mShaderPrograms.CommittedLayerStates(); - if (overlayApplier) - overlayApplier(layerStates, false); + ApplyOscOverlays(layerStates, false, oscSmoothing, commitRequests); mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); return true; } @@ -207,14 +330,12 @@ bool RenderEngine::ResolveRenderLayerStates( renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion; renderSnapshot.states = mCachedLayerRenderStates; - if (overlayApplier) - overlayApplier(renderSnapshot.states, true); + ApplyOscOverlays(renderSnapshot.states, true, oscSmoothing, commitRequests); if (mCachedParameterStateVersion != versions.parameterStateVersion && mRuntimeSnapshotProvider.TryRefreshSnapshotParameters(renderSnapshot)) { mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; - if (overlayApplier) - overlayApplier(renderSnapshot.states, true); + ApplyOscOverlays(renderSnapshot.states, true, oscSmoothing, commitRequests); } mCachedLayerRenderStates = renderSnapshot.states; @@ -231,19 +352,148 @@ bool RenderEngine::ResolveRenderLayerStates( mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion; mCachedRenderStateWidth = renderSnapshot.outputWidth; mCachedRenderStateHeight = renderSnapshot.outputHeight; - if (overlayApplier) - overlayApplier(mCachedLayerRenderStates, true); + ApplyOscOverlays(mCachedLayerRenderStates, true, oscSmoothing, commitRequests); layerStates = mCachedLayerRenderStates; return true; } - if (overlayApplier) - overlayApplier(mCachedLayerRenderStates, true); + ApplyOscOverlays(mCachedLayerRenderStates, true, oscSmoothing, commitRequests); layerStates = mCachedLayerRenderStates; mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates); return !layerStates.empty(); } +void RenderEngine::ApplyOscOverlays( + std::vector& states, + bool allowCommit, + double smoothing, + std::vector* commitRequests) +{ + if (states.empty() || mOscOverlayStates.empty()) + return; + + const auto now = std::chrono::steady_clock::now(); + const double clampedSmoothing = ClampOscAlpha(smoothing); + std::vector overlayKeysToRemove; + + for (auto& item : mOscOverlayStates) + { + const std::string& routeKey = item.first; + OscOverlayState& overlay = item.second; + auto stateIt = std::find_if(states.begin(), states.end(), + [&overlay](const RuntimeRenderState& state) + { + return MatchesOscControlKey(state.layerId, overlay.layerKey) || + MatchesOscControlKey(state.shaderId, overlay.layerKey) || + MatchesOscControlKey(state.shaderName, overlay.layerKey); + }); + if (stateIt == states.end()) + continue; + + auto definitionIt = std::find_if(stateIt->parameterDefinitions.begin(), stateIt->parameterDefinitions.end(), + [&overlay](const ShaderParameterDefinition& definition) + { + return MatchesOscControlKey(definition.id, overlay.parameterKey) || + MatchesOscControlKey(definition.label, overlay.parameterKey); + }); + if (definitionIt == stateIt->parameterDefinitions.end()) + continue; + + ShaderParameterValue targetValue; + std::string normalizeError; + if (!NormalizeAndValidateParameterValue(*definitionIt, overlay.targetValue, targetValue, normalizeError)) + continue; + + if (definitionIt->type == ShaderParameterType::Trigger) + { + ShaderParameterValue& value = stateIt->parameterValues[definitionIt->id]; + const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0]; + const double triggerTime = stateIt->timeSeconds; + value.numberValues = { previousCount + 1.0, triggerTime }; + overlayKeysToRemove.push_back(routeKey); + continue; + } + + const bool smoothable = + clampedSmoothing > 0.0 && + (definitionIt->type == ShaderParameterType::Float || + definitionIt->type == ShaderParameterType::Vec2 || + definitionIt->type == ShaderParameterType::Color); + if (!smoothable) + { + overlay.currentValue = targetValue; + overlay.hasCurrentValue = true; + stateIt->parameterValues[definitionIt->id] = overlay.currentValue; + if (allowCommit && + !overlay.commitQueued && + now - overlay.lastUpdatedTime >= kOscOverlayCommitDelay && + commitRequests) + { + commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, overlay.targetValue, overlay.generation }); + overlay.pendingCommitGeneration = overlay.generation; + overlay.commitQueued = true; + } + continue; + } + + if (!overlay.hasCurrentValue) + { + overlay.currentValue = DefaultValueForDefinition(*definitionIt); + auto currentIt = stateIt->parameterValues.find(definitionIt->id); + if (currentIt != stateIt->parameterValues.end()) + overlay.currentValue = currentIt->second; + overlay.hasCurrentValue = true; + } + + if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size()) + overlay.currentValue.numberValues = targetValue.numberValues; + + double smoothingAlpha = clampedSmoothing; + if (overlay.lastAppliedTime != std::chrono::steady_clock::time_point()) + { + const double deltaSeconds = + std::chrono::duration_cast>(now - overlay.lastAppliedTime).count(); + smoothingAlpha = ComputeTimeBasedOscAlpha(clampedSmoothing, deltaSeconds); + } + overlay.lastAppliedTime = now; + + ShaderParameterValue nextValue = targetValue; + bool converged = true; + for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index) + { + const double currentNumber = overlay.currentValue.numberValues[index]; + const double targetNumber = targetValue.numberValues[index]; + const double delta = targetNumber - currentNumber; + double nextNumber = currentNumber + delta * smoothingAlpha; + if (std::fabs(delta) <= 0.0005) + nextNumber = targetNumber; + else + converged = false; + nextValue.numberValues[index] = nextNumber; + } + + if (converged) + nextValue.numberValues = targetValue.numberValues; + + overlay.currentValue = nextValue; + overlay.hasCurrentValue = true; + stateIt->parameterValues[definitionIt->id] = overlay.currentValue; + if (allowCommit && + converged && + !overlay.commitQueued && + now - overlay.lastUpdatedTime >= kOscOverlayCommitDelay && + commitRequests) + { + commitRequests->push_back({ routeKey, overlay.layerKey, overlay.parameterKey, BuildOscCommitValue(*definitionIt, overlay.currentValue), overlay.generation }); + overlay.pendingCommitGeneration = overlay.generation; + overlay.commitQueued = true; + } + } + + for (const std::string& overlayKey : overlayKeysToRemove) + mOscOverlayStates.erase(overlayKey); +} + void RenderEngine::RenderLayerStack( bool hasInputSource, const std::vector& layerStates, diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h index f6b0c4b..e0e43ef 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -21,7 +22,29 @@ public: using RenderEffectCallback = std::function; using ScreenshotCallback = std::function; using PreviewPaintCallback = std::function; - using OverlayApplier = std::function& states, bool allowCommit)>; + + struct OscOverlayUpdate + { + std::string routeKey; + std::string layerKey; + std::string parameterKey; + JsonValue targetValue; + }; + + struct OscOverlayCommitCompletion + { + std::string routeKey; + uint64_t generation = 0; + }; + + struct OscOverlayCommitRequest + { + std::string routeKey; + std::string layerKey; + std::string parameterKey; + JsonValue value; + uint64_t generation = 0; + }; RenderEngine( RuntimeSnapshotProvider& runtimeSnapshotProvider, @@ -57,6 +80,10 @@ public: const std::vector& CommittedLayerStates() const; void ResetTemporalHistoryState(); void ResetShaderFeedbackState(); + void ClearOscOverlayState(); + void UpdateOscOverlayState( + const std::vector& updates, + const std::vector& completedCommits); void ResizeView(int width, int height); bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight); bool TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState); @@ -65,7 +92,8 @@ public: bool useCommittedLayerStates, unsigned renderWidth, unsigned renderHeight, - OverlayApplier overlayApplier, + double oscSmoothing, + std::vector* commitRequests, std::vector& layerStates); void RenderLayerStack( bool hasInputSource, @@ -87,10 +115,32 @@ private: CRITICAL_SECTION& mMutex; HDC mHdc; HGLRC mHglrc; + + struct OscOverlayState + { + std::string layerKey; + std::string parameterKey; + JsonValue targetValue; + ShaderParameterValue currentValue; + bool hasCurrentValue = false; + std::chrono::steady_clock::time_point lastUpdatedTime; + std::chrono::steady_clock::time_point lastAppliedTime; + uint64_t generation = 0; + uint64_t pendingCommitGeneration = 0; + bool commitQueued = false; + }; + + void ApplyOscOverlays( + std::vector& states, + bool allowCommit, + double smoothing, + std::vector* commitRequests); + std::vector mCachedLayerRenderStates; uint64_t mCachedRenderStateVersion = 0; uint64_t mCachedParameterStateVersion = 0; unsigned mCachedRenderStateWidth = 0; unsigned mCachedRenderStateHeight = 0; std::chrono::steady_clock::time_point mLastPreviewPresentTime; + std::map mOscOverlayStates; }; diff --git a/docs/ARCHITECTURE_RESILIENCE_REVIEW.md b/docs/ARCHITECTURE_RESILIENCE_REVIEW.md index 32d66ff..d8037ed 100644 --- a/docs/ARCHITECTURE_RESILIENCE_REVIEW.md +++ b/docs/ARCHITECTURE_RESILIENCE_REVIEW.md @@ -121,11 +121,11 @@ Recommended direction: The current design works better now, but it still relies on hand-managed reconciliation between: - persisted parameter state in `RuntimeHost` -- transient OSC overlay state in `OpenGLComposite` +- transient OSC overlay state in `RenderEngine` Relevant code: -- [OpenGLComposite.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h:66) +- [RenderEngine.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h:18) Recommended direction: diff --git a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md index dc068a0..c0e0ca4 100644 --- a/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md +++ b/docs/PHASE_1_SUBSYSTEM_BOUNDARIES_DESIGN.md @@ -120,6 +120,7 @@ These are still compatibility seams, not a completed subsystem extraction. Most - service ingress and polling coordination now route through `ControlServices` - timing and status writes now route through `HealthTelemetry` - render-side frame advancement and render-performance reporting now flow through `RuntimeSnapshotProvider` and `HealthTelemetry` instead of directly through `RuntimeHost` +- live OSC overlay state and smoothing/commit decisions now live under `RenderEngine` instead of `OpenGLComposite` - `OpenGLComposite` now owns a `RenderEngine` seam for renderer, pipeline, render-pass, and shader-program responsibilities - `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