temporal effects
This commit is contained in:
@@ -82,6 +82,8 @@ PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
||||
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
||||
PFNGLUSEPROGRAMPROC glUseProgram;
|
||||
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
||||
PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
|
||||
PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
|
||||
PFNGLUNIFORM1IPROC glUniform1i;
|
||||
PFNGLUNIFORM1FPROC glUniform1f;
|
||||
PFNGLUNIFORM2FPROC glUniform2f;
|
||||
@@ -149,6 +151,8 @@ bool ResolveGLExtensions()
|
||||
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC) wglGetProcAddress("glGetProgramInfoLog");
|
||||
glUseProgram = (PFNGLUSEPROGRAMPROC) wglGetProcAddress("glUseProgram");
|
||||
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC) wglGetProcAddress("glGetUniformLocation");
|
||||
glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC) wglGetProcAddress("glGetUniformBlockIndex");
|
||||
glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC) wglGetProcAddress("glUniformBlockBinding");
|
||||
glUniform1i = (PFNGLUNIFORM1IPROC) wglGetProcAddress("glUniform1i");
|
||||
glUniform1f = (PFNGLUNIFORM1FPROC) wglGetProcAddress("glUniform1f");
|
||||
glUniform2f = (PFNGLUNIFORM2FPROC) wglGetProcAddress("glUniform2f");
|
||||
@@ -192,6 +196,8 @@ bool ResolveGLExtensions()
|
||||
&& glGetProgramInfoLog
|
||||
&& glUseProgram
|
||||
&& glGetUniformLocation
|
||||
&& glGetUniformBlockIndex
|
||||
&& glUniformBlockBinding
|
||||
&& glUniform1i
|
||||
&& glUniform1f
|
||||
&& glUniform2f
|
||||
|
||||
@@ -112,6 +112,8 @@ typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0);
|
||||
typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
|
||||
typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1);
|
||||
typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
|
||||
typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar* uniformBlockName);
|
||||
typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
|
||||
typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture);
|
||||
typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer);
|
||||
typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
|
||||
@@ -171,6 +173,8 @@ extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
||||
extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
||||
extern PFNGLUSEPROGRAMPROC glUseProgram;
|
||||
extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
||||
extern PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
|
||||
extern PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
|
||||
extern PFNGLUNIFORM1IPROC glUniform1i;
|
||||
extern PFNGLUNIFORM1FPROC glUniform1f;
|
||||
extern PFNGLUNIFORM2FPROC glUniform2f;
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -55,6 +57,7 @@ DEFINE_GUID(IID_PinnedMemoryAllocator,
|
||||
namespace
|
||||
{
|
||||
constexpr GLuint kDecodedVideoTextureUnit = 1;
|
||||
constexpr GLuint kSourceHistoryTextureUnitBase = 2;
|
||||
constexpr GLuint kPackedVideoTextureUnit = 2;
|
||||
constexpr GLuint kGlobalParamsBindingPoint = 0;
|
||||
const char* kDisplayModeName = "1080p59.94";
|
||||
@@ -149,23 +152,34 @@ void AppendStd140Vec4(std::vector<unsigned char>& buffer, float x, float y, floa
|
||||
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
|
||||
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC),
|
||||
mCaptureDelegate(NULL), mPlayoutDelegate(NULL),
|
||||
mDLInput(NULL), mDLOutput(NULL),
|
||||
mDLInput(NULL), mDLOutput(NULL), mDLKeyer(NULL),
|
||||
mPlayoutAllocator(NULL),
|
||||
mFrameWidth(0), mFrameHeight(0),
|
||||
mHasNoInputSource(true),
|
||||
mDeckLinkSupportsInternalKeying(false),
|
||||
mDeckLinkSupportsExternalKeying(false),
|
||||
mDeckLinkKeyerInterfaceAvailable(false),
|
||||
mDeckLinkExternalKeyingActive(false),
|
||||
mFastTransferExtensionAvailable(false),
|
||||
mCaptureTexture(0),
|
||||
mDecodedTexture(0),
|
||||
mLayerTempTexture(0),
|
||||
mFBOTexture(0),
|
||||
mUnpinnedTextureBuffer(0),
|
||||
mDecodeFrameBuf(0),
|
||||
mLayerTempFrameBuf(0),
|
||||
mIdFrameBuf(0),
|
||||
mIdColorBuf(0),
|
||||
mIdDepthBuf(0),
|
||||
mFullscreenVAO(0),
|
||||
mGlobalParamsUBO(0),
|
||||
mDecodeProgram(0),
|
||||
mDecodeVertexShader(0),
|
||||
mDecodeFragmentShader(0),
|
||||
mGlobalParamsUBOSize(0)
|
||||
mGlobalParamsUBOSize(0),
|
||||
mViewWidth(0),
|
||||
mViewHeight(0),
|
||||
mTemporalHistoryNeedsReset(true)
|
||||
{
|
||||
InitializeCriticalSection(&pMutex);
|
||||
mRuntimeHost = std::make_unique<RuntimeHost>();
|
||||
@@ -203,6 +217,13 @@ OpenGLComposite::~OpenGLComposite()
|
||||
|
||||
if (mDLOutput != NULL)
|
||||
{
|
||||
if (mDLKeyer != NULL)
|
||||
{
|
||||
mDLKeyer->Disable();
|
||||
mDLKeyer->Release();
|
||||
mDLKeyer = NULL;
|
||||
}
|
||||
|
||||
mDLOutput->SetScheduledFrameCompletionCallback(NULL);
|
||||
|
||||
mDLOutput->Release();
|
||||
@@ -246,6 +267,7 @@ OpenGLComposite::~OpenGLComposite()
|
||||
if (mUnpinnedTextureBuffer != 0)
|
||||
glDeleteBuffers(1, &mUnpinnedTextureBuffer);
|
||||
|
||||
destroyTemporalHistoryResources();
|
||||
destroyLayerPrograms();
|
||||
destroyDecodeShaderProgram();
|
||||
if (mControlServer)
|
||||
@@ -276,6 +298,9 @@ bool OpenGLComposite::InitDeckLink()
|
||||
while (pDLIterator->Next(&pDL) == S_OK)
|
||||
{
|
||||
int64_t duplexMode;
|
||||
bool supportsInternalKeying = false;
|
||||
bool supportsExternalKeying = false;
|
||||
std::string modelName;
|
||||
|
||||
if (result = pDL->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK)
|
||||
{
|
||||
@@ -286,6 +311,20 @@ bool OpenGLComposite::InitDeckLink()
|
||||
}
|
||||
|
||||
result = deckLinkAttributes->GetInt(BMDDeckLinkDuplex, &duplexMode);
|
||||
BOOL attributeFlag = FALSE;
|
||||
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &attributeFlag) == S_OK)
|
||||
supportsInternalKeying = (attributeFlag != FALSE);
|
||||
attributeFlag = FALSE;
|
||||
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &attributeFlag) == S_OK)
|
||||
supportsExternalKeying = (attributeFlag != FALSE);
|
||||
BSTR modelNameBstr = NULL;
|
||||
if (deckLinkAttributes->GetString(BMDDeckLinkModelName, &modelNameBstr) == S_OK && modelNameBstr != NULL)
|
||||
{
|
||||
_bstr_t modelNameWrapper(modelNameBstr, false);
|
||||
const char* modelNameChars = modelNameWrapper;
|
||||
if (modelNameChars != NULL)
|
||||
modelName = modelNameChars;
|
||||
}
|
||||
deckLinkAttributes->Release();
|
||||
deckLinkAttributes = NULL;
|
||||
|
||||
@@ -306,6 +345,12 @@ bool OpenGLComposite::InitDeckLink()
|
||||
{
|
||||
if (pDL->QueryInterface(IID_IDeckLinkOutput, (void**)&mDLOutput) != S_OK)
|
||||
mDLOutput = NULL;
|
||||
else
|
||||
{
|
||||
mDeckLinkOutputModelName = modelName;
|
||||
mDeckLinkSupportsInternalKeying = supportsInternalKeying;
|
||||
mDeckLinkSupportsExternalKeying = supportsExternalKeying;
|
||||
}
|
||||
}
|
||||
|
||||
pDL->Release();
|
||||
@@ -353,6 +398,21 @@ bool OpenGLComposite::InitDeckLink()
|
||||
if (! InitOpenGLState())
|
||||
goto error;
|
||||
|
||||
if (mRuntimeHost)
|
||||
{
|
||||
mDeckLinkStatusMessage = mDeckLinkOutputModelName.empty()
|
||||
? "DeckLink output device selected."
|
||||
: ("Selected output device: " + mDeckLinkOutputModelName);
|
||||
mRuntimeHost->SetDeckLinkOutputStatus(
|
||||
mDeckLinkOutputModelName,
|
||||
mDeckLinkSupportsInternalKeying,
|
||||
mDeckLinkSupportsExternalKeying,
|
||||
mDeckLinkKeyerInterfaceAvailable,
|
||||
mRuntimeHost->ExternalKeyingEnabled(),
|
||||
mDeckLinkExternalKeyingActive,
|
||||
mDeckLinkStatusMessage);
|
||||
}
|
||||
|
||||
pDLDisplayMode->GetFrameRate(&mFrameDuration, &mFrameTimescale);
|
||||
|
||||
// Resize window to match video frame, but scale large formats down by half for viewing
|
||||
@@ -392,6 +452,46 @@ bool OpenGLComposite::InitDeckLink()
|
||||
if (mDLOutput->EnableVideoOutput(displayMode, bmdVideoOutputFlagDefault) != S_OK)
|
||||
goto error;
|
||||
|
||||
if (mDLOutput->QueryInterface(IID_IDeckLinkKeyer, (void**)&mDLKeyer) == S_OK && mDLKeyer != NULL)
|
||||
mDeckLinkKeyerInterfaceAvailable = true;
|
||||
|
||||
if (mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled())
|
||||
{
|
||||
if (!mDeckLinkSupportsExternalKeying)
|
||||
{
|
||||
mDeckLinkStatusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support.";
|
||||
}
|
||||
else if (!mDeckLinkKeyerInterfaceAvailable)
|
||||
{
|
||||
mDeckLinkStatusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface.";
|
||||
}
|
||||
else if (mDLKeyer->Enable(TRUE) != S_OK || mDLKeyer->SetLevel(255) != S_OK)
|
||||
{
|
||||
mDeckLinkStatusMessage = "External keying was requested, but enabling the DeckLink keyer failed.";
|
||||
}
|
||||
else
|
||||
{
|
||||
mDeckLinkExternalKeyingActive = true;
|
||||
mDeckLinkStatusMessage = "External keying is active on the selected DeckLink output.";
|
||||
}
|
||||
}
|
||||
else if (mDeckLinkSupportsExternalKeying)
|
||||
{
|
||||
mDeckLinkStatusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
|
||||
}
|
||||
|
||||
if (mRuntimeHost)
|
||||
{
|
||||
mRuntimeHost->SetDeckLinkOutputStatus(
|
||||
mDeckLinkOutputModelName,
|
||||
mDeckLinkSupportsInternalKeying,
|
||||
mDeckLinkSupportsExternalKeying,
|
||||
mDeckLinkKeyerInterfaceAvailable,
|
||||
mRuntimeHost->ExternalKeyingEnabled(),
|
||||
mDeckLinkExternalKeyingActive,
|
||||
mDeckLinkStatusMessage);
|
||||
}
|
||||
|
||||
// Create a queue of 10 IDeckLinkMutableVideoFrame objects to use for scheduling output video frames.
|
||||
// The ScheduledFrameCompleted() callback will immediately schedule a new frame using the next video frame from this queue.
|
||||
for (int i = 0; i < 10; i++)
|
||||
@@ -580,6 +680,7 @@ bool OpenGLComposite::InitOpenGLState()
|
||||
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
|
||||
return false;
|
||||
}
|
||||
resetTemporalHistoryState();
|
||||
|
||||
glClearColor( 0.0f, 0.0f, 0.0f, 0.5f ); // Black background
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
@@ -769,8 +870,13 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
|
||||
|
||||
// Draw the effect output to the off-screen framebuffer.
|
||||
const auto renderStartTime = std::chrono::steady_clock::now();
|
||||
if (mFastTransferExtensionAvailable)
|
||||
VideoFrameTransfer::beginTextureInUse(VideoFrameTransfer::GPUtoCPU);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
|
||||
renderEffect();
|
||||
glFlush();
|
||||
if (mFastTransferExtensionAvailable)
|
||||
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::GPUtoCPU);
|
||||
const auto renderEndTime = std::chrono::steady_clock::now();
|
||||
if (mRuntimeHost)
|
||||
{
|
||||
@@ -815,6 +921,7 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mIdFrameBuf);
|
||||
glReadPixels(0, 0, mFrameWidth, mFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame);
|
||||
paintGL();
|
||||
}
|
||||
@@ -976,6 +1083,29 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
|
||||
return false;
|
||||
}
|
||||
|
||||
const GLuint globalParamsIndex = glGetUniformBlockIndex(newProgram, "GlobalParams");
|
||||
if (globalParamsIndex != GL_INVALID_INDEX)
|
||||
glUniformBlockBinding(newProgram, globalParamsIndex, kGlobalParamsBindingPoint);
|
||||
|
||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||
glUseProgram(newProgram);
|
||||
const GLint videoInputLocation = glGetUniformLocation(newProgram, "gVideoInput");
|
||||
if (videoInputLocation >= 0)
|
||||
glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit));
|
||||
for (unsigned index = 0; index < historyCap; ++index)
|
||||
{
|
||||
const std::string sourceSamplerName = "gSourceHistory" + std::to_string(index);
|
||||
const GLint sourceSamplerLocation = glGetUniformLocation(newProgram, sourceSamplerName.c_str());
|
||||
if (sourceSamplerLocation >= 0)
|
||||
glUniform1i(sourceSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + index));
|
||||
|
||||
const std::string temporalSamplerName = "gTemporalHistory" + std::to_string(index);
|
||||
const GLint temporalSamplerLocation = glGetUniformLocation(newProgram, temporalSamplerName.c_str());
|
||||
if (temporalSamplerLocation >= 0)
|
||||
glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index));
|
||||
}
|
||||
glUseProgram(0);
|
||||
|
||||
layerProgram.layerId = state.layerId;
|
||||
layerProgram.shaderId = state.shaderId;
|
||||
layerProgram.program = newProgram;
|
||||
@@ -987,6 +1117,17 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
|
||||
bool OpenGLComposite::compileLayerPrograms(int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mFrameWidth, mFrameHeight) : std::vector<RuntimeRenderState>();
|
||||
std::string temporalError;
|
||||
if (!validateTemporalTextureUnitBudget(temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
if (!ensureTemporalHistoryResources(layerStates, temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
std::vector<LayerProgram> newPrograms;
|
||||
newPrograms.reserve(layerStates.size());
|
||||
|
||||
@@ -1115,6 +1256,211 @@ void OpenGLComposite::destroyDecodeShaderProgram()
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenGLComposite::validateTemporalTextureUnitBudget(std::string& error) const
|
||||
{
|
||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||
GLint maxTextureUnits = 0;
|
||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
|
||||
const unsigned requiredUnits = kSourceHistoryTextureUnitBase + historyCap + historyCap;
|
||||
const unsigned availableUnits = maxTextureUnits > 0 ? static_cast<unsigned>(maxTextureUnits) : 0u;
|
||||
if (requiredUnits > availableUnits)
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "Temporal history cap requires " << requiredUnits
|
||||
<< " fragment texture units, but only " << maxTextureUnits << " are available.";
|
||||
error = message.str();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::createHistoryRing(HistoryRing& ring, unsigned effectiveLength, TemporalHistorySource historySource, std::string& error)
|
||||
{
|
||||
destroyHistoryRing(ring);
|
||||
ring.effectiveLength = effectiveLength;
|
||||
ring.historySource = historySource;
|
||||
if (effectiveLength == 0)
|
||||
return true;
|
||||
|
||||
ring.slots.resize(effectiveLength);
|
||||
for (HistorySlot& slot : ring.slots)
|
||||
{
|
||||
glGenTextures(1, &slot.texture);
|
||||
glBindTexture(GL_TEXTURE_2D, slot.texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mFrameWidth, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
||||
|
||||
glGenFramebuffers(1, &slot.framebuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, slot.framebuffer);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, slot.texture, 0);
|
||||
const GLenum framebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
error = "Failed to initialize a temporal history framebuffer.";
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
destroyHistoryRing(ring);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLComposite::destroyHistoryRing(HistoryRing& ring)
|
||||
{
|
||||
for (HistorySlot& slot : ring.slots)
|
||||
{
|
||||
if (slot.framebuffer != 0)
|
||||
glDeleteFramebuffers(1, &slot.framebuffer);
|
||||
if (slot.texture != 0)
|
||||
glDeleteTextures(1, &slot.texture);
|
||||
slot.framebuffer = 0;
|
||||
slot.texture = 0;
|
||||
}
|
||||
ring.slots.clear();
|
||||
ring.nextWriteIndex = 0;
|
||||
ring.filledCount = 0;
|
||||
ring.effectiveLength = 0;
|
||||
ring.historySource = TemporalHistorySource::None;
|
||||
}
|
||||
|
||||
void OpenGLComposite::destroyTemporalHistoryResources()
|
||||
{
|
||||
destroyHistoryRing(mSourceHistoryRing);
|
||||
for (auto& historyEntry : mPreLayerHistoryByLayerId)
|
||||
destroyHistoryRing(historyEntry.second);
|
||||
mPreLayerHistoryByLayerId.clear();
|
||||
}
|
||||
|
||||
void OpenGLComposite::resetTemporalHistoryState()
|
||||
{
|
||||
mSourceHistoryRing.nextWriteIndex = 0;
|
||||
mSourceHistoryRing.filledCount = 0;
|
||||
for (auto& historyEntry : mPreLayerHistoryByLayerId)
|
||||
{
|
||||
historyEntry.second.nextWriteIndex = 0;
|
||||
historyEntry.second.filledCount = 0;
|
||||
}
|
||||
mTemporalHistoryNeedsReset = false;
|
||||
}
|
||||
|
||||
bool OpenGLComposite::ensureTemporalHistoryResources(const std::vector<RuntimeRenderState>& layerStates, std::string& error)
|
||||
{
|
||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||
const bool sourceHistoryNeeded = std::any_of(layerStates.begin(), layerStates.end(),
|
||||
[](const RuntimeRenderState& state) { return state.isTemporal && state.effectiveTemporalHistoryLength > 0; });
|
||||
const unsigned sourceHistoryLength = sourceHistoryNeeded ? historyCap : 0;
|
||||
|
||||
if (mSourceHistoryRing.effectiveLength != sourceHistoryLength)
|
||||
{
|
||||
if (!createHistoryRing(mSourceHistoryRing, sourceHistoryLength, TemporalHistorySource::Source, error))
|
||||
return false;
|
||||
mTemporalHistoryNeedsReset = true;
|
||||
}
|
||||
|
||||
std::set<std::string> requiredPreLayerIds;
|
||||
for (const RuntimeRenderState& state : layerStates)
|
||||
{
|
||||
if (!state.isTemporal || state.temporalHistorySource != TemporalHistorySource::PreLayerInput)
|
||||
continue;
|
||||
requiredPreLayerIds.insert(state.layerId);
|
||||
auto historyIt = mPreLayerHistoryByLayerId.find(state.layerId);
|
||||
if (historyIt == mPreLayerHistoryByLayerId.end() || historyIt->second.effectiveLength != state.effectiveTemporalHistoryLength)
|
||||
{
|
||||
HistoryRing replacement;
|
||||
if (!createHistoryRing(replacement, state.effectiveTemporalHistoryLength, TemporalHistorySource::PreLayerInput, error))
|
||||
return false;
|
||||
mPreLayerHistoryByLayerId[state.layerId] = std::move(replacement);
|
||||
mTemporalHistoryNeedsReset = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = mPreLayerHistoryByLayerId.begin(); it != mPreLayerHistoryByLayerId.end();)
|
||||
{
|
||||
if (requiredPreLayerIds.find(it->first) == requiredPreLayerIds.end())
|
||||
{
|
||||
destroyHistoryRing(it->second);
|
||||
it = mPreLayerHistoryByLayerId.erase(it);
|
||||
mTemporalHistoryNeedsReset = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (mTemporalHistoryNeedsReset)
|
||||
resetTemporalHistoryState();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLComposite::pushFramebufferToHistoryRing(GLuint sourceFramebuffer, HistoryRing& ring)
|
||||
{
|
||||
if (ring.effectiveLength == 0 || ring.slots.empty())
|
||||
return;
|
||||
|
||||
HistorySlot& targetSlot = ring.slots[ring.nextWriteIndex];
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetSlot.framebuffer);
|
||||
glBlitFramebuffer(0, 0, mFrameWidth, mFrameHeight, 0, 0, mFrameWidth, mFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
ring.nextWriteIndex = (ring.nextWriteIndex + 1) % ring.slots.size();
|
||||
ring.filledCount = std::min<std::size_t>(ring.filledCount + 1, ring.slots.size());
|
||||
}
|
||||
|
||||
GLuint OpenGLComposite::resolveHistoryTexture(const HistoryRing& ring, GLuint fallbackTexture, std::size_t framesAgo) const
|
||||
{
|
||||
if (ring.filledCount == 0 || ring.slots.empty())
|
||||
return fallbackTexture;
|
||||
|
||||
const std::size_t clampedOffset = std::min<std::size_t>(framesAgo, ring.filledCount - 1);
|
||||
const std::size_t newestIndex = (ring.nextWriteIndex + ring.slots.size() - 1) % ring.slots.size();
|
||||
const std::size_t slotIndex = (newestIndex + ring.slots.size() - clampedOffset) % ring.slots.size();
|
||||
return ring.slots[slotIndex].texture != 0 ? ring.slots[slotIndex].texture : fallbackTexture;
|
||||
}
|
||||
|
||||
unsigned OpenGLComposite::sourceHistoryAvailableCount() const
|
||||
{
|
||||
return static_cast<unsigned>(mSourceHistoryRing.filledCount);
|
||||
}
|
||||
|
||||
unsigned OpenGLComposite::temporalHistoryAvailableCountForLayer(const std::string& layerId) const
|
||||
{
|
||||
auto it = mPreLayerHistoryByLayerId.find(layerId);
|
||||
if (it == mPreLayerHistoryByLayerId.end())
|
||||
return 0;
|
||||
return static_cast<unsigned>(it->second.filledCount);
|
||||
}
|
||||
|
||||
void OpenGLComposite::bindHistorySamplers(const RuntimeRenderState& state, GLuint currentSourceTexture)
|
||||
{
|
||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||
for (unsigned index = 0; index < historyCap; ++index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + index);
|
||||
glBindTexture(GL_TEXTURE_2D, resolveHistoryTexture(mSourceHistoryRing, currentSourceTexture, index));
|
||||
}
|
||||
|
||||
const GLuint temporalBase = kSourceHistoryTextureUnitBase + historyCap;
|
||||
const HistoryRing* temporalRing = nullptr;
|
||||
auto it = mPreLayerHistoryByLayerId.find(state.layerId);
|
||||
if (it != mPreLayerHistoryByLayerId.end())
|
||||
temporalRing = &it->second;
|
||||
|
||||
for (unsigned index = 0; index < historyCap; ++index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + temporalBase + index);
|
||||
glBindTexture(GL_TEXTURE_2D, temporalRing ? resolveHistoryTexture(*temporalRing, currentSourceTexture, index) : currentSourceTexture);
|
||||
}
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
void OpenGLComposite::renderEffect()
|
||||
{
|
||||
PollRuntimeChanges();
|
||||
@@ -1143,15 +1489,25 @@ void OpenGLComposite::renderEffect()
|
||||
else
|
||||
{
|
||||
GLuint sourceTexture = mDecodedTexture;
|
||||
GLuint sourceFrameBuffer = mDecodeFrameBuf;
|
||||
for (std::size_t index = 0; index < layerStates.size() && index < mLayerPrograms.size(); ++index)
|
||||
{
|
||||
const std::size_t remaining = layerStates.size() - index;
|
||||
const bool writeToMain = (remaining % 2) == 1;
|
||||
renderShaderProgram(sourceTexture, writeToMain ? mIdFrameBuf : mLayerTempFrameBuf, mLayerPrograms[index], layerStates[index]);
|
||||
if (layerStates[index].temporalHistorySource == TemporalHistorySource::PreLayerInput)
|
||||
{
|
||||
auto historyIt = mPreLayerHistoryByLayerId.find(layerStates[index].layerId);
|
||||
if (historyIt != mPreLayerHistoryByLayerId.end())
|
||||
pushFramebufferToHistoryRing(sourceFrameBuffer, historyIt->second);
|
||||
}
|
||||
sourceTexture = writeToMain ? mFBOTexture : mLayerTempTexture;
|
||||
sourceFrameBuffer = writeToMain ? mIdFrameBuf : mLayerTempFrameBuf;
|
||||
}
|
||||
}
|
||||
|
||||
pushFramebufferToHistoryRing(mDecodeFrameBuf, mSourceHistoryRing);
|
||||
|
||||
if (mFastTransferExtensionAvailable)
|
||||
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
|
||||
}
|
||||
@@ -1163,12 +1519,22 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
||||
glBindTexture(GL_TEXTURE_2D, sourceTexture);
|
||||
bindHistorySamplers(state, sourceTexture);
|
||||
glBindVertexArray(mFullscreenVAO);
|
||||
glUseProgram(layerProgram.program);
|
||||
updateGlobalParamsBuffer(state);
|
||||
updateGlobalParamsBuffer(state, sourceHistoryAvailableCount(), temporalHistoryAvailableCountForLayer(state.layerId));
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glUseProgram(0);
|
||||
glBindVertexArray(0);
|
||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||
for (unsigned index = 0; index < historyCap; ++index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + index);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
@@ -1228,6 +1594,7 @@ bool OpenGLComposite::PollRuntimeChanges()
|
||||
return false;
|
||||
}
|
||||
|
||||
resetTemporalHistoryState();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
@@ -1238,7 +1605,7 @@ void OpenGLComposite::broadcastRuntimeState()
|
||||
mControlServer->BroadcastState();
|
||||
}
|
||||
|
||||
bool OpenGLComposite::updateGlobalParamsBuffer(const RuntimeRenderState& state)
|
||||
bool OpenGLComposite::updateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength)
|
||||
{
|
||||
std::vector<unsigned char> buffer;
|
||||
buffer.reserve(512);
|
||||
@@ -1249,6 +1616,14 @@ bool OpenGLComposite::updateGlobalParamsBuffer(const RuntimeRenderState& state)
|
||||
AppendStd140Float(buffer, static_cast<float>(state.frameCount));
|
||||
AppendStd140Float(buffer, static_cast<float>(state.mixAmount));
|
||||
AppendStd140Float(buffer, static_cast<float>(state.bypass));
|
||||
const unsigned effectiveSourceHistoryLength = availableSourceHistoryLength < state.effectiveTemporalHistoryLength
|
||||
? availableSourceHistoryLength
|
||||
: state.effectiveTemporalHistoryLength;
|
||||
const unsigned effectiveTemporalHistoryLength = (state.temporalHistorySource == TemporalHistorySource::PreLayerInput)
|
||||
? (availableTemporalHistoryLength < state.effectiveTemporalHistoryLength ? availableTemporalHistoryLength : state.effectiveTemporalHistoryLength)
|
||||
: 0u;
|
||||
AppendStd140Int(buffer, static_cast<int>(effectiveSourceHistoryLength));
|
||||
AppendStd140Int(buffer, static_cast<int>(effectiveTemporalHistoryLength));
|
||||
|
||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
||||
{
|
||||
@@ -1323,6 +1698,7 @@ bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
resetTemporalHistoryState();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
@@ -1333,6 +1709,7 @@ bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
resetTemporalHistoryState();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
@@ -1343,6 +1720,7 @@ bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
resetTemporalHistoryState();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
@@ -1353,6 +1731,7 @@ bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t t
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
resetTemporalHistoryState();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
@@ -1363,6 +1742,7 @@ bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed,
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
resetTemporalHistoryState();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
@@ -1373,6 +1753,7 @@ bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::stri
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
resetTemporalHistoryState();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
@@ -1414,6 +1795,7 @@ bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string
|
||||
return false;
|
||||
|
||||
ReloadShader();
|
||||
resetTemporalHistoryState();
|
||||
broadcastRuntimeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ private:
|
||||
// DeckLink
|
||||
IDeckLinkInput* mDLInput;
|
||||
IDeckLinkOutput* mDLOutput;
|
||||
IDeckLinkKeyer* mDLKeyer;
|
||||
std::deque<IDeckLinkMutableVideoFrame*> mDLOutputVideoFrameQueue;
|
||||
PinnedMemoryAllocator* mPlayoutAllocator;
|
||||
BMDTimeValue mFrameDuration;
|
||||
@@ -118,6 +119,12 @@ private:
|
||||
unsigned mFrameWidth;
|
||||
unsigned mFrameHeight;
|
||||
bool mHasNoInputSource;
|
||||
std::string mDeckLinkOutputModelName;
|
||||
bool mDeckLinkSupportsInternalKeying;
|
||||
bool mDeckLinkSupportsExternalKeying;
|
||||
bool mDeckLinkKeyerInterfaceAvailable;
|
||||
bool mDeckLinkExternalKeyingActive;
|
||||
std::string mDeckLinkStatusMessage;
|
||||
|
||||
// OpenGL data
|
||||
bool mFastTransferExtensionAvailable;
|
||||
@@ -152,6 +159,25 @@ private:
|
||||
};
|
||||
std::vector<LayerProgram> mLayerPrograms;
|
||||
|
||||
struct HistorySlot
|
||||
{
|
||||
GLuint texture = 0;
|
||||
GLuint framebuffer = 0;
|
||||
};
|
||||
|
||||
struct HistoryRing
|
||||
{
|
||||
std::vector<HistorySlot> slots;
|
||||
std::size_t nextWriteIndex = 0;
|
||||
std::size_t filledCount = 0;
|
||||
unsigned effectiveLength = 0;
|
||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
||||
};
|
||||
|
||||
HistoryRing mSourceHistoryRing;
|
||||
std::map<std::string, HistoryRing> mPreLayerHistoryByLayerId;
|
||||
bool mTemporalHistoryNeedsReset;
|
||||
|
||||
bool InitOpenGLState();
|
||||
bool compileLayerPrograms(int errorMessageSize, char* errorMessage);
|
||||
bool compileSingleLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
||||
@@ -164,7 +190,18 @@ private:
|
||||
void renderEffect();
|
||||
bool PollRuntimeChanges();
|
||||
void broadcastRuntimeState();
|
||||
bool updateGlobalParamsBuffer(const RuntimeRenderState& state);
|
||||
bool updateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength);
|
||||
bool validateTemporalTextureUnitBudget(std::string& error) const;
|
||||
bool ensureTemporalHistoryResources(const std::vector<RuntimeRenderState>& layerStates, std::string& error);
|
||||
bool createHistoryRing(HistoryRing& ring, unsigned effectiveLength, TemporalHistorySource historySource, std::string& error);
|
||||
void destroyHistoryRing(HistoryRing& ring);
|
||||
void destroyTemporalHistoryResources();
|
||||
void resetTemporalHistoryState();
|
||||
void pushFramebufferToHistoryRing(GLuint sourceFramebuffer, HistoryRing& ring);
|
||||
void bindHistorySamplers(const RuntimeRenderState& state, GLuint currentSourceTexture);
|
||||
GLuint resolveHistoryTexture(const HistoryRing& ring, GLuint fallbackTexture, std::size_t framesAgo) const;
|
||||
unsigned sourceHistoryAvailableCount() const;
|
||||
unsigned temporalHistoryAvailableCountForLayer(const std::string& layerId) const;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -45,6 +45,21 @@ struct ShaderParameterValue
|
||||
std::string enumValue;
|
||||
};
|
||||
|
||||
enum class TemporalHistorySource
|
||||
{
|
||||
None,
|
||||
Source,
|
||||
PreLayerInput
|
||||
};
|
||||
|
||||
struct TemporalSettings
|
||||
{
|
||||
bool enabled = false;
|
||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
||||
unsigned requestedHistoryLength = 0;
|
||||
unsigned effectiveHistoryLength = 0;
|
||||
};
|
||||
|
||||
struct ShaderPackage
|
||||
{
|
||||
std::string id;
|
||||
@@ -56,6 +71,7 @@ struct ShaderPackage
|
||||
std::filesystem::path shaderPath;
|
||||
std::filesystem::path manifestPath;
|
||||
std::vector<ShaderParameterDefinition> parameters;
|
||||
TemporalSettings temporal;
|
||||
std::filesystem::file_time_type shaderWriteTime;
|
||||
std::filesystem::file_time_type manifestWriteTime;
|
||||
};
|
||||
@@ -74,6 +90,10 @@ struct RuntimeRenderState
|
||||
unsigned inputHeight = 0;
|
||||
unsigned outputWidth = 0;
|
||||
unsigned outputHeight = 0;
|
||||
bool isTemporal = false;
|
||||
TemporalHistorySource temporalHistorySource = TemporalHistorySource::None;
|
||||
unsigned requestedTemporalHistoryLength = 0;
|
||||
unsigned effectiveTemporalHistoryLength = 0;
|
||||
};
|
||||
|
||||
class RuntimeHost
|
||||
@@ -100,6 +120,8 @@ public:
|
||||
|
||||
void SetCompileStatus(bool succeeded, const std::string& message);
|
||||
void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
void SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
||||
void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
void AdvanceFrame();
|
||||
|
||||
@@ -111,6 +133,8 @@ public:
|
||||
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
|
||||
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
|
||||
unsigned short GetServerPort() const { return mServerPort; }
|
||||
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
|
||||
bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; }
|
||||
void SetServerPort(unsigned short port);
|
||||
bool AutoReloadEnabled() const { return mAutoReloadEnabled; }
|
||||
|
||||
@@ -120,6 +144,19 @@ private:
|
||||
std::string shaderLibrary = "shaders";
|
||||
unsigned short serverPort = 8080;
|
||||
bool autoReload = true;
|
||||
unsigned maxTemporalHistoryFrames = 4;
|
||||
bool enableExternalKeying = false;
|
||||
};
|
||||
|
||||
struct DeckLinkOutputStatus
|
||||
{
|
||||
std::string modelName;
|
||||
bool supportsInternalKeying = false;
|
||||
bool supportsExternalKeying = false;
|
||||
bool keyerInterfaceAvailable = false;
|
||||
bool externalKeyingRequested = false;
|
||||
bool externalKeyingActive = false;
|
||||
std::string statusMessage;
|
||||
};
|
||||
|
||||
struct LayerPersistentState
|
||||
@@ -156,6 +193,7 @@ private:
|
||||
std::vector<std::string> GetStackPresetNamesLocked() const;
|
||||
std::string MakeSafePresetFileStem(const std::string& presetName) const;
|
||||
JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const;
|
||||
std::string TemporalHistorySourceToString(TemporalHistorySource source) const;
|
||||
LayerPersistentState* FindLayerById(const std::string& layerId);
|
||||
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
|
||||
std::string GenerateLayerId();
|
||||
@@ -186,6 +224,7 @@ private:
|
||||
double mFrameBudgetMilliseconds;
|
||||
double mRenderMilliseconds;
|
||||
double mSmoothedRenderMilliseconds;
|
||||
DeckLinkOutputStatus mDeckLinkOutputStatus;
|
||||
unsigned short mServerPort;
|
||||
bool mAutoReloadEnabled;
|
||||
std::chrono::steady_clock::time_point mStartTime;
|
||||
|
||||
Reference in New Issue
Block a user