OSC sync back
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m22s
CI / Windows Release Package (push) Successful in 2m29s

This commit is contained in:
Aiden
2026-05-10 18:58:26 +10:00
parent d7ca42b51b
commit 198639ae3f
6 changed files with 190 additions and 109 deletions

View File

@@ -36,7 +36,7 @@
"preArgs": "", "preArgs": "",
"typeTags": "", "typeTags": "",
"decimals": 2, "decimals": 2,
"target": "127.0.0.1:9000", "target": "192.168.1.46:9000",
"ignoreDefaults": false, "ignoreDefaults": false,
"bypass": false, "bypass": false,
"onCreate": "", "onCreate": "",
@@ -53,8 +53,8 @@
"visible": true, "visible": true,
"interaction": true, "interaction": true,
"comments": "XY control for Fisheye Reproject pan and tilt.", "comments": "XY control for Fisheye Reproject pan and tilt.",
"width": 420, "width": 460,
"height": 420, "height": 250,
"expand": false, "expand": false,
"colorText": "auto", "colorText": "auto",
"colorWidget": "auto", "colorWidget": "auto",
@@ -70,14 +70,14 @@
"css": "", "css": "",
"pips": true, "pips": true,
"snap": false, "snap": false,
"spring": false, "spring": true,
"rangeX": { "rangeX": {
"min": -60, "min": -1,
"max": 60 "max": 1
}, },
"rangeY": { "rangeY": {
"min": 45, "min": 1,
"max": -45 "max": -1
}, },
"logScaleX": false, "logScaleX": false,
"logScaleY": false, "logScaleY": false,
@@ -94,13 +94,13 @@
"address": "/VideoShaderToys/fisheye-reproject/xy", "address": "/VideoShaderToys/fisheye-reproject/xy",
"preArgs": "", "preArgs": "",
"typeTags": "", "typeTags": "",
"decimals": "2f", "decimals": "3f",
"target": "127.0.0.1:9000", "target": "192.168.1.46:9000",
"ignoreDefaults": false, "ignoreDefaults": false,
"bypass": true, "bypass": true,
"onCreate": "", "onCreate": "var state = globalThis.__fisheyePanTiltStick = globalThis.__fisheyePanTiltStick || {};\nstate.target = '192.168.1.46:9000';\nstate.panAddress = '/VideoShaderToys/fisheye-reproject/panDegrees';\nstate.tiltAddress = '/VideoShaderToys/fisheye-reproject/tiltDegrees';\nstate.minPan = -60;\nstate.maxPan = 60;\nstate.minTilt = -45;\nstate.maxTilt = 45;\nstate.pan = 0;\nstate.tilt = 0;\nstate.stickX = 0;\nstate.stickY = 0;\nstate.tickMs = 16;\nstate.stepPan = 0.75;\nstate.stepTilt = 0.75;\nstate.deadzone = 0.14;\nstate.applyCurve = function(input) {\n var amount = Math.abs(input);\n if (amount <= state.deadzone) {\n return 0;\n }\n var normalized = (amount - state.deadzone) / (1 - state.deadzone);\n var softened = normalized * normalized * (3 - (2 * normalized));\n return (input < 0 ? -1 : 1) * softened;\n};\nif (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n}",
"onValue": "var pan = Array.isArray(value) ? Number(value[0]) : 0;\nvar tilt = Array.isArray(value) ? Number(value[1]) : 0;\nsend('127.0.0.1:9000', '/VideoShaderToys/fisheye-reproject/panDegrees', {type: 'f', value: pan});\nsend('127.0.0.1:9000', '/VideoShaderToys/fisheye-reproject/tiltDegrees', {type: 'f', value: tilt});", "onValue": "var state = globalThis.__fisheyePanTiltStick = globalThis.__fisheyePanTiltStick || {};\nvar stickX = Array.isArray(value) ? Number(value[0]) : 0;\nvar stickY = Array.isArray(value) ? Number(value[1]) : 0;\nstate.stickX = isFinite(stickX) ? state.applyCurve(stickX) : 0;\nstate.stickY = isFinite(stickY) ? state.applyCurve(stickY) : 0;",
"onTouch": "", "onTouch": "var state = globalThis.__fisheyePanTiltStick = globalThis.__fisheyePanTiltStick || {};\nif (value) {\n if (!state.timer) {\n state.timer = setInterval(function() {\n if (!state.stickX && !state.stickY) {\n return;\n }\n state.pan = Math.max(state.minPan, Math.min(state.maxPan, state.pan + (state.stickX * state.stepPan)));\n state.tilt = Math.max(state.minTilt, Math.min(state.maxTilt, state.tilt + (state.stickY * state.stepTilt)));\n send(state.target, state.panAddress, {type: 'f', value: state.pan});\n send(state.target, state.tiltAddress, {type: 'f', value: state.tilt});\n }, state.tickMs);\n }\n} else {\n state.stickX = 0;\n state.stickY = 0;\n if (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n }\n}",
"pointSize": 20, "pointSize": 20,
"ephemeral": false, "ephemeral": false,
"label": "", "label": "",
@@ -121,7 +121,7 @@
"interaction": true, "interaction": true,
"comments": "", "comments": "",
"width": 90, "width": 90,
"height": 420, "height": 250,
"expand": false, "expand": false,
"colorText": "auto", "colorText": "auto",
"colorWidget": "auto", "colorWidget": "auto",
@@ -144,90 +144,29 @@
"gradient": [], "gradient": [],
"snap": false, "snap": false,
"touchZone": "all", "touchZone": "all",
"spring": false, "spring": true,
"doubleTap": false, "doubleTap": false,
"range": { "range": {
"min": 100, "min": -1,
"max": 10 "max": 1
}, },
"logScale": false, "logScale": false,
"sensitivity": 1, "sensitivity": 1,
"steps": "", "steps": "",
"origin": "auto", "origin": "auto",
"value": "", "value": 0,
"default": 90, "default": 0,
"linkId": "", "linkId": "",
"address": "/VideoShaderToys/fisheye-reproject/virtualFovDegrees", "address": "/VideoShaderToys/fisheye-reproject/virtualFovDegrees",
"preArgs": "", "preArgs": "",
"typeTags": "", "typeTags": "",
"decimals": 2, "decimals": "3f",
"target": "127.0.0.1:9000", "target": "192.168.1.46:9000",
"ignoreDefaults": false,
"bypass": false,
"onCreate": "",
"onValue": "",
"onTouch": ""
},
{
"type": "xy",
"top": 700,
"left": 190,
"lock": false,
"id": "Pan Pad",
"visible": true,
"interaction": true,
"comments": "",
"width": "auto",
"height": "auto",
"expand": false,
"colorText": "auto",
"colorWidget": "auto",
"colorStroke": "auto",
"colorFill": "auto",
"alphaStroke": "auto",
"alphaFillOff": "auto",
"alphaFillOn": "auto",
"lineWidth": "auto",
"borderRadius": "auto",
"padding": "auto",
"html": "",
"css": "",
"pointSize": 20,
"ephemeral": false,
"pips": true,
"label": "",
"snap": false,
"spring": false,
"rangeX": {
"min": -1,
"max": 1
},
"rangeY": {
"min": -1,
"max": 1
},
"logScaleX": false,
"logScaleY": false,
"stepsX": false,
"stepsY": false,
"clipX": "",
"clipY": "",
"axisLock": "",
"doubleTap": false,
"sensitivity": 1,
"value": "",
"default": "",
"linkId": "",
"address": "/VideoShaderToys/video-transform/pan",
"preArgs": "",
"typeTags": "",
"decimals": 2,
"target": "",
"ignoreDefaults": false, "ignoreDefaults": false,
"bypass": true, "bypass": true,
"onCreate": "", "onCreate": "var state = globalThis.__fisheyeFovStick = globalThis.__fisheyeFovStick || {};\nstate.target = '192.168.1.46:9000';\nstate.address = '/VideoShaderToys/fisheye-reproject/virtualFovDegrees';\nstate.minFov = 10;\nstate.maxFov = 100;\nstate.fov = 90;\nstate.stick = 0;\nstate.tickMs = 16;\nstate.stepFov = 0.6;\nstate.deadzone = 0.14;\nstate.applyCurve = function(input) {\n var amount = Math.abs(input);\n if (amount <= state.deadzone) {\n return 0;\n }\n var normalized = (amount - state.deadzone) / (1 - state.deadzone);\n var softened = normalized * normalized * (3 - (2 * normalized));\n return (input < 0 ? -1 : 1) * softened;\n};\nif (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n}",
"onValue": "var x = Array.isArray(value) ? Number(value[0]) : 0;\nvar y = Array.isArray(value) ? Number(value[1]) : 0;\nsend('127.0.0.1:9000', '/VideoShaderToys/video-transform/pan', {type: 'f', value: x}, {type: 'f', value: y});", "onValue": "var state = globalThis.__fisheyeFovStick = globalThis.__fisheyeFovStick || {};\nvar stick = Number(value);\nstate.stick = isFinite(stick) ? state.applyCurve(stick) : 0;",
"onTouch": "" "onTouch": "var state = globalThis.__fisheyeFovStick = globalThis.__fisheyeFovStick || {};\nif (value) {\n if (!state.timer) {\n state.timer = setInterval(function() {\n if (!state.stick) {\n return;\n }\n state.fov = Math.max(state.minFov, Math.min(state.maxFov, state.fov - (state.stick * state.stepFov)));\n send(state.target, state.address, {type: 'f', value: state.fov});\n }, state.tickMs);\n }\n} else {\n state.stick = 0;\n if (state.timer) {\n clearInterval(state.timer);\n state.timer = null;\n }\n}"
} }
], ],
"tabs": [] "tabs": []

