diff --git a/docs/used/SRP_URP_RendererSlotSemanticsPlan_完成归档_2026-04-21.md b/docs/used/SRP_URP_RendererSlotSemanticsPlan_完成归档_2026-04-21.md new file mode 100644 index 00000000..8d432641 --- /dev/null +++ b/docs/used/SRP_URP_RendererSlotSemanticsPlan_完成归档_2026-04-21.md @@ -0,0 +1,69 @@ +# SRP URP Renderer Slot Semantics Plan + +Date: 2026-04-21 + +## Goal + +Continue aligning the current managed SRP + URP stack with Unity-style renderer asset semantics before moving on to larger SRP features. + +This stage focuses on two structural gaps: + +- renderer slot resolution should treat configured renderer entries as real authored slots instead of silently synthesizing new renderer data for arbitrary null entries +- shared renderer feature ownership should live on `ScriptableRendererData`, not only on `UniversalRendererData` + +The point of this stage is to make the current renderer list architecture trustworthy, so later SRP work can build on it cleanly. + +## Current Problems + +1. `RendererBackedRenderPipelineAsset` currently materializes a new default renderer data into any resolved null slot. That makes renderer list behavior implicit and does not match Unity-style authored renderer slots. +2. Only `UniversalRendererData` publicly owns `rendererFeatures`, which means future renderer-data variants would immediately duplicate the same feature-list plumbing. +3. The current asset bootstrap path and the current slot fallback path are mixed together, so "create the default renderer list for an empty asset" and "resolve a camera-selected renderer entry" are not clearly separated. + +## Scope + +### 1. Clean up renderer slot fallback semantics + +Required work: + +- keep automatic default renderer creation only for empty renderer lists or a missing effective default renderer +- when a camera-selected renderer index is invalid or points to a null slot, fall back to the effective default renderer instead of silently creating a new renderer entry in that slot +- preserve the existing camera override model: asset chooses the renderer list, camera chooses an index override + +### 2. Move shared renderer feature ownership into `ScriptableRendererData` + +Required work: + +- move the public `rendererFeatures` storage and its default-initialization path into `ScriptableRendererData` +- keep `UniversalRendererData` focused on renderer-specific core block configuration +- preserve current default built-in feature composition for the universal renderer + +### 3. Keep the next renderer-variant stage easy to add + +Required work: + +- leave the renderer-data list and renderer-index flow structurally ready for additional renderer-data types later +- avoid introducing fake renderer families or placeholder deferred code in this stage + +## Out Of Scope + +- deferred renderer +- 2D renderer +- editor-side renderer asset UI +- moving all native shadow execution into managed URP +- render graph expansion + +## Done Criteria + +1. Renderer slot resolution no longer creates ad-hoc renderer data for non-default null entries. +2. Empty assets and broken default slots still recover to a usable default renderer. +3. `rendererFeatures` is owned by `ScriptableRendererData` instead of `UniversalRendererData`. +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 + +Once this stage closes, the next SRP work can move up one level again: + +- richer Unity-style renderer variants +- more managed URP ownership for renderer core blocks +- eventually, higher-level SRP package composition on top of the current C++ render-graph substrate diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs index 9d4f9ba3..ea281281 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererBackedRenderPipelineAsset.cs @@ -377,12 +377,6 @@ namespace XCEngine.Rendering.Universal return null; } - if (rendererDataList[resolvedRendererIndex] == null) - { - rendererDataList[resolvedRendererIndex] = - CreateDefaultRendererData(); - } - return rendererDataList[resolvedRendererIndex]; } @@ -439,6 +433,11 @@ namespace XCEngine.Rendering.Universal return ResolveDefaultRendererIndex(); } + if (rendererDataList[rendererIndex] == null) + { + return ResolveDefaultRendererIndex(); + } + return rendererIndex; } @@ -456,7 +455,35 @@ namespace XCEngine.Rendering.Universal defaultRendererIndex = 0; } + if (rendererDataList[defaultRendererIndex] != null) + { + return defaultRendererIndex; + } + + int firstValidRendererIndex = + FindFirstValidRendererIndex(); + if (firstValidRendererIndex >= 0) + { + defaultRendererIndex = firstValidRendererIndex; + return defaultRendererIndex; + } + + rendererDataList[defaultRendererIndex] = + CreateDefaultRendererData(); return defaultRendererIndex; } + + private int FindFirstValidRendererIndex() + { + for (int i = 0; i < rendererDataList.Length; ++i) + { + if (rendererDataList[i] != null) + { + return i; + } + } + + return -1; + } } } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs index d27db1a5..01b51782 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs @@ -6,6 +6,7 @@ namespace XCEngine.Rendering.Universal { public abstract class ScriptableRendererData : Object { + public ScriptableRendererFeature[] rendererFeatures; private ScriptableRendererFeature[] m_rendererFeatures; private ScriptableRenderer m_rendererInstance; private bool m_rendererInvalidated; @@ -13,6 +14,8 @@ namespace XCEngine.Rendering.Universal protected ScriptableRendererData() { + rendererFeatures = + Array.Empty(); } internal ScriptableRenderer CreateRendererInstance() @@ -196,13 +199,20 @@ namespace XCEngine.Rendering.Universal protected virtual ScriptableRendererFeature[] CreateRendererFeatures() { - return Array.Empty(); + return rendererFeatures ?? + Array.Empty(); } protected virtual void ReleaseRuntimeResources() { } + protected virtual ScriptableRendererFeature[] + CreateDefaultRendererFeatures() + { + return Array.Empty(); + } + protected bool isInvalidated { get @@ -279,6 +289,13 @@ namespace XCEngine.Rendering.Universal } } + protected void ResetRendererFeaturesToDefault() + { + rendererFeatures = + CreateDefaultRendererFeatures() ?? + Array.Empty(); + } + private ScriptableRendererFeature[] GetRendererFeatures() { if (m_rendererFeatures == null) diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs index 523dabd2..227502d7 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs @@ -9,15 +9,13 @@ namespace XCEngine.Rendering.Universal 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(); + ResetRendererFeaturesToDefault(); } protected override ScriptableRenderer CreateRenderer() @@ -25,10 +23,14 @@ namespace XCEngine.Rendering.Universal return new UniversalRenderer(this); } - protected override ScriptableRendererFeature[] CreateRendererFeatures() + protected override ScriptableRendererFeature[] + CreateDefaultRendererFeatures() { - return rendererFeatures ?? - Array.Empty(); + return new ScriptableRendererFeature[] + { + new BuiltinGaussianSplatRendererFeature(), + new BuiltinVolumetricRendererFeature() + }; } private protected override string GetPipelineRendererAssetKey() @@ -79,16 +81,6 @@ namespace XCEngine.Rendering.Universal return depthPrepass; } - - private static ScriptableRendererFeature[] - CreateDefaultRendererFeatures() - { - return new ScriptableRendererFeature[] - { - new BuiltinGaussianSplatRendererFeature(), - new BuiltinVolumetricRendererFeature() - }; - } } }