From 2a9264cfe4752806d5132959f8dc1ca2fa9c27fa Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 14 Apr 2026 16:49:06 +0800 Subject: [PATCH] Graph-ify forward feature injection points --- engine/CMakeLists.txt | 26 +- .../Pipelines/BuiltinForwardPipeline.h | 5 - .../Rendering/SceneRenderFeatureHost.h | 33 ++ .../Rendering/SceneRenderFeaturePass.h | 46 +++ .../Internal/BuiltinForwardPipelineFrame.cpp | 246 +++++++++----- .../BuiltinForwardPipelineScenePhases.cpp | 22 -- .../src/Rendering/SceneRenderFeatureHost.cpp | 193 +++++++++++ .../src/Rendering/SceneRenderFeaturePass.cpp | 99 ++++++ .../unit/test_builtin_forward_pipeline.cpp | 310 ++++++++++++++++++ 9 files changed, 861 insertions(+), 119 deletions(-) create mode 100644 engine/include/XCEngine/Rendering/SceneRenderFeatureHost.h create mode 100644 engine/src/Rendering/SceneRenderFeatureHost.cpp create mode 100644 engine/src/Rendering/SceneRenderFeaturePass.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 68509758..2e0136f2 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -486,6 +486,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/FrameExecutionContext.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/FrameStageRenderRequest.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/ScenePhase.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/SceneRenderInjectionPoint.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/SceneRenderer.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/CullingResults.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/RenderCameraData.h @@ -502,6 +503,10 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Picking/ObjectIdCodec.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Extraction/RenderSceneExtractor.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Extraction/RenderSceneUtility.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Graph/RenderGraphTypes.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Graph/RenderGraph.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Graph/RenderGraphCompiler.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Graph/RenderGraphExecutor.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Materials/RenderMaterialResolve.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Materials/RenderMaterialStateUtils.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderPass.h @@ -509,6 +514,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderPipelineAsset.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSurface.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeaturePass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeatureHost.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Caches/RenderResourceCache.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Caches/DirectionalShadowSurfaceCache.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Shadow/DirectionalShadowData.h @@ -523,8 +529,8 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinSelectionOutlinePass.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinGaussianSplatPass.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinVolumetricPass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Caches/DirectionalShadowSurfaceCache.cpp @@ -541,12 +547,17 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinSelectionOutlinePass.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinGaussianSplatPass.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinVolumetricPass.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/Internal/BuiltinGaussianSplatPassResources.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Features/BuiltinGaussianSplatPass.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Features/BuiltinVolumetricPass.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Features/Internal/BuiltinGaussianSplatPassResources.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSurface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/SceneRenderFeaturePass.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/SceneRenderFeatureHost.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Extraction/RenderSceneExtractor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Extraction/RenderSceneUtility.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Graph/RenderGraph.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Graph/RenderGraphCompiler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Graph/RenderGraphExecutor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Caches/RenderResourceCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/CameraFramePlanBuilder.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Planning/CameraFramePlanBuilder.cpp @@ -557,7 +568,12 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Shadow/DirectionalShadowRuntime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/SceneRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineLifecycle.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineScenePhases.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSkybox.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineSurface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineResources.cpp # Scene diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 06f089a2..53bc250e 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -335,11 +335,6 @@ private: const FrameExecutionContext& executionContext, size_t beginStepIndex, size_t endStepIndex); - bool ExecuteForwardSceneSegmentPass( - const FrameExecutionContext& executionContext, - size_t beginStepIndex, - size_t endStepIndex, - bool clearAttachments); bool BeginForwardScenePass( const RenderPassContext& context, bool clearAttachments = true); diff --git a/engine/include/XCEngine/Rendering/SceneRenderFeatureHost.h b/engine/include/XCEngine/Rendering/SceneRenderFeatureHost.h new file mode 100644 index 00000000..fcb80ed7 --- /dev/null +++ b/engine/include/XCEngine/Rendering/SceneRenderFeatureHost.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine { +namespace Rendering { + +class SceneRenderFeatureHost { +public: + void AddFeaturePass(std::unique_ptr featurePass); + size_t GetFeaturePassCount() const; + SceneRenderFeaturePass* GetFeaturePass(size_t index) const; + + bool Initialize(const RenderContext& context); + void Shutdown(); + bool Prepare(const FrameExecutionContext& executionContext) const; + bool Record( + const SceneRenderFeaturePassRenderGraphContext& context, + SceneRenderInjectionPoint injectionPoint, + bool* recordedAnyPass = nullptr) const; + bool Execute( + const FrameExecutionContext& executionContext, + SceneRenderInjectionPoint injectionPoint) const; + +private: + std::vector> m_featurePasses; +}; + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h b/engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h index 4fc5155f..2b351098 100644 --- a/engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h +++ b/engine/include/XCEngine/Rendering/SceneRenderFeaturePass.h @@ -1,16 +1,59 @@ #pragma once +#include +#include +#include #include +#include + +#include namespace XCEngine { namespace Rendering { +class RenderGraphBuilder; + +using SceneRenderFeaturePassBeginCallback = std::function; +using SceneRenderFeaturePassEndCallback = std::function; + +struct SceneRenderFeaturePassRenderGraphContext { + RenderGraphBuilder& graphBuilder; + Containers::String passName = {}; + const RenderContext& renderContext; + const RenderSceneData& sceneData; + RenderSurface surface = {}; + const RenderSurface* sourceSurface = nullptr; + RHI::RHIResourceView* sourceColorView = nullptr; + RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common; + std::vector colorTargets = {}; + RenderGraphTextureHandle depthTarget = {}; + RenderGraphTextureHandle mainDirectionalShadowTexture = {}; + bool clearAttachments = false; + bool* executionSucceeded = nullptr; + SceneRenderFeaturePassBeginCallback beginPassCallback = {}; + SceneRenderFeaturePassEndCallback endPassCallback = {}; +}; + class SceneRenderFeaturePass : public RenderPass { public: ~SceneRenderFeaturePass() override = default; + virtual SceneRenderInjectionPoint GetInjectionPoint() const { + return SceneRenderInjectionPoint::BeforeTransparent; + } + + bool SupportsInjectionPoint(SceneRenderInjectionPoint injectionPoint) const { + return GetInjectionPoint() == injectionPoint; + } + virtual bool IsActive(const RenderSceneData& sceneData) const = 0; + virtual bool Prepare(const FrameExecutionContext& executionContext) { + return Prepare( + executionContext.renderContext, + executionContext.sceneData); + } + virtual bool Prepare( const RenderContext& renderContext, const RenderSceneData& sceneData) { @@ -18,6 +61,9 @@ public: (void)sceneData; return true; } + + virtual bool RecordRenderGraph( + const SceneRenderFeaturePassRenderGraphContext& context); }; } // namespace Rendering diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp index 3e62740a..063d989d 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp @@ -2,6 +2,7 @@ #include "Debug/Logger.h" #include "Rendering/Graph/RenderGraph.h" +#include "Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h" #include "RHI/RHICommandList.h" #include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include "Rendering/RenderSurface.h" @@ -60,36 +61,24 @@ RenderSurface BuildGraphManagedForwardSceneSurface(const RenderSurface& template return surface; } -struct ForwardSceneGraphSegmentDesc { - const char* suffix = ""; - size_t beginStepIndex = 0u; - size_t endStepIndex = 0u; - bool clearAttachments = false; - bool samplesMainDirectionalShadow = false; -}; - struct ForwardSceneGraphExecutionState { bool initialized = false; }; -const std::array& GetForwardSceneGraphSegments() { - static constexpr std::array kSegments = {{ - { "Opaque", 0u, 3u, true, true }, - { "Skybox", 3u, 6u, false, false }, - { "Transparent", 6u, 9u, false, true } - }}; - return kSegments; -} - -Containers::String BuildForwardSceneSegmentPassName( +Containers::String BuildForwardScenePhasePassName( const Containers::String& baseName, - const char* suffix) { + ScenePhase scenePhase) { std::string name = baseName.CStr(); name += '.'; - name += suffix != nullptr ? suffix : "Segment"; + name += ToString(scenePhase); return Containers::String(name.c_str()); } +bool ScenePhaseSamplesMainDirectionalShadow(ScenePhase scenePhase) { + return scenePhase == ScenePhase::Opaque || + scenePhase == ScenePhase::Transparent; +} + } // namespace bool BuiltinForwardPipeline::ShouldSampleMainDirectionalShadowMap(const RenderSceneData& sceneData) { @@ -119,14 +108,108 @@ bool BuiltinForwardPipeline::RecordMainSceneRenderGraph( bool* const executionSucceeded = context.executionSucceeded; const std::shared_ptr graphExecutionState = std::make_shared(); + const SceneRenderFeaturePassBeginCallback beginRecordedPass = + [this, + graphExecutionState, + renderContext, + sceneData, + sourceSurface, + sourceColorView, + sourceColorState, + executionSucceeded]( + const RenderPassContext& passContext, + bool clearAttachments) -> bool { + if (executionSucceeded != nullptr && !(*executionSucceeded)) { + return false; + } - for (const ForwardSceneGraphSegmentDesc& segment : GetForwardSceneGraphSegments()) { - const Containers::String segmentPassName = - BuildForwardSceneSegmentPassName(passName, segment.suffix); + if (!graphExecutionState->initialized) { + if (!Initialize(*renderContext)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline::RecordMainSceneRenderGraph failed during execution: Initialize returned false"); + if (executionSucceeded != nullptr) { + *executionSucceeded = false; + } + return false; + } + + const FrameExecutionContext executionContext( + *renderContext, + passContext.surface, + *sceneData, + sourceSurface, + sourceColorView, + sourceColorState); + if (!m_forwardSceneFeatureHost.Prepare(executionContext)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline::RecordMainSceneRenderGraph failed during execution: SceneRenderFeatureHost::Prepare returned false"); + if (executionSucceeded != nullptr) { + *executionSucceeded = false; + } + return false; + } + + graphExecutionState->initialized = true; + } + + if (!BeginForwardScenePass(passContext, clearAttachments)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline::RecordMainSceneRenderGraph failed during execution: BeginForwardScenePass returned false"); + if (executionSucceeded != nullptr) { + *executionSucceeded = false; + } + return false; + } + + return true; + }; + const SceneRenderFeaturePassEndCallback endRecordedPass = + [this](const RenderPassContext& passContext) { + EndForwardScenePass(passContext); + }; + + bool clearAttachments = true; + for (const Pipelines::Internal::ForwardSceneStep& step : Pipelines::Internal::GetBuiltinForwardSceneSteps()) { + if (step.type == Pipelines::Internal::ForwardSceneStepType::InjectionPoint) { + const SceneRenderFeaturePassRenderGraphContext featureContext = { + context.graphBuilder, + passName, + *renderContext, + *sceneData, + surfaceTemplate, + sourceSurface, + sourceColorView, + sourceColorState, + colorTargets, + depthTarget, + mainDirectionalShadowTexture, + clearAttachments, + executionSucceeded, + beginRecordedPass, + endRecordedPass + }; + bool recordedAnyPass = false; + if (!m_forwardSceneFeatureHost.Record( + featureContext, + step.injectionPoint, + &recordedAnyPass)) { + return false; + } + + if (recordedAnyPass) { + clearAttachments = false; + } + continue; + } + + const Containers::String phasePassName = + BuildForwardScenePhasePassName(passName, step.scenePhase); context.graphBuilder.AddRasterPass( - segmentPassName, + phasePassName, [this, - graphExecutionState, surfaceTemplate, renderContext, sceneData, @@ -137,78 +220,67 @@ bool BuiltinForwardPipeline::RecordMainSceneRenderGraph( depthTarget, mainDirectionalShadowTexture, executionSucceeded, - segment]( + beginRecordedPass, + endRecordedPass, + clearAttachments, + scenePhase = step.scenePhase]( RenderGraphPassBuilder& passBuilder) { - for (RenderGraphTextureHandle colorTarget : colorTargets) { - if (colorTarget.IsValid()) { - passBuilder.WriteTexture(colorTarget); + for (RenderGraphTextureHandle colorTarget : colorTargets) { + if (colorTarget.IsValid()) { + passBuilder.WriteTexture(colorTarget); + } } - } - if (depthTarget.IsValid()) { - passBuilder.WriteDepthTexture(depthTarget); - } - if (segment.samplesMainDirectionalShadow && - mainDirectionalShadowTexture.IsValid()) { - passBuilder.ReadTexture(mainDirectionalShadowTexture); - } + if (depthTarget.IsValid()) { + passBuilder.WriteDepthTexture(depthTarget); + } + if (ScenePhaseSamplesMainDirectionalShadow(scenePhase) && + mainDirectionalShadowTexture.IsValid()) { + passBuilder.ReadTexture(mainDirectionalShadowTexture); + } - passBuilder.SetExecuteCallback( - [this, - graphExecutionState, - surfaceTemplate, - renderContext, - sceneData, - sourceSurface, - sourceColorView, - sourceColorState, - executionSucceeded, - segment]( - const RenderGraphExecutionContext&) { - if (executionSucceeded != nullptr && !(*executionSucceeded)) { - return; - } - - const FrameExecutionContext executionContext( - *renderContext, - surfaceTemplate, - *sceneData, - sourceSurface, - sourceColorView, - sourceColorState); - if (!graphExecutionState->initialized) { - if (!Initialize(*renderContext)) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline::RecordMainSceneRenderGraph failed during execution: Initialize returned false"); - if (executionSucceeded != nullptr) { - *executionSucceeded = false; - } + passBuilder.SetExecuteCallback( + [this, + surfaceTemplate, + renderContext, + sceneData, + sourceSurface, + sourceColorView, + sourceColorState, + executionSucceeded, + beginRecordedPass, + endRecordedPass, + clearAttachments, + scenePhase]( + const RenderGraphExecutionContext&) { + if (executionSucceeded != nullptr && !(*executionSucceeded)) { return; } - if (!m_forwardSceneFeatureHost.Prepare(executionContext)) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline::RecordMainSceneRenderGraph failed during execution: SceneRenderFeatureHost::Prepare returned false"); - if (executionSucceeded != nullptr) { - *executionSucceeded = false; - } + + const FrameExecutionContext executionContext( + *renderContext, + surfaceTemplate, + *sceneData, + sourceSurface, + sourceColorView, + sourceColorState); + const RenderPassContext passContext = + BuildRenderPassContext(executionContext); + if (!beginRecordedPass(passContext, clearAttachments)) { return; } - graphExecutionState->initialized = true; - } - const bool renderResult = - ExecuteForwardSceneSegmentPass( - executionContext, - segment.beginStepIndex, - segment.endStepIndex, - segment.clearAttachments); - if (executionSucceeded != nullptr) { - *executionSucceeded = renderResult; - } - }); + const ScenePhaseExecutionContext scenePhaseExecutionContext = + BuildScenePhaseExecutionContext(executionContext, scenePhase); + const bool renderResult = + ExecuteBuiltinScenePhase(scenePhaseExecutionContext); + endRecordedPass(passContext); + if (executionSucceeded != nullptr) { + *executionSucceeded = renderResult; + } + }); }); + clearAttachments = false; } return true; diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineScenePhases.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineScenePhases.cpp index 2cefd5e8..1ca05adf 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineScenePhases.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineScenePhases.cpp @@ -101,28 +101,6 @@ bool BuiltinForwardPipeline::ExecuteForwardSceneSteps( return true; } -bool BuiltinForwardPipeline::ExecuteForwardSceneSegmentPass( - const FrameExecutionContext& executionContext, - size_t beginStepIndex, - size_t endStepIndex, - bool clearAttachments) { - const RenderPassContext passContext = BuildRenderPassContext(executionContext); - if (!BeginForwardScenePass(passContext, clearAttachments)) { - Debug::Logger::Get().Error( - Debug::LogCategory::Rendering, - "BuiltinForwardPipeline::ExecuteForwardSceneSegmentPass failed: BeginForwardScenePass returned false"); - return false; - } - - const bool executeResult = - ExecuteForwardSceneSteps( - executionContext, - beginStepIndex, - endStepIndex); - EndForwardScenePass(passContext); - return executeResult; -} - } // namespace Pipelines } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/SceneRenderFeatureHost.cpp b/engine/src/Rendering/SceneRenderFeatureHost.cpp new file mode 100644 index 00000000..79213236 --- /dev/null +++ b/engine/src/Rendering/SceneRenderFeatureHost.cpp @@ -0,0 +1,193 @@ +#include "Rendering/SceneRenderFeatureHost.h" + +#include "Debug/Logger.h" + +#include + +namespace XCEngine { +namespace Rendering { +namespace { + +Containers::String BuildFeatureGraphPassName( + const Containers::String& basePassName, + SceneRenderInjectionPoint injectionPoint, + const SceneRenderFeaturePass& featurePass, + size_t featureIndex) { + std::string name = basePassName.CStr(); + if (!name.empty()) { + name += '.'; + } + + name += ToString(injectionPoint); + name += '.'; + name += featurePass.GetName() != nullptr + ? featurePass.GetName() + : "FeaturePass"; + name += '.'; + name += std::to_string(featureIndex); + return Containers::String(name.c_str()); +} + +} // namespace + +void SceneRenderFeatureHost::AddFeaturePass(std::unique_ptr featurePass) { + if (featurePass != nullptr) { + m_featurePasses.push_back(std::move(featurePass)); + } +} + +size_t SceneRenderFeatureHost::GetFeaturePassCount() const { + return m_featurePasses.size(); +} + +SceneRenderFeaturePass* SceneRenderFeatureHost::GetFeaturePass(size_t index) const { + return index < m_featurePasses.size() + ? m_featurePasses[index].get() + : nullptr; +} + +bool SceneRenderFeatureHost::Initialize(const RenderContext& context) { + size_t initializedCount = 0u; + for (const std::unique_ptr& featurePassOwner : m_featurePasses) { + SceneRenderFeaturePass* featurePass = featurePassOwner.get(); + if (featurePass == nullptr) { + continue; + } + + if (featurePass->Initialize(context)) { + ++initializedCount; + continue; + } + + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + (Containers::String("SceneRenderFeatureHost initialize failed: ") + + featurePass->GetName()).CStr()); + for (size_t shutdownIndex = initializedCount; shutdownIndex > 0u; --shutdownIndex) { + SceneRenderFeaturePass* initializedPass = m_featurePasses[shutdownIndex - 1u].get(); + if (initializedPass != nullptr) { + initializedPass->Shutdown(); + } + } + return false; + } + + return true; +} + +void SceneRenderFeatureHost::Shutdown() { + for (auto featurePassIt = m_featurePasses.rbegin(); featurePassIt != m_featurePasses.rend(); ++featurePassIt) { + if (*featurePassIt != nullptr) { + (*featurePassIt)->Shutdown(); + } + } +} + +bool SceneRenderFeatureHost::Prepare(const FrameExecutionContext& executionContext) const { + for (const std::unique_ptr& featurePassOwner : m_featurePasses) { + SceneRenderFeaturePass* featurePass = featurePassOwner.get(); + if (featurePass == nullptr || !featurePass->IsActive(executionContext.sceneData)) { + continue; + } + + if (!featurePass->Prepare(executionContext)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + (Containers::String("SceneRenderFeatureHost prepare failed: ") + + featurePass->GetName()).CStr()); + return false; + } + } + + return true; +} + +bool SceneRenderFeatureHost::Record( + const SceneRenderFeaturePassRenderGraphContext& context, + SceneRenderInjectionPoint injectionPoint, + bool* recordedAnyPass) const { + bool hasRecordedPass = false; + bool clearAttachments = context.clearAttachments; + + for (size_t featureIndex = 0u; featureIndex < m_featurePasses.size(); ++featureIndex) { + const std::unique_ptr& featurePassOwner = m_featurePasses[featureIndex]; + SceneRenderFeaturePass* featurePass = featurePassOwner.get(); + if (featurePass == nullptr || + !featurePass->SupportsInjectionPoint(injectionPoint) || + !featurePass->IsActive(context.sceneData)) { + continue; + } + + const SceneRenderFeaturePassRenderGraphContext featureContext = { + context.graphBuilder, + BuildFeatureGraphPassName( + context.passName, + injectionPoint, + *featurePass, + featureIndex), + context.renderContext, + context.sceneData, + context.surface, + context.sourceSurface, + context.sourceColorView, + context.sourceColorState, + context.colorTargets, + context.depthTarget, + context.mainDirectionalShadowTexture, + clearAttachments, + context.executionSucceeded, + context.beginPassCallback, + context.endPassCallback + }; + if (!featurePass->RecordRenderGraph(featureContext)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + (Containers::String("SceneRenderFeatureHost record failed at injection point '") + + ToString(injectionPoint) + + "': " + + featurePass->GetName()).CStr()); + if (recordedAnyPass != nullptr) { + *recordedAnyPass = hasRecordedPass; + } + return false; + } + + hasRecordedPass = true; + clearAttachments = false; + } + + if (recordedAnyPass != nullptr) { + *recordedAnyPass = hasRecordedPass; + } + return true; +} + +bool SceneRenderFeatureHost::Execute( + const FrameExecutionContext& executionContext, + SceneRenderInjectionPoint injectionPoint) const { + const RenderPassContext passContext = BuildRenderPassContext(executionContext); + + for (const std::unique_ptr& featurePassOwner : m_featurePasses) { + SceneRenderFeaturePass* featurePass = featurePassOwner.get(); + if (featurePass == nullptr || + !featurePass->SupportsInjectionPoint(injectionPoint) || + !featurePass->IsActive(executionContext.sceneData)) { + continue; + } + + if (!featurePass->Execute(passContext)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + (Containers::String("SceneRenderFeatureHost execute failed at injection point '") + + ToString(injectionPoint) + + "': " + + featurePass->GetName()).CStr()); + return false; + } + } + + return true; +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/SceneRenderFeaturePass.cpp b/engine/src/Rendering/SceneRenderFeaturePass.cpp new file mode 100644 index 00000000..3a5f85a6 --- /dev/null +++ b/engine/src/Rendering/SceneRenderFeaturePass.cpp @@ -0,0 +1,99 @@ +#include "Rendering/SceneRenderFeaturePass.h" + +#include "Rendering/Graph/RenderGraph.h" + +namespace XCEngine { +namespace Rendering { + +bool SceneRenderFeaturePass::RecordRenderGraph( + const SceneRenderFeaturePassRenderGraphContext& context) { + SceneRenderFeaturePass* const featurePass = this; + const Containers::String passName = context.passName; + const RenderContext* const renderContext = &context.renderContext; + const RenderSceneData* const sceneData = &context.sceneData; + const RenderSurface surface = context.surface; + const RenderSurface* const sourceSurface = context.sourceSurface; + RHI::RHIResourceView* const sourceColorView = context.sourceColorView; + const RHI::ResourceStates sourceColorState = context.sourceColorState; + const std::vector colorTargets = context.colorTargets; + const RenderGraphTextureHandle depthTarget = context.depthTarget; + const bool clearAttachments = context.clearAttachments; + bool* const executionSucceeded = context.executionSucceeded; + const SceneRenderFeaturePassBeginCallback beginPassCallback = context.beginPassCallback; + const SceneRenderFeaturePassEndCallback endPassCallback = context.endPassCallback; + + context.graphBuilder.AddRasterPass( + passName, + [featurePass, + renderContext, + sceneData, + surface, + sourceSurface, + sourceColorView, + sourceColorState, + colorTargets, + depthTarget, + clearAttachments, + executionSucceeded, + beginPassCallback, + endPassCallback]( + RenderGraphPassBuilder& passBuilder) { + for (RenderGraphTextureHandle colorTarget : colorTargets) { + if (colorTarget.IsValid()) { + passBuilder.WriteTexture(colorTarget); + } + } + + if (depthTarget.IsValid()) { + passBuilder.WriteDepthTexture(depthTarget); + } + + passBuilder.SetExecuteCallback( + [featurePass, + renderContext, + sceneData, + surface, + sourceSurface, + sourceColorView, + sourceColorState, + clearAttachments, + executionSucceeded, + beginPassCallback, + endPassCallback]( + const RenderGraphExecutionContext&) { + if (executionSucceeded != nullptr && !(*executionSucceeded)) { + return; + } + + const FrameExecutionContext executionContext( + *renderContext, + surface, + *sceneData, + sourceSurface, + sourceColorView, + sourceColorState); + const RenderPassContext passContext = + BuildRenderPassContext(executionContext); + if (beginPassCallback && + !beginPassCallback(passContext, clearAttachments)) { + if (executionSucceeded != nullptr) { + *executionSucceeded = false; + } + return; + } + + const bool executeResult = featurePass->Execute(passContext); + if (endPassCallback) { + endPassCallback(passContext); + } + + if (executionSucceeded != nullptr) { + *executionSucceeded = executeResult; + } + }); + }); + return true; +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 7a6e978c..c51b7dec 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include #include @@ -457,10 +458,18 @@ public: return true; } + bool RecordRenderGraph( + const SceneRenderFeaturePassRenderGraphContext& context) override { + ++recordGraphCallCount; + RecordEvent("RecordGraph"); + return SceneRenderFeaturePass::RecordRenderGraph(context); + } + size_t initializeCallCount = 0u; mutable size_t isActiveCallCount = 0u; size_t prepareCallCount = 0u; size_t executeCallCount = 0u; + size_t recordGraphCallCount = 0u; size_t shutdownCallCount = 0u; private: @@ -644,6 +653,109 @@ TEST(SceneRenderFeaturePass_Test, SupportsExplicitInjectionPointContract) { EXPECT_FALSE(feature.SupportsInjectionPoint(SceneRenderInjectionPoint::BeforeTransparent)); } +TEST(SceneRenderFeaturePass_Test, RecordsDefaultGraphPassAndExecutesWrappedCallbacks) { + std::vector eventLog = {}; + TestSceneRenderFeaturePass feature( + SceneRenderInjectionPoint::BeforeOpaque, + true, + &eventLog, + "FeatureGraph"); + + RenderGraph graph = {}; + RenderGraphBuilder graphBuilder(graph); + + RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 64u; + colorDesc.height = 64u; + colorDesc.format = static_cast(Format::R8G8B8A8_UNorm); + colorDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + colorDesc.sampleCount = 1u; + + RenderGraphTextureDesc depthDesc = {}; + depthDesc.width = 64u; + depthDesc.height = 64u; + depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); + depthDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + depthDesc.sampleCount = 1u; + + TestResourceView colorView( + ResourceViewType::RenderTarget, + ResourceViewDimension::Texture2D, + Format::R8G8B8A8_UNorm); + TestResourceView depthView( + ResourceViewType::DepthStencil, + ResourceViewDimension::Texture2D, + Format::D24_UNorm_S8_UInt); + + const RenderGraphImportedTextureOptions graphManagedImport = { + ResourceStates::Common, + ResourceStates::Common, + true + }; + const RenderGraphTextureHandle colorTarget = + graphBuilder.ImportTexture("Color", colorDesc, &colorView, graphManagedImport); + const RenderGraphTextureHandle depthTarget = + graphBuilder.ImportTexture("Depth", depthDesc, &depthView, graphManagedImport); + + RenderContext renderContext = {}; + MockForwardCommandList commandList; + renderContext.commandList = &commandList; + + RenderSceneData sceneData = {}; + RenderSurface surface(64u, 64u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + surface.SetAutoTransitionEnabled(false); + + size_t beginPassCalls = 0u; + size_t endPassCalls = 0u; + bool executionSucceeded = true; + const SceneRenderFeaturePassRenderGraphContext context = { + graphBuilder, + "FeatureGraphPass", + renderContext, + sceneData, + surface, + nullptr, + nullptr, + ResourceStates::Common, + { colorTarget }, + depthTarget, + {}, + false, + &executionSucceeded, + [&beginPassCalls](const RenderPassContext&, bool) { + ++beginPassCalls; + return true; + }, + [&endPassCalls](const RenderPassContext&) { + ++endPassCalls; + } + }; + + ASSERT_TRUE(feature.RecordRenderGraph(context)); + + CompiledRenderGraph compiledGraph = {}; + String errorMessage; + ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) + << errorMessage.CStr(); + ASSERT_EQ(compiledGraph.GetPassCount(), 1u); + EXPECT_STREQ(compiledGraph.GetPassName(0).CStr(), "FeatureGraphPass"); + + ASSERT_TRUE(RenderGraphExecutor::Execute(compiledGraph, renderContext, &errorMessage)) + << errorMessage.CStr(); + EXPECT_TRUE(executionSucceeded); + EXPECT_EQ(feature.recordGraphCallCount, 1u); + EXPECT_EQ(feature.executeCallCount, 1u); + EXPECT_EQ(beginPassCalls, 1u); + EXPECT_EQ(endPassCalls, 1u); + EXPECT_EQ( + eventLog, + (std::vector{ + "FeatureGraph:RecordGraph", + "FeatureGraph:Execute" })); +} + TEST(BuiltinForwardPipeline_Test, RegistersBuiltinDefaultForwardSceneFeatures) { BuiltinForwardPipeline pipeline; @@ -720,6 +832,127 @@ TEST(SceneRenderFeatureHost_Test, FiltersByInjectionPointAndShutsDownInReverseOr EXPECT_EQ(eventLog[eventLog.size() - 3u], "Inactive:Shutdown"); } +TEST(SceneRenderFeatureHost_Test, RecordsActiveInjectionPointFeaturesIntoRenderGraphInOrder) { + SceneRenderFeatureHost host; + std::vector eventLog = {}; + + auto first = std::make_unique( + SceneRenderInjectionPoint::BeforeOpaque, + true, + &eventLog, + "First"); + auto second = std::make_unique( + SceneRenderInjectionPoint::BeforeOpaque, + true, + &eventLog, + "Second"); + auto otherPoint = std::make_unique( + SceneRenderInjectionPoint::AfterOpaque, + true, + &eventLog, + "OtherPoint"); + auto inactive = std::make_unique( + SceneRenderInjectionPoint::BeforeOpaque, + false, + &eventLog, + "Inactive"); + + TestSceneRenderFeaturePass* firstRaw = first.get(); + TestSceneRenderFeaturePass* secondRaw = second.get(); + TestSceneRenderFeaturePass* otherPointRaw = otherPoint.get(); + TestSceneRenderFeaturePass* inactiveRaw = inactive.get(); + + host.AddFeaturePass(std::move(first)); + host.AddFeaturePass(std::move(second)); + host.AddFeaturePass(std::move(otherPoint)); + host.AddFeaturePass(std::move(inactive)); + + RenderGraph graph = {}; + RenderGraphBuilder graphBuilder(graph); + + RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 64u; + colorDesc.height = 64u; + colorDesc.format = static_cast(Format::R8G8B8A8_UNorm); + colorDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + colorDesc.sampleCount = 1u; + + RenderGraphTextureDesc depthDesc = {}; + depthDesc.width = 64u; + depthDesc.height = 64u; + depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); + depthDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + depthDesc.sampleCount = 1u; + + TestResourceView colorView( + ResourceViewType::RenderTarget, + ResourceViewDimension::Texture2D, + Format::R8G8B8A8_UNorm); + TestResourceView depthView( + ResourceViewType::DepthStencil, + ResourceViewDimension::Texture2D, + Format::D24_UNorm_S8_UInt); + + const RenderGraphImportedTextureOptions graphManagedImport = { + ResourceStates::Common, + ResourceStates::Common, + true + }; + const RenderGraphTextureHandle colorTarget = + graphBuilder.ImportTexture("Color", colorDesc, &colorView, graphManagedImport); + const RenderGraphTextureHandle depthTarget = + graphBuilder.ImportTexture("Depth", depthDesc, &depthView, graphManagedImport); + + RenderContext renderContext = {}; + RenderSceneData sceneData = {}; + RenderSurface surface(64u, 64u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + surface.SetAutoTransitionEnabled(false); + + bool executionSucceeded = true; + const SceneRenderFeaturePassRenderGraphContext context = { + graphBuilder, + "MainScene", + renderContext, + sceneData, + surface, + nullptr, + nullptr, + ResourceStates::Common, + { colorTarget }, + depthTarget, + {}, + true, + &executionSucceeded, + [](const RenderPassContext&, bool) { + return true; + }, + [](const RenderPassContext&) { + } + }; + + bool recordedAnyPass = false; + ASSERT_TRUE(host.Record( + context, + SceneRenderInjectionPoint::BeforeOpaque, + &recordedAnyPass)); + EXPECT_TRUE(recordedAnyPass); + + CompiledRenderGraph compiledGraph = {}; + String errorMessage; + ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) + << errorMessage.CStr(); + ASSERT_EQ(compiledGraph.GetPassCount(), 2u); + EXPECT_STREQ(compiledGraph.GetPassName(0).CStr(), "MainScene.BeforeOpaque.First.0"); + EXPECT_STREQ(compiledGraph.GetPassName(1).CStr(), "MainScene.BeforeOpaque.Second.1"); + + EXPECT_EQ(firstRaw->recordGraphCallCount, 1u); + EXPECT_EQ(secondRaw->recordGraphCallCount, 1u); + EXPECT_EQ(otherPointRaw->recordGraphCallCount, 0u); + EXPECT_EQ(inactiveRaw->recordGraphCallCount, 0u); +} + TEST(BuiltinForwardPipeline_Test, RegistersAdditionalForwardSceneFeaturePasses) { BuiltinForwardPipeline pipeline; const size_t initialFeaturePassCount = pipeline.GetForwardSceneFeaturePassCount(); @@ -738,6 +971,83 @@ TEST(BuiltinForwardPipeline_Test, RegistersAdditionalForwardSceneFeaturePasses) EXPECT_EQ(pipeline.GetForwardSceneFeaturePass(initialFeaturePassCount + 1u), nullptr); } +TEST(BuiltinForwardPipeline_Test, RecordsActiveFeatureInjectionPassesIntoMainSceneGraph) { + BuiltinForwardPipeline pipeline; + + RenderGraph graph = {}; + RenderGraphBuilder graphBuilder(graph); + + RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 320u; + colorDesc.height = 180u; + colorDesc.format = static_cast(Format::R8G8B8A8_UNorm); + colorDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + colorDesc.sampleCount = 1u; + + RenderGraphTextureDesc depthDesc = {}; + depthDesc.width = 320u; + depthDesc.height = 180u; + depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); + depthDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + depthDesc.sampleCount = 1u; + + TestResourceView colorView( + ResourceViewType::RenderTarget, + ResourceViewDimension::Texture2D, + Format::R8G8B8A8_UNorm); + TestResourceView depthView( + ResourceViewType::DepthStencil, + ResourceViewDimension::Texture2D, + Format::D24_UNorm_S8_UInt); + + const RenderGraphImportedTextureOptions graphManagedImport = { + ResourceStates::Common, + ResourceStates::Common, + true + }; + const RenderGraphTextureHandle colorTarget = + graphBuilder.ImportTexture("MainColor", colorDesc, &colorView, graphManagedImport); + const RenderGraphTextureHandle depthTarget = + graphBuilder.ImportTexture("MainDepth", depthDesc, &depthView, graphManagedImport); + + RenderSurface surface(320u, 180u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + + RenderContext renderContext = {}; + RenderSceneData sceneData = {}; + sceneData.visibleGaussianSplats.push_back({}); + sceneData.visibleVolumes.push_back({}); + bool executionSucceeded = true; + const RenderPipelineMainSceneRenderGraphContext context = { + graphBuilder, + "MainScene", + renderContext, + sceneData, + surface, + nullptr, + nullptr, + ResourceStates::Common, + { colorTarget }, + depthTarget, + {}, + &executionSucceeded + }; + + ASSERT_TRUE(pipeline.RecordMainSceneRenderGraph(context)); + + CompiledRenderGraph compiledGraph = {}; + String errorMessage; + ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) + << errorMessage.CStr(); + ASSERT_EQ(compiledGraph.GetPassCount(), 5u); + EXPECT_STREQ(compiledGraph.GetPassName(0).CStr(), "MainScene.Opaque"); + EXPECT_STREQ(compiledGraph.GetPassName(1).CStr(), "MainScene.Skybox"); + EXPECT_STREQ(compiledGraph.GetPassName(2).CStr(), "MainScene.BeforeTransparent.BuiltinGaussianSplatPass.0"); + EXPECT_STREQ(compiledGraph.GetPassName(3).CStr(), "MainScene.BeforeTransparent.BuiltinVolumetricPass.1"); + EXPECT_STREQ(compiledGraph.GetPassName(4).CStr(), "MainScene.Transparent"); +} + TEST(BuiltinForwardPipeline_Test, PropagatesForwardDrawFailureWhenPipelineStateCreationFails) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath());