Osc orchestration
This commit is contained in:
@@ -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<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) :
|
||||
@@ -376,7 +310,7 @@ void OpenGLComposite::renderEffect()
|
||||
ProcessRuntimePollResults();
|
||||
std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates;
|
||||
std::vector<RuntimeServices::CompletedOscCommit> 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<std::string> pendingOscRouteKeys;
|
||||
const auto oscNow = std::chrono::steady_clock::now();
|
||||
std::vector<RenderEngine::OscOverlayUpdate> 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<RuntimeRenderState>& states, bool allowCommit)
|
||||
std::vector<RenderEngine::OscOverlayCommitCompletion> 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<std::string> 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<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);
|
||||
};
|
||||
if (mRenderEngine)
|
||||
mRenderEngine->UpdateOscOverlayState(overlayUpdates, overlayCommitCompletions);
|
||||
|
||||
const bool hasInputSource = mVideoBackend->HasInputSource();
|
||||
std::vector<RuntimeRenderState> layerStates;
|
||||
std::vector<RenderEngine::OscOverlayCommitRequest> 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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user