refactor(srp): formalize renderer feature collections

This commit is contained in:
2026-04-22 02:31:07 +08:00
parent 6950bd15c2
commit 3187ccbfe1
6 changed files with 394 additions and 193 deletions

View File

@@ -0,0 +1,70 @@
# SRP / URP Renderer Feature Collection Plan
时间2026-04-22
## 背景
当前 feature 层的 runtime boundary 已经基本收好,但 `renderer data` 这一层还有明显的结构问题:
1. `ScriptableRendererData` 直接持有并操作 `rendererFeatures` 数组。
2. feature owner bind、active 遍历、runtime state hash、runtime release 这些逻辑在 `ScriptableRendererData` 里分散重复。
3. `UniversalRendererData` 的默认 feature 组合也还是直接在 `CreateDefaultRendererFeatures()` 里硬编码构造。
这会导致:
- `renderer data` 既在做 renderer asset 表达,又在做 feature collection 运行时管理。
- 默认 feature 组合没有正式落点,后续扩展不同 renderer profile 时不利于演进。
## 本阶段目标
`rendererFeatures` 这层正式收成 collection / factory 结构。
目标结果:
1. `ScriptableRendererFeatureCollection` 统一管理 feature 数组的 bind、active 遍历、hash、release。
2. `ScriptableRendererData` 不再散写 feature 集合生命周期。
3. `UniversalRendererData` 的默认 feature 组合改走明确工厂。
## 范围
本阶段只处理 renderer feature collection不做
- public renderer feature API 改造
- deferred rendering
- editor feature 分类面板
- C++ host / RenderGraph 改造
## 实施步骤
### 1. 新增 ScriptableRendererFeatureCollection
职责:
- 持有 feature 数组引用
- 统一 bind owner
- 统一 active 遍历
- 统一计算 collection hash
- 统一 runtime release
### 2. 接入 ScriptableRendererData
迁移:
- `ConfigureCameraRenderRequestInstance`
- `ConfigureCameraFramePlanInstance`
- `ConfigureRenderSceneSetupInstance`
- `ConfigureDirectionalShadowExecutionStateInstance`
- `ReleaseRendererSetupCache`
- renderer feature collection state sync
### 3. 抽离 Universal 默认 feature 工厂
新增 `UniversalDefaultRendererFeatureFactory`,负责默认 builtin feature 组合。
## 完成标准
满足以下条件才算本阶段收口:
1. `ScriptableRendererData` 不再自己散写 feature collection 生命周期细节。
2. `UniversalRendererData` 默认 feature 组合走明确工厂。
3. `XCEditor` 编译通过old editor 冒烟通过。

View File

