refactor(srp): align urp renderer feature ownership model
This commit is contained in:
@@ -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
|
||||
@@ -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++;
|
||||
|
||||
@@ -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<ScriptableRendererFeature>();
|
||||
}
|
||||
|
||||
protected virtual void ReleaseRuntimeResources()
|
||||
{
|
||||
}
|
||||
@@ -342,15 +331,21 @@ namespace XCEngine.Rendering.Universal
|
||||
rendererFeatures =
|
||||
CreateDefaultRendererFeatures() ??
|
||||
Array.Empty<ScriptableRendererFeature>();
|
||||
BindRendererFeatureOwners(rendererFeatures);
|
||||
}
|
||||
|
||||
private ScriptableRendererFeature[] GetRendererFeatures()
|
||||
{
|
||||
if (m_rendererFeatures == null)
|
||||
{
|
||||
ScriptableRendererFeature[]
|
||||
configuredRendererFeatures =
|
||||
rendererFeatures ??
|
||||
Array.Empty<ScriptableRendererFeature>();
|
||||
rendererFeatures =
|
||||
configuredRendererFeatures;
|
||||
m_rendererFeatures =
|
||||
CreateRendererFeatures() ??
|
||||
Array.Empty<ScriptableRendererFeature>();
|
||||
configuredRendererFeatures;
|
||||
}
|
||||
|
||||
BindRendererFeatureOwners(m_rendererFeatures);
|
||||
|
||||
@@ -4,6 +4,7 @@ using XCEngine.Rendering;
|
||||
namespace XCEngine.Rendering.Universal
|
||||
{
|
||||
public abstract class ScriptableRendererFeature
|
||||
: Object
|
||||
{
|
||||
private bool m_disposed;
|
||||
private bool m_runtimeCreated;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user