diff --git a/docs/used/SRP_URP_RendererFeatureCollectionPlan_2026-04-22_完成归档.md b/docs/used/SRP_URP_RendererFeatureCollectionPlan_2026-04-22_完成归档.md new file mode 100644 index 00000000..eb1aafbe --- /dev/null +++ b/docs/used/SRP_URP_RendererFeatureCollectionPlan_2026-04-22_完成归档.md @@ -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 冒烟通过。 diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index 3e364566..cf436ea2 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -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 diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs index 72339ee7..63688873 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs @@ -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(); + 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()); - 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(); - BindRendererFeatureOwners(rendererFeatures); + Array.Empty()); } private ScriptableRendererFeature[] GetRendererFeatures() { - if (m_rendererFeatures == null) - { - ScriptableRendererFeature[] - configuredRendererFeatures = - rendererFeatures ?? - Array.Empty(); - 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(); - 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(); } } } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeatureCollection.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeatureCollection.cs new file mode 100644 index 00000000..3347d0aa --- /dev/null +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeatureCollection.cs @@ -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(); + } + + public ScriptableRendererFeature[] GetFeatures( + ref ScriptableRendererFeature[] configuredFeatures) + { + if (m_features == null) + { + ScriptableRendererFeature[] resolvedFeatures = + configuredFeatures ?? + Array.Empty(); + configuredFeatures = resolvedFeatures; + m_features = resolvedFeatures; + } + + BindOwners(m_features); + return m_features; + } + + public void Reset( + ref ScriptableRendererFeature[] configuredFeatures, + ScriptableRendererFeature[] features) + { + configuredFeatures = + features ?? + Array.Empty(); + 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(); + 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; + } + } + } +} diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalDefaultRendererFeatureFactory.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalDefaultRendererFeatureFactory.cs new file mode 100644 index 00000000..f3ca317c --- /dev/null +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalDefaultRendererFeatureFactory.cs @@ -0,0 +1,25 @@ +using XCEngine; + +namespace XCEngine.Rendering.Universal +{ + internal static class UniversalDefaultRendererFeatureFactory + { + public static ScriptableRendererFeature[] + CreateDefaultRendererFeatures() + { + BuiltinGaussianSplatRendererFeature gaussianSplatFeature = + ScriptableObject + .CreateInstance() ?? + new BuiltinGaussianSplatRendererFeature(); + BuiltinVolumetricRendererFeature volumetricFeature = + ScriptableObject + .CreateInstance() ?? + new BuiltinVolumetricRendererFeature(); + return new ScriptableRendererFeature[] + { + gaussianSplatFeature, + volumetricFeature + }; + } + } +} diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs index 685c0e02..60e965b5 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs @@ -35,19 +35,8 @@ namespace XCEngine.Rendering.Universal protected override ScriptableRendererFeature[] CreateDefaultRendererFeatures() { - BuiltinGaussianSplatRendererFeature gaussianSplatFeature = - ScriptableObject - .CreateInstance() ?? - new BuiltinGaussianSplatRendererFeature(); - BuiltinVolumetricRendererFeature volumetricFeature = - ScriptableObject - .CreateInstance() ?? - new BuiltinVolumetricRendererFeature(); - return new ScriptableRendererFeature[] - { - gaussianSplatFeature, - volumetricFeature - }; + return UniversalDefaultRendererFeatureFactory + .CreateDefaultRendererFeatures(); } internal UniversalMainSceneData GetMainSceneInstance()