OSC sync back
This commit is contained in:
109
OSC/Test.json
109
OSC/Test.json
@@ -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": []
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)
|
for (const std::string& overlayKey : overlayKeysToRemove)
|
||||||
mOscOverlayStates.erase(overlayKey);
|
mOscOverlayStates.erase(overlayKey);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const bool hasInputSource = mVideoIO->HasInputSource();
|
const bool hasInputSource = mVideoIO->HasInputSource();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user