#include "OpenGLVideoIOBridge.h" #include "OpenGLRenderer.h" #include "RuntimeHost.h" #include #include OpenGLVideoIOBridge::OpenGLVideoIOBridge( VideoIODevice& videoIO, OpenGLRenderer& renderer, RuntimeHost& runtimeHost, CRITICAL_SECTION& mutex, HDC hdc, HGLRC hglrc, RenderEffectCallback renderEffect, OutputReadyCallback outputReady, PaintCallback paint) : mVideoIO(videoIO), mRenderer(renderer), mRuntimeHost(runtimeHost), mMutex(mutex), mHdc(hdc), mHglrc(hglrc), mRenderEffect(renderEffect), mOutputReady(outputReady), mPaint(paint) { } void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult 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 == VideoIOCompletionResult::DisplayedLate) ++mLateFrameCount; else if (completionResult == VideoIOCompletionResult::Dropped) ++mDroppedFrameCount; else if (completionResult == VideoIOCompletionResult::Flushed) ++mFlushedFrameCount; mRuntimeHost.TrySetFramePacingStats( mCompletionIntervalMilliseconds, mSmoothedCompletionIntervalMilliseconds, mMaxCompletionIntervalMilliseconds, mLateFrameCount, mDroppedFrameCount, mFlushedFrameCount); } void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame) { const VideoIOState& state = mVideoIO.State(); mRuntimeHost.TrySetSignalStatus(!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); 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, 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); } void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& completion) { RecordFramePacing(completion.result); EnterCriticalSection(&mMutex); VideoIOOutputFrame outputFrame; if (!mVideoIO.BeginOutputFrame(outputFrame)) { LeaveCriticalSection(&mMutex); return; } const VideoIOState& state = mVideoIO.State(); RenderPipelineFrameContext frameContext; frameContext.videoState = state; frameContext.completion = completion; // 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, state.inputFrameSize.width, state.inputFrameSize.height, 0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer()); if (mOutputReady) mOutputReady(); if (state.outputPixelFormat == VideoIOPixelFormat::V210) { glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer()); glViewport(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height); 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(state.outputFrameSize.width), static_cast(state.outputFrameSize.height)); if (activeWordsLocation >= 0) glUniform1f(activeWordsLocation, static_cast(ActiveV210WordsForWidth(state.outputFrameSize.width))); 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 = state.frameBudgetMilliseconds; const double renderMilliseconds = std::chrono::duration_cast>(renderEndTime - renderStartTime).count(); mRuntimeHost.TrySetPerformanceStats(frameBudgetMilliseconds, renderMilliseconds); mRuntimeHost.TryAdvanceFrame(); glPixelStorei(GL_PACK_ALIGNMENT, 4); glPixelStorei(GL_PACK_ROW_LENGTH, 0); if (state.outputPixelFormat == VideoIOPixelFormat::V210) { glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer()); glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, outputFrame.bytes); } else { glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer()); glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, outputFrame.bytes); } mPaint(); mVideoIO.EndOutputFrame(outputFrame); mVideoIO.AccountForCompletionResult(completion.result); // Schedule the next frame for playout mVideoIO.ScheduleOutputFrame(outputFrame); wglMakeCurrent(NULL, NULL); LeaveCriticalSection(&mMutex); }