From 7f0f60c0e38a7b7661a936e9b36d10b50034c4e9 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Sun, 10 May 2026 23:31:45 +1000 Subject: [PATCH] ore untangling --- CMakeLists.txt | 4 + .../LoopThroughWithOpenGLCompositing.vcxproj | 4 + ...roughWithOpenGLCompositing.vcxproj.filters | 12 ++ .../gl/OpenGLComposite.cpp | 166 +++++++--------- .../gl/OpenGLComposite.h | 17 +- .../gl/RenderEngine.cpp | 180 ++++++++++++++++++ .../gl/RenderEngine.h | 72 +++++++ .../gl/pipeline/OpenGLVideoIOBridge.cpp | 63 +----- .../gl/pipeline/OpenGLVideoIOBridge.h | 21 +- .../videoio/VideoBackend.cpp | 175 +++++++++++++++++ .../videoio/VideoBackend.h | 55 ++++++ 11 files changed, 593 insertions(+), 176 deletions(-) create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h create mode 100644 apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp create mode 100644 apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3eb89ce..9a50320 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,8 @@ set(APP_SOURCES "${APP_DIR}/gl/OpenGLComposite.cpp" "${APP_DIR}/gl/OpenGLComposite.h" "${APP_DIR}/gl/OpenGLCompositeRuntimeControls.cpp" + "${APP_DIR}/gl/RenderEngine.cpp" + "${APP_DIR}/gl/RenderEngine.h" "${APP_DIR}/gl/pipeline/OpenGLRenderPass.cpp" "${APP_DIR}/gl/pipeline/OpenGLRenderPass.h" "${APP_DIR}/gl/pipeline/OpenGLRenderPipeline.cpp" @@ -122,6 +124,8 @@ set(APP_SOURCES "${APP_DIR}/targetver.h" "${APP_DIR}/videoio/VideoIOFormat.cpp" "${APP_DIR}/videoio/VideoIOFormat.h" + "${APP_DIR}/videoio/VideoBackend.cpp" + "${APP_DIR}/videoio/VideoBackend.h" "${APP_DIR}/videoio/VideoIOTypes.h" "${APP_DIR}/videoio/VideoPlayoutScheduler.cpp" "${APP_DIR}/videoio/VideoPlayoutScheduler.h" diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj index e3e3669..e4e4868 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj @@ -186,6 +186,7 @@ + @@ -220,6 +221,7 @@ + @@ -236,6 +238,7 @@ + @@ -272,6 +275,7 @@ + diff --git a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters index cf8f9c8..b745b94 100644 --- a/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters +++ b/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.vcxproj.filters @@ -48,6 +48,9 @@ Source Files + + Source Files + Source Files @@ -138,6 +141,9 @@ Source Files + + Source Files + Source Files @@ -179,6 +185,9 @@ Header Files + + Header Files + Header Files @@ -287,6 +296,9 @@ Header Files + + Header Files + Header Files diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index 4e207bd..d24f81e 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -1,16 +1,13 @@ #include "DeckLinkDisplayMode.h" -#include "DeckLinkSession.h" #include "OpenGLComposite.h" #include "GLExtensions.h" #include "GlRenderConstants.h" -#include "OpenGLRenderPass.h" -#include "OpenGLRenderPipeline.h" -#include "OpenGLShaderPrograms.h" -#include "OpenGLVideoIOBridge.h" #include "PngScreenshotWriter.h" +#include "RenderEngine.h" #include "RuntimeParameterUtils.h" #include "RuntimeServices.h" #include "ShaderBuildQueue.h" +#include "VideoBackend.h" #include #include @@ -97,8 +94,6 @@ JsonValue BuildOscCommitValue(const ShaderParameterDefinition& definition, const OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC), - mVideoIO(std::make_unique()), - mRenderer(std::make_unique()), mUseCommittedLayerStates(false), mScreenshotRequested(false) { @@ -106,22 +101,16 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) : mRuntimeHost = std::make_unique(); mRuntimeStore = std::make_unique(*mRuntimeHost); mRuntimeSnapshotProvider = std::make_unique(*mRuntimeHost); - mRenderPipeline = std::make_unique( - *mRenderer, + mRenderEngine = std::make_unique( *mRuntimeHost, + *mRuntimeSnapshotProvider, + pMutex, + hGLDC, + hGLRC, [this]() { renderEffect(); }, [this]() { ProcessScreenshotRequest(); }, [this]() { paintGL(false); }); - mVideoIOBridge = std::make_unique( - *mVideoIO, - *mRenderer, - *mRenderPipeline, - *mRuntimeHost, - pMutex, - hGLDC, - hGLRC); - mRenderPass = std::make_unique(*mRenderer); - mShaderPrograms = std::make_unique(*mRenderer, *mRuntimeHost, *mRuntimeSnapshotProvider); + mVideoBackend = std::make_unique(*mRenderEngine, mRuntimeHost->GetHealthTelemetry()); mShaderBuildQueue = std::make_unique(*mRuntimeSnapshotProvider); mRuntimeServices = std::make_unique(); } @@ -132,8 +121,8 @@ OpenGLComposite::~OpenGLComposite() mRuntimeServices->Stop(); if (mShaderBuildQueue) mShaderBuildQueue->Stop(); - mVideoIO->ReleaseResources(); - mRenderer->DestroyResources(); + if (mVideoBackend) + mVideoBackend->ReleaseResources(); DeleteCriticalSection(&pMutex); } @@ -173,7 +162,7 @@ bool OpenGLComposite::InitVideoIO() } } - if (!mVideoIO->DiscoverDevicesAndModes(videoModes, initFailureReason)) + if (!mVideoBackend->DiscoverDevicesAndModes(videoModes, initFailureReason)) { const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application." ? "This application requires the DeckLink drivers installed." @@ -182,7 +171,7 @@ bool OpenGLComposite::InitVideoIO() return false; } const bool outputAlphaRequired = mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(); - if (!mVideoIO->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason)) + if (!mVideoBackend->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason)) goto error; if (! CheckOpenGLExtensions()) @@ -197,38 +186,38 @@ bool OpenGLComposite::InitVideoIO() goto error; } - PublishVideoIOStatus(mVideoIO->OutputModelName().empty() + PublishVideoIOStatus(mVideoBackend->OutputModelName().empty() ? "DeckLink output device selected." - : ("Selected output device: " + mVideoIO->OutputModelName())); + : ("Selected output device: " + mVideoBackend->OutputModelName())); // Resize window to match output video frame, but scale large formats down by half for viewing. - if (mVideoIO->OutputFrameWidth() < 1920) - resizeWindow(mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight()); + if (mVideoBackend->OutputFrameWidth() < 1920) + resizeWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight()); else - resizeWindow(mVideoIO->OutputFrameWidth() / 2, mVideoIO->OutputFrameHeight() / 2); + resizeWindow(mVideoBackend->OutputFrameWidth() / 2, mVideoBackend->OutputFrameHeight() / 2); - if (!mVideoIO->ConfigureInput([this](const VideoIOFrame& frame) { mVideoIOBridge->VideoFrameArrived(frame); }, videoModes.input, initFailureReason)) + if (!mVideoBackend->ConfigureInput(videoModes.input, initFailureReason)) { goto error; } - if (!mVideoIO->HasInputDevice() && mRuntimeHost) + if (!mVideoBackend->HasInputDevice() && mRuntimeHost) { - mRuntimeHost->SetSignalStatus(false, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), mVideoIO->InputDisplayModeName()); + mRuntimeHost->SetSignalStatus(false, mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), mVideoBackend->InputDisplayModeName()); } - if (!mVideoIO->ConfigureOutput([this](const VideoIOCompletion& completion) { mVideoIOBridge->PlayoutFrameCompleted(completion); }, videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason)) + if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason)) { goto error; } - PublishVideoIOStatus(mVideoIO->StatusMessage()); + PublishVideoIOStatus(mVideoBackend->StatusMessage()); return true; error: if (!initFailureReason.empty()) MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR); - mVideoIO->ReleaseResources(); + mVideoBackend->ReleaseResources(); return false; } @@ -252,23 +241,21 @@ void OpenGLComposite::paintGL(bool force) } } - if (!TryEnterCriticalSection(&pMutex)) + if (!mRenderEngine->TryPresentToWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight())) { ValidateRect(hGLWnd, NULL); return; } - mRenderer->PresentToWindow(hGLDC, mVideoIO->OutputFrameWidth(), mVideoIO->OutputFrameHeight()); mLastPreviewPresentTime = std::chrono::steady_clock::now(); ValidateRect(hGLWnd, NULL); - LeaveCriticalSection(&pMutex); } 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(). - mRenderer->ResizeView(width, height); + mRenderEngine->ResizeView(width, height); } void OpenGLComposite::resizeWindow(int width, int height) @@ -286,17 +273,17 @@ void OpenGLComposite::PublishVideoIOStatus(const std::string& statusMessage) return; if (!statusMessage.empty()) - mVideoIO->SetStatusMessage(statusMessage); + mVideoBackend->SetStatusMessage(statusMessage); mRuntimeHost->SetVideoIOStatus( "decklink", - mVideoIO->OutputModelName(), - mVideoIO->SupportsInternalKeying(), - mVideoIO->SupportsExternalKeying(), - mVideoIO->KeyerInterfaceAvailable(), + mVideoBackend->OutputModelName(), + mVideoBackend->SupportsInternalKeying(), + mVideoBackend->SupportsExternalKeying(), + mVideoBackend->KeyerInterfaceAvailable(), mRuntimeStore ? mRuntimeStore->IsExternalKeyingConfigured() : false, - mVideoIO->ExternalKeyingActive(), - mVideoIO->StatusMessage()); + mVideoBackend->ExternalKeyingActive(), + mVideoBackend->StatusMessage()); } bool OpenGLComposite::InitOpenGLState() @@ -319,41 +306,41 @@ bool OpenGLComposite::InitOpenGLState() // Prepare the runtime shader program generated from the active shader package. char compilerErrorMessage[1024]; - if (!mShaderPrograms->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage)) + if (!mRenderEngine->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage)) { MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK); return false; } - if (!mShaderPrograms->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage)) + if (!mRenderEngine->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage)) { MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK); return false; } std::string rendererError; - if (!mRenderer->InitializeResources( - mVideoIO->InputFrameWidth(), - mVideoIO->InputFrameHeight(), - mVideoIO->CaptureTextureWidth(), - mVideoIO->OutputFrameWidth(), - mVideoIO->OutputFrameHeight(), - mVideoIO->OutputPackTextureWidth(), + if (!mRenderEngine->InitializeResources( + mVideoBackend->InputFrameWidth(), + mVideoBackend->InputFrameHeight(), + mVideoBackend->CaptureTextureWidth(), + mVideoBackend->OutputFrameWidth(), + mVideoBackend->OutputFrameHeight(), + mVideoBackend->OutputPackTextureWidth(), rendererError)) { MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK); return false; } - if (!mShaderPrograms->CompileLayerPrograms(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage)) + if (!mRenderEngine->CompileLayerPrograms(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage)) { MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK); return false; } - mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates(); + mCachedLayerRenderStates = mRenderEngine->CommittedLayerStates(); mUseCommittedLayerStates = false; - mShaderPrograms->ResetTemporalHistoryState(); - mShaderPrograms->ResetShaderFeedbackState(); + mRenderEngine->ResetTemporalHistoryState(); + mRenderEngine->ResetShaderFeedbackState(); broadcastRuntimeState(); mRuntimeServices->BeginPolling(*mRuntimeHost); @@ -362,7 +349,7 @@ bool OpenGLComposite::InitOpenGLState() bool OpenGLComposite::Start() { - return mVideoIO->Start(); + return mVideoBackend->Start(); } bool OpenGLComposite::Stop() @@ -370,8 +357,8 @@ bool OpenGLComposite::Stop() if (mRuntimeServices) mRuntimeServices->Stop(); - const bool wasExternalKeyingActive = mVideoIO->ExternalKeyingActive(); - mVideoIO->Stop(); + const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive(); + mVideoBackend->Stop(); if (wasExternalKeyingActive) PublishVideoIOStatus("External keying has been disabled."); @@ -587,19 +574,19 @@ void OpenGLComposite::renderEffect() mOscOverlayStates.erase(overlayKey); }; - const bool hasInputSource = mVideoIO->HasInputSource(); + const bool hasInputSource = mVideoBackend->HasInputSource(); std::vector layerStates; if (mUseCommittedLayerStates) { - layerStates = mShaderPrograms->CommittedLayerStates(); + layerStates = mRenderEngine->CommittedLayerStates(); applyOscOverlays(layerStates, false); if (mRuntimeSnapshotProvider) mRuntimeSnapshotProvider->RefreshDynamicRenderStateFields(layerStates); } else if (mRuntimeSnapshotProvider) { - const unsigned renderWidth = mVideoIO->InputFrameWidth(); - const unsigned renderHeight = mVideoIO->InputFrameHeight(); + const unsigned renderWidth = mVideoBackend->InputFrameWidth(); + const unsigned renderHeight = mVideoBackend->InputFrameHeight(); const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider->GetVersions(); const bool renderStateCacheValid = !mCachedLayerRenderStates.empty() && @@ -650,20 +637,14 @@ void OpenGLComposite::renderEffect() } } const unsigned historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0; - mRenderPass->Render( + mRenderEngine->RenderLayerStack( hasInputSource, layerStates, - mVideoIO->InputFrameWidth(), - mVideoIO->InputFrameHeight(), - mVideoIO->CaptureTextureWidth(), - mVideoIO->InputPixelFormat(), - historyCap, - [this](const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) { - return mShaderPrograms->UpdateTextBindingTexture(state, textBinding, error); - }, - [this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) { - return mShaderPrograms->UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable); - }); + mVideoBackend->InputFrameWidth(), + mVideoBackend->InputFrameHeight(), + mVideoBackend->CaptureTextureWidth(), + mVideoBackend->InputPixelFormat(), + historyCap); } void OpenGLComposite::ProcessScreenshotRequest() @@ -671,21 +652,16 @@ void OpenGLComposite::ProcessScreenshotRequest() if (!mScreenshotRequested.exchange(false)) return; - const unsigned width = mVideoIO ? mVideoIO->OutputFrameWidth() : 0; - const unsigned height = mVideoIO ? mVideoIO->OutputFrameHeight() : 0; + const unsigned width = mVideoBackend ? mVideoBackend->OutputFrameWidth() : 0; + const unsigned height = mVideoBackend ? mVideoBackend->OutputFrameHeight() : 0; if (width == 0 || height == 0) return; - std::vector bottomUpPixels(static_cast(width) * height * 4); + std::vector bottomUpPixels; + if (!mRenderEngine->ReadOutputFrameRgba(width, height, bottomUpPixels)) + return; std::vector topDownPixels(bottomUpPixels.size()); - glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer->OutputFramebuffer()); - glReadBuffer(GL_COLOR_ATTACHMENT0); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data()); - glPixelStorei(GL_PACK_ALIGNMENT, 4); - const std::size_t rowBytes = static_cast(width) * 4; for (unsigned y = 0; y < height; ++y) { @@ -752,7 +728,7 @@ bool OpenGLComposite::ProcessRuntimePollResults() return true; char compilerErrorMessage[1024] = {}; - if (!mShaderPrograms->CommitPreparedLayerPrograms(readyBuild, mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage)) + if (!mRenderEngine->CommitPreparedLayerPrograms(readyBuild, mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage)) { mRuntimeStore->SetCompileStatus(false, compilerErrorMessage); mUseCommittedLayerStates = true; @@ -762,10 +738,10 @@ bool OpenGLComposite::ProcessRuntimePollResults() } mUseCommittedLayerStates = false; - mCachedLayerRenderStates = mShaderPrograms->CommittedLayerStates(); - mShaderPrograms->ResetTemporalHistoryState(); + mCachedLayerRenderStates = mRenderEngine->CommittedLayerStates(); + mRenderEngine->ResetTemporalHistoryState(); if (!mPreserveFeedbackOnNextShaderBuild) - mShaderPrograms->ResetShaderFeedbackState(); + mRenderEngine->ResetShaderFeedbackState(); mPreserveFeedbackOnNextShaderBuild = false; broadcastRuntimeState(); return true; @@ -780,13 +756,13 @@ bool OpenGLComposite::ProcessRuntimePollResults() void OpenGLComposite::RequestShaderBuild() { - if (!mShaderBuildQueue || !mVideoIO) + if (!mShaderBuildQueue || !mVideoBackend) return; mUseCommittedLayerStates = true; if (mRuntimeHost) mRuntimeStore->ClearReloadRequest(); - mShaderBuildQueue->RequestBuild(mVideoIO->InputFrameWidth(), mVideoIO->InputFrameHeight()); + mShaderBuildQueue->RequestBuild(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight()); } void OpenGLComposite::broadcastRuntimeState() @@ -797,8 +773,8 @@ void OpenGLComposite::broadcastRuntimeState() void OpenGLComposite::resetTemporalHistoryState() { - mShaderPrograms->ResetTemporalHistoryState(); - mShaderPrograms->ResetShaderFeedbackState(); + mRenderEngine->ResetTemporalHistoryState(); + mRenderEngine->ResetShaderFeedbackState(); } bool OpenGLComposite::CheckOpenGLExtensions() diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h index 1e41bab..a50c5a8 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h @@ -12,7 +12,6 @@ #include #include "GLExtensions.h" -#include "OpenGLRenderer.h" #include "RuntimeHost.h" #include "RuntimeSnapshotProvider.h" #include "RuntimeStore.h" @@ -27,13 +26,10 @@ #include #include -class VideoIODevice; -class OpenGLVideoIOBridge; -class OpenGLRenderPass; -class OpenGLRenderPipeline; -class OpenGLShaderPrograms; +class RenderEngine; class RuntimeServices; class ShaderBuildQueue; +class VideoBackend; class OpenGLComposite @@ -74,7 +70,6 @@ private: void resizeWindow(int width, int height); bool CheckOpenGLExtensions(); void PublishVideoIOStatus(const std::string& statusMessage); - using LayerProgram = OpenGLRenderer::LayerProgram; struct OscOverlayState { std::string layerKey; @@ -94,17 +89,13 @@ private: HGLRC hGLRC; CRITICAL_SECTION pMutex; - std::unique_ptr mVideoIO; - std::unique_ptr mRenderer; std::unique_ptr mRuntimeHost; std::unique_ptr mRuntimeStore; std::unique_ptr mRuntimeSnapshotProvider; - std::unique_ptr mVideoIOBridge; - std::unique_ptr mRenderPass; - std::unique_ptr mRenderPipeline; - std::unique_ptr mShaderPrograms; + std::unique_ptr mRenderEngine; std::unique_ptr mShaderBuildQueue; std::unique_ptr mRuntimeServices; + std::unique_ptr mVideoBackend; std::vector mCachedLayerRenderStates; uint64_t mCachedRenderStateVersion = 0; uint64_t mCachedParameterStateVersion = 0; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp new file mode 100644 index 0000000..0e836d4 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp @@ -0,0 +1,180 @@ +#include "RenderEngine.h" + +#include + +RenderEngine::RenderEngine( + RuntimeHost& runtimeHost, + RuntimeSnapshotProvider& runtimeSnapshotProvider, + CRITICAL_SECTION& mutex, + HDC hdc, + HGLRC hglrc, + RenderEffectCallback renderEffect, + ScreenshotCallback screenshotReady, + PreviewPaintCallback previewPaint) : + mRenderer(), + mRenderPass(mRenderer), + mRenderPipeline(mRenderer, runtimeHost, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)), + mShaderPrograms(mRenderer, runtimeHost, runtimeSnapshotProvider), + mMutex(mutex), + mHdc(hdc), + mHglrc(hglrc) +{ +} + +RenderEngine::~RenderEngine() +{ + mRenderer.DestroyResources(); +} + +bool RenderEngine::CompileDecodeShader(int errorMessageSize, char* errorMessage) +{ + return mShaderPrograms.CompileDecodeShader(errorMessageSize, errorMessage); +} + +bool RenderEngine::CompileOutputPackShader(int errorMessageSize, char* errorMessage) +{ + return mShaderPrograms.CompileOutputPackShader(errorMessageSize, errorMessage); +} + +bool RenderEngine::InitializeResources( + unsigned inputFrameWidth, + unsigned inputFrameHeight, + unsigned captureTextureWidth, + unsigned outputFrameWidth, + unsigned outputFrameHeight, + unsigned outputPackTextureWidth, + std::string& error) +{ + return mRenderer.InitializeResources( + inputFrameWidth, + inputFrameHeight, + captureTextureWidth, + outputFrameWidth, + outputFrameHeight, + outputPackTextureWidth, + error); +} + +bool RenderEngine::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage) +{ + return mShaderPrograms.CompileLayerPrograms(inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage); +} + +bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage) +{ + return mShaderPrograms.CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage); +} + +const std::vector& RenderEngine::CommittedLayerStates() const +{ + return mShaderPrograms.CommittedLayerStates(); +} + +void RenderEngine::ResetTemporalHistoryState() +{ + mShaderPrograms.ResetTemporalHistoryState(); +} + +void RenderEngine::ResetShaderFeedbackState() +{ + mShaderPrograms.ResetShaderFeedbackState(); +} + +void RenderEngine::ResizeView(int width, int height) +{ + mRenderer.ResizeView(width, height); +} + +bool RenderEngine::TryPresentToWindow(unsigned outputFrameWidth, unsigned outputFrameHeight) +{ + if (!TryEnterCriticalSection(&mMutex)) + return false; + + mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight); + LeaveCriticalSection(&mMutex); + return true; +} + +bool RenderEngine::TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState) +{ + if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr) + return true; + + const long textureSize = inputFrame.rowBytes * static_cast(inputFrame.height); + if (!TryEnterCriticalSection(&mMutex)) + return false; + + wglMakeCurrent(mHdc, mHglrc); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer()); + glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW); + glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture()); + if (inputFrame.pixelFormat == VideoIOPixelFormat::V210) + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + else + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + wglMakeCurrent(NULL, NULL); + LeaveCriticalSection(&mMutex); + return true; +} + +bool RenderEngine::RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame) +{ + EnterCriticalSection(&mMutex); + wglMakeCurrent(mHdc, mHglrc); + const bool rendered = mRenderPipeline.RenderFrame(context, outputFrame); + wglMakeCurrent(NULL, NULL); + LeaveCriticalSection(&mMutex); + return rendered; +} + +void RenderEngine::RenderLayerStack( + bool hasInputSource, + const std::vector& layerStates, + unsigned inputFrameWidth, + unsigned inputFrameHeight, + unsigned captureTextureWidth, + VideoIOPixelFormat inputPixelFormat, + unsigned historyCap) +{ + mRenderPass.Render( + hasInputSource, + layerStates, + inputFrameWidth, + inputFrameHeight, + captureTextureWidth, + inputPixelFormat, + historyCap, + [this](const RuntimeRenderState& state, OpenGLRenderer::LayerProgram::TextBinding& textBinding, std::string& error) { + return mShaderPrograms.UpdateTextBindingTexture(state, textBinding, error); + }, + [this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) { + return mShaderPrograms.UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable); + }); +} + +bool RenderEngine::ReadOutputFrameRgba(unsigned width, unsigned height, std::vector& bottomUpPixels) +{ + if (width == 0 || height == 0) + return false; + + EnterCriticalSection(&mMutex); + wglMakeCurrent(mHdc, mHglrc); + + bottomUpPixels.resize(static_cast(width) * height * 4); + glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer()); + glReadBuffer(GL_COLOR_ATTACHMENT0); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data()); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + + wglMakeCurrent(NULL, NULL); + LeaveCriticalSection(&mMutex); + return true; +} diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h new file mode 100644 index 0000000..f9007f3 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.h @@ -0,0 +1,72 @@ +#pragma once + +#include "OpenGLRenderPass.h" +#include "OpenGLRenderPipeline.h" +#include "OpenGLRenderer.h" +#include "OpenGLShaderPrograms.h" +#include "RuntimeHost.h" +#include "RuntimeSnapshotProvider.h" + +#include + +#include +#include +#include + +class RenderEngine +{ +public: + using RenderEffectCallback = std::function; + using ScreenshotCallback = std::function; + using PreviewPaintCallback = std::function; + + RenderEngine( + RuntimeHost& runtimeHost, + RuntimeSnapshotProvider& runtimeSnapshotProvider, + CRITICAL_SECTION& mutex, + HDC hdc, + HGLRC hglrc, + RenderEffectCallback renderEffect, + ScreenshotCallback screenshotReady, + PreviewPaintCallback previewPaint); + ~RenderEngine(); + + bool CompileDecodeShader(int errorMessageSize, char* errorMessage); + bool CompileOutputPackShader(int errorMessageSize, char* errorMessage); + bool InitializeResources( + unsigned inputFrameWidth, + unsigned inputFrameHeight, + unsigned captureTextureWidth, + unsigned outputFrameWidth, + unsigned outputFrameHeight, + unsigned outputPackTextureWidth, + std::string& error); + bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); + bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage); + + const std::vector& CommittedLayerStates() const; + void ResetTemporalHistoryState(); + void ResetShaderFeedbackState(); + void ResizeView(int width, int height); + bool TryPresentToWindow(unsigned outputFrameWidth, unsigned outputFrameHeight); + bool TryUploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState); + bool RenderOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame); + void RenderLayerStack( + bool hasInputSource, + const std::vector& layerStates, + unsigned inputFrameWidth, + unsigned inputFrameHeight, + unsigned captureTextureWidth, + VideoIOPixelFormat inputPixelFormat, + unsigned historyCap); + bool ReadOutputFrameRgba(unsigned width, unsigned height, std::vector& bottomUpPixels); + +private: + OpenGLRenderer mRenderer; + OpenGLRenderPass mRenderPass; + OpenGLRenderPipeline mRenderPipeline; + OpenGLShaderPrograms mShaderPrograms; + CRITICAL_SECTION& mMutex; + HDC mHdc; + HGLRC mHglrc; +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp index 11501c8..c819d08 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp @@ -1,26 +1,17 @@ #include "OpenGLVideoIOBridge.h" -#include "OpenGLRenderer.h" -#include "RuntimeHost.h" +#include "HealthTelemetry.h" +#include "RenderEngine.h" #include -#include OpenGLVideoIOBridge::OpenGLVideoIOBridge( VideoIODevice& videoIO, - OpenGLRenderer& renderer, - OpenGLRenderPipeline& renderPipeline, - RuntimeHost& runtimeHost, - CRITICAL_SECTION& mutex, - HDC hdc, - HGLRC hglrc) : + RenderEngine& renderEngine, + HealthTelemetry& healthTelemetry) : mVideoIO(videoIO), - mRenderer(renderer), - mRenderPipeline(renderPipeline), - mRuntimeHost(runtimeHost), - mMutex(mutex), - mHdc(hdc), - mHglrc(hglrc) + mRenderEngine(renderEngine), + mHealthTelemetry(healthTelemetry) { } @@ -46,7 +37,7 @@ void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionRe else if (completionResult == VideoIOCompletionResult::Flushed) ++mFlushedFrameCount; - mRuntimeHost.GetHealthTelemetry().TryRecordFramePacingStats( + mHealthTelemetry.TryRecordFramePacingStats( mCompletionIntervalMilliseconds, mSmoothedCompletionIntervalMilliseconds, mMaxCompletionIntervalMilliseconds, @@ -58,38 +49,12 @@ void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionRe void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame) { const VideoIOState& state = mVideoIO.State(); - mRuntimeHost.GetHealthTelemetry().TryReportSignalStatus(!inputFrame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName); + mHealthTelemetry.TryReportSignalStatus(!inputFrame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName); if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr) return; // don't transfer texture when there's no input - const long textureSize = inputFrame.rowBytes * static_cast(inputFrame.height); - - // Never let input upload stall the playout/render callback. If the GL bridge - // is busy producing an output frame, skip this upload and use the next input. - if (!TryEnterCriticalSection(&mMutex)) - return; - - wglMakeCurrent(mHdc, mHglrc); // make OpenGL context current in this thread - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer()); - glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW); - glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture()); - - // NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data. - if (inputFrame.pixelFormat == VideoIOPixelFormat::V210) - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); - - glBindTexture(GL_TEXTURE_2D, 0); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - - wglMakeCurrent(NULL, NULL); - - LeaveCriticalSection(&mMutex); + mRenderEngine.TryUploadInputFrame(inputFrame, state); } void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& completion) @@ -104,15 +69,7 @@ void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& complet frameContext.videoState = state; frameContext.completion = completion; - EnterCriticalSection(&mMutex); - - // make GL context current in this thread - wglMakeCurrent(mHdc, mHglrc); - - mRenderPipeline.RenderFrame(frameContext, outputFrame); - wglMakeCurrent(NULL, NULL); - - LeaveCriticalSection(&mMutex); + mRenderEngine.RenderOutputFrame(frameContext, outputFrame); mVideoIO.EndOutputFrame(outputFrame); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h index 7cca4f8..9f1db55 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h +++ b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.h @@ -2,24 +2,19 @@ #include "OpenGLRenderPipeline.h" -#include - #include #include -class RuntimeHost; +class HealthTelemetry; +class RenderEngine; class OpenGLVideoIOBridge { public: OpenGLVideoIOBridge( VideoIODevice& videoIO, - OpenGLRenderer& renderer, - OpenGLRenderPipeline& renderPipeline, - RuntimeHost& runtimeHost, - CRITICAL_SECTION& mutex, - HDC hdc, - HGLRC hglrc); + RenderEngine& renderEngine, + HealthTelemetry& healthTelemetry); void VideoFrameArrived(const VideoIOFrame& inputFrame); void PlayoutFrameCompleted(const VideoIOCompletion& completion); @@ -28,12 +23,8 @@ private: void RecordFramePacing(VideoIOCompletionResult completionResult); VideoIODevice& mVideoIO; - OpenGLRenderer& mRenderer; - OpenGLRenderPipeline& mRenderPipeline; - RuntimeHost& mRuntimeHost; - CRITICAL_SECTION& mMutex; - HDC mHdc; - HGLRC mHglrc; + RenderEngine& mRenderEngine; + HealthTelemetry& mHealthTelemetry; std::chrono::steady_clock::time_point mLastPlayoutCompletionTime; double mCompletionIntervalMilliseconds = 0.0; double mSmoothedCompletionIntervalMilliseconds = 0.0; diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp new file mode 100644 index 0000000..8b0a3ec --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.cpp @@ -0,0 +1,175 @@ +#include "VideoBackend.h" + +#include "DeckLinkSession.h" +#include "OpenGLVideoIOBridge.h" +#include "RenderEngine.h" +#include "HealthTelemetry.h" + +VideoBackend::VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry) : + mVideoIODevice(std::make_unique()), + mBridge(std::make_unique(*mVideoIODevice, renderEngine, healthTelemetry)) +{ +} + +VideoBackend::~VideoBackend() +{ + ReleaseResources(); +} + +void VideoBackend::ReleaseResources() +{ + if (mVideoIODevice) + mVideoIODevice->ReleaseResources(); +} + +bool VideoBackend::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) +{ + return mVideoIODevice->DiscoverDevicesAndModes(videoModes, error); +} + +bool VideoBackend::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) +{ + return mVideoIODevice->SelectPreferredFormats(videoModes, outputAlphaRequired, error); +} + +bool VideoBackend::ConfigureInput(const VideoFormat& inputVideoMode, std::string& error) +{ + return mVideoIODevice->ConfigureInput( + [this](const VideoIOFrame& frame) { mBridge->VideoFrameArrived(frame); }, + inputVideoMode, + error); +} + +bool VideoBackend::ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) +{ + return mVideoIODevice->ConfigureOutput( + [this](const VideoIOCompletion& completion) { mBridge->PlayoutFrameCompleted(completion); }, + outputVideoMode, + externalKeyingEnabled, + error); +} + +bool VideoBackend::Start() +{ + return mVideoIODevice->Start(); +} + +bool VideoBackend::Stop() +{ + return mVideoIODevice->Stop(); +} + +const VideoIOState& VideoBackend::State() const +{ + return mVideoIODevice->State(); +} + +VideoIOState& VideoBackend::MutableState() +{ + return mVideoIODevice->MutableState(); +} + +bool VideoBackend::BeginOutputFrame(VideoIOOutputFrame& frame) +{ + return mVideoIODevice->BeginOutputFrame(frame); +} + +void VideoBackend::EndOutputFrame(VideoIOOutputFrame& frame) +{ + mVideoIODevice->EndOutputFrame(frame); +} + +bool VideoBackend::ScheduleOutputFrame(const VideoIOOutputFrame& frame) +{ + return mVideoIODevice->ScheduleOutputFrame(frame); +} + +void VideoBackend::AccountForCompletionResult(VideoIOCompletionResult result) +{ + mVideoIODevice->AccountForCompletionResult(result); +} + +bool VideoBackend::HasInputDevice() const +{ + return mVideoIODevice->HasInputDevice(); +} + +bool VideoBackend::HasInputSource() const +{ + return mVideoIODevice->HasInputSource(); +} + +unsigned VideoBackend::InputFrameWidth() const +{ + return mVideoIODevice->InputFrameWidth(); +} + +unsigned VideoBackend::InputFrameHeight() const +{ + return mVideoIODevice->InputFrameHeight(); +} + +unsigned VideoBackend::OutputFrameWidth() const +{ + return mVideoIODevice->OutputFrameWidth(); +} + +unsigned VideoBackend::OutputFrameHeight() const +{ + return mVideoIODevice->OutputFrameHeight(); +} + +unsigned VideoBackend::CaptureTextureWidth() const +{ + return mVideoIODevice->CaptureTextureWidth(); +} + +unsigned VideoBackend::OutputPackTextureWidth() const +{ + return mVideoIODevice->OutputPackTextureWidth(); +} + +VideoIOPixelFormat VideoBackend::InputPixelFormat() const +{ + return mVideoIODevice->InputPixelFormat(); +} + +const std::string& VideoBackend::InputDisplayModeName() const +{ + return mVideoIODevice->InputDisplayModeName(); +} + +const std::string& VideoBackend::OutputModelName() const +{ + return mVideoIODevice->OutputModelName(); +} + +bool VideoBackend::SupportsInternalKeying() const +{ + return mVideoIODevice->SupportsInternalKeying(); +} + +bool VideoBackend::SupportsExternalKeying() const +{ + return mVideoIODevice->SupportsExternalKeying(); +} + +bool VideoBackend::KeyerInterfaceAvailable() const +{ + return mVideoIODevice->KeyerInterfaceAvailable(); +} + +bool VideoBackend::ExternalKeyingActive() const +{ + return mVideoIODevice->ExternalKeyingActive(); +} + +const std::string& VideoBackend::StatusMessage() const +{ + return mVideoIODevice->StatusMessage(); +} + +void VideoBackend::SetStatusMessage(const std::string& message) +{ + mVideoIODevice->SetStatusMessage(message); +} diff --git a/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h new file mode 100644 index 0000000..d5a1702 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/videoio/VideoBackend.h @@ -0,0 +1,55 @@ +#pragma once + +#include "VideoIOTypes.h" + +#include +#include + +class HealthTelemetry; +class OpenGLVideoIOBridge; +class RenderEngine; +class VideoIODevice; + +class VideoBackend +{ +public: + VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry); + ~VideoBackend(); + + void ReleaseResources(); + bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error); + bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error); + bool ConfigureInput(const VideoFormat& inputVideoMode, std::string& error); + bool ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error); + bool Start(); + bool Stop(); + + const VideoIOState& State() const; + VideoIOState& MutableState(); + bool BeginOutputFrame(VideoIOOutputFrame& frame); + void EndOutputFrame(VideoIOOutputFrame& frame); + bool ScheduleOutputFrame(const VideoIOOutputFrame& frame); + void AccountForCompletionResult(VideoIOCompletionResult result); + + bool HasInputDevice() const; + bool HasInputSource() const; + unsigned InputFrameWidth() const; + unsigned InputFrameHeight() const; + unsigned OutputFrameWidth() const; + unsigned OutputFrameHeight() const; + unsigned CaptureTextureWidth() const; + unsigned OutputPackTextureWidth() const; + VideoIOPixelFormat InputPixelFormat() const; + const std::string& InputDisplayModeName() const; + const std::string& OutputModelName() const; + bool SupportsInternalKeying() const; + bool SupportsExternalKeying() const; + bool KeyerInterfaceAvailable() const; + bool ExternalKeyingActive() const; + const std::string& StatusMessage() const; + void SetStatusMessage(const std::string& message); + +private: + std::unique_ptr mVideoIODevice; + std::unique_ptr mBridge; +};