@@ -251,9 +251,11 @@ set(XCENGINE_RENDER_PIPELINES_UNIVERSAL_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderPass.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeatureCollection.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/StageColorData.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalAdditionalCameraData.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalDefaultRendererFeatureFactory.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalFinalColorSettings.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalFinalOutputBlock.cs
${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalMainSceneData.cs

View File

@@ -9,7 +9,8 @@ namespace XCEngine.Rendering.Universal
: ScriptableObject
{
public ScriptableRendererFeature[] rendererFeatures;
private ScriptableRendererFeature[] m_rendererFeatures;
private readonly ScriptableRendererFeatureCollection
m_featureCollection;
private ScriptableRenderer m_rendererInstance;
private bool m_rendererInvalidated;
private int m_runtimeStateVersion = 1;
@@ -20,6 +21,8 @@ namespace XCEngine.Rendering.Universal
{
rendererFeatures =
Array.Empty<ScriptableRendererFeature>();
m_featureCollection =
new ScriptableRendererFeatureCollection(this);
}
internal ScriptableRenderer CreateRendererInstance()
@@ -60,11 +63,10 @@ namespace XCEngine.Rendering.Universal
internal void InvalidateRendererFeaturesInstance()
{
m_rendererFeatureCollectionHash =
ComputeRendererFeatureCollectionHash(
rendererFeatures ??
Array.Empty<ScriptableRendererFeature>());
m_rendererFeatureCollectionHashResolved = true;
m_featureCollection.Invalidate(
ref rendererFeatures,
ref m_rendererFeatureCollectionHash,
ref m_rendererFeatureCollectionHashResolved);
SetDirty();
}
@@ -85,22 +87,9 @@ namespace XCEngine.Rendering.Universal
CameraRenderRequestContext context)
{
ConfigureCameraRenderRequest(context);
ScriptableRendererFeature[] rendererFeatures =
GetRendererFeatures();
for (int i = 0; i < rendererFeatures.Length; ++i)
{
ScriptableRendererFeature rendererFeature =
rendererFeatures[i];
if (rendererFeature == null ||
!rendererFeature.isActive)
{
continue;
}
rendererFeature.ConfigureCameraRenderRequest(
context);
}
m_featureCollection.ConfigureCameraRenderRequest(
ref rendererFeatures,
context);
}
internal void ConfigureCameraFramePlanInstance(
@@ -116,21 +105,9 @@ namespace XCEngine.Rendering.Universal
context);
}
ScriptableRendererFeature[] rendererFeatures =
GetRendererFeatures();
for (int i = 0; i < rendererFeatures.Length; ++i)
{
ScriptableRendererFeature rendererFeature =
rendererFeatures[i];
if (rendererFeature == null ||
!rendererFeature.isActive)
{
continue;
}
rendererFeature.ConfigureCameraFramePlan(
context);
}
m_featureCollection.ConfigureCameraFramePlan(
ref rendererFeatures,
context);
if (renderer != null)
{
@@ -152,21 +129,9 @@ namespace XCEngine.Rendering.Universal
context);
}
ScriptableRendererFeature[] rendererFeatures =
GetRendererFeatures();
for (int i = 0; i < rendererFeatures.Length; ++i)
{
ScriptableRendererFeature rendererFeature =
rendererFeatures[i];
if (rendererFeature == null ||
!rendererFeature.isActive)
{
continue;
}
rendererFeature.ConfigureRenderSceneSetup(
context);
}
m_featureCollection.ConfigureRenderSceneSetup(
ref rendererFeatures,
context);
return context != null &&
context.isConfigured;
@@ -187,22 +152,10 @@ namespace XCEngine.Rendering.Universal
context);
}
ScriptableRendererFeature[] rendererFeatures =
GetRendererFeatures();
for (int i = 0; i < rendererFeatures.Length; ++i)
{
ScriptableRendererFeature rendererFeature =
rendererFeatures[i];
if (rendererFeature == null ||
!rendererFeature.isActive)
{
continue;
}
rendererFeature
.ConfigureDirectionalShadowExecutionState(
context);
}
m_featureCollection
.ConfigureDirectionalShadowExecutionState(
ref rendererFeatures,
context);
return context != null &&
context.isConfigured;
@@ -329,110 +282,25 @@ namespace XCEngine.Rendering.Universal
protected void ResetRendererFeaturesToDefault()
{
rendererFeatures =
m_featureCollection.Reset(
ref rendererFeatures,
CreateDefaultRendererFeatures() ??
Array.Empty<ScriptableRendererFeature>();
BindRendererFeatureOwners(rendererFeatures);
Array.Empty<ScriptableRendererFeature>());
}
private ScriptableRendererFeature[] GetRendererFeatures()
{
if (m_rendererFeatures == null)
{
ScriptableRendererFeature[]
configuredRendererFeatures =
rendererFeatures ??
Array.Empty<ScriptableRendererFeature>();
rendererFeatures =
configuredRendererFeatures;
m_rendererFeatures =
configuredRendererFeatures;
}
BindRendererFeatureOwners(m_rendererFeatures);
return m_rendererFeatures;
return m_featureCollection.GetFeatures(
ref rendererFeatures);
}
private void SynchronizeRendererFeatureCollectionState()
{
ScriptableRendererFeature[] configuredRendererFeatures =
rendererFeatures ??
Array.Empty<ScriptableRendererFeature>();
BindRendererFeatureOwners(configuredRendererFeatures);
int collectionHash =
ComputeRendererFeatureCollectionHash(
configuredRendererFeatures);
if (!m_rendererFeatureCollectionHashResolved)
{
m_rendererFeatureCollectionHash = collectionHash;
m_rendererFeatureCollectionHashResolved = true;
return;
}
if (collectionHash == m_rendererFeatureCollectionHash)
{
return;
}
m_rendererFeatureCollectionHash = collectionHash;
SetDirty();
}
private void BindRendererFeatureOwners(
ScriptableRendererFeature[] rendererFeatureCollection)
{
if (rendererFeatureCollection == null)
{
return;
}
for (int i = 0; i < rendererFeatureCollection.Length; ++i)
{
ScriptableRendererFeature rendererFeature =
rendererFeatureCollection[i];
if (rendererFeature != null)
{
rendererFeature.BindOwnerInstance(this);
}
}
}
private static int ComputeRendererFeatureCollectionHash(
ScriptableRendererFeature[] rendererFeatureCollection)
{
unchecked
{
int hash = 17;
if (rendererFeatureCollection == null)
{
return hash;
}
hash =
(hash * 31) +
rendererFeatureCollection.Length;
for (int i = 0; i < rendererFeatureCollection.Length; ++i)
{
ScriptableRendererFeature rendererFeature =
rendererFeatureCollection[i];
if (rendererFeature == null)
{
hash = (hash * 31) + 1;
continue;
}
hash =
(hash * 31) +
RuntimeHelpers.GetHashCode(rendererFeature);
hash =
(hash * 31) +
rendererFeature
.GetRuntimeStateVersionInstance();
}
return hash;
}
m_featureCollection.Synchronize(
ref rendererFeatures,
ref m_rendererFeatureCollectionHash,
ref m_rendererFeatureCollectionHashResolved,
SetDirty);
}
private void ReleaseRendererSetupCache()
@@ -441,26 +309,11 @@ namespace XCEngine.Rendering.Universal
{
m_rendererInstance.ReleaseRuntimeResourcesInstance();
m_rendererInstance = null;
m_rendererFeatures = null;
m_featureCollection.ResetResolvedCache();
return;
}
if (m_rendererFeatures == null)
{
return;
}
for (int i = 0; i < m_rendererFeatures.Length; ++i)
{
ScriptableRendererFeature rendererFeature =
m_rendererFeatures[i];
if (rendererFeature != null)
{
rendererFeature.ReleaseRuntimeResourcesInstance();
}
}
m_rendererFeatures = null;
m_featureCollection.ReleaseRuntimeResources();
}
}
}

