From 97f3c3486a5ee47f5b1118a35239eeec80adcb60 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 26 Apr 2026 02:12:06 +0800 Subject: [PATCH] rendering: document managed srp v1 boundaries --- ...模块长期目标与下一阶段执行计划_2026-04-26.md | 2 + managed/GameScripts/RenderPipelineApiProbe.cs | 62 +++++++ .../Universal/UniversalPostProcessBlock.cs | 3 + .../Core/DirectionalShadowExecutionContext.cs | 3 + .../Rendering/Core/RenderSceneSetupContext.cs | 2 + .../Rendering/Core/ScriptableRenderContext.cs | 9 + ...ScriptableRenderPipelinePlanningContext.cs | 5 + .../Graph/RenderGraphRasterPassBuilder.cs | 5 + tests/scripting/test_mono_script_runtime.cpp | 155 ++++++++++++++++++ 9 files changed, 246 insertions(+) diff --git a/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md b/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md index bd4de3eb..4c8d614c 100644 --- a/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md +++ b/docs/plan/渲染模块长期目标与下一阶段执行计划_2026-04-26.md @@ -81,6 +81,8 @@ ### 4.3 固化 managed SRP v1 能力边界 +状态: `Completed` (`2026-04-26`) + 目标: 把现有 managed API 从“隐式能力集合”收敛成明确的 v1 contract,便于后续扩展而不是继续误判能力上限。 执行: diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index 021861d9..7ab0a7ea 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -2054,6 +2054,35 @@ namespace Gameplay } } + public sealed class ManagedInvalidFullscreenStagePlanningProbeAsset + : UniversalRenderPipelineAsset + { + public ManagedInvalidFullscreenStagePlanningProbeAsset() + { + rendererDataList = + ProbeScriptableObjectFactory + .CreateRendererDataList( + ProbeScriptableObjectFactory + .Create()); + } + + protected override void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + if (context == null) + { + return; + } + + context.RequestFullscreenStage( + CameraFrameStage.MainScene, + CameraFrameColorSource.MainSceneColor); + context.RequestFullscreenStage( + CameraFrameStage.PostProcess, + CameraFrameColorSource.MainSceneColor); + } + } + public sealed class ManagedClearedPostProcessRenderPipelineProbeAsset : UniversalRenderPipelineAsset { @@ -2180,6 +2209,39 @@ namespace Gameplay } } + internal sealed class ManagedMainSceneRasterPassProbePipeline + : ScriptableRenderPipeline + { + protected override bool SupportsStageRenderGraph( + CameraFrameStage stage) + { + return stage == CameraFrameStage.MainScene; + } + + protected override bool RecordStageRenderGraph( + ScriptableRenderContext context) + { + return context != null && + context + .AddRasterPass( + "Managed.InvalidMainSceneRasterPass") + .SetColorAttachment( + context.primaryColorTarget) + .SetColorScaleFullscreenExecution( + new Vector4(1.0f, 1.0f, 1.0f, 1.0f)) + .Commit(); + } + } + + public sealed class ManagedMainSceneRasterPassProbeAsset + : ScriptableRenderPipelineAsset + { + protected override ScriptableRenderPipeline CreatePipeline() + { + return new ManagedMainSceneRasterPassProbePipeline(); + } + } + internal sealed class ManagedRenderPipelineProbe : ProbeSceneRenderer { diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalPostProcessBlock.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalPostProcessBlock.cs index 3c70af27..50cade2b 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalPostProcessBlock.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalPostProcessBlock.cs @@ -3,6 +3,9 @@ using XCEngine.Rendering; namespace XCEngine.Rendering.Universal { + // This block currently owns stage planning / promotion only. The actual + // post-process work is still authored through fullscreen features / passes, + // while final-color application remains in the built-in FinalOutput path. internal sealed class UniversalPostProcessBlock { public void EnqueueRenderPasses( diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/DirectionalShadowExecutionContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/DirectionalShadowExecutionContext.cs index 9eb9de9f..6896e720 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/DirectionalShadowExecutionContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/DirectionalShadowExecutionContext.cs @@ -1,5 +1,8 @@ namespace XCEngine.Rendering { + // SRP v1 shadow hook only configures whether the native main directional + // shadow execution should run. It does not expose a custom managed shadow + // graph or scene backend. public sealed class DirectionalShadowExecutionContext { private readonly ulong m_nativeHandle; diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/RenderSceneSetupContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/RenderSceneSetupContext.cs index d260ef03..0bfa9f88 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/RenderSceneSetupContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/RenderSceneSetupContext.cs @@ -1,5 +1,7 @@ namespace XCEngine.Rendering { + // Managed hook for scene-setup policy only. Scene extraction, culling, and + // the actual scene draws still execute through the native backend. public sealed class RenderSceneSetupContext { private readonly ulong m_nativeHandle; diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs index c350cd51..d5612b34 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs @@ -2,6 +2,9 @@ using XCEngine; namespace XCEngine.Rendering { + // SRP v1 managed recording surface. Scene recording still delegates to the + // native SceneDrawBackend, and raster-pass recording is limited to the + // fullscreen PostProcess / FinalOutput stages. public sealed class ScriptableRenderContext { private readonly ulong m_nativeHandle; @@ -38,6 +41,9 @@ namespace XCEngine.Rendering .Rendering_ScriptableRenderContext_GetDepthTargetHandle( m_nativeHandle)); + // Records the default native scene sequence for MainScene or explicit + // scene-pass-request stages. This does not replace the native scene + // extraction / culling / draw backend. public bool RecordScene() { return RecordBeforeOpaqueInjection() && @@ -241,6 +247,9 @@ namespace XCEngine.Rendering name)); } + // SRP v1 wrapper over managed fullscreen raster requests. Commit only + // succeeds on fullscreen sequence stages and the execution kinds remain + // limited to the built-in fullscreen pass family. public RenderGraphRasterPassBuilder AddRasterPass( string passName) { diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs index 566c2733..927a153d 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs @@ -2,6 +2,9 @@ using XCEngine; namespace XCEngine.Rendering { + // SRP v1 planning surface. Managed planning can request fullscreen stages + // plus depth / shadow side stages, but main-scene backend ownership stays + // on the native pipeline and scene draw backend. public sealed class ScriptableRenderPipelinePlanningContext { private readonly ulong m_nativeHandle; @@ -49,6 +52,8 @@ namespace XCEngine.Rendering CameraFrameColorSource source, bool usesGraphManagedOutputColor = false) { + // Only PostProcess / FinalOutput are valid fullscreen sequence + // stages here. Other stages return false and remain unchanged. return InternalCalls .Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage( m_nativeHandle, diff --git a/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs b/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs index 9f395a9b..1461a95c 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Graph/RenderGraphRasterPassBuilder.cs @@ -12,6 +12,9 @@ namespace XCEngine.Rendering FinalColorFullscreen = 3 } + // Managed SRP v1 raster recording is intentionally narrow: this builder + // only describes fullscreen raster passes that map onto the built-in + // ColorScale / ShaderVector / FinalColor executions. public sealed class RenderGraphRasterPassBuilder { private struct TextureBindingRequest @@ -184,6 +187,8 @@ namespace XCEngine.Rendering public bool Commit() { + // Native bridge rejects raster pass recording outside the + // fullscreen sequence stages. if (m_finalized || !HasExecutionConfigured() || !HasAnyColorAttachment()) diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 36eb2ad2..045d5019 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -3944,6 +3944,93 @@ TEST_F( recorder->Shutdown(); } +TEST_F( + MonoScriptRuntimeTest, + ManagedStageRecorderRejectsRasterPassRecordingOutsideFullscreenStages) { + const auto bridge = + XCEngine::Rendering::Pipelines::GetManagedRenderPipelineBridge(); + ASSERT_NE(bridge, nullptr); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedMainSceneRasterPassProbeAsset" + }; + + std::shared_ptr + assetRuntime = bridge->CreateAssetRuntime(descriptor); + ASSERT_NE(assetRuntime, nullptr); + std::unique_ptr recorder = + assetRuntime->CreateStageRecorder(); + ASSERT_NE(recorder, nullptr); + + const XCEngine::Rendering::RenderContext renderContext = {}; + ASSERT_TRUE(recorder->Initialize(renderContext)); + ASSERT_TRUE( + recorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::MainScene)); + EXPECT_FALSE( + recorder->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + + XCEngine::Rendering::RenderGraph graph; + XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph); + XCEngine::Rendering::RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 64u; + colorDesc.height = 64u; + colorDesc.format = + static_cast( + XCEngine::RHI::Format::R8G8B8A8_UNorm); + XCEngine::Rendering::RenderGraphTextureDesc depthDesc = colorDesc; + depthDesc.format = + static_cast( + XCEngine::RHI::Format::D32_Float); + const XCEngine::Rendering::RenderGraphTextureHandle colorTarget = + graphBuilder.CreateTransientTexture( + "ManagedInvalidMainSceneRasterColor", + colorDesc); + const XCEngine::Rendering::RenderGraphTextureHandle depthTarget = + graphBuilder.CreateTransientTexture( + "ManagedInvalidMainSceneRasterDepth", + depthDesc); + + const XCEngine::Rendering::RenderSceneData sceneData = {}; + const XCEngine::Rendering::RenderSurface surface(64u, 64u); + bool executionSucceeded = true; + XCEngine::Rendering::RenderGraphBlackboard blackboard = {}; + const XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = { + graphBuilder, + "ManagedInvalidMainSceneRaster", + XCEngine::Rendering::CameraFrameStage::MainScene, + renderContext, + sceneData, + surface, + nullptr, + nullptr, + XCEngine::RHI::ResourceStates::Common, + {}, + { colorTarget }, + depthTarget, + {}, + &executionSucceeded, + &blackboard + }; + + EXPECT_FALSE(recorder->RecordStageRenderGraph(graphContext)); + + XCEngine::Rendering::CompiledRenderGraph compiledGraph = {}; + XCEngine::Containers::String errorMessage; + ASSERT_TRUE( + XCEngine::Rendering::RenderGraphCompiler::Compile( + graph, + compiledGraph, + &errorMessage)) + << errorMessage.CStr(); + EXPECT_EQ(compiledGraph.GetPassCount(), 0u); + + recorder->Shutdown(); +} + TEST_F( MonoScriptRuntimeTest, ManagedRenderPipelineAssetPlansFullscreenStagesFromPipelineStageSupport) { @@ -3997,6 +4084,74 @@ TEST_F( EXPECT_TRUE(plan.IsFinalOutputStageValid()); } +TEST_F( + MonoScriptRuntimeTest, + ManagedPlanningContextRejectsNonFullscreenStageRequests) { + Scene* runtimeScene = + CreateScene("ManagedInvalidFullscreenStagePlanningScene"); + GameObject* cameraObject = runtimeScene->CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + ASSERT_NE(camera, nullptr); + camera->SetPrimary(true); + + TestRenderDevice device; + TestRenderCommandList commandList; + TestRenderCommandQueue commandQueue; + TestRenderResourceView colorView( + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::R8G8B8A8_UNorm); + TestRenderResourceView depthView( + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::D32_Float); + + const XCEngine::Rendering::RenderContext context = + CreateRenderContext( + device, + commandList, + commandQueue); + XCEngine::Rendering::RenderSurface surface(64u, 64u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + descriptor = { + "GameScripts", + "Gameplay", + "ManagedInvalidFullscreenStagePlanningProbeAsset" + }; + auto asset = + std::make_shared< + XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset>( + descriptor); + + XCEngine::Rendering::CameraRenderRequest request = {}; + request.scene = runtimeScene; + request.camera = camera; + request.context = context; + request.surface = surface; + + XCEngine::Rendering::RenderPipelineHost host(asset); + const std::vector plans = + host.BuildFramePlans({ request }); + + ASSERT_EQ(plans.size(), 1u); + const XCEngine::Rendering::CameraFramePlan& plan = plans[0]; + + EXPECT_FALSE( + plan.IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::MainScene)); + EXPECT_TRUE( + plan.IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + EXPECT_EQ( + plan.ResolveStageColorSource( + XCEngine::Rendering::CameraFrameStage::PostProcess), + XCEngine::Rendering::CameraFrameColorSource::MainSceneColor); + EXPECT_TRUE(plan.IsPostProcessStageValid()); +} + TEST_F( MonoScriptRuntimeTest, ManagedRendererFeatureConfiguresCameraFramePlanThroughPlanningContext) {