Osc orchestration
This commit is contained in:
@@ -24,72 +24,6 @@
|
|||||||
|
|
||||||
namespace
|
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<char>(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) :
|
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
|
||||||
@@ -376,7 +310,7 @@ void OpenGLComposite::renderEffect()
|
|||||||
ProcessRuntimePollResults();
|
ProcessRuntimePollResults();
|
||||||
std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates;
|
std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates;
|
||||||
std::vector<RuntimeServices::CompletedOscCommit> completedOscCommits;
|
std::vector<RuntimeServices::CompletedOscCommit> completedOscCommits;
|
||||||
if (mRuntimeHost && mRuntimeServices)
|
if (mRuntimeServices)
|
||||||
{
|
{
|
||||||
std::string oscError;
|
std::string oscError;
|
||||||
if (!mRuntimeServices->ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty())
|
if (!mRuntimeServices->ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty())
|
||||||
@@ -384,190 +318,52 @@ void OpenGLComposite::renderEffect()
|
|||||||
mRuntimeServices->ConsumeCompletedOscCommits(completedOscCommits);
|
mRuntimeServices->ConsumeCompletedOscCommits(completedOscCommits);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits)
|
std::vector<RenderEngine::OscOverlayUpdate> overlayUpdates;
|
||||||
{
|
overlayUpdates.reserve(appliedOscUpdates.size());
|
||||||
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<std::string> pendingOscRouteKeys;
|
|
||||||
const auto oscNow = std::chrono::steady_clock::now();
|
|
||||||
for (const RuntimeServices::AppliedOscUpdate& update : appliedOscUpdates)
|
for (const RuntimeServices::AppliedOscUpdate& update : appliedOscUpdates)
|
||||||
{
|
{
|
||||||
const std::string routeKey = update.routeKey;
|
overlayUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue });
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto applyOscOverlays = [&](std::vector<RuntimeRenderState>& states, bool allowCommit)
|
std::vector<RenderEngine::OscOverlayCommitCompletion> overlayCommitCompletions;
|
||||||
|
overlayCommitCompletions.reserve(completedOscCommits.size());
|
||||||
|
for (const RuntimeServices::CompletedOscCommit& completedCommit : completedOscCommits)
|
||||||
{
|
{
|
||||||
if (states.empty() || mOscOverlayStates.empty())
|
overlayCommitCompletions.push_back({ completedCommit.routeKey, completedCommit.generation });
|
||||||
return;
|
}
|
||||||
|
|
||||||
const double smoothing = ClampOscAlpha(mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0);
|
if (mRenderEngine)
|
||||||
std::vector<std::string> overlayKeysToRemove;
|
mRenderEngine->UpdateOscOverlayState(overlayUpdates, overlayCommitCompletions);
|
||||||
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<std::chrono::duration<double>>(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);
|
|
||||||
};
|
|
||||||
|
|
||||||
const bool hasInputSource = mVideoBackend->HasInputSource();
|
const bool hasInputSource = mVideoBackend->HasInputSource();
|
||||||
std::vector<RuntimeRenderState> layerStates;
|
std::vector<RuntimeRenderState> layerStates;
|
||||||
|
std::vector<RenderEngine::OscOverlayCommitRequest> overlayCommitRequests;
|
||||||
|
const double smoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0;
|
||||||
mRenderEngine->ResolveRenderLayerStates(
|
mRenderEngine->ResolveRenderLayerStates(
|
||||||
mUseCommittedLayerStates.load(),
|
mUseCommittedLayerStates.load(),
|
||||||
mVideoBackend->InputFrameWidth(),
|
mVideoBackend->InputFrameWidth(),
|
||||||
mVideoBackend->InputFrameHeight(),
|
mVideoBackend->InputFrameHeight(),
|
||||||
applyOscOverlays,
|
smoothing,
|
||||||
|
&overlayCommitRequests,
|
||||||
layerStates);
|
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;
|
const unsigned historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0;
|
||||||
mRenderEngine->RenderLayerStack(
|
mRenderEngine->RenderLayerStack(
|
||||||
hasInputSource,
|
hasInputSource,
|
||||||
@@ -706,7 +502,8 @@ bool OpenGLComposite::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResu
|
|||||||
|
|
||||||
if (result.clearTransientOscState)
|
if (result.clearTransientOscState)
|
||||||
{
|
{
|
||||||
mOscOverlayStates.clear();
|
if (mRenderEngine)
|
||||||
|
mRenderEngine->ClearOscOverlayState();
|
||||||
if (mRuntimeServices)
|
if (mRuntimeServices)
|
||||||
mRuntimeServices->ClearOscState();
|
mRuntimeServices->ClearOscState();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,9 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <deque>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
class RenderEngine;
|
class RenderEngine;
|
||||||
class RuntimeServices;
|
class RuntimeServices;
|
||||||
@@ -71,19 +68,6 @@ private:
|
|||||||
void resizeWindow(int width, int height);
|
void resizeWindow(int width, int height);
|
||||||
bool CheckOpenGLExtensions();
|
bool CheckOpenGLExtensions();
|
||||||
void PublishVideoIOStatus(const std::string& statusMessage);
|
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;
|
HWND hGLWnd;
|
||||||
HDC hGLDC;
|
HDC hGLDC;
|
||||||
@@ -98,7 +82,6 @@ private:
|
|||||||
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
|
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
|
||||||
std::unique_ptr<RuntimeServices> mRuntimeServices;
|
std::unique_ptr<RuntimeServices> mRuntimeServices;
|
||||||
std::unique_ptr<VideoBackend> mVideoBackend;
|
std::unique_ptr<VideoBackend> mVideoBackend;
|
||||||
std::map<std::string, OscOverlayState> mOscOverlayStates;
|
|
||||||
std::atomic<bool> mUseCommittedLayerStates;
|
std::atomic<bool> mUseCommittedLayerStates;
|
||||||
std::atomic<bool> mScreenshotRequested;
|
std::atomic<bool> mScreenshotRequested;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,84 @@
|
|||||||
#include "RenderEngine.h"
|
#include "RenderEngine.h"
|
||||||
|
|
||||||
|
#include "RuntimeParameterUtils.h"
|
||||||
|
|
||||||
#include <gl/gl.h>
|
#include <gl/gl.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
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<char>(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(
|
RenderEngine::RenderEngine(
|
||||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||||
HealthTelemetry& healthTelemetry,
|
HealthTelemetry& healthTelemetry,
|
||||||
@@ -106,6 +180,55 @@ void RenderEngine::ResetShaderFeedbackState()
|
|||||||
mShaderPrograms.ResetShaderFeedbackState();
|
mShaderPrograms.ResetShaderFeedbackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderEngine::ClearOscOverlayState()
|
||||||
|
{
|
||||||
|
mOscOverlayStates.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderEngine::UpdateOscOverlayState(
|
||||||
|
const std::vector<OscOverlayUpdate>& updates,
|
||||||
|
const std::vector<OscOverlayCommitCompletion>& 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)
|
void RenderEngine::ResizeView(int width, int height)
|
||||||
{
|
{
|
||||||
mRenderer.ResizeView(width, height);
|
mRenderer.ResizeView(width, height);
|
||||||
@@ -178,15 +301,15 @@ bool RenderEngine::ResolveRenderLayerStates(
|
|||||||
bool useCommittedLayerStates,
|
bool useCommittedLayerStates,
|
||||||
unsigned renderWidth,
|
unsigned renderWidth,
|
||||||
unsigned renderHeight,
|
unsigned renderHeight,
|
||||||
OverlayApplier overlayApplier,
|
double oscSmoothing,
|
||||||
|
std::vector<OscOverlayCommitRequest>* commitRequests,
|
||||||
std::vector<RuntimeRenderState>& layerStates)
|
std::vector<RuntimeRenderState>& layerStates)
|
||||||
{
|
{
|
||||||
layerStates.clear();
|
layerStates.clear();
|
||||||
if (useCommittedLayerStates)
|
if (useCommittedLayerStates)
|
||||||
{
|
{
|
||||||
layerStates = mShaderPrograms.CommittedLayerStates();
|
layerStates = mShaderPrograms.CommittedLayerStates();
|
||||||
if (overlayApplier)
|
ApplyOscOverlays(layerStates, false, oscSmoothing, commitRequests);
|
||||||
overlayApplier(layerStates, false);
|
|
||||||
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates);
|
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -207,14 +330,12 @@ bool RenderEngine::ResolveRenderLayerStates(
|
|||||||
renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion;
|
renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion;
|
||||||
renderSnapshot.states = mCachedLayerRenderStates;
|
renderSnapshot.states = mCachedLayerRenderStates;
|
||||||
|
|
||||||
if (overlayApplier)
|
ApplyOscOverlays(renderSnapshot.states, true, oscSmoothing, commitRequests);
|
||||||
overlayApplier(renderSnapshot.states, true);
|
|
||||||
if (mCachedParameterStateVersion != versions.parameterStateVersion &&
|
if (mCachedParameterStateVersion != versions.parameterStateVersion &&
|
||||||
mRuntimeSnapshotProvider.TryRefreshSnapshotParameters(renderSnapshot))
|
mRuntimeSnapshotProvider.TryRefreshSnapshotParameters(renderSnapshot))
|
||||||
{
|
{
|
||||||
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
||||||
if (overlayApplier)
|
ApplyOscOverlays(renderSnapshot.states, true, oscSmoothing, commitRequests);
|
||||||
overlayApplier(renderSnapshot.states, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mCachedLayerRenderStates = renderSnapshot.states;
|
mCachedLayerRenderStates = renderSnapshot.states;
|
||||||
@@ -231,19 +352,148 @@ bool RenderEngine::ResolveRenderLayerStates(
|
|||||||
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
|
||||||
mCachedRenderStateWidth = renderSnapshot.outputWidth;
|
mCachedRenderStateWidth = renderSnapshot.outputWidth;
|
||||||
mCachedRenderStateHeight = renderSnapshot.outputHeight;
|
mCachedRenderStateHeight = renderSnapshot.outputHeight;
|
||||||
if (overlayApplier)
|
ApplyOscOverlays(mCachedLayerRenderStates, true, oscSmoothing, commitRequests);
|
||||||
overlayApplier(mCachedLayerRenderStates, true);
|
|
||||||
layerStates = mCachedLayerRenderStates;
|
layerStates = mCachedLayerRenderStates;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overlayApplier)
|
ApplyOscOverlays(mCachedLayerRenderStates, true, oscSmoothing, commitRequests);
|
||||||
overlayApplier(mCachedLayerRenderStates, true);
|
|
||||||
layerStates = mCachedLayerRenderStates;
|
layerStates = mCachedLayerRenderStates;
|
||||||
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates);
|
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(layerStates);
|
||||||
return !layerStates.empty();
|
return !layerStates.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderEngine::ApplyOscOverlays(
|
||||||
|
std::vector<RuntimeRenderState>& states,
|
||||||
|
bool allowCommit,
|
||||||
|
double smoothing,
|
||||||
|
std::vector<OscOverlayCommitRequest>* commitRequests)
|
||||||
|
{
|
||||||
|
if (states.empty() || mOscOverlayStates.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const double clampedSmoothing = ClampOscAlpha(smoothing);
|
||||||
|
std::vector<std::string> 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<std::chrono::duration<double>>(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(
|
void RenderEngine::RenderLayerStack(
|
||||||
bool hasInputSource,
|
bool hasInputSource,
|
||||||
const std::vector<RuntimeRenderState>& layerStates,
|
const std::vector<RuntimeRenderState>& layerStates,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -21,7 +22,29 @@ public:
|
|||||||
using RenderEffectCallback = std::function<void()>;
|
using RenderEffectCallback = std::function<void()>;
|
||||||
using ScreenshotCallback = std::function<void()>;
|
using ScreenshotCallback = std::function<void()>;
|
||||||
using PreviewPaintCallback = std::function<void()>;
|
using PreviewPaintCallback = std::function<void()>;
|
||||||
using OverlayApplier = std::function<void(std::vector<RuntimeRenderState>& 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(
|
RenderEngine(
|
||||||
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
RuntimeSnapshotProvider& runtimeSnapshotProvider,
|
||||||
@@ -57,6 +80,10 @@ public:
|
|||||||
const std::vector<RuntimeRenderState>& CommittedLayerStates() const;
|
const std::vector<RuntimeRenderState>& CommittedLayerStates() const;
|
||||||
void ResetTemporalHistoryState();
|
void ResetTemporalHistoryState();
|
||||||
void ResetShaderFeedbackState();
|
void ResetShaderFeedbackState();
|
||||||
|
void ClearOscOverlayState();
|
||||||
|
void UpdateOscOverlayState(
|
||||||
|
const std::vector<OscOverlayUpdate>& updates,
|
||||||
|
const std::vector<OscOverlayCommitCompletion>& completedCommits);
|
||||||
void ResizeView(int width, int height);
|
void ResizeView(int width, int height);
|
||||||
bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight);
|
bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight);
|
||||||
bool TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
|
bool TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
|
||||||
@@ -65,7 +92,8 @@ public:
|
|||||||
bool useCommittedLayerStates,
|
bool useCommittedLayerStates,
|
||||||
unsigned renderWidth,
|
unsigned renderWidth,
|
||||||
unsigned renderHeight,
|
unsigned renderHeight,
|
||||||
OverlayApplier overlayApplier,
|
double oscSmoothing,
|
||||||
|
std::vector<OscOverlayCommitRequest>* commitRequests,
|
||||||
std::vector<RuntimeRenderState>& layerStates);
|
std::vector<RuntimeRenderState>& layerStates);
|
||||||
void RenderLayerStack(
|
void RenderLayerStack(
|
||||||
bool hasInputSource,
|
bool hasInputSource,
|
||||||
@@ -87,10 +115,32 @@ private:
|
|||||||
CRITICAL_SECTION& mMutex;
|
CRITICAL_SECTION& mMutex;
|
||||||
HDC mHdc;
|
HDC mHdc;
|
||||||
HGLRC mHglrc;
|
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<RuntimeRenderState>& states,
|
||||||
|
bool allowCommit,
|
||||||
|
double smoothing,
|
||||||
|
std::vector<OscOverlayCommitRequest>* commitRequests);
|
||||||
|
|
||||||
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
|
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
|
||||||
uint64_t mCachedRenderStateVersion = 0;
|
uint64_t mCachedRenderStateVersion = 0;
|
||||||
uint64_t mCachedParameterStateVersion = 0;
|
uint64_t mCachedParameterStateVersion = 0;
|
||||||
unsigned mCachedRenderStateWidth = 0;
|
unsigned mCachedRenderStateWidth = 0;
|
||||||
unsigned mCachedRenderStateHeight = 0;
|
unsigned mCachedRenderStateHeight = 0;
|
||||||
std::chrono::steady_clock::time_point mLastPreviewPresentTime;
|
std::chrono::steady_clock::time_point mLastPreviewPresentTime;
|
||||||
|
std::map<std::string, OscOverlayState> mOscOverlayStates;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -121,11 +121,11 @@ Recommended direction:
|
|||||||
The current design works better now, but it still relies on hand-managed reconciliation between:
|
The current design works better now, but it still relies on hand-managed reconciliation between:
|
||||||
|
|
||||||
- persisted parameter state in `RuntimeHost`
|
- persisted parameter state in `RuntimeHost`
|
||||||
- transient OSC overlay state in `OpenGLComposite`
|
- transient OSC overlay state in `RenderEngine`
|
||||||
|
|
||||||
Relevant code:
|
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:
|
Recommended direction:
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ These are still compatibility seams, not a completed subsystem extraction. Most
|
|||||||
- service ingress and polling coordination now route through `ControlServices`
|
- service ingress and polling coordination now route through `ControlServices`
|
||||||
- timing and status writes now route through `HealthTelemetry`
|
- 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`
|
- 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 `RenderEngine` seam for renderer, pipeline, render-pass, and shader-program responsibilities
|
||||||
- `OpenGLComposite` now owns a `VideoBackend` seam for device/session ownership and callback wiring
|
- `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
|
- `OpenGLVideoIOBridge` now acts as an explicit compatibility adapter between `VideoBackend` and `RenderEngine`, instead of `OpenGLComposite` directly owning both sides
|
||||||
|
|||||||
Reference in New Issue
Block a user