diff --git a/docs/used/SRP_URP_RendererFeatureAuthoringModelPlan_2026-04-21_完成归档.md b/docs/used/SRP_URP_RendererFeatureAuthoringModelPlan_2026-04-21_完成归档.md new file mode 100644 index 00000000..8855058d --- /dev/null +++ b/docs/used/SRP_URP_RendererFeatureAuthoringModelPlan_2026-04-21_完成归档.md @@ -0,0 +1,103 @@ +# SRP URP Renderer Feature Authoring Model Plan + +Date: 2026-04-21 + +## Background + +The last few SRP stages already cleaned up the following seams: + +- shared native backend substrate ownership +- renderer feature lifecycle invalidation +- renderer feature runtime-state synchronization + +However, the managed URP side still has one architecture mismatch against the +Unity-style direction: + +- `ScriptableRendererData` still exposes two feature ownership paths: + - persistent configured field: `rendererFeatures` + - runtime factory seam: `CreateRendererFeatures()` + +That dual model makes the current design harder to reason about: + +- the renderer-data asset is not the single source of truth for feature config +- feature lifetime semantics are blurred between "configured object" and + "runtime-created helper" +- project probes and managed probes still encode a non-Unity authoring style +- future editor integration and SRP asset editing will inherit this ambiguity + +If the engine is meant to converge toward a Unity-style `SRP + URP` model, +`ScriptableRendererData` should own a stable set of feature configuration +objects, and runtime invalidation should rebuild renderer runtime state from +that configured list rather than from an alternate feature factory seam. + +## Goal + +Make the managed URP renderer feature model closer to Unity: + +- `rendererFeatures` becomes the single configured source of truth +- `ScriptableRendererData` no longer creates an alternate feature collection via + `CreateRendererFeatures()` +- renderer runtime rebuild continues to work by snapshotting and releasing the + configured feature instances correctly +- `ScriptableRendererFeature` is modeled as a managed engine object rather than + a plain helper class +- managed probes and project probes are migrated to explicit feature + configuration instead of runtime feature factory overrides + +## Why Now + +This is the right point to do it because: + +- the lifecycle and dirty/version chain is already stable enough +- the next SRP stages need a clearer asset/config model, not more invalidation + patches +- future renderer authoring, editor exposure, and custom URP feature workflows + all depend on a clean ownership model + +If this seam stays open, later SRP work will keep mixing: + +- asset configuration logic +- runtime cache rebuild logic +- probe-only factory patterns + +That would move the codebase away from the Unity-style architecture the project +is aiming for. + +## Scope + +This stage stays focused on managed SRP/URP ownership cleanup. + +Included: + +- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs` +- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs` +- affected managed probes under `managed/GameScripts/RenderPipelineApiProbe.cs` +- affected project probes under `project/Assets/Scripts/ProjectRenderPipelineProbe.cs` + +Not included: + +- deferred rendering +- native render backend changes +- new editor work + +## Implementation Plan + +1. Collapse renderer feature ownership onto `rendererFeatures` +2. Remove the `CreateRendererFeatures()` seam from `ScriptableRendererData` +3. Keep runtime rebuild safe by retaining the last runtime-bound feature + snapshot for disposal +4. Make `ScriptableRendererFeature` inherit from managed engine `Object` +5. Migrate probes to constructor/config-field based feature assignment +6. Rebuild `XCEditor` +7. Run old editor smoke for at least 10 seconds and verify a fresh + `SceneReady` +8. Archive the plan, commit, and push + +## Expected Result + +After this stage: + +- renderer data is the authoritative owner of renderer feature configuration +- runtime invalidation semantics remain intact but are simpler to reason about +- custom URP-style feature authoring is closer to the Unity mental model +- the next SRP stages can build on a cleaner managed asset/config substrate diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index ad3f808f..61eae00d 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -521,19 +521,20 @@ namespace Gameplay internal sealed class ManagedFeaturePassOrderProbeRendererData : ProbeRendererData { - protected override ScriptableRenderer CreateProbeRenderer() + public ManagedFeaturePassOrderProbeRendererData() + : base(false) { - return new ManagedFeaturePassOrderProbeRenderer(); - } - - protected override ScriptableRendererFeature[] CreateRendererFeatures() - { - return new ScriptableRendererFeature[] + rendererFeatures = new ScriptableRendererFeature[] { new ManagedFeaturePassOrderCustomFeature("CustomA"), new ManagedFeaturePassOrderCustomFeature("CustomB") }; } + + protected override ScriptableRenderer CreateProbeRenderer() + { + return new ManagedFeaturePassOrderProbeRenderer(); + } } internal sealed class CameraDataObservationPass @@ -978,6 +979,16 @@ namespace Gameplay internal abstract class ProbeRendererData : UniversalRendererData { + protected ProbeRendererData() + { + } + + protected ProbeRendererData( + bool initializeDefaultRendererFeatures) + : base(initializeDefaultRendererFeatures) + { + } + protected sealed override ScriptableRenderer CreateRenderer() { return CreateProbeRenderer(); @@ -1111,6 +1122,16 @@ namespace Gameplay internal sealed class ManagedRendererInvalidationProbeRendererData : ProbeRendererData { + public ManagedRendererInvalidationProbeRendererData() + : base(false) + { + ManagedRendererInvalidationProbeState.CreateFeatureCallCount++; + rendererFeatures = new ScriptableRendererFeature[] + { + new ManagedRendererInvalidationProbeFeature() + }; + } + protected override ScriptableRenderer CreateProbeRenderer() { ManagedRendererInvalidationProbeState.CreateRendererCallCount++; @@ -1124,15 +1145,6 @@ namespace Gameplay base.SetupRenderer(renderer); } - protected override ScriptableRendererFeature[] CreateRendererFeatures() - { - ManagedRendererInvalidationProbeState.CreateFeatureCallCount++; - return new ScriptableRendererFeature[] - { - new ManagedRendererInvalidationProbeFeature() - }; - } - public void InvalidateForTest() { ManagedRendererInvalidationProbeState.InvalidateRendererCallCount++; @@ -1187,8 +1199,18 @@ namespace Gameplay internal sealed class ManagedPersistentFeatureProbeRendererData : ProbeRendererData { - private readonly ManagedPersistentFeatureProbeRendererFeature m_feature = - new ManagedPersistentFeatureProbeRendererFeature(); + private readonly ManagedPersistentFeatureProbeRendererFeature m_feature; + + public ManagedPersistentFeatureProbeRendererData() + : base(false) + { + m_feature = + new ManagedPersistentFeatureProbeRendererFeature(); + rendererFeatures = new ScriptableRendererFeature[] + { + m_feature + }; + } protected override ScriptableRenderer CreateProbeRenderer() { @@ -1197,14 +1219,6 @@ namespace Gameplay return new ManagedPersistentFeatureProbeRenderer(); } - protected override ScriptableRendererFeature[] CreateRendererFeatures() - { - return new ScriptableRendererFeature[] - { - m_feature - }; - } - public void InvalidateForTest() { ManagedPersistentFeatureProbeState @@ -1243,18 +1257,19 @@ namespace Gameplay internal sealed class ManagedFeaturePlannedPostProcessRendererData : ProbeRendererData { - protected override ScriptableRenderer CreateProbeRenderer() + public ManagedFeaturePlannedPostProcessRendererData() + : base(false) { - return new ProbeSceneRenderer(); - } - - protected override ScriptableRendererFeature[] CreateRendererFeatures() - { - return new ScriptableRendererFeature[] + rendererFeatures = new ScriptableRendererFeature[] { new ManagedFeaturePlannedPostProcessRendererFeature() }; } + + protected override ScriptableRenderer CreateProbeRenderer() + { + return new ProbeSceneRenderer(); + } } internal sealed class ManagedRenderContextCameraDataProbeRendererData @@ -1335,6 +1350,16 @@ namespace Gameplay internal sealed class ManagedLifecycleProbeRendererData : ProbeRendererData { + public ManagedLifecycleProbeRendererData() + : base(false) + { + ManagedLifecycleProbeState.CreateFeatureCallCount++; + rendererFeatures = new ScriptableRendererFeature[] + { + new ManagedLifecycleProbeRendererFeature() + }; + } + protected override ScriptableRenderer CreateProbeRenderer() { ManagedLifecycleProbeState.CreateRendererCallCount++; @@ -1348,15 +1373,6 @@ namespace Gameplay base.SetupRenderer(renderer); } - protected override ScriptableRendererFeature[] CreateRendererFeatures() - { - ManagedLifecycleProbeState.CreateFeatureCallCount++; - return new ScriptableRendererFeature[] - { - new ManagedLifecycleProbeRendererFeature() - }; - } - protected override void ReleaseRuntimeResources() { ManagedLifecycleProbeState.ReleaseRendererDataRuntimeResourcesCallCount++; diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs index 7ec716dd..8136d298 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs @@ -41,11 +41,6 @@ namespace XCEngine.Rendering.Universal return m_rendererInstance; } - internal ScriptableRendererFeature[] CreateRendererFeaturesInstance() - { - return GetRendererFeatures(); - } - internal void SetupRendererInstance( ScriptableRenderer renderer) { @@ -245,12 +240,6 @@ namespace XCEngine.Rendering.Universal { } - protected virtual ScriptableRendererFeature[] CreateRendererFeatures() - { - return rendererFeatures ?? - Array.Empty(); - } - protected virtual void ReleaseRuntimeResources() { } @@ -342,15 +331,21 @@ namespace XCEngine.Rendering.Universal rendererFeatures = CreateDefaultRendererFeatures() ?? Array.Empty(); + BindRendererFeatureOwners(rendererFeatures); } private ScriptableRendererFeature[] GetRendererFeatures() { if (m_rendererFeatures == null) { + ScriptableRendererFeature[] + configuredRendererFeatures = + rendererFeatures ?? + Array.Empty(); + rendererFeatures = + configuredRendererFeatures; m_rendererFeatures = - CreateRendererFeatures() ?? - Array.Empty(); + configuredRendererFeatures; } BindRendererFeatureOwners(m_rendererFeatures); diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs index f490272f..cfa6120b 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs @@ -4,6 +4,7 @@ using XCEngine.Rendering; namespace XCEngine.Rendering.Universal { public abstract class ScriptableRendererFeature + : Object { private bool m_disposed; private bool m_runtimeCreated; diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs index 48381a08..4abd0dc5 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs @@ -11,11 +11,20 @@ namespace XCEngine.Rendering.Universal public DepthPrepassBlockData depthPrepass; public UniversalRendererData() + : this(true) + { + } + + protected UniversalRendererData( + bool initializeDefaultRendererFeatures) { mainScene = UniversalMainSceneData.CreateDefault(); shadowCaster = ShadowCasterBlockData.CreateDefault(); depthPrepass = DepthPrepassBlockData.CreateDefault(); - ResetRendererFeaturesToDefault(); + if (initializeDefaultRendererFeatures) + { + ResetRendererFeaturesToDefault(); + } } protected override ScriptableRenderer CreateRenderer() diff --git a/project/Assets/Scripts/ProjectRenderPipelineProbe.cs b/project/Assets/Scripts/ProjectRenderPipelineProbe.cs index 5411d4cf..f7a70eff 100644 --- a/project/Assets/Scripts/ProjectRenderPipelineProbe.cs +++ b/project/Assets/Scripts/ProjectRenderPipelineProbe.cs @@ -302,6 +302,17 @@ namespace ProjectScripts public sealed class ProjectRendererInvalidationProbeRendererData : UniversalRendererData { + public ProjectRendererInvalidationProbeRendererData() + : base(false) + { + ProjectRendererInvalidationProbeState + .CreateFeatureCallCount++; + rendererFeatures = new ScriptableRendererFeature[] + { + new ProjectRendererInvalidationProbeFeature() + }; + } + protected override ScriptableRenderer CreateRenderer() { ProjectRendererInvalidationProbeState @@ -317,17 +328,6 @@ namespace ProjectScripts base.SetupRenderer(renderer); } - protected override ScriptableRendererFeature[] - CreateRendererFeatures() - { - ProjectRendererInvalidationProbeState - .CreateFeatureCallCount++; - return new ScriptableRendererFeature[] - { - new ProjectRendererInvalidationProbeFeature() - }; - } - public void InvalidateForTest() { ProjectRendererInvalidationProbeState @@ -432,8 +432,18 @@ namespace ProjectScripts : UniversalRendererData { private readonly ProjectPersistentFeatureProbeRendererFeature + m_feature; + + public ProjectPersistentFeatureProbeRendererData() + : base(false) + { m_feature = new ProjectPersistentFeatureProbeRendererFeature(); + rendererFeatures = new ScriptableRendererFeature[] + { + m_feature + }; + } protected override ScriptableRenderer CreateRenderer() { @@ -442,15 +452,6 @@ namespace ProjectScripts return new ProjectPersistentFeatureProbeRenderer(); } - protected override ScriptableRendererFeature[] - CreateRendererFeatures() - { - return new ScriptableRendererFeature[] - { - m_feature - }; - } - public void InvalidateForTest() { ProjectPersistentFeatureProbeState