View File

@@ -102,6 +102,7 @@ bool RuntimeServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appl
} }
AppliedOscUpdate appliedUpdate; AppliedOscUpdate appliedUpdate;
appliedUpdate.routeKey = entry.first;
appliedUpdate.layerKey = entry.second.layerKey; appliedUpdate.layerKey = entry.second.layerKey;
appliedUpdate.parameterKey = entry.second.parameterKey; appliedUpdate.parameterKey = entry.second.parameterKey;
appliedUpdate.targetValue = targetValue; appliedUpdate.targetValue = targetValue;
@@ -112,6 +113,35 @@ bool RuntimeServices::ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appl
return true; return true;
} }
bool RuntimeServices::QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error)
{
(void)error;
PendingOscCommit commit;
commit.routeKey = routeKey;
commit.layerKey = layerKey;
commit.parameterKey = parameterKey;
commit.value = value;
commit.generation = generation;
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
mPendingOscCommits[routeKey] = std::move(commit);
}
return true;
}
void RuntimeServices::ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits)
{
completedCommits.clear();
std::lock_guard<std::mutex> lock(mCompletedOscCommitMutex);
if (mCompletedOscCommits.empty())
return;
completedCommits.swap(mCompletedOscCommits);
}
RuntimePollEvents RuntimeServices::ConsumePollEvents() RuntimePollEvents RuntimeServices::ConsumePollEvents()
{ {
RuntimePollEvents events; RuntimePollEvents events;
@@ -149,6 +179,33 @@ void RuntimeServices::PollLoop(RuntimeHost& runtimeHost)
{ {
while (mPollRunning) while (mPollRunning)
{ {
std::map<std::string, PendingOscCommit> pendingCommits;
{
std::lock_guard<std::mutex> lock(mPendingOscCommitMutex);
pendingCommits.swap(mPendingOscCommits);
}
for (const auto& entry : pendingCommits)
{
std::string commitError;
if (runtimeHost.UpdateLayerParameterByControlKey(
entry.second.layerKey,
entry.second.parameterKey,
entry.second.value,
false,
commitError))
{
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));
}
else if (!commitError.empty())
{
OutputDebugStringA(("OSC commit failed: " + commitError + "\n").c_str());
}
}
bool registryChanged = false; bool registryChanged = false;
bool reloadRequested = false; bool reloadRequested = false;
std::string runtimeError; std::string runtimeError;

View File

@@ -28,11 +28,18 @@ class RuntimeServices
public: public:
struct AppliedOscUpdate struct AppliedOscUpdate
{ {
std::string routeKey;
std::string layerKey; std::string layerKey;
std::string parameterKey; std::string parameterKey;
JsonValue targetValue; JsonValue targetValue;
}; };
struct CompletedOscCommit
{
std::string routeKey;
uint64_t generation = 0;
};
RuntimeServices(); RuntimeServices();
~RuntimeServices(); ~RuntimeServices();
@@ -43,6 +50,8 @@ public:
void RequestBroadcastState(); void RequestBroadcastState();
bool QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error); bool QueueOscUpdate(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error); bool ApplyPendingOscUpdates(std::vector<AppliedOscUpdate>& appliedUpdates, std::string& error);
bool QueueOscCommit(const std::string& routeKey, const std::string& layerKey, const std::string& parameterKey, const JsonValue& value, uint64_t generation, std::string& error);
void ConsumeCompletedOscCommits(std::vector<CompletedOscCommit>& completedCommits);
RuntimePollEvents ConsumePollEvents(); RuntimePollEvents ConsumePollEvents();
private: private:
@@ -53,6 +62,15 @@ private:
std::string valueJson; std::string valueJson;
}; };
struct PendingOscCommit
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue value;
uint64_t generation = 0;
};
void StartPolling(RuntimeHost& runtimeHost); void StartPolling(RuntimeHost& runtimeHost);
void StopPolling(); void StopPolling();
void PollLoop(RuntimeHost& runtimeHost); void PollLoop(RuntimeHost& runtimeHost);
@@ -68,4 +86,8 @@ private:
std::string mPollError; std::string mPollError;
std::mutex mPendingOscMutex; std::mutex mPendingOscMutex;
std::map<std::string, PendingOscUpdate> mPendingOscUpdates; std::map<std::string, PendingOscUpdate> mPendingOscUpdates;
std::mutex mPendingOscCommitMutex;
std::map<std::string, PendingOscCommit> mPendingOscCommits;
std::mutex mCompletedOscCommitMutex;
std::vector<CompletedOscCommit> mCompletedOscCommits;
}; };

