temporal effects
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include "RuntimeHost.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
@@ -114,6 +115,21 @@ std::string SlangTypeForParameter(ShaderParameterType type)
|
||||
return "uniform float";
|
||||
}
|
||||
|
||||
bool ParseTemporalHistorySource(const std::string& sourceName, TemporalHistorySource& source)
|
||||
{
|
||||
if (sourceName == "source")
|
||||
{
|
||||
source = TemporalHistorySource::Source;
|
||||
return true;
|
||||
}
|
||||
if (sourceName == "preLayerInput")
|
||||
{
|
||||
source = TemporalHistorySource::PreLayerInput;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
|
||||
{
|
||||
if (typeName == "float")
|
||||
@@ -576,6 +592,19 @@ void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned heigh
|
||||
mSignalModeName = modeName;
|
||||
}
|
||||
|
||||
void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mDeckLinkOutputStatus.modelName = modelName;
|
||||
mDeckLinkOutputStatus.supportsInternalKeying = supportsInternalKeying;
|
||||
mDeckLinkOutputStatus.supportsExternalKeying = supportsExternalKeying;
|
||||
mDeckLinkOutputStatus.keyerInterfaceAvailable = keyerInterfaceAvailable;
|
||||
mDeckLinkOutputStatus.externalKeyingRequested = externalKeyingRequested;
|
||||
mDeckLinkOutputStatus.externalKeyingActive = externalKeyingActive;
|
||||
mDeckLinkOutputStatus.statusMessage = statusMessage;
|
||||
}
|
||||
|
||||
void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
@@ -670,6 +699,10 @@ std::vector<RuntimeRenderState> RuntimeHost::GetLayerRenderStates(unsigned outpu
|
||||
state.outputWidth = outputWidth;
|
||||
state.outputHeight = outputHeight;
|
||||
state.parameterDefinitions = shaderIt->second.parameters;
|
||||
state.isTemporal = shaderIt->second.temporal.enabled;
|
||||
state.temporalHistorySource = shaderIt->second.temporal.historySource;
|
||||
state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength;
|
||||
state.effectiveTemporalHistoryLength = shaderIt->second.temporal.effectiveHistoryLength;
|
||||
|
||||
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
|
||||
{
|
||||
@@ -716,6 +749,13 @@ bool RuntimeHost::LoadConfig(std::string& error)
|
||||
mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mConfig.serverPort));
|
||||
if (const JsonValue* autoReloadValue = configJson.find("autoReload"))
|
||||
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
|
||||
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
|
||||
{
|
||||
const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast<double>(mConfig.maxTemporalHistoryFrames));
|
||||
mConfig.maxTemporalHistoryFrames = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
|
||||
}
|
||||
if (const JsonValue* enableExternalKeyingValue = configJson.find("enableExternalKeying"))
|
||||
mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying);
|
||||
|
||||
mAutoReloadEnabled = mConfig.autoReload;
|
||||
return true;
|
||||
@@ -941,6 +981,51 @@ bool RuntimeHost::ParseShaderManifest(const std::filesystem::path& manifestPath,
|
||||
shaderPackage.shaderWriteTime = std::filesystem::last_write_time(shaderPackage.shaderPath);
|
||||
shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);
|
||||
|
||||
if (const JsonValue* temporalValue = manifestJson.find("temporal"))
|
||||
{
|
||||
if (!temporalValue->isObject())
|
||||
{
|
||||
error = "Shader manifest 'temporal' field must be an object in: " + manifestPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
const JsonValue* enabledValue = temporalValue->find("enabled");
|
||||
if (enabledValue && enabledValue->asBoolean(false))
|
||||
{
|
||||
const JsonValue* historySourceValue = temporalValue->find("historySource");
|
||||
const JsonValue* historyLengthValue = temporalValue->find("historyLength");
|
||||
if (!historySourceValue || Trim(historySourceValue->asString()).empty())
|
||||
{
|
||||
error = "Temporal shader is missing required 'historySource' in: " + manifestPath.string();
|
||||
return false;
|
||||
}
|
||||
if (!historyLengthValue || !historyLengthValue->isNumber())
|
||||
{
|
||||
error = "Temporal shader is missing required numeric 'historyLength' in: " + manifestPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
||||
if (!ParseTemporalHistorySource(historySourceValue->asString(), historySource))
|
||||
{
|
||||
error = "Unsupported temporal historySource '" + historySourceValue->asString() + "' in: " + manifestPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
const double requestedHistoryLength = historyLengthValue->asNumber();
|
||||
if (!IsFiniteNumber(requestedHistoryLength) || requestedHistoryLength <= 0.0 || std::floor(requestedHistoryLength) != requestedHistoryLength)
|
||||
{
|
||||
error = "Temporal shader 'historyLength' must be a positive integer in: " + manifestPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
shaderPackage.temporal.enabled = true;
|
||||
shaderPackage.temporal.historySource = historySource;
|
||||
shaderPackage.temporal.requestedHistoryLength = static_cast<unsigned>(requestedHistoryLength);
|
||||
shaderPackage.temporal.effectiveHistoryLength = std::min(shaderPackage.temporal.requestedHistoryLength, mConfig.maxTemporalHistoryFrames);
|
||||
}
|
||||
}
|
||||
|
||||
const JsonValue* parametersValue = manifestJson.find("parameters");
|
||||
if (parametersValue && parametersValue->isArray())
|
||||
{
|
||||
@@ -1181,6 +1266,8 @@ std::string RuntimeHost::BuildWrapperSlangSource(const ShaderPackage& shaderPack
|
||||
source << "\tfloat frameCount;\n";
|
||||
source << "\tfloat mixAmount;\n";
|
||||
source << "\tfloat bypass;\n";
|
||||
source << "\tint sourceHistoryLength;\n";
|
||||
source << "\tint temporalHistoryLength;\n";
|
||||
source << "};\n\n";
|
||||
source << "cbuffer GlobalParams\n";
|
||||
source << "{\n";
|
||||
@@ -1190,14 +1277,53 @@ std::string RuntimeHost::BuildWrapperSlangSource(const ShaderPackage& shaderPack
|
||||
source << "\tfloat gFrameCount;\n";
|
||||
source << "\tfloat gMixAmount;\n";
|
||||
source << "\tfloat gBypass;\n";
|
||||
source << "\tint gSourceHistoryLength;\n";
|
||||
source << "\tint gTemporalHistoryLength;\n";
|
||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
||||
source << "\t" << SlangTypeForParameter(definition.type).substr(strlen("uniform ")) << " " << definition.id << ";\n";
|
||||
source << "};\n\n";
|
||||
source << "Sampler2D<float4> gVideoInput;\n\n";
|
||||
source << "Sampler2D<float4> gVideoInput;\n";
|
||||
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
||||
source << "Sampler2D<float4> gSourceHistory" << index << ";\n";
|
||||
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
||||
source << "Sampler2D<float4> gTemporalHistory" << index << ";\n";
|
||||
source << "\n";
|
||||
source << "float4 sampleVideo(float2 tc)\n";
|
||||
source << "{\n";
|
||||
source << "\treturn gVideoInput.Sample(tc);\n";
|
||||
source << "}\n\n";
|
||||
source << "float4 sampleSourceHistory(int framesAgo, float2 tc)\n";
|
||||
source << "{\n";
|
||||
source << "\tif (gSourceHistoryLength <= 0)\n";
|
||||
source << "\t\treturn sampleVideo(tc);\n";
|
||||
source << "\tint clampedIndex = framesAgo;\n";
|
||||
source << "\tif (clampedIndex < 0)\n";
|
||||
source << "\t\tclampedIndex = 0;\n";
|
||||
source << "\tif (clampedIndex >= gSourceHistoryLength)\n";
|
||||
source << "\t\tclampedIndex = gSourceHistoryLength - 1;\n";
|
||||
source << "\tswitch (clampedIndex)\n";
|
||||
source << "\t{\n";
|
||||
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
||||
source << "\tcase " << index << ": return gSourceHistory" << index << ".Sample(tc);\n";
|
||||
source << "\tdefault: return sampleVideo(tc);\n";
|
||||
source << "\t}\n";
|
||||
source << "}\n\n";
|
||||
source << "float4 sampleTemporalHistory(int framesAgo, float2 tc)\n";
|
||||
source << "{\n";
|
||||
source << "\tif (gTemporalHistoryLength <= 0)\n";
|
||||
source << "\t\treturn sampleVideo(tc);\n";
|
||||
source << "\tint clampedIndex = framesAgo;\n";
|
||||
source << "\tif (clampedIndex < 0)\n";
|
||||
source << "\t\tclampedIndex = 0;\n";
|
||||
source << "\tif (clampedIndex >= gTemporalHistoryLength)\n";
|
||||
source << "\t\tclampedIndex = gTemporalHistoryLength - 1;\n";
|
||||
source << "\tswitch (clampedIndex)\n";
|
||||
source << "\t{\n";
|
||||
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
||||
source << "\tcase " << index << ": return gTemporalHistory" << index << ".Sample(tc);\n";
|
||||
source << "\tdefault: return sampleVideo(tc);\n";
|
||||
source << "\t}\n";
|
||||
source << "}\n\n";
|
||||
source << "#include \"" << shaderPackage.shaderPath.generic_string() << "\"\n\n";
|
||||
source << "[shader(\"fragment\")]\n";
|
||||
source << "float4 fragmentMain(FragmentInput input) : SV_Target\n";
|
||||
@@ -1211,6 +1337,8 @@ std::string RuntimeHost::BuildWrapperSlangSource(const ShaderPackage& shaderPack
|
||||
source << "\tcontext.frameCount = gFrameCount;\n";
|
||||
source << "\tcontext.mixAmount = gMixAmount;\n";
|
||||
source << "\tcontext.bypass = gBypass;\n";
|
||||
source << "\tcontext.sourceHistoryLength = gSourceHistoryLength;\n";
|
||||
source << "\tcontext.temporalHistoryLength = gTemporalHistoryLength;\n";
|
||||
source << "\tfloat4 effectedColor = " << shaderPackage.entryPoint << "(context);\n";
|
||||
source << "\tfloat mixValue = clamp(gBypass > 0.5 ? 0.0 : gMixAmount, 0.0, 1.0);\n";
|
||||
source << "\treturn lerp(context.sourceColor, effectedColor, mixValue);\n";
|
||||
@@ -1372,6 +1500,8 @@ JsonValue RuntimeHost::BuildStateValue() const
|
||||
JsonValue app = JsonValue::MakeObject();
|
||||
app.set("serverPort", JsonValue(static_cast<double>(mServerPort)));
|
||||
app.set("autoReload", JsonValue(mAutoReloadEnabled));
|
||||
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(mConfig.maxTemporalHistoryFrames)));
|
||||
app.set("enableExternalKeying", JsonValue(mConfig.enableExternalKeying));
|
||||
root.set("app", app);
|
||||
|
||||
JsonValue runtime = JsonValue::MakeObject();
|
||||
@@ -1387,6 +1517,16 @@ JsonValue RuntimeHost::BuildStateValue() const
|
||||
video.set("modeName", JsonValue(mSignalModeName));
|
||||
root.set("video", video);
|
||||
|
||||
JsonValue deckLink = JsonValue::MakeObject();
|
||||
deckLink.set("modelName", JsonValue(mDeckLinkOutputStatus.modelName));
|
||||
deckLink.set("supportsInternalKeying", JsonValue(mDeckLinkOutputStatus.supportsInternalKeying));
|
||||
deckLink.set("supportsExternalKeying", JsonValue(mDeckLinkOutputStatus.supportsExternalKeying));
|
||||
deckLink.set("keyerInterfaceAvailable", JsonValue(mDeckLinkOutputStatus.keyerInterfaceAvailable));
|
||||
deckLink.set("externalKeyingRequested", JsonValue(mDeckLinkOutputStatus.externalKeyingRequested));
|
||||
deckLink.set("externalKeyingActive", JsonValue(mDeckLinkOutputStatus.externalKeyingActive));
|
||||
deckLink.set("statusMessage", JsonValue(mDeckLinkOutputStatus.statusMessage));
|
||||
root.set("decklink", deckLink);
|
||||
|
||||
JsonValue performance = JsonValue::MakeObject();
|
||||
performance.set("frameBudgetMs", JsonValue(mFrameBudgetMilliseconds));
|
||||
performance.set("renderMs", JsonValue(mRenderMilliseconds));
|
||||
@@ -1406,6 +1546,15 @@ JsonValue RuntimeHost::BuildStateValue() const
|
||||
shader.set("name", JsonValue(shaderIt->second.displayName));
|
||||
shader.set("description", JsonValue(shaderIt->second.description));
|
||||
shader.set("category", JsonValue(shaderIt->second.category));
|
||||
if (shaderIt->second.temporal.enabled)
|
||||
{
|
||||
JsonValue temporal = JsonValue::MakeObject();
|
||||
temporal.set("enabled", JsonValue(true));
|
||||
temporal.set("historySource", JsonValue(TemporalHistorySourceToString(shaderIt->second.temporal.historySource)));
|
||||
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderIt->second.temporal.requestedHistoryLength)));
|
||||
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderIt->second.temporal.effectiveHistoryLength)));
|
||||
shader.set("temporal", temporal);
|
||||
}
|
||||
shaderLibrary.pushBack(shader);
|
||||
}
|
||||
root.set("shaders", shaderLibrary);
|
||||
@@ -1434,6 +1583,15 @@ JsonValue RuntimeHost::SerializeLayerStackLocked() const
|
||||
layerValue.set("shaderId", JsonValue(layer.shaderId));
|
||||
layerValue.set("shaderName", JsonValue(shaderIt->second.displayName));
|
||||
layerValue.set("bypass", JsonValue(layer.bypass));
|
||||
if (shaderIt->second.temporal.enabled)
|
||||
{
|
||||
JsonValue temporal = JsonValue::MakeObject();
|
||||
temporal.set("enabled", JsonValue(true));
|
||||
temporal.set("historySource", JsonValue(TemporalHistorySourceToString(shaderIt->second.temporal.historySource)));
|
||||
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderIt->second.temporal.requestedHistoryLength)));
|
||||
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderIt->second.temporal.effectiveHistoryLength)));
|
||||
layerValue.set("temporal", temporal);
|
||||
}
|
||||
|
||||
JsonValue parameters = JsonValue::MakeArray();
|
||||
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
|
||||
@@ -1615,6 +1773,20 @@ JsonValue RuntimeHost::SerializeParameterValue(const ShaderParameterDefinition&
|
||||
return JsonValue();
|
||||
}
|
||||
|
||||
std::string RuntimeHost::TemporalHistorySourceToString(TemporalHistorySource source) const
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
case TemporalHistorySource::Source:
|
||||
return "source";
|
||||
case TemporalHistorySource::PreLayerInput:
|
||||
return "preLayerInput";
|
||||
case TemporalHistorySource::None:
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
RuntimeHost::LayerPersistentState* RuntimeHost::FindLayerById(const std::string& layerId)
|
||||
{
|
||||
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
|
||||
|
||||
Reference in New Issue
Block a user