220 lines
6.6 KiB
C++
220 lines
6.6 KiB
C++
#include "RuntimeRenderScene.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#ifndef GL_FRAMEBUFFER_BINDING
|
|
#define GL_FRAMEBUFFER_BINDING 0x8CA6
|
|
#endif
|
|
|
|
bool RuntimeRenderScene::HasLayers()
|
|
{
|
|
ConsumePreparedPrograms();
|
|
|
|
for (const std::string& layerId : mLayerOrder)
|
|
{
|
|
const LayerProgram* layer = FindLayer(layerId);
|
|
if (!layer)
|
|
continue;
|
|
for (const LayerProgram::PassProgram& pass : layer->passes)
|
|
{
|
|
if (pass.renderer && pass.renderer->HasProgram())
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture)
|
|
{
|
|
ConsumePreparedPrograms();
|
|
|
|
std::vector<LayerProgram*> readyLayers;
|
|
for (const std::string& layerId : mLayerOrder)
|
|
{
|
|
LayerProgram* layer = FindLayer(layerId);
|
|
if (!layer)
|
|
continue;
|
|
for (const LayerProgram::PassProgram& pass : layer->passes)
|
|
{
|
|
if (pass.renderer && pass.renderer->HasProgram())
|
|
{
|
|
readyLayers.push_back(layer);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (readyLayers.empty())
|
|
return;
|
|
|
|
GLint outputFramebuffer = 0;
|
|
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &outputFramebuffer);
|
|
|
|
if (readyLayers.size() == 1)
|
|
{
|
|
RenderLayer(*readyLayers.front(), frameIndex, width, height, videoInputTexture, videoInputTexture, static_cast<GLuint>(outputFramebuffer), true);
|
|
return;
|
|
}
|
|
|
|
if (!EnsureLayerTargets(width, height))
|
|
{
|
|
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
|
|
RenderLayer(*readyLayers.back(), frameIndex, width, height, videoInputTexture, videoInputTexture, static_cast<GLuint>(outputFramebuffer), true);
|
|
return;
|
|
}
|
|
|
|
// Shader source contract:
|
|
// - gVideoInput is the decoded latest input texture for every layer in the stack.
|
|
// - gLayerInput starts as gVideoInput for the first layer, then becomes the previous layer output.
|
|
GLuint layerInputTexture = videoInputTexture;
|
|
std::size_t nextTargetIndex = 0;
|
|
for (std::size_t layerIndex = 0; layerIndex < readyLayers.size(); ++layerIndex)
|
|
{
|
|
const bool isFinalLayer = layerIndex == readyLayers.size() - 1;
|
|
if (isFinalLayer)
|
|
{
|
|
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
|
|
RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, videoInputTexture, layerInputTexture, static_cast<GLuint>(outputFramebuffer), true);
|
|
continue;
|
|
}
|
|
|
|
RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, videoInputTexture, layerInputTexture, mLayerFramebuffers[nextTargetIndex], true);
|
|
layerInputTexture = mLayerTextures[nextTargetIndex];
|
|
nextTargetIndex = 1 - nextTargetIndex;
|
|
}
|
|
}
|
|
|
|
GLuint RuntimeRenderScene::RenderLayer(
|
|
LayerProgram& layer,
|
|
uint64_t frameIndex,
|
|
unsigned width,
|
|
unsigned height,
|
|
GLuint videoInputTexture,
|
|
GLuint layerInputTexture,
|
|
GLuint outputFramebuffer,
|
|
bool renderToOutput)
|
|
{
|
|
GLuint namedOutputs[2] = {};
|
|
std::string namedOutputNames[2];
|
|
std::size_t nextTargetIndex = 2;
|
|
GLuint lastOutputTexture = layerInputTexture;
|
|
|
|
for (LayerProgram::PassProgram& pass : layer.passes)
|
|
{
|
|
if (!pass.renderer || !pass.renderer->HasProgram())
|
|
continue;
|
|
|
|
GLuint sourceTexture = videoInputTexture;
|
|
if (!pass.inputNames.empty())
|
|
{
|
|
const std::string& inputName = pass.inputNames.front();
|
|
if (inputName == "videoInput")
|
|
{
|
|
sourceTexture = videoInputTexture;
|
|
}
|
|
else if (inputName != "layerInput")
|
|
{
|
|
// Named intermediate pass inputs currently use the gVideoInput binding slot as the
|
|
// selected pass source. Layer stack shaders should use gLayerInput for previous-layer
|
|
// sampling and gVideoInput for the original input frame.
|
|
for (std::size_t index = 0; index < 2; ++index)
|
|
{
|
|
if (namedOutputNames[index] == inputName)
|
|
{
|
|
sourceTexture = namedOutputs[index];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool writesLayerOutput = pass.outputName == "layerOutput";
|
|
if (writesLayerOutput && renderToOutput)
|
|
{
|
|
glBindFramebuffer(GL_FRAMEBUFFER, outputFramebuffer);
|
|
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, layerInputTexture);
|
|
lastOutputTexture = 0;
|
|
continue;
|
|
}
|
|
|
|
if (!EnsureLayerTargets(width, height))
|
|
continue;
|
|
|
|
const std::size_t targetIndex = nextTargetIndex;
|
|
nextTargetIndex = nextTargetIndex == 2 ? 3 : 2;
|
|
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[targetIndex]);
|
|
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, layerInputTexture);
|
|
const std::size_t namedIndex = targetIndex - 2;
|
|
namedOutputs[namedIndex] = mLayerTextures[targetIndex];
|
|
namedOutputNames[namedIndex] = pass.outputName;
|
|
lastOutputTexture = mLayerTextures[targetIndex];
|
|
}
|
|
|
|
return lastOutputTexture;
|
|
}
|
|
|
|
bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
|
|
{
|
|
if (width == 0 || height == 0)
|
|
return false;
|
|
if (mLayerFramebuffers[0] != 0 && mLayerFramebuffers[1] != 0 && mLayerFramebuffers[2] != 0 && mLayerFramebuffers[3] != 0
|
|
&& mLayerTextures[0] != 0 && mLayerTextures[1] != 0 && mLayerTextures[2] != 0 && mLayerTextures[3] != 0
|
|
&& mLayerTargetWidth == width && mLayerTargetHeight == height)
|
|
return true;
|
|
|
|
DestroyLayerTargets();
|
|
mLayerTargetWidth = width;
|
|
mLayerTargetHeight = height;
|
|
|
|
glGenFramebuffers(4, mLayerFramebuffers);
|
|
glGenTextures(4, mLayerTextures);
|
|
for (int index = 0; index < 4; ++index)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, mLayerTextures[index]);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexImage2D(
|
|
GL_TEXTURE_2D,
|
|
0,
|
|
GL_RGBA8,
|
|
static_cast<GLsizei>(width),
|
|
static_cast<GLsizei>(height),
|
|
0,
|
|
GL_BGRA,
|
|
GL_UNSIGNED_INT_8_8_8_8_REV,
|
|
nullptr);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[index]);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mLayerTextures[index], 0);
|
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
{
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
DestroyLayerTargets();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
return true;
|
|
}
|
|
|
|
void RuntimeRenderScene::DestroyLayerTargets()
|
|
{
|
|
if (mLayerFramebuffers[0] != 0 || mLayerFramebuffers[1] != 0 || mLayerFramebuffers[2] != 0 || mLayerFramebuffers[3] != 0)
|
|
glDeleteFramebuffers(4, mLayerFramebuffers);
|
|
if (mLayerTextures[0] != 0 || mLayerTextures[1] != 0 || mLayerTextures[2] != 0 || mLayerTextures[3] != 0)
|
|
glDeleteTextures(4, mLayerTextures);
|
|
for (int index = 0; index < 4; ++index)
|
|
{
|
|
mLayerFramebuffers[index] = 0;
|
|
mLayerTextures[index] = 0;
|
|
}
|
|
mLayerTargetWidth = 0;
|
|
mLayerTargetHeight = 0;
|
|
}
|