diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4be480f..1fa9a22 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -56,11 +56,15 @@ set(APP_SOURCES
"${APP_DIR}/gl/OpenGLComposite.cpp"
"${APP_DIR}/gl/OpenGLComposite.h"
"${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp"
+ "${APP_DIR}/gl/OpenGLRenderer.cpp"
+ "${APP_DIR}/gl/OpenGLRenderer.h"
"${APP_DIR}/gl/Std140Buffer.h"
"${APP_DIR}/gl/TextRasterizer.cpp"
"${APP_DIR}/gl/TextRasterizer.h"
"${APP_DIR}/gl/TextureAssetLoader.cpp"
"${APP_DIR}/gl/TextureAssetLoader.h"
+ "${APP_DIR}/gl/TemporalHistoryBuffers.cpp"
+ "${APP_DIR}/gl/TemporalHistoryBuffers.h"
"${APP_DIR}/gl/VideoFrameTransfer.cpp"
"${APP_DIR}/gl/VideoFrameTransfer.h"
"${APP_DIR}/LoopThroughWithOpenGLCompositing.cpp"
diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj
index 6b11a6d..fed6c09 100644
--- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj
+++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj
@@ -195,6 +195,8 @@
+
+
Create
Create
@@ -208,6 +210,8 @@
+
+
diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters
index 5826e94..3867475 100644
--- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters
+++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters
@@ -27,6 +27,12 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
Source Files
@@ -47,6 +53,12 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
Header Files
diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/GLExtensions.h b/apps/LoopThroughWithOpenGLCompositing/gl/GLExtensions.h
index 88c0f03..7aafdee 100644
--- a/apps/LoopThroughWithOpenGLCompositing/gl/GLExtensions.h
+++ b/apps/LoopThroughWithOpenGLCompositing/gl/GLExtensions.h
@@ -67,6 +67,7 @@
#define GL_VERTEX_SHADER 0x8B31
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
+#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
#define GL_RENDERBUFFER_EXT 0x8D41
#define GL_FRAMEBUFFER_EXT 0x8D40
#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5
diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp
index 27caff3..6ec8a44 100644
--- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp
+++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp
@@ -58,8 +58,6 @@
#include
#include
#include
-#include
-#include
#include
#include
@@ -116,28 +114,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
mDeckLinkSupportsExternalKeying(false),
mDeckLinkKeyerInterfaceAvailable(false),
mDeckLinkExternalKeyingActive(false),
- mFastTransferExtensionAvailable(false),
- mCaptureTexture(0),
- mDecodedTexture(0),
- mLayerTempTexture(0),
- mFBOTexture(0),
- mOutputTexture(0),
- mUnpinnedTextureBuffer(0),
- mDecodeFrameBuf(0),
- mLayerTempFrameBuf(0),
- mIdFrameBuf(0),
- mOutputFrameBuf(0),
- mIdColorBuf(0),
- mIdDepthBuf(0),
- mFullscreenVAO(0),
- mGlobalParamsUBO(0),
- mDecodeProgram(0),
- mDecodeVertexShader(0),
- mDecodeFragmentShader(0),
- mGlobalParamsUBOSize(0),
- mViewWidth(0),
- mViewHeight(0),
- mTemporalHistoryNeedsReset(true)
+ mRenderer(std::make_unique())
{
InitializeCriticalSection(&pMutex);
mRuntimeHost = std::make_unique();
@@ -201,38 +178,7 @@ OpenGLComposite::~OpenGLComposite()
mPlayoutAllocator = NULL;
}
- if (mFullscreenVAO != 0)
- glDeleteVertexArrays(1, &mFullscreenVAO);
- if (mGlobalParamsUBO != 0)
- glDeleteBuffers(1, &mGlobalParamsUBO);
- if (mDecodeFrameBuf != 0)
- glDeleteFramebuffers(1, &mDecodeFrameBuf);
- if (mLayerTempFrameBuf != 0)
- glDeleteFramebuffers(1, &mLayerTempFrameBuf);
- if (mIdFrameBuf != 0)
- glDeleteFramebuffers(1, &mIdFrameBuf);
- if (mIdColorBuf != 0)
- glDeleteRenderbuffers(1, &mIdColorBuf);
- if (mIdDepthBuf != 0)
- glDeleteRenderbuffers(1, &mIdDepthBuf);
- if (mCaptureTexture != 0)
- glDeleteTextures(1, &mCaptureTexture);
- if (mDecodedTexture != 0)
- glDeleteTextures(1, &mDecodedTexture);
- if (mLayerTempTexture != 0)
- glDeleteTextures(1, &mLayerTempTexture);
- if (mFBOTexture != 0)
- glDeleteTextures(1, &mFBOTexture);
- if (mOutputTexture != 0)
- glDeleteTextures(1, &mOutputTexture);
- if (mOutputFrameBuf != 0)
- glDeleteFramebuffers(1, &mOutputFrameBuf);
- if (mUnpinnedTextureBuffer != 0)
- glDeleteBuffers(1, &mUnpinnedTextureBuffer);
-
- destroyTemporalHistoryResources();
- destroyLayerPrograms();
- destroyDecodeShaderProgram();
+ mRenderer->DestroyResources();
if (mOscServer)
mOscServer->Stop();
if (mControlServer)
@@ -418,7 +364,7 @@ bool OpenGLComposite::InitDeckLink()
}
if (mInputFrameWidth != mOutputFrameWidth || mInputFrameHeight != mOutputFrameHeight)
{
- mFastTransferExtensionAvailable = false;
+ mRenderer->mFastTransferExtensionAvailable = false;
OutputDebugStringA("Input/output dimensions differ; using regular OpenGL transfer fallback instead of fast transfer.\n");
}
@@ -451,10 +397,10 @@ bool OpenGLComposite::InitDeckLink()
else
resizeWindow(mOutputFrameWidth / 2, mOutputFrameHeight / 2);
- if (mFastTransferExtensionAvailable)
+ if (mRenderer->mFastTransferExtensionAvailable)
{
// Initialize fast video frame transfers
- if (! VideoFrameTransfer::initialize(mInputFrameWidth, mInputFrameHeight, mCaptureTexture, mOutputTexture))
+ if (! VideoFrameTransfer::initialize(mInputFrameWidth, mInputFrameHeight, mRenderer->mCaptureTexture, mRenderer->mOutputTexture))
{
MessageBox(NULL, _T("Cannot initialize video transfers."), _T("VideoFrameTransfer error."), MB_OK);
goto error;
@@ -661,44 +607,7 @@ void OpenGLComposite::paintGL()
return;
}
- // The DeckLink API provides IDeckLinkGLScreenPreviewHelper as a convenient way to view the playout video frames
- // in a window. However, it performs a copy from host memory to the GPU which is wasteful in this case since
- // we already have the rendered frame to be played out sitting in the GPU in the mIdFrameBuf frame buffer.
-
- // Copy the off-screen frame buffer to the on-screen frame buffer while preserving the
- // incoming video aspect ratio. Any extra window area is cleared to black.
- int destWidth = mViewWidth;
- int destHeight = mViewHeight;
- int destX = 0;
- int destY = 0;
-
- if (mOutputFrameWidth > 0 && mOutputFrameHeight > 0 && mViewWidth > 0 && mViewHeight > 0)
- {
- const double frameAspect = static_cast(mOutputFrameWidth) / static_cast(mOutputFrameHeight);
- const double viewAspect = static_cast(mViewWidth) / static_cast(mViewHeight);
-
- if (viewAspect > frameAspect)
- {
- destHeight = mViewHeight;
- destWidth = static_cast(destHeight * frameAspect + 0.5);
- destX = (mViewWidth - destWidth) / 2;
- }
- else
- {
- destWidth = mViewWidth;
- destHeight = static_cast(destWidth / frameAspect + 0.5);
- destY = (mViewHeight - destHeight) / 2;
- }
- }
-
- glBindFramebuffer(GL_READ_FRAMEBUFFER, mOutputFrameBuf);
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
- glViewport(0, 0, mViewWidth, mViewHeight);
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- glBlitFramebuffer(0, 0, mOutputFrameWidth, mOutputFrameHeight, destX, destY, destX + destWidth, destY + destHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
-
- SwapBuffers(hGLDC);
+ mRenderer->PresentToWindow(hGLDC, mOutputFrameWidth, mOutputFrameHeight);
ValidateRect(hGLWnd, NULL);
LeaveCriticalSection(&pMutex);
}
@@ -707,8 +616,7 @@ void OpenGLComposite::resizeGL(WORD width, WORD height)
{
// We don't set the project or model matrices here since the window data is copied directly from
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
- mViewWidth = width;
- mViewHeight = height;
+ mRenderer->ResizeView(width, height);
}
void OpenGLComposite::resizeWindow(int width, int height)
@@ -753,132 +661,13 @@ bool OpenGLComposite::InitOpenGLState()
}
resetTemporalHistoryState();
- glClearColor( 0.0f, 0.0f, 0.0f, 0.5f ); // Black background
- glDisable(GL_DEPTH_TEST);
-
- if (! mFastTransferExtensionAvailable)
+ std::string rendererError;
+ if (!mRenderer->InitializeResources(mInputFrameWidth, mInputFrameHeight, mOutputFrameWidth, mOutputFrameHeight, rendererError))
{
- glGenBuffers(1, &mUnpinnedTextureBuffer);
- }
-
- // Setup the texture which will hold the captured video frame pixels
- glGenTextures(1, &mCaptureTexture);
- glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
-
- // Parameters to control how texels are sampled from the 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);
-
- // Create texture with empty data, we will update it using glTexSubImage2D each frame.
- // The captured video is YCbCr 4:2:2 packed into a UYVY macropixel. OpenGL has no YCbCr format
- // so treat it as RGBA 4:4:4:4 by halving the width and using GL_RGBA internal format.
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mInputFrameWidth / 2, mInputFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
- glBindTexture(GL_TEXTURE_2D, 0);
-
- glGenTextures(1, &mDecodedTexture);
- glBindTexture(GL_TEXTURE_2D, mDecodedTexture);
- 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, mInputFrameWidth, mInputFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
- glBindTexture(GL_TEXTURE_2D, 0);
-
- glGenTextures(1, &mLayerTempTexture);
- glBindTexture(GL_TEXTURE_2D, mLayerTempTexture);
- 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, mInputFrameWidth, mInputFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
- glBindTexture(GL_TEXTURE_2D, 0);
-
-
- // Create Frame Buffer Object (FBO) to perform off-screen rendering of scene.
- // This allows the render to be done on a framebuffer with width and height exactly matching the video format.
- glGenFramebuffers(1, &mDecodeFrameBuf);
- glGenFramebuffers(1, &mLayerTempFrameBuf);
- glGenFramebuffers(1, &mIdFrameBuf);
- glGenFramebuffers(1, &mOutputFrameBuf);
- glGenRenderbuffers(1, &mIdColorBuf);
- glGenRenderbuffers(1, &mIdDepthBuf);
- glGenVertexArrays(1, &mFullscreenVAO);
- glGenBuffers(1, &mGlobalParamsUBO);
-
- glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFrameBuf);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mDecodedTexture, 0);
- GLenum glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- if (glStatus != GL_FRAMEBUFFER_COMPLETE)
- {
- MessageBox(NULL, _T("Cannot initialize decode framebuffer."), _T("OpenGL initialization error."), MB_OK);
+ MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
return false;
}
- glBindFramebuffer(GL_FRAMEBUFFER, mLayerTempFrameBuf);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mLayerTempTexture, 0);
- glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- if (glStatus != GL_FRAMEBUFFER_COMPLETE)
- {
- MessageBox(NULL, _T("Cannot initialize layer framebuffer."), _T("OpenGL initialization error."), MB_OK);
- return false;
- }
-
- glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
-
- // Texture for FBO
- glGenTextures(1, &mFBOTexture);
- glBindTexture(GL_TEXTURE_2D, mFBOTexture);
- 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, mInputFrameWidth, mInputFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
-
- // Attach a depth buffer
- glBindRenderbuffer(GL_RENDERBUFFER, mIdDepthBuf);
- glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mInputFrameWidth, mInputFrameHeight);
-
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, mIdDepthBuf);
-
- // Attach the texture which stores the playback image
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFBOTexture, 0);
-
- glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- if (glStatus != GL_FRAMEBUFFER_COMPLETE)
- {
- MessageBox(NULL, _T("Cannot initialize framebuffer."), _T("OpenGL initialization error."), MB_OK);
- return false;
- }
-
- glGenTextures(1, &mOutputTexture);
- glBindTexture(GL_TEXTURE_2D, mOutputTexture);
- 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, mOutputFrameWidth, mOutputFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
-
- glBindFramebuffer(GL_FRAMEBUFFER, mOutputFrameBuf);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mOutputTexture, 0);
- glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- if (glStatus != GL_FRAMEBUFFER_COMPLETE)
- {
- MessageBox(NULL, _T("Cannot initialize output framebuffer."), _T("OpenGL initialization error."), MB_OK);
- return false;
- }
-
- glBindTexture(GL_TEXTURE_2D, 0);
- glBindRenderbuffer(GL_RENDERBUFFER, 0);
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
- glBindVertexArray(mFullscreenVAO);
- glBindVertexArray(0);
- glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsUBO);
- glBufferData(GL_UNIFORM_BUFFER, 1024, NULL, GL_DYNAMIC_DRAW);
- glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
- glBindBuffer(GL_UNIFORM_BUFFER, 0);
-
broadcastRuntimeState();
return true;
}
@@ -913,10 +702,10 @@ void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bo
wglMakeCurrent( hGLDC, hGLRC ); // make OpenGL context current in this thread
- if (mFastTransferExtensionAvailable)
+ if (mRenderer->mFastTransferExtensionAvailable)
{
CComQIPtr allocator(inputFrameBuffer);
- if (!allocator || !allocator->transferFrame(videoPixels, mCaptureTexture))
+ if (!allocator || !allocator->transferFrame(videoPixels, mRenderer->mCaptureTexture))
OutputDebugStringA("Capture: transferFrame() failed\n");
allocator->waitForTransferComplete(videoPixels);
@@ -924,9 +713,9 @@ void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bo
else
{
// Use a straightforward texture buffer
- glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mUnpinnedTextureBuffer);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer->mUnpinnedTextureBuffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, videoPixels, GL_DYNAMIC_DRAW);
- glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
+ glBindTexture(GL_TEXTURE_2D, mRenderer->mCaptureTexture);
// NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mInputFrameWidth / 2, mInputFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
@@ -959,16 +748,16 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
// Draw the effect output to the off-screen framebuffer.
const auto renderStartTime = std::chrono::steady_clock::now();
- if (mFastTransferExtensionAvailable)
+ if (mRenderer->mFastTransferExtensionAvailable)
VideoFrameTransfer::beginTextureInUse(VideoFrameTransfer::GPUtoCPU);
- glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
+ glBindFramebuffer(GL_FRAMEBUFFER, mRenderer->mIdFrameBuf);
renderEffect();
- glBindFramebuffer(GL_READ_FRAMEBUFFER, mIdFrameBuf);
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mOutputFrameBuf);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->mIdFrameBuf);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer->mOutputFrameBuf);
glBlitFramebuffer(0, 0, mInputFrameWidth, mInputFrameHeight, 0, 0, mOutputFrameWidth, mOutputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
- glBindFramebuffer(GL_FRAMEBUFFER, mOutputFrameBuf);
+ glBindFramebuffer(GL_FRAMEBUFFER, mRenderer->mOutputFrameBuf);
glFlush();
- if (mFastTransferExtensionAvailable)
+ if (mRenderer->mFastTransferExtensionAvailable)
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::GPUtoCPU);
const auto renderEndTime = std::chrono::steady_clock::now();
if (mRuntimeHost)
@@ -999,13 +788,13 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
void* pFrame;
outputVideoFrameBuffer->GetBytes(&pFrame);
- if (mFastTransferExtensionAvailable)
+ if (mRenderer->mFastTransferExtensionAvailable)
{
- // Finished with mCaptureTexture
+ // Finished with mRenderer->mCaptureTexture
if (!mHasNoInputSource)
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
- if (! mPlayoutAllocator->transferFrame(pFrame, mOutputTexture))
+ if (! mPlayoutAllocator->transferFrame(pFrame, mRenderer->mOutputTexture))
OutputDebugStringA("Playback: transferFrame() failed\n");
paintGL();
@@ -1015,7 +804,7 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
}
else
{
- glBindFramebuffer(GL_READ_FRAMEBUFFER, mOutputFrameBuf);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->mOutputFrameBuf);
glReadPixels(0, 0, mOutputFrameWidth, mOutputFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame);
paintGL();
}
@@ -1342,7 +1131,7 @@ bool OpenGLComposite::compileLayerPrograms(int errorMessageSize, char* errorMess
}
destroyLayerPrograms();
- mLayerPrograms.swap(newPrograms);
+ mRenderer->mLayerPrograms.swap(newPrograms);
if (mRuntimeHost)
{
@@ -1393,57 +1182,20 @@ bool OpenGLComposite::compileDecodeShader(int errorMessageSize, char* errorMessa
}
destroyDecodeShaderProgram();
- mDecodeProgram = newProgram.release();
- mDecodeVertexShader = newVertexShader.release();
- mDecodeFragmentShader = newFragmentShader.release();
+ mRenderer->mDecodeProgram = newProgram.release();
+ mRenderer->mDecodeVertexShader = newVertexShader.release();
+ mRenderer->mDecodeFragmentShader = newFragmentShader.release();
return true;
}
void OpenGLComposite::destroySingleLayerProgram(LayerProgram& layerProgram)
{
- for (LayerProgram::TextureBinding& textureBinding : layerProgram.textureBindings)
- {
- if (textureBinding.texture != 0)
- {
- glDeleteTextures(1, &textureBinding.texture);
- textureBinding.texture = 0;
- }
- }
- layerProgram.textureBindings.clear();
- for (LayerProgram::TextBinding& textBinding : layerProgram.textBindings)
- {
- if (textBinding.texture != 0)
- {
- glDeleteTextures(1, &textBinding.texture);
- textBinding.texture = 0;
- }
- }
- layerProgram.textBindings.clear();
-
- if (layerProgram.program != 0)
- {
- glDeleteProgram(layerProgram.program);
- layerProgram.program = 0;
- }
-
- if (layerProgram.fragmentShader != 0)
- {
- glDeleteShader(layerProgram.fragmentShader);
- layerProgram.fragmentShader = 0;
- }
-
- if (layerProgram.vertexShader != 0)
- {
- glDeleteShader(layerProgram.vertexShader);
- layerProgram.vertexShader = 0;
- }
+ mRenderer->DestroySingleLayerProgram(layerProgram);
}
void OpenGLComposite::destroyLayerPrograms()
{
- for (LayerProgram& layerProgram : mLayerPrograms)
- destroySingleLayerProgram(layerProgram);
- mLayerPrograms.clear();
+ mRenderer->DestroyLayerPrograms();
}
bool OpenGLComposite::loadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
@@ -1508,241 +1260,40 @@ void OpenGLComposite::bindLayerTextureAssets(const LayerProgram& layerProgram)
void OpenGLComposite::destroyDecodeShaderProgram()
{
- if (mDecodeProgram != 0)
- {
- glDeleteProgram(mDecodeProgram);
- mDecodeProgram = 0;
- }
-
- if (mDecodeFragmentShader != 0)
- {
- glDeleteShader(mDecodeFragmentShader);
- mDecodeFragmentShader = 0;
- }
-
- if (mDecodeVertexShader != 0)
- {
- glDeleteShader(mDecodeVertexShader);
- mDecodeVertexShader = 0;
- }
+ mRenderer->DestroyDecodeShaderProgram();
}
bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector& layerStates, std::string& error) const
{
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
- unsigned requiredUnits = kSourceHistoryTextureUnitBase;
- for (const RuntimeRenderState& state : layerStates)
- {
- unsigned textTextureCount = 0;
- for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
- {
- if (definition.type == ShaderParameterType::Text)
- ++textTextureCount;
- }
- const unsigned totalShaderTextures = static_cast(state.textureAssets.size()) + textTextureCount;
- const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + totalShaderTextures;
- if (layerRequiredUnits > requiredUnits)
- requiredUnits = layerRequiredUnits;
- }
- GLint maxTextureUnits = 0;
- glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
- const unsigned availableUnits = maxTextureUnits > 0 ? static_cast(maxTextureUnits) : 0u;
- if (requiredUnits > availableUnits)
- {
- std::ostringstream message;
- message << "The current history and shader texture asset configuration 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, mInputFrameWidth, mInputFrameHeight, 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();
+ return mRenderer->mTemporalHistory.ValidateTextureUnitBudget(layerStates, historyCap, error);
}
void OpenGLComposite::resetTemporalHistoryState()
{
- mSourceHistoryRing.nextWriteIndex = 0;
- mSourceHistoryRing.filledCount = 0;
- for (auto& historyEntry : mPreLayerHistoryByLayerId)
- {
- historyEntry.second.nextWriteIndex = 0;
- historyEntry.second.filledCount = 0;
- }
- mTemporalHistoryNeedsReset = false;
+ mRenderer->mTemporalHistory.ResetState();
}
bool OpenGLComposite::ensureTemporalHistoryResources(const std::vector& 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 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, mInputFrameWidth, mInputFrameHeight, 0, 0, mInputFrameWidth, mInputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
- ring.nextWriteIndex = (ring.nextWriteIndex + 1) % ring.slots.size();
- ring.filledCount = std::min(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(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;
+ return mRenderer->mTemporalHistory.EnsureResources(layerStates, historyCap, mInputFrameWidth, mInputFrameHeight, error);
}
unsigned OpenGLComposite::sourceHistoryAvailableCount() const
{
- return static_cast(mSourceHistoryRing.filledCount);
+ return mRenderer->mTemporalHistory.SourceAvailableCount();
}
unsigned OpenGLComposite::temporalHistoryAvailableCountForLayer(const std::string& layerId) const
{
- auto it = mPreLayerHistoryByLayerId.find(layerId);
- if (it == mPreLayerHistoryByLayerId.end())
- return 0;
- return static_cast(it->second.filledCount);
+ return mRenderer->mTemporalHistory.AvailableCountForLayer(layerId);
}
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);
+ mRenderer->mTemporalHistory.BindSamplers(state, currentSourceTexture, historyCap);
}
void OpenGLComposite::renderEffect()
@@ -1750,9 +1301,9 @@ void OpenGLComposite::renderEffect()
PollRuntimeChanges();
const bool hasInputSource = !mHasNoInputSource;
- if (hasInputSource && mFastTransferExtensionAvailable)
+ if (hasInputSource && mRenderer->mFastTransferExtensionAvailable)
{
- // Signal that we're about to draw using mCaptureTexture onto mFBOTexture.
+ // Signal that we're about to draw using mRenderer->mCaptureTexture onto mRenderer->mFBOTexture.
VideoFrameTransfer::beginTextureInUse(VideoFrameTransfer::CPUtoGPU);
}
@@ -1764,43 +1315,39 @@ void OpenGLComposite::renderEffect()
}
else
{
- glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFrameBuf);
+ glBindFramebuffer(GL_FRAMEBUFFER, mRenderer->mDecodeFrameBuf);
glViewport(0, 0, mInputFrameWidth, mInputFrameHeight);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
const std::vector layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mInputFrameWidth, mInputFrameHeight) : std::vector();
- if (layerStates.empty() || mLayerPrograms.empty())
+ if (layerStates.empty() || mRenderer->mLayerPrograms.empty())
{
- glBindFramebuffer(GL_READ_FRAMEBUFFER, mDecodeFrameBuf);
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mIdFrameBuf);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->mDecodeFrameBuf);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer->mIdFrameBuf);
glBlitFramebuffer(0, 0, mInputFrameWidth, mInputFrameHeight, 0, 0, mInputFrameWidth, mInputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
- glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
+ glBindFramebuffer(GL_FRAMEBUFFER, mRenderer->mIdFrameBuf);
}
else
{
- GLuint sourceTexture = mDecodedTexture;
- GLuint sourceFrameBuffer = mDecodeFrameBuf;
- for (std::size_t index = 0; index < layerStates.size() && index < mLayerPrograms.size(); ++index)
+ GLuint sourceTexture = mRenderer->mDecodedTexture;
+ GLuint sourceFrameBuffer = mRenderer->mDecodeFrameBuf;
+ for (std::size_t index = 0; index < layerStates.size() && index < mRenderer->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]);
+ renderShaderProgram(sourceTexture, writeToMain ? mRenderer->mIdFrameBuf : mRenderer->mLayerTempFrameBuf, mRenderer->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;
+ mRenderer->mTemporalHistory.PushPreLayerFramebuffer(layerStates[index].layerId, sourceFrameBuffer, mInputFrameWidth, mInputFrameHeight);
+ sourceTexture = writeToMain ? mRenderer->mFBOTexture : mRenderer->mLayerTempTexture;
+ sourceFrameBuffer = writeToMain ? mRenderer->mIdFrameBuf : mRenderer->mLayerTempFrameBuf;
}
}
- pushFramebufferToHistoryRing(mDecodeFrameBuf, mSourceHistoryRing);
+ mRenderer->mTemporalHistory.PushSourceFramebuffer(mRenderer->mDecodeFrameBuf, mInputFrameWidth, mInputFrameHeight);
- if (hasInputSource && mFastTransferExtensionAvailable)
+ if (hasInputSource && mRenderer->mFastTransferExtensionAvailable)
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
}
@@ -1820,7 +1367,7 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati
glBindTexture(GL_TEXTURE_2D, sourceTexture);
bindHistorySamplers(state, sourceTexture);
bindLayerTextureAssets(layerProgram);
- glBindVertexArray(mFullscreenVAO);
+ glBindVertexArray(mRenderer->mFullscreenVAO);
glUseProgram(layerProgram.program);
updateGlobalParamsBuffer(state, sourceHistoryAvailableCount(), temporalHistoryAvailableCountForLayer(state.layerId));
glDrawArrays(GL_TRIANGLES, 0, 3);
@@ -1847,16 +1394,16 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati
void OpenGLComposite::renderDecodePass()
{
- glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFrameBuf);
+ glBindFramebuffer(GL_FRAMEBUFFER, mRenderer->mDecodeFrameBuf);
glViewport(0, 0, mInputFrameWidth, mInputFrameHeight);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0 + kPackedVideoTextureUnit);
- glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
- glBindVertexArray(mFullscreenVAO);
- glUseProgram(mDecodeProgram);
+ glBindTexture(GL_TEXTURE_2D, mRenderer->mCaptureTexture);
+ glBindVertexArray(mRenderer->mFullscreenVAO);
+ glUseProgram(mRenderer->mDecodeProgram);
- const GLint packedResolutionLocation = glGetUniformLocation(mDecodeProgram, "uPackedVideoResolution");
- const GLint decodedResolutionLocation = glGetUniformLocation(mDecodeProgram, "uDecodedVideoResolution");
+ const GLint packedResolutionLocation = glGetUniformLocation(mRenderer->mDecodeProgram, "uPackedVideoResolution");
+ const GLint decodedResolutionLocation = glGetUniformLocation(mRenderer->mDecodeProgram, "uDecodedVideoResolution");
if (packedResolutionLocation >= 0)
glUniform2f(packedResolutionLocation, static_cast(mInputFrameWidth / 2), static_cast(mInputFrameHeight));
if (decodedResolutionLocation >= 0)
@@ -1979,17 +1526,17 @@ bool OpenGLComposite::updateGlobalParamsBuffer(const RuntimeRenderState& state,
buffer.resize(AlignStd140(buffer.size(), 16), 0);
- glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsUBO);
- if (mGlobalParamsUBOSize != static_cast(buffer.size()))
+ glBindBuffer(GL_UNIFORM_BUFFER, mRenderer->mGlobalParamsUBO);
+ if (mRenderer->mGlobalParamsUBOSize != static_cast(buffer.size()))
{
glBufferData(GL_UNIFORM_BUFFER, static_cast(buffer.size()), buffer.data(), GL_DYNAMIC_DRAW);
- mGlobalParamsUBOSize = static_cast(buffer.size());
+ mRenderer->mGlobalParamsUBOSize = static_cast(buffer.size());
}
else
{
glBufferSubData(GL_UNIFORM_BUFFER, 0, static_cast(buffer.size()), buffer.data());
}
- glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
+ glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mRenderer->mGlobalParamsUBO);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
return true;
@@ -1997,12 +1544,13 @@ bool OpenGLComposite::updateGlobalParamsBuffer(const RuntimeRenderState& state,
bool OpenGLComposite::CheckOpenGLExtensions()
{
- mFastTransferExtensionAvailable = VideoFrameTransfer::checkFastMemoryTransferAvailable();
+ mRenderer->mFastTransferExtensionAvailable = VideoFrameTransfer::checkFastMemoryTransferAvailable();
- if (!mFastTransferExtensionAvailable)
+ if (!mRenderer->mFastTransferExtensionAvailable)
OutputDebugStringA("Fast memory transfer extension not available, using regular OpenGL transfer fallback instead\n");
return true;
}
////////////////////////////////////////////
+
diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h
index 717395c..c2afd3a 100644
--- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h
+++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h
@@ -53,6 +53,7 @@
#include "DeckLinkAPI_h.h"
#include "GLExtensions.h"
+#include "OpenGLRenderer.h"
#include "RuntimeHost.h"
#include
@@ -101,6 +102,7 @@ public:
private:
void resizeWindow(int width, int height);
bool CheckOpenGLExtensions();
+ using LayerProgram = OpenGLRenderer::LayerProgram;
CaptureDelegate* mCaptureDelegate;
PlayoutDelegate* mPlayoutDelegate;
@@ -132,82 +134,11 @@ private:
bool mDeckLinkExternalKeyingActive;
std::string mDeckLinkStatusMessage;
- // OpenGL data
- bool mFastTransferExtensionAvailable;
- GLuint mCaptureTexture;
- GLuint mDecodedTexture;
- GLuint mLayerTempTexture;
- GLuint mFBOTexture;
- GLuint mOutputTexture;
- GLuint mUnpinnedTextureBuffer;
- GLuint mDecodeFrameBuf;
- GLuint mLayerTempFrameBuf;
- GLuint mIdFrameBuf;
- GLuint mOutputFrameBuf;
- GLuint mIdColorBuf;
- GLuint mIdDepthBuf;
- GLuint mFullscreenVAO;
- GLuint mGlobalParamsUBO;
- GLuint mDecodeProgram;
- GLuint mDecodeVertexShader;
- GLuint mDecodeFragmentShader;
- GLsizeiptr mGlobalParamsUBOSize;
- int mViewWidth;
- int mViewHeight;
+ std::unique_ptr mRenderer;
std::unique_ptr mRuntimeHost;
std::unique_ptr mControlServer;
std::unique_ptr mOscServer;
- struct LayerProgram
- {
- struct TextureBinding
- {
- std::string samplerName;
- std::filesystem::path sourcePath;
- GLuint texture = 0;
- };
-
- struct TextBinding
- {
- std::string parameterId;
- std::string samplerName;
- std::string fontId;
- GLuint texture = 0;
- std::string renderedText;
- unsigned renderedWidth = 0;
- unsigned renderedHeight = 0;
- };
-
- std::string layerId;
- std::string shaderId;
- GLuint shaderTextureBase = 0;
- GLuint program = 0;
- GLuint vertexShader = 0;
- GLuint fragmentShader = 0;
- std::vector textureBindings;
- std::vector textBindings;
- };
- std::vector mLayerPrograms;
-
- struct HistorySlot
- {
- GLuint texture = 0;
- GLuint framebuffer = 0;
- };
-
- struct HistoryRing
- {
- std::vector slots;
- std::size_t nextWriteIndex = 0;
- std::size_t filledCount = 0;
- unsigned effectiveLength = 0;
- TemporalHistorySource historySource = TemporalHistorySource::None;
- };
-
- HistoryRing mSourceHistoryRing;
- std::map mPreLayerHistoryByLayerId;
- bool mTemporalHistoryNeedsReset;
-
bool InitOpenGLState();
bool compileLayerPrograms(int errorMessageSize, char* errorMessage);
bool compileSingleLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
@@ -226,13 +157,8 @@ private:
bool updateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength);
bool validateTemporalTextureUnitBudget(const std::vector& layerStates, std::string& error) const;
bool ensureTemporalHistoryResources(const std::vector& 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;
};
diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.cpp
new file mode 100644
index 0000000..ee6c7a2
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.cpp
@@ -0,0 +1,266 @@
+#include "OpenGLRenderer.h"
+
+#include "GlRenderConstants.h"
+
+namespace
+{
+ void ConfigureFrameTexture(unsigned width, unsigned height)
+ {
+ 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, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
+ }
+}
+
+bool OpenGLRenderer::InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned outputFrameWidth, unsigned outputFrameHeight, std::string& error)
+{
+ glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
+ glDisable(GL_DEPTH_TEST);
+
+ if (!mFastTransferExtensionAvailable)
+ glGenBuffers(1, &mUnpinnedTextureBuffer);
+
+ glGenTextures(1, &mCaptureTexture);
+ glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
+ ConfigureFrameTexture(inputFrameWidth / 2, inputFrameHeight);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glGenTextures(1, &mDecodedTexture);
+ glBindTexture(GL_TEXTURE_2D, mDecodedTexture);
+ ConfigureFrameTexture(inputFrameWidth, inputFrameHeight);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glGenTextures(1, &mLayerTempTexture);
+ glBindTexture(GL_TEXTURE_2D, mLayerTempTexture);
+ ConfigureFrameTexture(inputFrameWidth, inputFrameHeight);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glGenFramebuffers(1, &mDecodeFrameBuf);
+ glGenFramebuffers(1, &mLayerTempFrameBuf);
+ glGenFramebuffers(1, &mIdFrameBuf);
+ glGenFramebuffers(1, &mOutputFrameBuf);
+ glGenRenderbuffers(1, &mIdColorBuf);
+ glGenRenderbuffers(1, &mIdDepthBuf);
+ glGenVertexArrays(1, &mFullscreenVAO);
+ glGenBuffers(1, &mGlobalParamsUBO);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFrameBuf);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mDecodedTexture, 0);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ {
+ error = "Cannot initialize decode framebuffer.";
+ return false;
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, mLayerTempFrameBuf);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mLayerTempTexture, 0);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ {
+ error = "Cannot initialize layer framebuffer.";
+ return false;
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
+ glGenTextures(1, &mFBOTexture);
+ glBindTexture(GL_TEXTURE_2D, mFBOTexture);
+ ConfigureFrameTexture(inputFrameWidth, inputFrameHeight);
+
+ glBindRenderbuffer(GL_RENDERBUFFER, mIdDepthBuf);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, inputFrameWidth, inputFrameHeight);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, mIdDepthBuf);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFBOTexture, 0);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ {
+ error = "Cannot initialize framebuffer.";
+ return false;
+ }
+
+ glGenTextures(1, &mOutputTexture);
+ glBindTexture(GL_TEXTURE_2D, mOutputTexture);
+ ConfigureFrameTexture(outputFrameWidth, outputFrameHeight);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, mOutputFrameBuf);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mOutputTexture, 0);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
+ {
+ error = "Cannot initialize output framebuffer.";
+ return false;
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glBindVertexArray(mFullscreenVAO);
+ glBindVertexArray(0);
+ glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsUBO);
+ glBufferData(GL_UNIFORM_BUFFER, 1024, NULL, GL_DYNAMIC_DRAW);
+ glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
+ glBindBuffer(GL_UNIFORM_BUFFER, 0);
+
+ return true;
+}
+
+void OpenGLRenderer::ResizeView(int width, int height)
+{
+ mViewWidth = width;
+ mViewHeight = height;
+}
+
+void OpenGLRenderer::PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigned outputFrameHeight)
+{
+ int destWidth = mViewWidth;
+ int destHeight = mViewHeight;
+ int destX = 0;
+ int destY = 0;
+
+ if (outputFrameWidth > 0 && outputFrameHeight > 0 && mViewWidth > 0 && mViewHeight > 0)
+ {
+ const double frameAspect = static_cast(outputFrameWidth) / static_cast(outputFrameHeight);
+ const double viewAspect = static_cast(mViewWidth) / static_cast(mViewHeight);
+
+ if (viewAspect > frameAspect)
+ {
+ destHeight = mViewHeight;
+ destWidth = static_cast(destHeight * frameAspect + 0.5);
+ destX = (mViewWidth - destWidth) / 2;
+ }
+ else
+ {
+ destWidth = mViewWidth;
+ destHeight = static_cast(destWidth / frameAspect + 0.5);
+ destY = (mViewHeight - destHeight) / 2;
+ }
+ }
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, mOutputFrameBuf);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ glViewport(0, 0, mViewWidth, mViewHeight);
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glBlitFramebuffer(0, 0, outputFrameWidth, outputFrameHeight, destX, destY, destX + destWidth, destY + destHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+
+ SwapBuffers(hdc);
+}
+
+void OpenGLRenderer::DestroyResources()
+{
+ if (mFullscreenVAO != 0)
+ glDeleteVertexArrays(1, &mFullscreenVAO);
+ if (mGlobalParamsUBO != 0)
+ glDeleteBuffers(1, &mGlobalParamsUBO);
+ if (mDecodeFrameBuf != 0)
+ glDeleteFramebuffers(1, &mDecodeFrameBuf);
+ if (mLayerTempFrameBuf != 0)
+ glDeleteFramebuffers(1, &mLayerTempFrameBuf);
+ if (mIdFrameBuf != 0)
+ glDeleteFramebuffers(1, &mIdFrameBuf);
+ if (mOutputFrameBuf != 0)
+ glDeleteFramebuffers(1, &mOutputFrameBuf);
+ if (mIdColorBuf != 0)
+ glDeleteRenderbuffers(1, &mIdColorBuf);
+ if (mIdDepthBuf != 0)
+ glDeleteRenderbuffers(1, &mIdDepthBuf);
+ if (mCaptureTexture != 0)
+ glDeleteTextures(1, &mCaptureTexture);
+ if (mDecodedTexture != 0)
+ glDeleteTextures(1, &mDecodedTexture);
+ if (mLayerTempTexture != 0)
+ glDeleteTextures(1, &mLayerTempTexture);
+ if (mFBOTexture != 0)
+ glDeleteTextures(1, &mFBOTexture);
+ if (mOutputTexture != 0)
+ glDeleteTextures(1, &mOutputTexture);
+ if (mUnpinnedTextureBuffer != 0)
+ glDeleteBuffers(1, &mUnpinnedTextureBuffer);
+
+ mFullscreenVAO = 0;
+ mGlobalParamsUBO = 0;
+ mDecodeFrameBuf = 0;
+ mLayerTempFrameBuf = 0;
+ mIdFrameBuf = 0;
+ mOutputFrameBuf = 0;
+ mIdColorBuf = 0;
+ mIdDepthBuf = 0;
+ mCaptureTexture = 0;
+ mDecodedTexture = 0;
+ mLayerTempTexture = 0;
+ mFBOTexture = 0;
+ mOutputTexture = 0;
+ mUnpinnedTextureBuffer = 0;
+ mGlobalParamsUBOSize = 0;
+
+ mTemporalHistory.DestroyResources();
+ DestroyLayerPrograms();
+ DestroyDecodeShaderProgram();
+}
+
+void OpenGLRenderer::DestroySingleLayerProgram(LayerProgram& layerProgram)
+{
+ for (LayerProgram::TextureBinding& binding : layerProgram.textureBindings)
+ {
+ if (binding.texture != 0)
+ {
+ glDeleteTextures(1, &binding.texture);
+ binding.texture = 0;
+ }
+ }
+ layerProgram.textureBindings.clear();
+
+ for (LayerProgram::TextBinding& binding : layerProgram.textBindings)
+ {
+ if (binding.texture != 0)
+ {
+ glDeleteTextures(1, &binding.texture);
+ binding.texture = 0;
+ }
+ }
+ layerProgram.textBindings.clear();
+
+ if (layerProgram.program != 0)
+ {
+ glDeleteProgram(layerProgram.program);
+ layerProgram.program = 0;
+ }
+
+ if (layerProgram.fragmentShader != 0)
+ {
+ glDeleteShader(layerProgram.fragmentShader);
+ layerProgram.fragmentShader = 0;
+ }
+
+ if (layerProgram.vertexShader != 0)
+ {
+ glDeleteShader(layerProgram.vertexShader);
+ layerProgram.vertexShader = 0;
+ }
+}
+
+void OpenGLRenderer::DestroyLayerPrograms()
+{
+ for (LayerProgram& layerProgram : mLayerPrograms)
+ DestroySingleLayerProgram(layerProgram);
+ mLayerPrograms.clear();
+}
+
+void OpenGLRenderer::DestroyDecodeShaderProgram()
+{
+ if (mDecodeProgram != 0)
+ {
+ glDeleteProgram(mDecodeProgram);
+ mDecodeProgram = 0;
+ }
+
+ if (mDecodeFragmentShader != 0)
+ {
+ glDeleteShader(mDecodeFragmentShader);
+ mDecodeFragmentShader = 0;
+ }
+
+ if (mDecodeVertexShader != 0)
+ {
+ glDeleteShader(mDecodeVertexShader);
+ mDecodeVertexShader = 0;
+ }
+}
diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.h
new file mode 100644
index 0000000..dab9a76
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include "GLExtensions.h"
+#include "ShaderTypes.h"
+#include "TemporalHistoryBuffers.h"
+
+#include
+
+#include
+#include
+#include
+#include
+
+class OpenGLRenderer
+{
+public:
+ struct LayerProgram
+ {
+ struct TextureBinding
+ {
+ std::string samplerName;
+ std::filesystem::path sourcePath;
+ GLuint texture = 0;
+ };
+
+ struct TextBinding
+ {
+ std::string parameterId;
+ std::string samplerName;
+ std::string fontId;
+ GLuint texture = 0;
+ std::string renderedText;
+ unsigned renderedWidth = 0;
+ unsigned renderedHeight = 0;
+ };
+
+ std::string layerId;
+ std::string shaderId;
+ GLuint shaderTextureBase = 0;
+ GLuint program = 0;
+ GLuint vertexShader = 0;
+ GLuint fragmentShader = 0;
+ std::vector textureBindings;
+ std::vector textBindings;
+ };
+
+ bool mFastTransferExtensionAvailable = false;
+ GLuint mCaptureTexture = 0;
+ GLuint mDecodedTexture = 0;
+ GLuint mLayerTempTexture = 0;
+ GLuint mFBOTexture = 0;
+ GLuint mOutputTexture = 0;
+ GLuint mUnpinnedTextureBuffer = 0;
+ GLuint mDecodeFrameBuf = 0;
+ GLuint mLayerTempFrameBuf = 0;
+ GLuint mIdFrameBuf = 0;
+ GLuint mOutputFrameBuf = 0;
+ GLuint mIdColorBuf = 0;
+ GLuint mIdDepthBuf = 0;
+ GLuint mFullscreenVAO = 0;
+ GLuint mGlobalParamsUBO = 0;
+ GLuint mDecodeProgram = 0;
+ GLuint mDecodeVertexShader = 0;
+ GLuint mDecodeFragmentShader = 0;
+ GLsizeiptr mGlobalParamsUBOSize = 0;
+ int mViewWidth = 0;
+ int mViewHeight = 0;
+ std::vector mLayerPrograms;
+ TemporalHistoryBuffers mTemporalHistory;
+
+ bool InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned outputFrameWidth, unsigned outputFrameHeight, std::string& error);
+ void ResizeView(int width, int height);
+ void PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigned outputFrameHeight);
+ void DestroyResources();
+ void DestroySingleLayerProgram(LayerProgram& layerProgram);
+ void DestroyLayerPrograms();
+ void DestroyDecodeShaderProgram();
+};
diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/TemporalHistoryBuffers.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/TemporalHistoryBuffers.cpp
new file mode 100644
index 0000000..246fce6
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/gl/TemporalHistoryBuffers.cpp
@@ -0,0 +1,237 @@
+#include "TemporalHistoryBuffers.h"
+
+#include "GlRenderConstants.h"
+#include "ShaderTypes.h"
+
+#include
+#include
+#include
+
+bool TemporalHistoryBuffers::ValidateTextureUnitBudget(const std::vector& layerStates, unsigned historyCap, std::string& error) const
+{
+ unsigned requiredUnits = kSourceHistoryTextureUnitBase;
+ for (const RuntimeRenderState& state : layerStates)
+ {
+ unsigned textTextureCount = 0;
+ for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
+ {
+ if (definition.type == ShaderParameterType::Text)
+ ++textTextureCount;
+ }
+ const unsigned totalShaderTextures = static_cast(state.textureAssets.size()) + textTextureCount;
+ const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + totalShaderTextures;
+ if (layerRequiredUnits > requiredUnits)
+ requiredUnits = layerRequiredUnits;
+ }
+
+ GLint maxTextureUnits = 0;
+ glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
+ const unsigned availableUnits = maxTextureUnits > 0 ? static_cast(maxTextureUnits) : 0u;
+ if (requiredUnits > availableUnits)
+ {
+ std::ostringstream message;
+ message << "The current history and shader texture asset configuration requires " << requiredUnits
+ << " fragment texture units, but only " << maxTextureUnits << " are available.";
+ error = message.str();
+ return false;
+ }
+ return true;
+}
+
+bool TemporalHistoryBuffers::EnsureResources(const std::vector& layerStates, unsigned historyCap, unsigned frameWidth, unsigned frameHeight, std::string& error)
+{
+ 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 (sourceHistoryRing.effectiveLength != sourceHistoryLength)
+ {
+ if (!CreateRing(sourceHistoryRing, sourceHistoryLength, TemporalHistorySource::Source, frameWidth, frameHeight, error))
+ return false;
+ mNeedsReset = true;
+ }
+
+ std::set requiredPreLayerIds;
+ for (const RuntimeRenderState& state : layerStates)
+ {
+ if (!state.isTemporal || state.temporalHistorySource != TemporalHistorySource::PreLayerInput)
+ continue;
+ requiredPreLayerIds.insert(state.layerId);
+ auto historyIt = preLayerHistoryByLayerId.find(state.layerId);
+ if (historyIt == preLayerHistoryByLayerId.end() || historyIt->second.effectiveLength != state.effectiveTemporalHistoryLength)
+ {
+ Ring replacement;
+ if (!CreateRing(replacement, state.effectiveTemporalHistoryLength, TemporalHistorySource::PreLayerInput, frameWidth, frameHeight, error))
+ return false;
+ preLayerHistoryByLayerId[state.layerId] = std::move(replacement);
+ mNeedsReset = true;
+ }
+ }
+
+ for (auto it = preLayerHistoryByLayerId.begin(); it != preLayerHistoryByLayerId.end();)
+ {
+ if (requiredPreLayerIds.find(it->first) == requiredPreLayerIds.end())
+ {
+ DestroyRing(it->second);
+ it = preLayerHistoryByLayerId.erase(it);
+ mNeedsReset = true;
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ if (mNeedsReset)
+ ResetState();
+
+ return true;
+}
+
+bool TemporalHistoryBuffers::CreateRing(Ring& ring, unsigned effectiveLength, TemporalHistorySource historySource, unsigned frameWidth, unsigned frameHeight, std::string& error)
+{
+ DestroyRing(ring);
+ ring.effectiveLength = effectiveLength;
+ ring.historySource = historySource;
+ if (effectiveLength == 0)
+ return true;
+
+ ring.slots.resize(effectiveLength);
+ for (Slot& 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, frameWidth, frameHeight, 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);
+ DestroyRing(ring);
+ return false;
+ }
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ return true;
+}
+
+void TemporalHistoryBuffers::DestroyRing(Ring& ring)
+{
+ for (Slot& 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 TemporalHistoryBuffers::DestroyResources()
+{
+ DestroyRing(sourceHistoryRing);
+ for (auto& historyEntry : preLayerHistoryByLayerId)
+ DestroyRing(historyEntry.second);
+ preLayerHistoryByLayerId.clear();
+ mNeedsReset = true;
+}
+
+void TemporalHistoryBuffers::ResetState()
+{
+ sourceHistoryRing.nextWriteIndex = 0;
+ sourceHistoryRing.filledCount = 0;
+ for (auto& historyEntry : preLayerHistoryByLayerId)
+ {
+ historyEntry.second.nextWriteIndex = 0;
+ historyEntry.second.filledCount = 0;
+ }
+ mNeedsReset = false;
+}
+
+void TemporalHistoryBuffers::PushFramebuffer(GLuint sourceFramebuffer, Ring& ring, unsigned frameWidth, unsigned frameHeight)
+{
+ if (ring.effectiveLength == 0 || ring.slots.empty())
+ return;
+
+ Slot& targetSlot = ring.slots[ring.nextWriteIndex];
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetSlot.framebuffer);
+ glBlitFramebuffer(0, 0, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ ring.nextWriteIndex = (ring.nextWriteIndex + 1) % ring.slots.size();
+ ring.filledCount = std::min(ring.filledCount + 1, ring.slots.size());
+}
+
+void TemporalHistoryBuffers::PushSourceFramebuffer(GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
+{
+ PushFramebuffer(sourceFramebuffer, sourceHistoryRing, frameWidth, frameHeight);
+}
+
+void TemporalHistoryBuffers::PushPreLayerFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
+{
+ auto historyIt = preLayerHistoryByLayerId.find(layerId);
+ if (historyIt != preLayerHistoryByLayerId.end())
+ PushFramebuffer(sourceFramebuffer, historyIt->second, frameWidth, frameHeight);
+}
+
+void TemporalHistoryBuffers::BindSamplers(const RuntimeRenderState& state, GLuint currentSourceTexture, unsigned historyCap)
+{
+ for (unsigned index = 0; index < historyCap; ++index)
+ {
+ glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + index);
+ glBindTexture(GL_TEXTURE_2D, ResolveTexture(sourceHistoryRing, currentSourceTexture, index));
+ }
+
+ const GLuint temporalBase = kSourceHistoryTextureUnitBase + historyCap;
+ const Ring* temporalRing = nullptr;
+ auto it = preLayerHistoryByLayerId.find(state.layerId);
+ if (it != preLayerHistoryByLayerId.end())
+ temporalRing = &it->second;
+
+ for (unsigned index = 0; index < historyCap; ++index)
+ {
+ glActiveTexture(GL_TEXTURE0 + temporalBase + index);
+ glBindTexture(GL_TEXTURE_2D, temporalRing ? ResolveTexture(*temporalRing, currentSourceTexture, index) : currentSourceTexture);
+ }
+ glActiveTexture(GL_TEXTURE0);
+}
+
+GLuint TemporalHistoryBuffers::ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const
+{
+ if (ring.filledCount == 0 || ring.slots.empty())
+ return fallbackTexture;
+
+ const std::size_t clampedOffset = std::min(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 TemporalHistoryBuffers::SourceAvailableCount() const
+{
+ return static_cast(sourceHistoryRing.filledCount);
+}
+
+unsigned TemporalHistoryBuffers::AvailableCountForLayer(const std::string& layerId) const
+{
+ auto it = preLayerHistoryByLayerId.find(layerId);
+ if (it == preLayerHistoryByLayerId.end())
+ return 0;
+ return static_cast(it->second.filledCount);
+}
diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/TemporalHistoryBuffers.h b/apps/LoopThroughWithOpenGLCompositing/gl/TemporalHistoryBuffers.h
new file mode 100644
index 0000000..b8b66fd
--- /dev/null
+++ b/apps/LoopThroughWithOpenGLCompositing/gl/TemporalHistoryBuffers.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "GLExtensions.h"
+#include "ShaderTypes.h"
+
+#include
+
+#include
+#include