277 lines
11 KiB
C++
277 lines
11 KiB
C++
#include "OpenGLRenderPass.h"
|
|
|
|
#include "GlRenderConstants.h"
|
|
|
|
#include <map>
|
|
|
|
OpenGLRenderPass::OpenGLRenderPass(OpenGLRenderer& renderer) :
|
|
mRenderer(renderer)
|
|
{
|
|
}
|
|
|
|
void OpenGLRenderPass::Render(
|
|
bool hasInputSource,
|
|
const std::vector<RuntimeRenderState>& 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<LayerProgram>& 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<RenderPassDescriptor>& 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<float>(captureTextureWidth), static_cast<float>(inputFrameHeight));
|
|
if (decodedResolutionLocation >= 0)
|
|
glUniform2f(decodedResolutionLocation, static_cast<float>(inputFrameWidth), static_cast<float>(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<RenderPassDescriptor> OpenGLRenderPass::BuildLayerPassDescriptors(
|
|
const std::vector<RuntimeRenderState>& layerStates,
|
|
std::vector<LayerProgram>& 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<RenderPassDescriptor>& 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<std::string, std::pair<GLuint, GLuint>> 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<GLuint> sourceHistoryTextures = mRenderer.TemporalHistory().ResolveSourceHistoryTextures(sourceTexture, state.isTemporal ? historyCap : 0);
|
|
const std::vector<GLuint> 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);
|
|
}
|