temporal effects
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user