diff --git a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.cpp b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.cpp index 07e7de3..d63eaa3 100644 --- a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.cpp @@ -344,7 +344,48 @@ bool DeckLinkSession::ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hgl return true; } -bool DeckLinkSession::Start(unsigned outputHeight) +double DeckLinkSession::FrameBudgetMilliseconds() const +{ + return frameTimescale != 0 + ? (static_cast(frameDuration) * 1000.0) / static_cast(frameTimescale) + : 0.0; +} + +IDeckLinkMutableVideoFrame* DeckLinkSession::RotateOutputFrame() +{ + IDeckLinkMutableVideoFrame* outputVideoFrame = outputVideoFrameQueue.front(); + outputVideoFrameQueue.push_back(outputVideoFrame); + outputVideoFrameQueue.pop_front(); + return outputVideoFrame; +} + +bool DeckLinkSession::TransferPlayoutFrame(void* address, GLuint outputTexture) +{ + return playoutAllocator != nullptr && playoutAllocator->transferFrame(address, outputTexture); +} + +void DeckLinkSession::WaitForPlayoutTransferComplete(void* address) +{ + if (playoutAllocator != nullptr) + playoutAllocator->waitForTransferComplete(address); +} + +void DeckLinkSession::AccountForCompletionResult(BMDOutputFrameCompletionResult completionResult) +{ + if (completionResult == bmdOutputFrameDisplayedLate || completionResult == bmdOutputFrameDropped) + totalPlayoutFrames += 2; +} + +bool DeckLinkSession::ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame) +{ + if (output->ScheduleVideoFrame(outputVideoFrame, (totalPlayoutFrames * frameDuration), frameDuration, frameTimescale) != S_OK) + return false; + + totalPlayoutFrames++; + return true; +} + +bool DeckLinkSession::Start() { totalPlayoutFrames = 0; if (!output) @@ -380,7 +421,7 @@ bool DeckLinkSession::Start(unsigned outputHeight) void* pFrame; outputVideoFrameBuffer->GetBytes((void**)&pFrame); - memset(pFrame, 0, outputVideoFrame->GetRowBytes() * outputHeight); + memset(pFrame, 0, outputVideoFrame->GetRowBytes() * outputFrameHeight); outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite); outputVideoFrameBuffer->Release(); diff --git a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.h b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.h index e9fee12..e640e33 100644 --- a/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.h +++ b/apps/LoopThroughWithOpenGLCompositing/decklink/DeckLinkSession.h @@ -18,9 +18,33 @@ public: bool DiscoverDevicesAndModes(BMDDisplayMode inputDisplayMode, BMDDisplayMode outputDisplayMode, const std::string& requestedInputDisplayModeName, const std::string& requestedOutputDisplayModeName, std::string& error); bool ConfigureInput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, BMDDisplayMode inputDisplayMode, std::string& error); bool ConfigureOutput(OpenGLComposite* owner, HDC hdc, HGLRC hglrc, BMDDisplayMode outputDisplayMode, bool externalKeyingEnabled, std::string& error); - bool Start(unsigned outputFrameHeight); + bool Start(); bool Stop(); + bool HasInputDevice() const { return input != nullptr; } + bool HasInputSource() const { return !hasNoInputSource; } + void SetInputSourceMissing(bool missing) { hasNoInputSource = missing; } + bool InputOutputDimensionsDiffer() const { return inputFrameWidth != outputFrameWidth || inputFrameHeight != outputFrameHeight; } + unsigned InputFrameWidth() const { return inputFrameWidth; } + unsigned InputFrameHeight() const { return inputFrameHeight; } + unsigned OutputFrameWidth() const { return outputFrameWidth; } + unsigned OutputFrameHeight() const { return outputFrameHeight; } + const std::string& InputDisplayModeName() const { return inputDisplayModeName; } + const std::string& OutputModelName() const { return outputModelName; } + bool SupportsInternalKeying() const { return supportsInternalKeying; } + bool SupportsExternalKeying() const { return supportsExternalKeying; } + bool KeyerInterfaceAvailable() const { return keyerInterfaceAvailable; } + bool ExternalKeyingActive() const { return externalKeyingActive; } + const std::string& StatusMessage() const { return statusMessage; } + void SetStatusMessage(const std::string& message) { statusMessage = message; } + double FrameBudgetMilliseconds() const; + IDeckLinkMutableVideoFrame* RotateOutputFrame(); + bool TransferPlayoutFrame(void* address, GLuint outputTexture); + void WaitForPlayoutTransferComplete(void* address); + void AccountForCompletionResult(BMDOutputFrameCompletionResult completionResult); + bool ScheduleOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame); + +private: CaptureDelegate* captureDelegate = nullptr; PlayoutDelegate* playoutDelegate = nullptr; IDeckLinkInput* input = nullptr; diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index 3d9d059..7c7a08f 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -139,7 +139,7 @@ bool OpenGLComposite::InitDeckLink() initFailureReason = "OpenGL extension checks failed."; goto error; } - if (mDeckLink->inputFrameWidth != mDeckLink->outputFrameWidth || mDeckLink->inputFrameHeight != mDeckLink->outputFrameHeight) + if (mDeckLink->InputOutputDimensionsDiffer()) { mRenderer->SetFastTransferAvailable(false); OutputDebugStringA("Input/output dimensions differ; using regular OpenGL transfer fallback instead of fast transfer.\n"); @@ -151,20 +151,20 @@ bool OpenGLComposite::InitDeckLink() goto error; } - PublishDeckLinkOutputStatus(mDeckLink->outputModelName.empty() + PublishDeckLinkOutputStatus(mDeckLink->OutputModelName().empty() ? "DeckLink output device selected." - : ("Selected output device: " + mDeckLink->outputModelName)); + : ("Selected output device: " + mDeckLink->OutputModelName())); // Resize window to match output video frame, but scale large formats down by half for viewing. - if (mDeckLink->outputFrameWidth < 1920) - resizeWindow(mDeckLink->outputFrameWidth, mDeckLink->outputFrameHeight); + if (mDeckLink->OutputFrameWidth() < 1920) + resizeWindow(mDeckLink->OutputFrameWidth(), mDeckLink->OutputFrameHeight()); else - resizeWindow(mDeckLink->outputFrameWidth / 2, mDeckLink->outputFrameHeight / 2); + resizeWindow(mDeckLink->OutputFrameWidth() / 2, mDeckLink->OutputFrameHeight() / 2); if (mRenderer->FastTransferAvailable()) { // Initialize fast video frame transfers - if (! VideoFrameTransfer::initialize(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, mRenderer->CaptureTexture(), mRenderer->OutputTexture())) + if (! VideoFrameTransfer::initialize(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), mRenderer->CaptureTexture(), mRenderer->OutputTexture())) { MessageBox(NULL, _T("Cannot initialize video transfers."), _T("VideoFrameTransfer error."), MB_OK); goto error; @@ -175,9 +175,9 @@ bool OpenGLComposite::InitDeckLink() { goto error; } - if (!mDeckLink->input && mRuntimeHost) + if (!mDeckLink->HasInputDevice() && mRuntimeHost) { - mRuntimeHost->SetSignalStatus(false, mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, mDeckLink->inputDisplayModeName); + mRuntimeHost->SetSignalStatus(false, mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), mDeckLink->InputDisplayModeName()); } if (!mDeckLink->ConfigureOutput(this, hGLDC, hGLRC, outputDisplayMode, mRuntimeHost && mRuntimeHost->ExternalKeyingEnabled(), initFailureReason)) @@ -185,7 +185,7 @@ bool OpenGLComposite::InitDeckLink() goto error; } - PublishDeckLinkOutputStatus(mDeckLink->statusMessage); + PublishDeckLinkOutputStatus(mDeckLink->StatusMessage()); return true; @@ -204,7 +204,7 @@ void OpenGLComposite::paintGL() return; } - mRenderer->PresentToWindow(hGLDC, mDeckLink->outputFrameWidth, mDeckLink->outputFrameHeight); + mRenderer->PresentToWindow(hGLDC, mDeckLink->OutputFrameWidth(), mDeckLink->OutputFrameHeight()); ValidateRect(hGLWnd, NULL); LeaveCriticalSection(&pMutex); } @@ -231,16 +231,16 @@ void OpenGLComposite::PublishDeckLinkOutputStatus(const std::string& statusMessa return; if (!statusMessage.empty()) - mDeckLink->statusMessage = statusMessage; + mDeckLink->SetStatusMessage(statusMessage); mRuntimeHost->SetDeckLinkOutputStatus( - mDeckLink->outputModelName, - mDeckLink->supportsInternalKeying, - mDeckLink->supportsExternalKeying, - mDeckLink->keyerInterfaceAvailable, + mDeckLink->OutputModelName(), + mDeckLink->SupportsInternalKeying(), + mDeckLink->SupportsExternalKeying(), + mDeckLink->KeyerInterfaceAvailable(), mRuntimeHost->ExternalKeyingEnabled(), - mDeckLink->externalKeyingActive, - mDeckLink->statusMessage); + mDeckLink->ExternalKeyingActive(), + mDeckLink->StatusMessage()); } bool OpenGLComposite::InitOpenGLState() @@ -269,7 +269,7 @@ bool OpenGLComposite::InitOpenGLState() return false; } - if (!mShaderPrograms->CompileLayerPrograms(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, sizeof(compilerErrorMessage), compilerErrorMessage)) + if (!mShaderPrograms->CompileLayerPrograms(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage)) { MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK); return false; @@ -277,7 +277,7 @@ bool OpenGLComposite::InitOpenGLState() mShaderPrograms->ResetTemporalHistoryState(); std::string rendererError; - if (!mRenderer->InitializeResources(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, mDeckLink->outputFrameWidth, mDeckLink->outputFrameHeight, rendererError)) + if (!mRenderer->InitializeResources(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), mDeckLink->OutputFrameWidth(), mDeckLink->OutputFrameHeight(), rendererError)) { MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK); return false; @@ -300,7 +300,7 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, bool OpenGLComposite::Start() { - return mDeckLink->Start(mDeckLink->outputFrameHeight); + return mDeckLink->Start(); } bool OpenGLComposite::Stop() @@ -311,7 +311,7 @@ bool OpenGLComposite::Stop() if (mControlServer) mControlServer->Stop(); - const bool wasExternalKeyingActive = mDeckLink->externalKeyingActive; + const bool wasExternalKeyingActive = mDeckLink->ExternalKeyingActive(); mDeckLink->Stop(); if (wasExternalKeyingActive) PublishDeckLinkOutputStatus("External keying has been disabled."); @@ -326,7 +326,7 @@ bool OpenGLComposite::ReloadShader() EnterCriticalSection(&pMutex); wglMakeCurrent(hGLDC, hGLRC); - bool success = mShaderPrograms->CompileLayerPrograms(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, sizeof(compilerErrorMessage), compilerErrorMessage); + bool success = mShaderPrograms->CompileLayerPrograms(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage); if (mRuntimeHost) mRuntimeHost->ClearReloadRequest(); @@ -353,14 +353,14 @@ void OpenGLComposite::renderEffect() { PollRuntimeChanges(); - const bool hasInputSource = !mDeckLink->hasNoInputSource; - const std::vector layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight) : std::vector(); + const bool hasInputSource = mDeckLink->HasInputSource(); + const std::vector layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight()) : std::vector(); const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; mRenderPass->Render( hasInputSource, layerStates, - mDeckLink->inputFrameWidth, - mDeckLink->inputFrameHeight, + mDeckLink->InputFrameWidth(), + mDeckLink->InputFrameHeight(), historyCap, [this](const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error) { return mShaderPrograms->UpdateTextBindingTexture(state, textBinding, error); @@ -392,7 +392,7 @@ bool OpenGLComposite::PollRuntimeChanges() return true; char compilerErrorMessage[1024] = {}; - if (!mShaderPrograms->CompileLayerPrograms(mDeckLink->inputFrameWidth, mDeckLink->inputFrameHeight, sizeof(compilerErrorMessage), compilerErrorMessage)) + if (!mShaderPrograms->CompileLayerPrograms(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage)) { mRuntimeHost->SetCompileStatus(false, compilerErrorMessage); mRuntimeHost->ClearReloadRequest(); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp index 6608aae..124935b 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp @@ -32,10 +32,10 @@ OpenGLDeckLinkBridge::OpenGLDeckLinkBridge( void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource) { - mDeckLink.hasNoInputSource = hasNoInputSource; - mRuntimeHost.SetSignalStatus(!hasNoInputSource, mDeckLink.inputFrameWidth, mDeckLink.inputFrameHeight, mDeckLink.inputDisplayModeName); + mDeckLink.SetInputSourceMissing(hasNoInputSource); + mRuntimeHost.SetSignalStatus(!hasNoInputSource, mDeckLink.InputFrameWidth(), mDeckLink.InputFrameHeight(), mDeckLink.InputDisplayModeName()); - if (mDeckLink.hasNoInputSource) + if (!mDeckLink.HasInputSource()) return; // don't transfer texture when there's no input long textureSize = inputFrame->GetRowBytes() * inputFrame->GetHeight(); @@ -72,7 +72,7 @@ void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFram glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture()); // NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDeckLink.inputFrameWidth / 2, mDeckLink.inputFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDeckLink.InputFrameWidth() / 2, mDeckLink.InputFrameHeight(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); @@ -93,9 +93,7 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF EnterCriticalSection(&mMutex); // Get the first frame from the queue - IDeckLinkMutableVideoFrame* outputVideoFrame = mDeckLink.outputVideoFrameQueue.front(); - mDeckLink.outputVideoFrameQueue.push_back(outputVideoFrame); - mDeckLink.outputVideoFrameQueue.pop_front(); + IDeckLinkMutableVideoFrame* outputVideoFrame = mDeckLink.RotateOutputFrame(); // make GL context current in this thread wglMakeCurrent(mHdc, mHglrc); @@ -108,15 +106,13 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF mRenderEffect(); glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.CompositeFramebuffer()); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputFramebuffer()); - glBlitFramebuffer(0, 0, mDeckLink.inputFrameWidth, mDeckLink.inputFrameHeight, 0, 0, mDeckLink.outputFrameWidth, mDeckLink.outputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); + glBlitFramebuffer(0, 0, mDeckLink.InputFrameWidth(), mDeckLink.InputFrameHeight(), 0, 0, mDeckLink.OutputFrameWidth(), mDeckLink.OutputFrameHeight(), GL_COLOR_BUFFER_BIT, GL_LINEAR); glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer()); glFlush(); if (mRenderer.FastTransferAvailable()) VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::GPUtoCPU); const auto renderEndTime = std::chrono::steady_clock::now(); - const double frameBudgetMilliseconds = mDeckLink.frameTimescale != 0 - ? (static_cast(mDeckLink.frameDuration) * 1000.0) / static_cast(mDeckLink.frameTimescale) - : 0.0; + const double frameBudgetMilliseconds = mDeckLink.FrameBudgetMilliseconds(); const double renderMilliseconds = std::chrono::duration_cast>(renderEndTime - renderStartTime).count(); mRuntimeHost.SetPerformanceStats(frameBudgetMilliseconds, renderMilliseconds); mRuntimeHost.AdvanceFrame(); @@ -141,35 +137,31 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF if (mRenderer.FastTransferAvailable()) { // Finished sampling the capture texture for this frame. - if (!mDeckLink.hasNoInputSource) + if (mDeckLink.HasInputSource()) VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU); - if (!mDeckLink.playoutAllocator->transferFrame(pFrame, mRenderer.OutputTexture())) + if (!mDeckLink.TransferPlayoutFrame(pFrame, mRenderer.OutputTexture())) OutputDebugStringA("Playback: transferFrame() failed\n"); mPaint(); // Wait for transfer to system memory to complete - mDeckLink.playoutAllocator->waitForTransferComplete(pFrame); + mDeckLink.WaitForPlayoutTransferComplete(pFrame); } else { glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer()); - glReadPixels(0, 0, mDeckLink.outputFrameWidth, mDeckLink.outputFrameHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame); + glReadPixels(0, 0, mDeckLink.OutputFrameWidth(), mDeckLink.OutputFrameHeight(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame); mPaint(); } outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite); outputVideoFrameBuffer->Release(); - // If the last completed frame was late or dropped, bump the scheduled time further into the future - if (completionResult == bmdOutputFrameDisplayedLate || completionResult == bmdOutputFrameDropped) - mDeckLink.totalPlayoutFrames += 2; + mDeckLink.AccountForCompletionResult(completionResult); // Schedule the next frame for playout - HRESULT hr = mDeckLink.output->ScheduleVideoFrame(outputVideoFrame, (mDeckLink.totalPlayoutFrames * mDeckLink.frameDuration), mDeckLink.frameDuration, mDeckLink.frameTimescale); - if (SUCCEEDED(hr)) - mDeckLink.totalPlayoutFrames++; + mDeckLink.ScheduleOutputFrame(outputVideoFrame); wglMakeCurrent(NULL, NULL);