diff --git a/engine/include/XCEngine/Rendering/AGENTS.md b/engine/include/XCEngine/Rendering/AGENTS.md index 614f43a3..f5bda439 100644 --- a/engine/include/XCEngine/Rendering/AGENTS.md +++ b/engine/include/XCEngine/Rendering/AGENTS.md @@ -91,6 +91,9 @@ Unity 兼容的公开命名、对象所有权和扩展点。 哪些 stages 存在;native 只执行这些声明,不夹带 built-in pipeline policy。 - Hidden fallback 很危险。如果 managed URP stage 声明支持但无法 record,失败必须可见。不要静默用 default built-in path 画同一个 stage,然后称之为 URP。 +- Camera-frame graph dispatch 必须询问所选 pipeline 是否允许 legacy stage fallback。Managed + `ScriptableRenderPipelineHost` 在 stage recorder 或 managed runtime 权威时不允许 fallback;此时缺少 + recorder、sequence 或 standalone pass 要在 graph recording 阶段失败,而不是排入 built-in fallback。 - 测试体系已经有有价值的覆盖,但还不足以宣称 SRP/URP stack 完全锁定。随着架构边界收口,应补高价值 contract tests;现阶段不要为了大范围测试体系重写而暂停架构工作。 @@ -333,3 +336,6 @@ Scene data 每个 camera frame 提取一次,然后由 pipeline 调整。 `CameraFrameStage` 和 `CameraFramePlan`。 - Final color processing 表示为 policy 和 final output stage,而不是 implicit swapchain behavior。 - Object-id rendering 是 top-level tooling pass,并由 `XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT` guard。 +- Camera-frame graph dispatch 已收紧 legacy fallback gate:只有所选 pipeline 明确允许时,未被 + render-graph recorder、pass sequence 或 standalone pass 处理的 stage 才能进入 fallback raster pass。 + Managed SRP/URP host 不允许 hidden built-in fallback。 diff --git a/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h b/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h index 66480c48..cbbfedd5 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h +++ b/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h @@ -49,6 +49,9 @@ public: bool SupportsStageRenderGraph(CameraFrameStage stage) const override; bool SupportsStageRenderGraph( const RenderPipelineStageSupportContext& context) const override; + bool AllowsCameraFrameStageFallback(CameraFrameStage stage) const override; + bool AllowsCameraFrameStageFallback( + const RenderPipelineStageSupportContext& context) const override; bool RecordStageRenderGraph( const RenderPipelineStageRenderGraphContext& context) override; bool Render(const FrameExecutionContext& executionContext) override; diff --git a/engine/include/XCEngine/Rendering/RenderPipeline.h b/engine/include/XCEngine/Rendering/RenderPipeline.h index 8912624b..2499d5e9 100644 --- a/engine/include/XCEngine/Rendering/RenderPipeline.h +++ b/engine/include/XCEngine/Rendering/RenderPipeline.h @@ -103,6 +103,13 @@ public: const RenderPipelineStageSupportContext& context) const { return SupportsStageRenderGraph(context.stage); } + virtual bool AllowsCameraFrameStageFallback(CameraFrameStage) const { + return true; + } + virtual bool AllowsCameraFrameStageFallback( + const RenderPipelineStageSupportContext& context) const { + return AllowsCameraFrameStageFallback(context.stage); + } virtual bool RecordStageRenderGraph( const RenderPipelineStageRenderGraphContext&) { return false; diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageDispatch.cpp b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageDispatch.cpp index e4049a02..a0317f87 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageDispatch.cpp +++ b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageDispatch.cpp @@ -4,6 +4,8 @@ #include "Rendering/Execution/Internal/CameraFrameGraph/PassRecorder.h" #include "Rendering/Execution/Internal/CameraFrameGraph/SequenceRecorder.h" #include "Rendering/Execution/Internal/CameraFrameGraph/State.h" +#include "Debug/Logger.h" +#include "Rendering/RenderPipeline.h" #include @@ -23,6 +25,25 @@ constexpr std::array kCameraFrameStageRecordH &TryRecordCameraFrameStageStandaloneRenderGraphPass }; +RenderPipelineStageSupportContext BuildStageSupportContext( + const CameraFrameStageGraphBuildState& stageState, + const CameraFrameRenderGraphStageContext& context) { + return RenderPipelineStageSupportContext{ + stageState.stage, + context.plan.request.rendererIndex }; +} + +bool AllowsCameraFrameStageFallback( + const CameraFrameStageGraphBuildState& stageState, + const CameraFrameRenderGraphStageContext& context) { + RenderPipeline* const pipeline = context.builder.executionState.pipeline; + return pipeline == nullptr || + pipeline->AllowsCameraFrameStageFallback( + BuildStageSupportContext( + stageState, + context)); +} + } // namespace bool RecordCameraFrameRenderGraphStage( @@ -44,6 +65,15 @@ bool RecordCameraFrameRenderGraphStage( } } + if (!AllowsCameraFrameStageFallback(stageState, context)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + Containers::String( + "CameraRenderer::Render failed: stage has no render-graph recorder or sequence, and the selected pipeline disallows fallback: ") + + stageState.stageName); + return false; + } + AddCameraFrameStageFallbackRasterPass(stageState, context); return true; } diff --git a/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp index 7023acd0..3f331344 100644 --- a/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp +++ b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp @@ -118,6 +118,24 @@ bool ScriptableRenderPipelineHost::SupportsStageRenderGraph( m_pipelineBackend->SupportsStageRenderGraph(context); } +bool ScriptableRenderPipelineHost::AllowsCameraFrameStageFallback( + CameraFrameStage stage) const { + return AllowsCameraFrameStageFallback( + RenderPipelineStageSupportContext{ stage, -1 }); +} + +bool ScriptableRenderPipelineHost::AllowsCameraFrameStageFallback( + const RenderPipelineStageSupportContext& context) const { + if (UsesAuthoritativeStageRecorder( + m_stageRecorder.get(), + m_managedAssetRuntime.get())) { + return false; + } + + return m_pipelineBackend != nullptr && + m_pipelineBackend->AllowsCameraFrameStageFallback(context); +} + bool ScriptableRenderPipelineHost::RecordStageRenderGraph( const RenderPipelineStageRenderGraphContext& context) { if (!EnsureInitialized(context.renderContext)) { diff --git a/tests/Rendering/unit/test_camera_frame_graph_stage_contract.cpp b/tests/Rendering/unit/test_camera_frame_graph_stage_contract.cpp index 59da8cb5..e7637fc5 100644 --- a/tests/Rendering/unit/test_camera_frame_graph_stage_contract.cpp +++ b/tests/Rendering/unit/test_camera_frame_graph_stage_contract.cpp @@ -8,6 +8,7 @@ #include "Rendering/Execution/Internal/CameraFrameGraph/BuilderContext.h" #include "Rendering/Execution/Internal/CameraFrameGraph/StageContract.h" +#include "Rendering/Execution/Internal/CameraFrameGraph/StageDispatch.h" #include "Rendering/Execution/Internal/CameraFrameGraph/ExecutionState.h" #include "Rendering/Graph/RenderGraph.h" @@ -102,7 +103,13 @@ public: } bool SupportsStageRenderGraph(CameraFrameStage stage) const override { - return SupportsCameraFramePipelineGraphRecording(stage); + return supportsMainSceneRenderGraph && + SupportsCameraFramePipelineGraphRecording(stage); + } + + bool AllowsCameraFrameStageFallback(CameraFrameStage stage) const override { + (void)stage; + return allowsStageFallback; } bool RecordStageRenderGraph( @@ -138,6 +145,8 @@ public: bool recordResult = true; bool renderResult = true; + bool supportsMainSceneRenderGraph = true; + bool allowsStageFallback = true; int recordCalls = 0; int renderCalls = 0; std::string lastPassName = {}; @@ -443,22 +452,26 @@ TEST(CameraFrameRenderGraphStageContract_Test, ResolvesStandaloneStagePassFromEx EXPECT_EQ( ResolveCameraFrameStandaloneStagePass( CameraFrameStage::ObjectId, - executionState), + executionState, + -1), objectIdPass); EXPECT_EQ( ResolveCameraFrameStandaloneStagePass( CameraFrameStage::DepthOnly, - executionState), + executionState, + -1), depthOnlyPass); EXPECT_EQ( ResolveCameraFrameStandaloneStagePass( CameraFrameStage::ShadowCaster, - executionState), + executionState, + -1), shadowCasterPass); EXPECT_EQ( ResolveCameraFrameStandaloneStagePass( CameraFrameStage::MainScene, - executionState), + executionState, + -1), nullptr); } @@ -1547,3 +1560,21 @@ TEST(CameraFrameRenderGraphStageContract_Test, ExecutesDepthOnlyFallbackPassUsin EXPECT_EQ(depthOnlyPass->lastExecuteSceneData.cameraData.clearFlags, RenderClearFlags::Color); } +TEST(CameraFrameRenderGraphStageContract_Test, FailsRecordingWhenSelectedPipelineDisallowsStageFallback) { + StageContractTestContext testContext = {}; + RecordingPipeline pipeline = {}; + pipeline.supportsMainSceneRenderGraph = false; + pipeline.allowsStageFallback = false; + testContext.executionState.pipeline = &pipeline; + testContext.plan.request.surface = RenderSurface(320, 180); + + const CameraFrameRenderGraphStageContext context = + testContext.BuildStageContext(); + + EXPECT_FALSE(RecordCameraFrameRenderGraphStage( + CameraFrameStage::MainScene, + context)); + EXPECT_EQ(pipeline.recordCalls, 0); + EXPECT_EQ(pipeline.renderCalls, 0); + EXPECT_EQ(testContext.graph.GetPassCount(), 0u); +}