diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPass.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPass.cpp index 51451ac..65d50d5 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPass.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLRenderPass.cpp @@ -93,6 +93,9 @@ 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; const std::size_t passCount = layerStates.size() < layerPrograms.size() ? layerStates.size() : layerPrograms.size(); std::size_t descriptorCount = 0; @@ -108,6 +111,9 @@ std::vector OpenGLRenderPass::BuildLayerPassDescriptors( 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(); @@ -132,6 +138,8 @@ std::vector OpenGLRenderPass::BuildLayerPassDescriptors( 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") { @@ -159,6 +167,8 @@ std::vector OpenGLRenderPass::BuildLayerPassDescriptors( 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); @@ -186,6 +196,8 @@ std::vector OpenGLRenderPass::BuildLayerPassDescriptors( 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; @@ -253,6 +265,8 @@ void OpenGLRenderPass::RenderShaderProgram( 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); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp index 48dacb8..410d32d 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/shader/OpenGLShaderPrograms.cpp @@ -16,6 +16,8 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er std::size_t RequiredTemporaryRenderTargets(const std::vector& layerPrograms) { + // Only one layer renders at a time, so the pool needs to cover the widest + // layer, not the sum of every intermediate pass in the stack. std::size_t requiredTargets = 0; for (const OpenGLRenderer::LayerProgram& layerProgram : layerPrograms) { @@ -51,6 +53,8 @@ bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsign return false; } + // Initial startup still compiles synchronously; auto-reload uses the build + // queue so Slang/file work stays off the playback path. std::vector newPrograms; newPrograms.reserve(layerStates.size()); @@ -106,6 +110,8 @@ bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild return false; } + // The prepared build already contains GLSL text for each pass. This commit + // step performs the short GL work on the render thread. std::vector newPrograms; newPrograms.reserve(preparedBuild.layers.size()); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp index a1569c7..5281225 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp @@ -1324,6 +1324,8 @@ bool RuntimeHost::BuildLayerPassFragmentShaderSources(const std::string& layerId } ShaderCompiler compiler(mRepoRoot, mWrapperPath, mGeneratedGlslPath, mPatchedGlslPath, mConfig.maxTemporalHistoryFrames); + // Compile every declared pass while the caller remains backend-neutral. + // The GL layer decides how the resulting pass sources are routed. passSources.clear(); passSources.reserve(shaderPackage.passes.size()); for (const ShaderPassDefinition& pass : shaderPackage.passes) diff --git a/apps/LoopThroughWithOpenGLCompositing/shader/ShaderPackageRegistry.cpp b/apps/LoopThroughWithOpenGLCompositing/shader/ShaderPackageRegistry.cpp index 64c3e44..6312ba8 100644 --- a/apps/LoopThroughWithOpenGLCompositing/shader/ShaderPackageRegistry.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/shader/ShaderPackageRegistry.cpp @@ -258,6 +258,8 @@ bool ParsePassDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPa if (!passesValue) { + // Existing shader packages are treated as a single implicit pass, so + // multipass support does not require manifest churn. ShaderPassDefinition pass; pass.id = "main"; pass.entryPoint = shaderPackage.entryPoint; @@ -334,6 +336,8 @@ bool ParsePassDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPa } } + // Keep source validation in the registry. Bad pass declarations then + // appear as unavailable shaders instead of failing at render time. if (!std::filesystem::exists(pass.sourcePath)) { error = "Shader pass source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();