View File

@@ -0,0 +1,262 @@
using System;
using System.Runtime.CompilerServices;
using XCEngine;
using XCEngine.Rendering;
namespace XCEngine.Rendering.Universal
{
internal sealed class ScriptableRendererFeatureCollection
{
private readonly ScriptableRendererData m_owner;
private ScriptableRendererFeature[] m_features;
public ScriptableRendererFeatureCollection(
ScriptableRendererData owner)
{
m_owner = owner;
m_features = Array.Empty<ScriptableRendererFeature>();
}
public ScriptableRendererFeature[] GetFeatures(
ref ScriptableRendererFeature[] configuredFeatures)
{
if (m_features == null)
{
ScriptableRendererFeature[] resolvedFeatures =
configuredFeatures ??
Array.Empty<ScriptableRendererFeature>();
configuredFeatures = resolvedFeatures;
m_features = resolvedFeatures;
}
BindOwners(m_features);
return m_features;
}
public void Reset(
ref ScriptableRendererFeature[] configuredFeatures,
ScriptableRendererFeature[] features)
{
configuredFeatures =
features ??
Array.Empty<ScriptableRendererFeature>();
m_features = configuredFeatures;
BindOwners(m_features);
}
public void Invalidate(
ref ScriptableRendererFeature[] configuredFeatures,
ref int collectionHash,
ref bool collectionHashResolved)
{
ScriptableRendererFeature[] features =
ResolveConfiguredFeatures(
ref configuredFeatures);
collectionHash =
ComputeCollectionHash(features);
collectionHashResolved = true;
}
public void Synchronize(
ref ScriptableRendererFeature[] configuredFeatures,
ref int collectionHash,
ref bool collectionHashResolved,
Action onChanged)
{
ScriptableRendererFeature[] features =
ResolveConfiguredFeatures(
ref configuredFeatures);
int resolvedHash =
ComputeCollectionHash(features);
if (!collectionHashResolved)
{
collectionHash = resolvedHash;
collectionHashResolved = true;
return;
}
if (resolvedHash == collectionHash)
{
return;
}
collectionHash = resolvedHash;
onChanged?.Invoke();
}
public void ConfigureCameraRenderRequest(
ref ScriptableRendererFeature[] configuredFeatures,
CameraRenderRequestContext context)
{
ScriptableRendererFeature[] features =
GetFeatures(ref configuredFeatures);
for (int i = 0; i < features.Length; ++i)
{
ScriptableRendererFeature feature =
features[i];
if (feature == null ||
!feature.isActive)
{
continue;
}
feature.ConfigureCameraRenderRequest(
context);
}
}
public void ConfigureCameraFramePlan(
ref ScriptableRendererFeature[] configuredFeatures,
ScriptableRenderPipelinePlanningContext context)
{
ScriptableRendererFeature[] features =
GetFeatures(ref configuredFeatures);
for (int i = 0; i < features.Length; ++i)
{
ScriptableRendererFeature feature =
features[i];
if (feature == null ||
!feature.isActive)
{
continue;
}
feature.ConfigureCameraFramePlan(
context);
}
}
public void ConfigureRenderSceneSetup(
ref ScriptableRendererFeature[] configuredFeatures,
RenderSceneSetupContext context)
{
ScriptableRendererFeature[] features =
GetFeatures(ref configuredFeatures);
for (int i = 0; i < features.Length; ++i)
{
ScriptableRendererFeature feature =
features[i];
if (feature == null ||
!feature.isActive)
{
continue;
}
feature.ConfigureRenderSceneSetup(
context);
}
}
public void ConfigureDirectionalShadowExecutionState(
ref ScriptableRendererFeature[] configuredFeatures,
DirectionalShadowExecutionContext context)
{
ScriptableRendererFeature[] features =
GetFeatures(ref configuredFeatures);
for (int i = 0; i < features.Length; ++i)
{
ScriptableRendererFeature feature =
features[i];
if (feature == null ||
!feature.isActive)
{
continue;
}
feature
.ConfigureDirectionalShadowExecutionState(
context);
}
}
public void ReleaseRuntimeResources()
{
if (m_features == null)
{
return;
}
for (int i = 0; i < m_features.Length; ++i)
{
ScriptableRendererFeature feature =
m_features[i];
if (feature != null)
{
feature.ReleaseRuntimeResourcesInstance();
}
}
m_features = null;
}
public void ResetResolvedCache()
{
m_features = null;
}
private ScriptableRendererFeature[] ResolveConfiguredFeatures(
ref ScriptableRendererFeature[] configuredFeatures)
{
ScriptableRendererFeature[] features =
configuredFeatures ??
Array.Empty<ScriptableRendererFeature>();
configuredFeatures = features;
m_features = features;
BindOwners(features);
return features;
}
private void BindOwners(
ScriptableRendererFeature[] features)
{
if (features == null)
{
return;
}
for (int i = 0; i < features.Length; ++i)
{
ScriptableRendererFeature feature =
features[i];
if (feature != null)
{
feature.BindOwnerInstance(m_owner);
}
}
}
private static int ComputeCollectionHash(
ScriptableRendererFeature[] features)
{
unchecked
{
int hash = 17;
if (features == null)
{
return hash;
}
hash = (hash * 31) + features.Length;
for (int i = 0; i < features.Length; ++i)
{
ScriptableRendererFeature feature =
features[i];
if (feature == null)
{
hash = (hash * 31) + 1;
continue;
}
hash =
(hash * 31) +
RuntimeHelpers.GetHashCode(feature);
hash =
(hash * 31) +
feature.GetRuntimeStateVersionInstance();
}
return hash;
}
}
}
}

