From 1ba73fdf0a307ac4d4ed668dd8359f6a624b8ef6 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 18 Apr 2026 01:19:02 +0800 Subject: [PATCH] refactor(rendering): extract native scene renderer contract for srp --- .../XCEngine/Rendering/NativeSceneRenderer.h | 27 +++ .../Pipelines/BuiltinForwardPipeline.h | 14 +- .../Pipelines/BuiltinForwardSceneRecorder.h | 6 +- .../Pipelines/BuiltinForwardSceneRecorder.cpp | 50 +++-- .../Internal/BuiltinForwardPipelineFrame.cpp | 31 +++- .../BuiltinForwardPipelineScenePhases.cpp | 16 ++ .../unit/test_builtin_forward_pipeline.cpp | 175 ++++++++++++++++++ 7 files changed, 280 insertions(+), 39 deletions(-) create mode 100644 engine/include/XCEngine/Rendering/NativeSceneRenderer.h diff --git a/engine/include/XCEngine/Rendering/NativeSceneRenderer.h b/engine/include/XCEngine/Rendering/NativeSceneRenderer.h new file mode 100644 index 00000000..baa03046 --- /dev/null +++ b/engine/include/XCEngine/Rendering/NativeSceneRenderer.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +namespace XCEngine { +namespace Rendering { + +class NativeSceneRenderer { +public: + virtual ~NativeSceneRenderer() = default; + + virtual bool Initialize(const RenderContext& context) = 0; + virtual bool PrepareScene(const FrameExecutionContext& executionContext) = 0; + virtual SceneRenderFeatureHost* GetFeatureHost() = 0; + virtual bool BeginScenePass( + const RenderPassContext& context, + bool clearAttachments = true) = 0; + virtual void EndScenePass(const RenderPassContext& context) = 0; + virtual bool ExecuteScenePhase( + const RenderPassContext& context, + ScenePhase scenePhase) = 0; +}; + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 34ab8cbf..e4161393 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -48,7 +49,7 @@ namespace Pipelines { class BuiltinForwardSceneRecorder; -class BuiltinForwardPipeline : public RenderPipeline { +class BuiltinForwardPipeline : public RenderPipeline, public NativeSceneRenderer { public: BuiltinForwardPipeline(); ~BuiltinForwardPipeline() override; @@ -62,6 +63,15 @@ public: SceneRenderFeaturePass* GetForwardSceneFeaturePass(size_t index) const; bool Initialize(const RenderContext& context) override; + bool PrepareScene(const FrameExecutionContext& executionContext) override; + SceneRenderFeatureHost* GetFeatureHost() override; + bool BeginScenePass( + const RenderPassContext& context, + bool clearAttachments = true) override; + void EndScenePass(const RenderPassContext& context) override; + bool ExecuteScenePhase( + const RenderPassContext& context, + ScenePhase scenePhase) override; void Shutdown() override; bool SupportsStageRenderGraph(CameraFrameStage stage) const override; bool RecordStageRenderGraph( @@ -387,8 +397,6 @@ private: RHI::RHIResourceView* m_skyboxBoundCubemapTextureView = nullptr; SceneRenderFeatureHost m_forwardSceneFeatureHost; - friend class BuiltinForwardSceneRecorder; - friend struct Internal::BuiltinForwardStageGraphBuilder; }; class BuiltinForwardPipelineAsset final : public RenderPipelineAsset { diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardSceneRecorder.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardSceneRecorder.h index ef1c732b..e46fb753 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardSceneRecorder.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardSceneRecorder.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -15,13 +16,12 @@ struct RenderPipelineStageRenderGraphContext; namespace Pipelines { -class BuiltinForwardPipeline; struct BuiltinForwardSceneRecorderState; class BuiltinForwardSceneRecorder { public: BuiltinForwardSceneRecorder( - BuiltinForwardPipeline& pipeline, + NativeSceneRenderer& sceneRenderer, const RenderPipelineStageRenderGraphContext& context); ~BuiltinForwardSceneRecorder(); @@ -35,7 +35,7 @@ private: SceneRenderFeaturePassEndCallback BuildEndRecordedPassCallback() const; RenderPipelineStageRenderGraphContext BuildGraphContext() const; - BuiltinForwardPipeline& m_pipeline; + NativeSceneRenderer& m_sceneRenderer; const RenderPipelineStageRenderGraphContext& m_context; RenderSurface m_graphManagedSurface = {}; RenderGraphTextureHandle m_mainDirectionalShadowTexture = {}; diff --git a/engine/src/Rendering/Pipelines/BuiltinForwardSceneRecorder.cpp b/engine/src/Rendering/Pipelines/BuiltinForwardSceneRecorder.cpp index df75c71f..9f6e62b3 100644 --- a/engine/src/Rendering/Pipelines/BuiltinForwardSceneRecorder.cpp +++ b/engine/src/Rendering/Pipelines/BuiltinForwardSceneRecorder.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include @@ -30,9 +29,9 @@ bool ScenePhaseSamplesMainDirectionalShadow(ScenePhase scenePhase) { } // namespace BuiltinForwardSceneRecorder::BuiltinForwardSceneRecorder( - BuiltinForwardPipeline& pipeline, + NativeSceneRenderer& sceneRenderer, const RenderPipelineStageRenderGraphContext& context) - : m_pipeline(pipeline) + : m_sceneRenderer(sceneRenderer) , m_context(context) , m_graphManagedSurface( BuildRenderGraphManagedSurfaceTemplate(context.surfaceTemplate)) @@ -49,9 +48,9 @@ BuiltinForwardSceneRecorder::~BuiltinForwardSceneRecorder() = default; SceneRenderFeaturePassBeginCallback BuiltinForwardSceneRecorder::BuildBeginRecordedPassCallback( bool* executionSucceeded) const { - BuiltinForwardPipeline& pipeline = m_pipeline; + NativeSceneRenderer& sceneRenderer = m_sceneRenderer; const std::shared_ptr state = m_state; - return [&pipeline, state, executionSucceeded]( + return [&sceneRenderer, state, executionSucceeded]( const RenderPassContext& passContext, bool clearAttachments) -> bool { if (executionSucceeded != nullptr && @@ -60,7 +59,7 @@ BuiltinForwardSceneRecorder::BuildBeginRecordedPassCallback( } if (!state->initialized) { - if (!pipeline.Initialize(passContext.renderContext)) { + if (!sceneRenderer.Initialize(passContext.renderContext)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinForwardSceneRecorder failed during execution: Initialize returned false"); @@ -77,10 +76,10 @@ BuiltinForwardSceneRecorder::BuildBeginRecordedPassCallback( passContext.sourceSurface, passContext.sourceColorView, passContext.sourceColorState); - if (!pipeline.m_forwardSceneFeatureHost.Prepare(executionContext)) { + if (!sceneRenderer.PrepareScene(executionContext)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, - "BuiltinForwardSceneRecorder failed during execution: SceneRenderFeatureHost::Prepare returned false"); + "BuiltinForwardSceneRecorder failed during execution: NativeSceneRenderer::PrepareScene returned false"); if (executionSucceeded != nullptr) { *executionSucceeded = false; } @@ -90,12 +89,12 @@ BuiltinForwardSceneRecorder::BuildBeginRecordedPassCallback( state->initialized = true; } - if (!pipeline.BeginForwardScenePass( + if (!sceneRenderer.BeginScenePass( passContext, clearAttachments)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, - "BuiltinForwardSceneRecorder failed during execution: BeginForwardScenePass returned false"); + "BuiltinForwardSceneRecorder failed during execution: NativeSceneRenderer::BeginScenePass returned false"); if (executionSucceeded != nullptr) { *executionSucceeded = false; } @@ -108,9 +107,9 @@ BuiltinForwardSceneRecorder::BuildBeginRecordedPassCallback( SceneRenderFeaturePassEndCallback BuiltinForwardSceneRecorder::BuildEndRecordedPassCallback() const { - BuiltinForwardPipeline& pipeline = m_pipeline; - return [&pipeline](const RenderPassContext& passContext) { - pipeline.EndForwardScenePass(passContext); + NativeSceneRenderer& sceneRenderer = m_sceneRenderer; + return [&sceneRenderer](const RenderPassContext& passContext) { + sceneRenderer.EndScenePass(passContext); }; } @@ -164,21 +163,11 @@ bool BuiltinForwardSceneRecorder::RecordScenePhase( if (!::XCEngine::Rendering::RecordRenderPipelineStagePhasePass( graphContext, scenePhase, - [&pipeline = m_pipeline, scenePhase]( + [&sceneRenderer = m_sceneRenderer, scenePhase]( const RenderPassContext& passContext) { - const FrameExecutionContext executionContext( - passContext.renderContext, - passContext.surface, - passContext.sceneData, - passContext.sourceSurface, - passContext.sourceColorView, - passContext.sourceColorState); - const ScenePhaseExecutionContext scenePhaseExecutionContext = - pipeline.BuildScenePhaseExecutionContext( - executionContext, - scenePhase); - return pipeline.ExecuteBuiltinScenePhase( - scenePhaseExecutionContext); + return sceneRenderer.ExecuteScenePhase( + passContext, + scenePhase); }, beginPhasePass, endRecordedPass, @@ -202,10 +191,15 @@ bool BuiltinForwardSceneRecorder::RecordInjectionPoint( BuildBeginRecordedPassCallback(graphContext.executionSucceeded); const SceneRenderFeaturePassEndCallback endRecordedPass = BuildEndRecordedPassCallback(); + SceneRenderFeatureHost* const featureHost = + m_sceneRenderer.GetFeatureHost(); + if (featureHost == nullptr) { + return false; + } bool recordedAnyPass = false; if (!::XCEngine::Rendering::RecordRenderPipelineStageFeaturePasses( graphContext, - m_pipeline.m_forwardSceneFeatureHost, + *featureHost, injectionPoint, m_clearAttachments, beginRecordedPass, diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp index e17940e7..8073268f 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp @@ -59,6 +59,15 @@ bool BuiltinForwardPipeline::ShouldSampleMainDirectionalShadowMap(const RenderSc IsDepthFormat(sceneData.lighting.mainDirectionalShadow.shadowMap->GetFormat()); } +bool BuiltinForwardPipeline::PrepareScene( + const FrameExecutionContext& executionContext) { + return m_forwardSceneFeatureHost.Prepare(executionContext); +} + +SceneRenderFeatureHost* BuiltinForwardPipeline::GetFeatureHost() { + return &m_forwardSceneFeatureHost; +} + bool BuiltinForwardPipeline::SupportsStageRenderGraph( CameraFrameStage stage) const { return SupportsCameraFramePipelineGraphRecording(stage); @@ -85,19 +94,19 @@ bool BuiltinForwardPipeline::ExecuteForwardSceneFrame( return false; } - if (!m_forwardSceneFeatureHost.Prepare(executionContext)) { + if (!PrepareScene(executionContext)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, - "BuiltinForwardPipeline::Render failed: SceneRenderFeatureHost::Prepare returned false"); + "BuiltinForwardPipeline::Render failed: PrepareScene returned false"); return false; } const RenderPassContext passContext = BuildRenderPassContext(executionContext); - if (!BeginForwardScenePass(passContext)) { + if (!BeginScenePass(passContext)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, - "BuiltinForwardPipeline::Render failed: BeginForwardScenePass returned false"); + "BuiltinForwardPipeline::Render failed: BeginScenePass returned false"); return false; } @@ -117,11 +126,19 @@ bool BuiltinForwardPipeline::ExecuteForwardSceneFrame( executionContext.renderContext, executionContext.sceneData); } - EndForwardScenePass(passContext); + EndScenePass(passContext); return renderResult; } +bool BuiltinForwardPipeline::BeginScenePass( + const RenderPassContext& context, + bool clearAttachments) { + return BeginForwardScenePass( + context, + clearAttachments); +} + bool BuiltinForwardPipeline::BeginForwardScenePass( const RenderPassContext& passContext, bool clearAttachments) { @@ -212,6 +229,10 @@ bool BuiltinForwardPipeline::Render( return Render(FrameExecutionContext(context, surface, sceneData)); } +void BuiltinForwardPipeline::EndScenePass(const RenderPassContext& passContext) { + EndForwardScenePass(passContext); +} + void BuiltinForwardPipeline::EndForwardScenePass(const RenderPassContext& passContext) { const RenderContext& context = passContext.renderContext; const RenderSurface& surface = passContext.surface; diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineScenePhases.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineScenePhases.cpp index 1ca05adf..7ae8d20c 100644 --- a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineScenePhases.cpp +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineScenePhases.cpp @@ -52,6 +52,22 @@ bool BuiltinForwardPipeline::ExecuteBuiltinScenePhase( } } +bool BuiltinForwardPipeline::ExecuteScenePhase( + const RenderPassContext& context, + ScenePhase scenePhase) { + const FrameExecutionContext executionContext( + context.renderContext, + context.surface, + context.sceneData, + context.sourceSurface, + context.sourceColorView, + context.sourceColorState); + return ExecuteBuiltinScenePhase( + BuildScenePhaseExecutionContext( + executionContext, + scenePhase)); +} + bool BuiltinForwardPipeline::ExecuteForwardScene( const FrameExecutionContext& executionContext) { return ExecuteForwardSceneSteps( diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index 9afd9ead..b205a11d 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -519,6 +519,60 @@ private: std::string m_label; }; +class MockNativeSceneRenderer final : public NativeSceneRenderer { +public: + bool Initialize(const RenderContext&) override { + ++initializeCallCount; + return initializeResult; + } + + bool PrepareScene(const FrameExecutionContext& executionContext) override { + ++prepareCallCount; + if (!prepareResult) { + return false; + } + + return featureHost.Prepare(executionContext); + } + + SceneRenderFeatureHost* GetFeatureHost() override { + return &featureHost; + } + + bool BeginScenePass( + const RenderPassContext&, + bool clearAttachments) override { + ++beginScenePassCallCount; + lastClearAttachments = clearAttachments; + return beginScenePassResult; + } + + void EndScenePass(const RenderPassContext&) override { + ++endScenePassCallCount; + } + + bool ExecuteScenePhase( + const RenderPassContext&, + ScenePhase scenePhase) override { + ++executeScenePhaseCallCount; + executedScenePhases.push_back(scenePhase); + return executeScenePhaseResult; + } + + SceneRenderFeatureHost featureHost = {}; + bool initializeResult = true; + bool prepareResult = true; + bool beginScenePassResult = true; + bool executeScenePhaseResult = true; + size_t initializeCallCount = 0u; + size_t prepareCallCount = 0u; + size_t beginScenePassCallCount = 0u; + size_t endScenePassCallCount = 0u; + size_t executeScenePhaseCallCount = 0u; + bool lastClearAttachments = false; + std::vector executedScenePhases = {}; +}; + } // namespace TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { @@ -784,6 +838,127 @@ TEST(BuiltinForwardSceneRecorder_Test, RecordsExplicitSceneStepsInCallerOrder) { "MainScene.AfterOpaque.AfterOpaqueFeature.1"); } +TEST(BuiltinForwardSceneRecorder_Test, RecordsSceneStepsThroughNativeSceneRendererContract) { + MockNativeSceneRenderer sceneRenderer; + std::vector eventLog = {}; + auto beforeOpaqueFeature = std::make_unique( + SceneRenderInjectionPoint::BeforeOpaque, + true, + &eventLog, + "BeforeOpaqueFeature"); + auto afterOpaqueFeature = std::make_unique( + SceneRenderInjectionPoint::AfterOpaque, + true, + &eventLog, + "AfterOpaqueFeature"); + TestSceneRenderFeaturePass* beforeOpaqueFeatureRaw = beforeOpaqueFeature.get(); + TestSceneRenderFeaturePass* afterOpaqueFeatureRaw = afterOpaqueFeature.get(); + sceneRenderer.featureHost.AddFeaturePass(std::move(beforeOpaqueFeature)); + sceneRenderer.featureHost.AddFeaturePass(std::move(afterOpaqueFeature)); + + 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); + + MockForwardCommandList commandList; + RenderContext renderContext = {}; + renderContext.commandList = &commandList; + + RenderSurface surface(320u, 180u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + surface.SetAutoTransitionEnabled(false); + + RenderSceneData sceneData = {}; + RenderGraphBlackboard blackboard = {}; + bool executionSucceeded = true; + const RenderPipelineStageRenderGraphContext context = { + graphBuilder, + "MainScene", + CameraFrameStage::MainScene, + renderContext, + sceneData, + surface, + nullptr, + nullptr, + ResourceStates::Common, + {}, + { colorTarget }, + depthTarget, + &executionSucceeded, + &blackboard + }; + + BuiltinForwardSceneRecorder recorder(sceneRenderer, context); + ASSERT_TRUE(recorder.RecordInjectionPoint(SceneRenderInjectionPoint::BeforeOpaque)); + ASSERT_TRUE(recorder.RecordScenePhase(ScenePhase::Opaque)); + ASSERT_TRUE(recorder.RecordInjectionPoint(SceneRenderInjectionPoint::AfterOpaque)); + + CompiledRenderGraph compiledGraph = {}; + String errorMessage; + ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) + << errorMessage.CStr(); + ASSERT_EQ(compiledGraph.GetPassCount(), 3u); + EXPECT_STREQ( + compiledGraph.GetPassName(0).CStr(), + "MainScene.BeforeOpaque.BeforeOpaqueFeature.0"); + EXPECT_STREQ( + compiledGraph.GetPassName(1).CStr(), + "MainScene.Opaque"); + EXPECT_STREQ( + compiledGraph.GetPassName(2).CStr(), + "MainScene.AfterOpaque.AfterOpaqueFeature.1"); + + ASSERT_TRUE(RenderGraphExecutor::Execute(compiledGraph, renderContext, &errorMessage)) + << errorMessage.CStr(); + EXPECT_TRUE(executionSucceeded); + EXPECT_EQ(sceneRenderer.initializeCallCount, 1u); + EXPECT_EQ(sceneRenderer.prepareCallCount, 1u); + EXPECT_EQ(sceneRenderer.beginScenePassCallCount, 3u); + EXPECT_EQ(sceneRenderer.endScenePassCallCount, 3u); + EXPECT_EQ(sceneRenderer.executeScenePhaseCallCount, 1u); + ASSERT_EQ(sceneRenderer.executedScenePhases.size(), 1u); + EXPECT_EQ(sceneRenderer.executedScenePhases[0], ScenePhase::Opaque); + ASSERT_NE(beforeOpaqueFeatureRaw, nullptr); + ASSERT_NE(afterOpaqueFeatureRaw, nullptr); + EXPECT_EQ(beforeOpaqueFeatureRaw->prepareCallCount, 1u); + EXPECT_EQ(afterOpaqueFeatureRaw->prepareCallCount, 1u); + EXPECT_EQ(beforeOpaqueFeatureRaw->executeCallCount, 1u); + EXPECT_EQ(afterOpaqueFeatureRaw->executeCallCount, 1u); +} + TEST(SceneRenderFeaturePass_Test, SupportsExplicitInjectionPointContract) { TestSceneRenderFeaturePass feature(SceneRenderInjectionPoint::AfterOpaque);