diff --git a/docs/used/SRP_URP_RendererCoreShadowPrepassPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_RendererCoreShadowPrepassPlan_完成归档_2026-04-21.md new file mode 100644 index 00000000..c518f22a --- /dev/null +++ b/docs/used/SRP_URP_RendererCoreShadowPrepassPlan_完成归档_2026-04-21.md @@ -0,0 +1,75 @@ +# SRP URP Renderer Core Shadow Prepass Plan + +Date: 2026-04-21 + +## Goal + +Continue aligning the current managed rendering stack with Unity-style SRP + URP ownership: + +- `UniversalRenderer` owns the default shadow and depth-prepass core blocks +- `ScriptableRendererFeature` injects or explicitly overrides stage existence through planning hooks +- standalone stage fallback remains secondary instead of being the primary owner of URP core blocks + +This stage does not add deferred rendering. + +## Current Problems + +The current code still has three architectural gaps: + +1. `UniversalRenderer` explicitly owns final output, but shadow and depth-prepass core blocks are still not renderer-owned managed defaults. +2. `ScriptableRenderer.FinalizeCameraFramePlan()` infers standalone stages from the queued pass list without respecting explicit shadow/depth planning decisions, so a feature-level `ClearShadowCasterStage()` can be silently undone later. +3. `UniversalRendererData` still exposes native standalone pass keys for `ShadowCaster` and `DepthOnly`, so the managed renderer core is not yet the obvious primary ownership seam. + +## Scope + +### 1. Make shadow / depth stage planning explicit + +Required work: + +- expose explicit standalone-stage planning state to managed code +- skip inferred shadow/depth stage requests when the renderer or a feature has already configured those stages explicitly +- preserve the existing inferred path only for renderers that still rely on implicit standalone-stage discovery + +### 2. Move default shadow / depth core passes into `UniversalRenderer` + +Required work: + +- add default managed `DrawObjectsPass` instances for: + - `ShadowCaster` + - `DepthOnly` +- make `UniversalRenderer` decide whether those blocks exist +- keep shadow stage tied to actual shadow-plan availability +- keep depth prepass opt-in instead of enabling it unconditionally + +### 3. Reduce native fallback ownership in URP data + +Required work: + +- stop treating native standalone asset keys as the primary URP shadow/depth implementation path +- make `UniversalRendererData` describe the renderer-owned default blocks instead +- keep fallback compatibility only where it is still useful during transition + +## Out Of Scope + +- deferred renderer +- GBuffer +- editor-side renderer configuration UX +- moving every shadow implementation detail out of native code +- multiple URP renderer variants in this stage + +## Done Criteria + +1. `UniversalRenderer` owns default managed shadow and depth-prepass block composition. +2. Feature-level explicit shadow/depth stage planning is no longer overwritten by generic queue inference. +3. URP shadow/depth defaults no longer depend on `UniversalRendererData.GetCameraFrameStandalonePassAssetKey(...)` as the main ownership seam. +4. Old `XCEditor` builds in Debug. +5. Old editor smoke passes for at least about 10 seconds and a fresh `SceneReady` appears in `editor/bin/Debug/editor.log`. + +## Follow-Up + +After this stage closes, the next SRP stage can move higher toward Unity-style renderer composition: + +- renderer variants / multiple renderer assets +- managed shadow block enrichment +- managed prepass expansion +- eventually, SRP-facing C# pipeline composition with clearer URP package layering diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 78d9bb0e..7ab21051 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -5914,6 +5914,18 @@ void InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowC state->plan->ClearShadowCasterStage(true); } +mono_bool +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitShadowCasterStageConfiguration( + uint64_t nativeHandle) { + const ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + return state != nullptr && + state->plan != nullptr && + state->plan->HasExplicitShadowCasterStageConfiguration() + ? 1 + : 0; +} + mono_bool InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage( uint64_t nativeHandle) { @@ -5937,6 +5949,18 @@ void InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOn state->plan->ClearDepthOnlyStage(true); } +mono_bool +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitDepthOnlyStageConfiguration( + uint64_t nativeHandle) { + const ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + return state != nullptr && + state->plan != nullptr && + state->plan->HasExplicitDepthOnlyStageConfiguration() + ? 1 + : 0; +} + mono_bool InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing( uint64_t nativeHandle) { @@ -6155,8 +6179,10 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestShadowCasterStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestShadowCasterStage)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowCasterStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowCasterStage)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitShadowCasterStageConfiguration", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitShadowCasterStageConfiguration)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitDepthOnlyStageConfiguration", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitDepthOnlyStageConfiguration)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedRequestCount", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedRequestCount)); diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index dd40e2b4..41fae5a8 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -215,6 +215,7 @@ set(XCENGINE_RENDER_PIPELINES_UNIVERSAL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/DirectionalLightData.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/DirectionalShadowData.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/DepthPrepassBlockData.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/DisableDirectionalShadowRendererFeature.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/DrawObjectsPass.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/DrawSkyboxPass.cs @@ -233,6 +234,7 @@ set(XCENGINE_RENDER_PIPELINES_UNIVERSAL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererRecordingContext.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingDataResolver.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ShadowCasterBlockData.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ShadowData.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/DepthPrepassBlockData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/DepthPrepassBlockData.cs new file mode 100644 index 00000000..ae6f2dd6 --- /dev/null +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/DepthPrepassBlockData.cs @@ -0,0 +1,30 @@ +using XCEngine; +using XCEngine.Rendering; + +namespace XCEngine.Rendering.Universal +{ + public sealed class DepthPrepassBlockData + { + public bool enabled; + public RenderPassEvent passEvent = + RenderPassEvent.BeforeRenderingPrePasses; + public RendererListDesc rendererListDesc = + RendererListDesc.CreateDefault( + RendererListType.Opaque); + public DrawingSettings drawingSettings = + DrawingSettings.CreateDefault(); + + public static DepthPrepassBlockData CreateDefault() + { + return new DepthPrepassBlockData(); + } + + public static DepthPrepassBlockData CreateEnabled() + { + return new DepthPrepassBlockData + { + enabled = true + }; + } + } +} diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs index 8b982cc5..a27b59c2 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs @@ -283,13 +283,15 @@ namespace XCEngine.Rendering.Universal private void ApplyInferredStandaloneStageRequests( ScriptableRenderPipelinePlanningContext context) { - if (HasQueuedPassForStage( + if (!context.HasExplicitShadowCasterStageConfiguration() && + HasQueuedPassForStage( CameraFrameStage.ShadowCaster)) { context.RequestShadowCasterStage(); } - if (HasQueuedPassForStage( + if (!context.HasExplicitDepthOnlyStageConfiguration() && + HasQueuedPassForStage( CameraFrameStage.DepthOnly)) { context.RequestDepthOnlyStage(); diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ShadowCasterBlockData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ShadowCasterBlockData.cs new file mode 100644 index 00000000..2194c20e --- /dev/null +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ShadowCasterBlockData.cs @@ -0,0 +1,30 @@ +using XCEngine; +using XCEngine.Rendering; + +namespace XCEngine.Rendering.Universal +{ + public sealed class ShadowCasterBlockData + { + public bool enabled = true; + public RenderPassEvent passEvent = + RenderPassEvent.BeforeRenderingShadows; + public RendererListDesc rendererListDesc = + RendererListDesc.CreateDefault( + RendererListType.ShadowCaster); + public DrawingSettings drawingSettings = + DrawingSettings.CreateDefault(); + + public static ShadowCasterBlockData CreateDefault() + { + return new ShadowCasterBlockData(); + } + + public static ShadowCasterBlockData CreateDisabled() + { + return new ShadowCasterBlockData + { + enabled = false + }; + } + } +} diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs index 2d62e909..738e1436 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderer.cs @@ -8,6 +8,18 @@ namespace XCEngine.Rendering.Universal private readonly UniversalRendererData m_rendererData; private readonly BuiltinFinalColorPass m_builtinFinalColorPass = new BuiltinFinalColorPass(); + private readonly DrawObjectsPass m_drawShadowCasterPass = + new DrawObjectsPass( + RenderPassEvent.BeforeRenderingShadows, + SceneRenderPhase.Opaque, + RendererListDesc.CreateDefault( + RendererListType.ShadowCaster)); + private readonly DrawObjectsPass m_drawDepthPrepass = + new DrawObjectsPass( + RenderPassEvent.BeforeRenderingPrePasses, + SceneRenderPhase.Opaque, + RendererListDesc.CreateDefault( + RendererListType.Opaque)); private readonly DrawObjectsPass m_drawOpaqueObjectsPass = new DrawObjectsPass( RenderPassEvent.RenderOpaques, @@ -38,7 +50,12 @@ namespace XCEngine.Rendering.Universal return; } - ConfigureShadowCasterStage(context); + ConfigureShadowCasterStage( + context, + m_rendererData.GetShadowCasterBlockInstance()); + ConfigureDepthOnlyStage( + context, + m_rendererData.GetDepthPrepassBlockInstance()); } protected override void FinalizeCameraFramePlan( @@ -63,6 +80,20 @@ namespace XCEngine.Rendering.Universal UniversalMainSceneData mainScene = m_rendererData.GetMainSceneInstance(); + ShadowCasterBlockData shadowCaster = + m_rendererData.GetShadowCasterBlockInstance(); + DepthPrepassBlockData depthPrepass = + m_rendererData.GetDepthPrepassBlockInstance(); + + if (shadowCaster.enabled) + { + EnqueueShadowCasterPasses(shadowCaster); + } + + if (depthPrepass.enabled) + { + EnqueueDepthPrepass(depthPrepass); + } if (mainScene.renderOpaque) { @@ -82,6 +113,28 @@ namespace XCEngine.Rendering.Universal EnqueueFinalOutputPasses(renderingData); } + private void EnqueueShadowCasterPasses( + ShadowCasterBlockData shadowCaster) + { + m_drawShadowCasterPass.Configure( + shadowCaster.passEvent, + SceneRenderPhase.Opaque, + shadowCaster.rendererListDesc, + shadowCaster.drawingSettings); + EnqueuePass(m_drawShadowCasterPass); + } + + private void EnqueueDepthPrepass( + DepthPrepassBlockData depthPrepass) + { + m_drawDepthPrepass.Configure( + depthPrepass.passEvent, + SceneRenderPhase.Opaque, + depthPrepass.rendererListDesc, + depthPrepass.drawingSettings); + EnqueuePass(m_drawDepthPrepass); + } + private void EnqueueOpaquePasses( UniversalMainSceneData mainScene) { @@ -113,10 +166,11 @@ namespace XCEngine.Rendering.Universal } private static void ConfigureShadowCasterStage( - ScriptableRenderPipelinePlanningContext context) + ScriptableRenderPipelinePlanningContext context, + ShadowCasterBlockData shadowCaster) { - if (context.IsStageRequested( - CameraFrameStage.ShadowCaster)) + if (shadowCaster != null && + shadowCaster.enabled) { context.RequestShadowCasterStage(); return; @@ -125,6 +179,20 @@ namespace XCEngine.Rendering.Universal context.ClearShadowCasterStage(); } + private static void ConfigureDepthOnlyStage( + ScriptableRenderPipelinePlanningContext context, + DepthPrepassBlockData depthPrepass) + { + if (depthPrepass != null && + depthPrepass.enabled) + { + context.RequestDepthOnlyStage(); + return; + } + + context.ClearDepthOnlyStage(); + } + private static void ConfigureFinalOutputStage( ScriptableRenderPipelinePlanningContext context) { diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs index 87523370..523dabd2 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs @@ -7,11 +7,15 @@ namespace XCEngine.Rendering.Universal public class UniversalRendererData : ScriptableRendererData { public UniversalMainSceneData mainScene; + public ShadowCasterBlockData shadowCaster; + public DepthPrepassBlockData depthPrepass; public ScriptableRendererFeature[] rendererFeatures; public UniversalRendererData() { mainScene = UniversalMainSceneData.CreateDefault(); + shadowCaster = ShadowCasterBlockData.CreateDefault(); + depthPrepass = DepthPrepassBlockData.CreateDefault(); rendererFeatures = CreateDefaultRendererFeatures(); } @@ -37,22 +41,6 @@ namespace XCEngine.Rendering.Universal return "BuiltinDefaultSceneSetup"; } - private protected override string GetCameraFrameStandalonePassAssetKey( - CameraFrameStage stage) - { - switch (stage) - { - case CameraFrameStage.ShadowCaster: - return "BuiltinShadowCaster"; - case CameraFrameStage.DepthOnly: - return "BuiltinDepthOnly"; - default: - return base - .GetCameraFrameStandalonePassAssetKey( - stage); - } - } - private protected override string GetDirectionalShadowExecutionPolicyAssetKey() { @@ -70,6 +58,28 @@ namespace XCEngine.Rendering.Universal return mainScene; } + internal ShadowCasterBlockData GetShadowCasterBlockInstance() + { + if (shadowCaster == null) + { + shadowCaster = + ShadowCasterBlockData.CreateDefault(); + } + + return shadowCaster; + } + + internal DepthPrepassBlockData GetDepthPrepassBlockInstance() + { + if (depthPrepass == null) + { + depthPrepass = + DepthPrepassBlockData.CreateDefault(); + } + + return depthPrepass; + } + private static ScriptableRendererFeature[] CreateDefaultRendererFeatures() { diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index 0552ba9e..45594037 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -532,6 +532,11 @@ namespace XCEngine Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowCasterStage( ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool + Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitShadowCasterStageConfiguration( + ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage( @@ -542,6 +547,11 @@ namespace XCEngine Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage( ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool + Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitDepthOnlyStageConfiguration( + ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing( diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs index dcca8502..e39805f2 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs @@ -80,6 +80,13 @@ namespace XCEngine.Rendering m_nativeHandle); } + public bool HasExplicitShadowCasterStageConfiguration() + { + return InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitShadowCasterStageConfiguration( + m_nativeHandle); + } + public bool RequestDepthOnlyStage() { return InternalCalls @@ -94,6 +101,13 @@ namespace XCEngine.Rendering m_nativeHandle); } + public bool HasExplicitDepthOnlyStageConfiguration() + { + return InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitDepthOnlyStageConfiguration( + m_nativeHandle); + } + public bool HasFinalColorProcessing() { return InternalCalls