refactor(srp): move urp shadow and prepass core blocks into renderer

This commit is contained in:
2026-04-21 16:43:56 +08:00
parent eb5b51ddb1
commit 1cb23cd178
10 changed files with 289 additions and 22 deletions

View File

@@ -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

View File

@@ -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<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestShadowCasterStage", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestShadowCasterStage));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowCasterStage", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearShadowCasterStage));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitShadowCasterStageConfiguration", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitShadowCasterStageConfiguration));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestDepthOnlyStage));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearDepthOnlyStage));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitDepthOnlyStageConfiguration", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasExplicitDepthOnlyStageConfiguration));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing", reinterpret_cast<const void*>(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetHasFinalColorProcessing));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount));
mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedRequestCount", reinterpret_cast<const void*>(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedRequestCount));

View File

@@ -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

View File

@@ -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
};
}
}
}

View File

@@ -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();

View File

@@ -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
};
}
}
}

View File

@@ -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)
{

View File

@@ -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()
{

View File

@@ -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(

View File

@@ -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