#include "OpenGLRenderPass.h" #include "GlRenderConstants.h" #include OpenGLRenderPass::OpenGLRenderPass(OpenGLRenderer& renderer) : mRenderer(renderer) { } void OpenGLRenderPass::Render( bool hasInputSource, const std::vector& layerStates, unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat, unsigned historyCap, const TextBindingUpdater& updateTextBinding, const GlobalParamsUpdater& updateGlobalParams) { glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); if (hasInputSource) { RenderDecodePass(inputFrameWidth, inputFrameHeight, captureTextureWidth, inputPixelFormat); } else { glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer()); glViewport(0, 0, inputFrameWidth, inputFrameHeight); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); } std::vector& layerPrograms = mRenderer.LayerPrograms(); if (layerStates.empty() || layerPrograms.empty()) { glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.DecodeFramebuffer()); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.CompositeFramebuffer()); glBlitFramebuffer(0, 0, inputFrameWidth, inputFrameHeight, 0, 0, inputFrameWidth, inputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer()); } else { const std::vector& passes = BuildLayerPassDescriptors(layerStates, layerPrograms); for (const RenderPassDescriptor& pass : passes) { RenderLayerPass( pass, inputFrameWidth, inputFrameHeight, historyCap, updateTextBinding, updateGlobalParams); } } mRenderer.TemporalHistory().PushSourceFramebuffer(mRenderer.DecodeFramebuffer(), inputFrameWidth, inputFrameHeight); } void OpenGLRenderPass::RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat) { glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer()); glViewport(0, 0, inputFrameWidth, inputFrameHeight); glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(GL_TEXTURE0 + kPackedVideoTextureUnit); glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture()); glBindVertexArray(mRenderer.FullscreenVertexArray()); glUseProgram(mRenderer.DecodeProgram()); const GLint packedResolutionLocation = mRenderer.DecodePackedResolutionLocation(); const GLint decodedResolutionLocation = mRenderer.DecodeDecodedResolutionLocation(); const GLint inputPixelFormatLocation = mRenderer.DecodeInputPixelFormatLocation(); if (packedResolutionLocation >= 0) glUniform2f(packedResolutionLocation, static_cast(captureTextureWidth), static_cast(inputFrameHeight)); if (decodedResolutionLocation >= 0) glUniform2f(decodedResolutionLocation, static_cast(inputFrameWidth), static_cast(inputFrameHeight)); if (inputPixelFormatLocation >= 0) glUniform1i(inputPixelFormatLocation, inputPixelFormat == VideoIOPixelFormat::V210 ? 1 : 0); glDrawArrays(GL_TRIANGLES, 0, 3); glUseProgram(0); glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); } std::vector OpenGLRenderPass::BuildLayerPassDescriptors( const std::vector& layerStates, std::vector& layerPrograms) const { // Flatten the layer stack into concrete GL passes. A layer may now contain // several shader passes, but the outer stack still sees one visible output // per layer. std::vector& passes = mPassScratch; passes.clear(); const std::size_t passCount = layerStates.size() < layerPrograms.size() ? layerStates.size() : layerPrograms.size(); std::size_t descriptorCount = 0; for (std::size_t index = 0; index < passCount; ++index) descriptorCount += layerPrograms[index].passes.size(); passes.reserve(descriptorCount); GLuint sourceTexture = mRenderer.DecodedTexture(); GLuint sourceFramebuffer = mRenderer.DecodeFramebuffer(); for (std::size_t index = 0; index < passCount; ++index) { const RuntimeRenderState& state = layerStates[index]; LayerProgram& layerProgram = layerPrograms[index]; if (layerProgram.passes.empty()) continue; // Preserve the original two-target layer ping-pong. Intermediate passes // inside this layer are routed through pooled temporary targets instead. const std::size_t remaining = layerStates.size() - index; const bool writeToMain = (remaining % 2) == 1; const GLuint layerOutputTexture = writeToMain ? mRenderer.CompositeTexture() : mRenderer.LayerTempTexture(); const GLuint layerOutputFramebuffer = writeToMain ? mRenderer.CompositeFramebuffer() : mRenderer.LayerTempFramebuffer(); const RenderPassOutputTarget layerOutputTarget = writeToMain ? RenderPassOutputTarget::Composite : RenderPassOutputTarget::LayerTemp; const GLuint layerInputTexture = sourceTexture; const GLuint layerInputFramebuffer = sourceFramebuffer; GLuint previousPassTexture = layerInputTexture; GLuint previousPassFramebuffer = layerInputFramebuffer; std::map> namedOutputs; std::size_t temporaryTargetIndex = 0; for (std::size_t passIndex = 0; passIndex < layerProgram.passes.size(); ++passIndex) { PassProgram& passProgram = layerProgram.passes[passIndex]; const bool lastPassForLayer = passIndex + 1 == layerProgram.passes.size(); const std::string outputName = passProgram.outputName.empty() ? passProgram.passId : passProgram.outputName; const bool writesLayerOutput = outputName == "layerOutput" || lastPassForLayer; GLuint passSourceTexture = previousPassTexture; GLuint passSourceFramebuffer = previousPassFramebuffer; if (!passProgram.inputNames.empty()) { // v1 multipass uses the first declared input as gVideoInput. // Later inputs are parsed for forward compatibility. const std::string& inputName = passProgram.inputNames.front(); if (inputName == "layerInput") { passSourceTexture = layerInputTexture; passSourceFramebuffer = layerInputFramebuffer; } else if (inputName == "previousPass") { passSourceTexture = previousPassTexture; passSourceFramebuffer = previousPassFramebuffer; } else { auto namedOutputIt = namedOutputs.find(inputName); if (namedOutputIt != namedOutputs.end()) { passSourceTexture = namedOutputIt->second.first; passSourceFramebuffer = namedOutputIt->second.second; } } } GLuint passDestinationTexture = layerOutputTexture; GLuint passDestinationFramebuffer = layerOutputFramebuffer; RenderPassOutputTarget outputTarget = layerOutputTarget; if (!writesLayerOutput) { // Temporary targets are reserved when the shader stack is // committed, avoiding texture allocation during playback. if (temporaryTargetIndex < mRenderer.TemporaryRenderTargetCount()) { const RenderTarget& temporaryTarget = mRenderer.TemporaryRenderTarget(temporaryTargetIndex); ++temporaryTargetIndex; passDestinationTexture = temporaryTarget.texture; passDestinationFramebuffer = temporaryTarget.framebuffer; outputTarget = RenderPassOutputTarget::Temporary; } } RenderPassDescriptor pass; pass.kind = RenderPassKind::LayerEffect; pass.outputTarget = outputTarget; pass.passIndex = passes.size(); pass.passId = passProgram.passId; pass.layerId = state.layerId; pass.shaderId = state.shaderId; pass.sourceTexture = passSourceTexture; pass.sourceFramebuffer = passIndex == 0 ? layerInputFramebuffer : passSourceFramebuffer; pass.destinationTexture = passDestinationTexture; pass.destinationFramebuffer = passDestinationFramebuffer; pass.layerProgram = &layerProgram; pass.passProgram = &passProgram; pass.layerState = &state; pass.capturePreLayerHistory = passIndex == 0 && state.temporalHistorySource == TemporalHistorySource::PreLayerInput; passes.push_back(pass); // A later pass can reference either the explicit output name or the // pass id, which keeps small manifests pleasant to write. namedOutputs[outputName] = std::make_pair(passDestinationTexture, passDestinationFramebuffer); namedOutputs[passProgram.passId] = std::make_pair(passDestinationTexture, passDestinationFramebuffer); previousPassTexture = passDestinationTexture; previousPassFramebuffer = passDestinationFramebuffer; } sourceTexture = layerOutputTexture; sourceFramebuffer = layerOutputFramebuffer; } return passes; } void OpenGLRenderPass::RenderLayerPass( const RenderPassDescriptor& pass, unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned historyCap, const TextBindingUpdater& updateTextBinding, const GlobalParamsUpdater& updateGlobalParams) { if (pass.passProgram == nullptr || pass.layerState == nullptr) return; RenderShaderProgram( pass.sourceTexture, pass.destinationFramebuffer, *pass.passProgram, *pass.layerState, inputFrameWidth, inputFrameHeight, historyCap, updateTextBinding, updateGlobalParams); if (pass.capturePreLayerHistory) mRenderer.TemporalHistory().PushPreLayerFramebuffer(pass.layerId, pass.sourceFramebuffer, inputFrameWidth, inputFrameHeight); } void OpenGLRenderPass::RenderShaderProgram( GLuint sourceTexture, GLuint destinationFrameBuffer, PassProgram& passProgram, const RuntimeRenderState& state, unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned historyCap, const TextBindingUpdater& updateTextBinding, const GlobalParamsUpdater& updateGlobalParams) { for (LayerProgram::TextBinding& textBinding : passProgram.textBindings) { std::string textError; if (!updateTextBinding(state, textBinding, textError)) OutputDebugStringA((textError + "\n").c_str()); } glBindFramebuffer(GL_FRAMEBUFFER, destinationFrameBuffer); glViewport(0, 0, inputFrameWidth, inputFrameHeight); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); const std::vector sourceHistoryTextures = mRenderer.TemporalHistory().ResolveSourceHistoryTextures(sourceTexture, state.isTemporal ? historyCap : 0); const std::vector temporalHistoryTextures = mRenderer.TemporalHistory().ResolveTemporalHistoryTextures(state, sourceTexture, state.isTemporal ? historyCap : 0); const ShaderTextureBindings::RuntimeTextureBindingPlan texturePlan = mTextureBindings.BuildLayerRuntimeBindingPlan(passProgram, sourceTexture, sourceHistoryTextures, temporalHistoryTextures); mTextureBindings.BindRuntimeTexturePlan(texturePlan); glBindVertexArray(mRenderer.FullscreenVertexArray()); glUseProgram(passProgram.program); // The UBO is shared by every pass in a layer; texture routing is what // changes from pass to pass. updateGlobalParams(state, mRenderer.TemporalHistory().SourceAvailableCount(), mRenderer.TemporalHistory().AvailableCountForLayer(state.layerId)); glDrawArrays(GL_TRIANGLES, 0, 3); glUseProgram(0); glBindVertexArray(0); mTextureBindings.UnbindRuntimeTexturePlan(texturePlan); }