#include "OpenGLDeckLinkBridge.h" #include "DeckLinkSession.h" #include "OpenGLRenderer.h" #include "RuntimeHost.h" #include #include OpenGLDeckLinkBridge::OpenGLDeckLinkBridge( DeckLinkSession& deckLink, OpenGLRenderer& renderer, RuntimeHost& runtimeHost, CRITICAL_SECTION& mutex, HDC hdc, HGLRC hglrc, RenderEffectCallback renderEffect, PaintCallback paint) : mDeckLink(deckLink), mRenderer(renderer), mRuntimeHost(runtimeHost), mMutex(mutex), mHdc(hdc), mHglrc(hglrc), mRenderEffect(renderEffect), mPaint(paint) { } void OpenGLDeckLinkBridge::RecordFramePacing(BMDOutputFrameCompletionResult completionResult) { const auto now = std::chrono::steady_clock::now(); if (mLastPlayoutCompletionTime != std::chrono::steady_clock::time_point()) { mCompletionIntervalMilliseconds = std::chrono::duration_cast>(now - mLastPlayoutCompletionTime).count(); if (mSmoothedCompletionIntervalMilliseconds <= 0.0) mSmoothedCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds; else mSmoothedCompletionIntervalMilliseconds = mSmoothedCompletionIntervalMilliseconds * 0.9 + mCompletionIntervalMilliseconds * 0.1; if (mCompletionIntervalMilliseconds > mMaxCompletionIntervalMilliseconds) mMaxCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds; } mLastPlayoutCompletionTime = now; if (completionResult == bmdOutputFrameDisplayedLate) ++mLateFrameCount; else if (completionResult == bmdOutputFrameDropped) ++mDroppedFrameCount; else if (completionResult == bmdOutputFrameFlushed) ++mFlushedFrameCount; mRuntimeHost.TrySetFramePacingStats( mCompletionIntervalMilliseconds, mSmoothedCompletionIntervalMilliseconds, mMaxCompletionIntervalMilliseconds, mLateFrameCount, mDroppedFrameCount, mFlushedFrameCount); } void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource) { mDeckLink.SetInputSourceMissing(hasNoInputSource); mRuntimeHost.TrySetSignalStatus(!hasNoInputSource, mDeckLink.InputFrameWidth(), mDeckLink.InputFrameHeight(), mDeckLink.InputDisplayModeName()); if (!mDeckLink.HasInputSource()) return; // don't transfer texture when there's no input long textureSize = inputFrame->GetRowBytes() * inputFrame->GetHeight(); IDeckLinkVideoBuffer* inputFrameBuffer = NULL; void* videoPixels; if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&inputFrameBuffer) != S_OK) return; if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK) { inputFrameBuffer->Release(); return; } inputFrameBuffer->GetBytes(&videoPixels); EnterCriticalSection(&mMutex); 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, videoPixels, 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 (mDeckLink.InputPixelFormat() == VideoIOPixelFormat::V210) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDeckLink.CaptureTextureWidth(), mDeckLink.InputFrameHeight(), GL_RGBA, GL_UNSIGNED_BYTE, NULL); else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mDeckLink.CaptureTextureWidth(), mDeckLink.InputFrameHeight(), 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); inputFrameBuffer->EndAccess(bmdBufferAccessRead); inputFrameBuffer->Release(); } void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult) { (void)completedFrame; RecordFramePacing(completionResult); EnterCriticalSection(&mMutex); // Get the first frame from the queue IDeckLinkMutableVideoFrame* outputVideoFrame = mDeckLink.RotateOutputFrame(); // make GL context current in this thread wglMakeCurrent(mHdc, mHglrc); // Draw the effect output to the off-screen framebuffer. const auto renderStartTime = std::chrono::steady_clock::now(); glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer()); 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); glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer()); if (mDeckLink.OutputIsTenBit()) { glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer()); glViewport(0, 0, mDeckLink.OutputPackTextureWidth(), mDeckLink.OutputFrameHeight()); glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, mRenderer.OutputTexture()); glBindVertexArray(mRenderer.FullscreenVertexArray()); glUseProgram(mRenderer.OutputPackProgram()); const GLint outputResolutionLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uOutputVideoResolution"); const GLint activeWordsLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uActiveV210Words"); if (outputResolutionLocation >= 0) glUniform2f(outputResolutionLocation, static_cast(mDeckLink.OutputFrameWidth()), static_cast(mDeckLink.OutputFrameHeight())); if (activeWordsLocation >= 0) glUniform1f(activeWordsLocation, static_cast(ActiveV210WordsForWidth(mDeckLink.OutputFrameWidth()))); glDrawArrays(GL_TRIANGLES, 0, 3); glUseProgram(0); glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); } glFlush(); const auto renderEndTime = std::chrono::steady_clock::now(); const double frameBudgetMilliseconds = mDeckLink.FrameBudgetMilliseconds(); const double renderMilliseconds = std::chrono::duration_cast>(renderEndTime - renderStartTime).count(); mRuntimeHost.TrySetPerformanceStats(frameBudgetMilliseconds, renderMilliseconds); mRuntimeHost.TryAdvanceFrame(); IDeckLinkVideoBuffer* outputVideoFrameBuffer; if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK) { wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return; } if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK) { outputVideoFrameBuffer->Release(); wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); return; } void* pFrame; outputVideoFrameBuffer->GetBytes(&pFrame); glPixelStorei(GL_PACK_ALIGNMENT, 4); glPixelStorei(GL_PACK_ROW_LENGTH, 0); if (mDeckLink.OutputIsTenBit()) { glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer()); glReadPixels(0, 0, mDeckLink.OutputPackTextureWidth(), mDeckLink.OutputFrameHeight(), GL_RGBA, GL_UNSIGNED_BYTE, 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); } mPaint(); outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite); outputVideoFrameBuffer->Release(); mDeckLink.AccountForCompletionResult(completionResult); // Schedule the next frame for playout mDeckLink.ScheduleOutputFrame(outputVideoFrame); wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); }