#include "RuntimeRenderScene.h" #include #include #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 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(outputFramebuffer), true); return; } if (!EnsureLayerTargets(width, height)) { glBindFramebuffer(GL_FRAMEBUFFER, static_cast(outputFramebuffer)); RenderLayer(*readyLayers.back(), frameIndex, width, height, videoInputTexture, videoInputTexture, static_cast(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(outputFramebuffer)); RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, videoInputTexture, layerInputTexture, static_cast(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(width), static_cast(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; }