View File

@@ -0,0 +1,25 @@
using XCEngine;
namespace XCEngine.Rendering.Universal
{
internal static class UniversalDefaultRendererFeatureFactory
{
public static ScriptableRendererFeature[]
CreateDefaultRendererFeatures()
{
BuiltinGaussianSplatRendererFeature gaussianSplatFeature =
ScriptableObject
.CreateInstance<BuiltinGaussianSplatRendererFeature>() ??
new BuiltinGaussianSplatRendererFeature();
BuiltinVolumetricRendererFeature volumetricFeature =
ScriptableObject
.CreateInstance<BuiltinVolumetricRendererFeature>() ??
new BuiltinVolumetricRendererFeature();
return new ScriptableRendererFeature[]
{
gaussianSplatFeature,
volumetricFeature
};
}
}
}

View File

@@ -35,19 +35,8 @@ namespace XCEngine.Rendering.Universal
protected override ScriptableRendererFeature[]
CreateDefaultRendererFeatures()
{
BuiltinGaussianSplatRendererFeature gaussianSplatFeature =
ScriptableObject
.CreateInstance<BuiltinGaussianSplatRendererFeature>() ??
new BuiltinGaussianSplatRendererFeature();
BuiltinVolumetricRendererFeature volumetricFeature =
ScriptableObject
.CreateInstance<BuiltinVolumetricRendererFeature>() ??
new BuiltinVolumetricRendererFeature();
return new ScriptableRendererFeature[]
{
gaussianSplatFeature,
volumetricFeature
};
return UniversalDefaultRendererFeatureFactory
.CreateDefaultRendererFeatures();
}
internal UniversalMainSceneData GetMainSceneInstance()