View File

@@ -15,6 +15,7 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <chrono> #include <chrono>
#include <cmath>
#include <ctime> #include <ctime>
#include <filesystem> #include <filesystem>
#include <iomanip> #include <iomanip>
@@ -27,6 +28,8 @@
namespace namespace
{ {
constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150); 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 SimplifyOscControlKey(const std::string& text)
{ {
@@ -49,6 +52,22 @@ double ClampOscAlpha(double value)
return (std::max)(0.0, (std::min)(1.0, 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) JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
{ {
switch (definition.type) switch (definition.type)
@@ -378,18 +397,35 @@ void OpenGLComposite::renderEffect()
{ {
ProcessRuntimePollResults(); ProcessRuntimePollResults();
std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates; std::vector<RuntimeServices::AppliedOscUpdate> appliedOscUpdates;
std::vector<RuntimeServices::CompletedOscCommit> completedOscCommits;
if (mRuntimeHost && mRuntimeServices) if (mRuntimeHost && mRuntimeServices)
{ {
std::string oscError; std::string oscError;
if (!mRuntimeServices->ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty()) if (!mRuntimeServices->ApplyPendingOscUpdates(appliedOscUpdates, oscError) && !oscError.empty())
OutputDebugStringA(("OSC apply failed: " + oscError + "\n").c_str()); OutputDebugStringA(("OSC apply failed: " + oscError + "\n").c_str());
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; std::set<std::string> pendingOscRouteKeys;
const auto oscNow = std::chrono::steady_clock::now(); 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.layerKey + "\n" + update.parameterKey; const std::string routeKey = update.routeKey;
auto overlayIt = mOscOverlayStates.find(routeKey); auto overlayIt = mOscOverlayStates.find(routeKey);
if (overlayIt == mOscOverlayStates.end()) if (overlayIt == mOscOverlayStates.end())
{ {
@@ -398,12 +434,16 @@ void OpenGLComposite::renderEffect()
overlay.parameterKey = update.parameterKey; overlay.parameterKey = update.parameterKey;
overlay.targetValue = update.targetValue; overlay.targetValue = update.targetValue;
overlay.lastUpdatedTime = oscNow; overlay.lastUpdatedTime = oscNow;
overlay.lastAppliedTime = oscNow;
overlay.generation = 1;
mOscOverlayStates[routeKey] = std::move(overlay); mOscOverlayStates[routeKey] = std::move(overlay);
} }
else else
{ {
overlayIt->second.targetValue = update.targetValue; overlayIt->second.targetValue = update.targetValue;
overlayIt->second.lastUpdatedTime = oscNow; overlayIt->second.lastUpdatedTime = oscNow;
overlayIt->second.generation += 1;
overlayIt->second.commitQueued = false;
} }
pendingOscRouteKeys.insert(routeKey); pendingOscRouteKeys.insert(routeKey);
} }
@@ -465,11 +505,17 @@ void OpenGLComposite::renderEffect()
overlay.currentValue = targetValue; overlay.currentValue = targetValue;
overlay.hasCurrentValue = true; overlay.hasCurrentValue = true;
stateIt->parameterValues[definitionIt->id] = overlay.currentValue; stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
if (allowCommit && oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay) if (allowCommit &&
!overlay.commitQueued &&
oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay &&
mRuntimeServices)
{ {
std::string commitError; std::string commitError;
if (mRuntimeHost->UpdateLayerParameterByControlKey(overlay.layerKey, overlay.parameterKey, overlay.targetValue, false, commitError)) if (mRuntimeServices->QueueOscCommit(item.first, overlay.layerKey, overlay.parameterKey, overlay.targetValue, overlay.generation, commitError))
overlayKeysToRemove.push_back(item.first); {
overlay.pendingCommitGeneration = overlay.generation;
overlay.commitQueued = true;
}
} }
continue; continue;
} }
@@ -486,6 +532,15 @@ void OpenGLComposite::renderEffect()
if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size()) if (overlay.currentValue.numberValues.size() != targetValue.numberValues.size())
overlay.currentValue.numberValues = targetValue.numberValues; 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; ShaderParameterValue nextValue = targetValue;
bool converged = true; bool converged = true;
for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index) for (std::size_t index = 0; index < targetValue.numberValues.size(); ++index)
@@ -493,7 +548,7 @@ void OpenGLComposite::renderEffect()
const double currentNumber = overlay.currentValue.numberValues[index]; const double currentNumber = overlay.currentValue.numberValues[index];
const double targetNumber = targetValue.numberValues[index]; const double targetNumber = targetValue.numberValues[index];
const double delta = targetNumber - currentNumber; const double delta = targetNumber - currentNumber;
double nextNumber = currentNumber + delta * smoothing; double nextNumber = currentNumber + delta * smoothingAlpha;
if (std::fabs(delta) <= 0.0005) if (std::fabs(delta) <= 0.0005)
nextNumber = targetNumber; nextNumber = targetNumber;
else else
@@ -507,20 +562,24 @@ void OpenGLComposite::renderEffect()
overlay.currentValue = nextValue; overlay.currentValue = nextValue;
overlay.hasCurrentValue = true; overlay.hasCurrentValue = true;
stateIt->parameterValues[definitionIt->id] = overlay.currentValue; stateIt->parameterValues[definitionIt->id] = overlay.currentValue;
if (allowCommit && converged && oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay) if (allowCommit &&
converged &&
!overlay.commitQueued &&
oscNow - overlay.lastUpdatedTime >= kOscOverlayCommitDelay &&
mRuntimeServices)
{ {
std::string commitError; std::string commitError;
JsonValue committedValue = BuildOscCommitValue(*definitionIt, overlay.currentValue); JsonValue committedValue = BuildOscCommitValue(*definitionIt, overlay.currentValue);
if (mRuntimeHost->UpdateLayerParameterByControlKey(overlay.layerKey, overlay.parameterKey, committedValue, false, commitError)) if (mRuntimeServices->QueueOscCommit(item.first, overlay.layerKey, overlay.parameterKey, committedValue, overlay.generation, commitError))
overlayKeysToRemove.push_back(item.first); {
overlay.pendingCommitGeneration = overlay.generation;
overlay.commitQueued = true;
}
} }
} }
if (allowCommit) for (const std::string& overlayKey : overlayKeysToRemove)
{ mOscOverlayStates.erase(overlayKey);
for (const std::string& overlayKey : overlayKeysToRemove)
mOscOverlayStates.erase(overlayKey);
}
}; };
const bool hasInputSource = mVideoIO->HasInputSource(); const bool hasInputSource = mVideoIO->HasInputSource();

View File

@@ -81,6 +81,10 @@ private:
ShaderParameterValue currentValue; ShaderParameterValue currentValue;
bool hasCurrentValue = false; bool hasCurrentValue = false;
std::chrono::steady_clock::time_point lastUpdatedTime; 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;

View File

@@ -65,7 +65,10 @@ void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame)
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height); const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
EnterCriticalSection(&mMutex); // Never let input upload stall the playout/render callback. If the GL bridge
// is busy producing an output frame, skip this upload and use the next input.
if (!TryEnterCriticalSection(&mMutex))
return;
wglMakeCurrent(mHdc, mHglrc); // make OpenGL context current in this thread wglMakeCurrent(mHdc, mHglrc); // make OpenGL context current in this thread
@@ -93,32 +96,29 @@ void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& complet
{ {
RecordFramePacing(completion.result); RecordFramePacing(completion.result);
EnterCriticalSection(&mMutex);
VideoIOOutputFrame outputFrame; VideoIOOutputFrame outputFrame;
if (!mVideoIO.BeginOutputFrame(outputFrame)) if (!mVideoIO.BeginOutputFrame(outputFrame))
{
LeaveCriticalSection(&mMutex);
return; return;
}
const VideoIOState& state = mVideoIO.State(); const VideoIOState& state = mVideoIO.State();
RenderPipelineFrameContext frameContext; RenderPipelineFrameContext frameContext;
frameContext.videoState = state; frameContext.videoState = state;
frameContext.completion = completion; frameContext.completion = completion;
EnterCriticalSection(&mMutex);
// make GL context current in this thread // make GL context current in this thread
wglMakeCurrent(mHdc, mHglrc); wglMakeCurrent(mHdc, mHglrc);
mRenderPipeline.RenderFrame(frameContext, outputFrame); mRenderPipeline.RenderFrame(frameContext, outputFrame);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
mVideoIO.EndOutputFrame(outputFrame); mVideoIO.EndOutputFrame(outputFrame);
mVideoIO.AccountForCompletionResult(completion.result); mVideoIO.AccountForCompletionResult(completion.result);
// Schedule the next frame for playout // Schedule the next frame for playout after the GL bridge is released so
// input uploads are not blocked by non-GL output bookkeeping.
mVideoIO.ScheduleOutputFrame(outputFrame); mVideoIO.ScheduleOutputFrame(outputFrame);
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&mMutex);
} }