refactor(srp): synchronize urp renderer feature runtime state

This commit is contained in:
2026-04-21 19:59:20 +08:00
parent ffe4717d8f
commit 0d0919b276
6 changed files with 401 additions and 16 deletions

View File

@@ -0,0 +1,85 @@
# SRP URP Renderer Feature Runtime State Sync Plan
日期2026-04-21
## 背景
上一阶段已经把 `ScriptableRendererFeature` / `ScriptableRendererData` 的 owner、dirty、lifecycle 收口了一轮:
- feature 的激活状态变化会触发 invalidation
- feature 可以向 owner 传播 dirty
- renderer feature 集合变化能使 renderer cache 失效
但当前还有一个关键缺口:
- feature 的普通配置字段变化,系统本身还不会主动感知
例如:
- `RenderObjectsRendererFeature.passEvent`
- `RenderObjectsRendererFeature.overrideMaterialPath`
- `ColorScalePostProcessRendererFeature.colorScale`
- 内置 feature 的 `passEvent`
这些值如果被代码改掉,当前多数场景只是“碰巧还能工作”,而不是被明确建模成 runtime-state 变化。
## 本阶段目标
`ScriptableRendererFeature` 增加统一的 runtime-state 同步入口,并把当前内置 feature 的配置接到这条链路上:
- `ScriptableRendererFeature` 自身能同步 runtime state version
- `ScriptableRendererData` 读取 feature version 时会拿到同步后的值
- 内置 feature 的关键配置字段参与 runtime-state 哈希
- feature 配置变化会走统一 invalidation 语义,而不是散落在各处的偶然行为
## 为什么现在做
如果这层不做,后面继续做:
- 更多 URP feature
- 用户自定义 renderer feature
- editor/序列化 对 feature 字段的编辑
都会建立在一个“字段变了,但系统不一定知道这算一次 runtime state 变化”的前提上。
这会让后面调试、缓存失效、资源重建的行为越来越难预测。
## 改动范围
本阶段只处理 managed URP feature 的 runtime-state 同步,不碰 new editor不改 deferred不回头扩 native。
主要涉及:
- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs`
- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderObjectsRendererFeature.cs`
- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ColorScalePostProcessRendererFeature.cs`
- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinGaussianSplatRendererFeature.cs`
- `managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/BuiltinVolumetricRendererFeature.cs`
如有必要,可加一个小型哈希辅助实现,但保持范围只在 managed URP。
## 预期结果
收口后:
- `ScriptableRendererFeature` 会像 `ScriptableRenderPipelineAsset` / `ScriptableRendererData` 一样具备同步 runtime-state 的能力
- feature 配置字段变化会转化为明确的 version 变化
- `ScriptableRendererData` 不再只知道 “feature 对象还是不是同一个”,也知道 “同一个 feature 的配置有没有变”
- 后面继续做用户自定义 URP feature 时,失效语义有稳定扩展点
## 实施步骤
1.`ScriptableRendererFeature` 增加 runtime-state 同步入口
2. 为当前内置 feature 接入配置哈希/同步逻辑
3. 保持和上一阶段的 owner/dirty/lifecycle 逻辑兼容
4. 编译 `XCEditor`
5. 运行旧版 `editor/bin/Debug/XCEngine.exe` 冒烟至少 10 秒,并确认新的 `SceneReady`
6. 归档计划,提交并推送
## 验证标准
- `cmake --build . --config Debug --target XCEditor` 成功
- 运行 `editor/bin/Debug/XCEngine.exe`
- 冒烟至少 10 秒
- `editor/bin/Debug/editor.log` 出现新的 `SceneReady`
- 本阶段提交只包含 SRP/rendering 主线代码与计划文档

View File

@@ -11,6 +11,17 @@ namespace XCEngine.Rendering.Universal
private NativeSceneFeaturePass m_pass;
protected override int ComputeRuntimeStateHash()
{
int hash =
base.ComputeRuntimeStateHash();
hash =
RuntimeStateHashUtility.Combine(
hash,
(int)passEvent);
return hash;
}
public override void Create()
{
m_pass = new NativeSceneFeaturePass(
@@ -27,10 +38,7 @@ namespace XCEngine.Rendering.Universal
return;
}
if (m_pass == null)
{
CreateInstance();
}
CreateInstance();
m_pass.Configure(passEvent);
renderer.EnqueuePass(m_pass);

View File

@@ -11,6 +11,17 @@ namespace XCEngine.Rendering.Universal
private NativeSceneFeaturePass m_pass;
protected override int ComputeRuntimeStateHash()
{
int hash =
base.ComputeRuntimeStateHash();
hash =
RuntimeStateHashUtility.Combine(
hash,
(int)passEvent);
return hash;
}
public override void Create()
{
m_pass = new NativeSceneFeaturePass(
@@ -27,10 +38,7 @@ namespace XCEngine.Rendering.Universal
return;
}
if (m_pass == null)
{
CreateInstance();
}
CreateInstance();
m_pass.Configure(passEvent);
renderer.EnqueuePass(m_pass);

View File

@@ -48,6 +48,17 @@ namespace XCEngine.Rendering.Universal
1.0f,
1.0f);
protected override int ComputeRuntimeStateHash()
{
int hash =
base.ComputeRuntimeStateHash();
hash =
RuntimeStateHashUtility.Combine(
hash,
colorScale);
return hash;
}
public override void Create()
{
m_pass =
@@ -64,10 +75,7 @@ namespace XCEngine.Rendering.Universal
return;
}
if (m_pass == null)
{
CreateInstance();
}
CreateInstance();
renderer.EnqueuePass(m_pass);
}

View File

@@ -28,6 +28,73 @@ namespace XCEngine.Rendering.Universal
public bool overrideSortingSettings;
public SortingSettings sortingSettings;
protected override int ComputeRuntimeStateHash()
{
int hash =
base.ComputeRuntimeStateHash();
hash =
RuntimeStateHashUtility.Combine(
hash,
(int)passEvent);
hash =
RuntimeStateHashUtility.Combine(
hash,
(int)scenePhase);
hash =
RuntimeStateHashUtility.Combine(
hash,
(int)rendererListType);
hash =
RuntimeStateHashUtility.Combine(
hash,
overrideRenderQueueRange);
hash =
RuntimeStateHashUtility.Combine(
hash,
renderQueueRange);
hash =
RuntimeStateHashUtility.Combine(
hash,
overrideRenderLayerMask);
hash =
RuntimeStateHashUtility.Combine(
hash,
renderLayerMask);
hash =
RuntimeStateHashUtility.Combine(
hash,
overrideMaterialPath);
hash =
RuntimeStateHashUtility.Combine(
hash,
shaderPassName);
hash =
RuntimeStateHashUtility.Combine(
hash,
overrideRenderStateBlock);
hash =
RuntimeStateHashUtility.Combine(
hash,
renderStateBlock);
hash =
RuntimeStateHashUtility.Combine(
hash,
overrideFilteringSettings);
hash =
RuntimeStateHashUtility.Combine(
hash,
filteringSettings);
hash =
RuntimeStateHashUtility.Combine(
hash,
overrideSortingSettings);
hash =
RuntimeStateHashUtility.Combine(
hash,
sortingSettings);
return hash;
}
public override void Create()
{
RendererListDesc rendererListDesc =
@@ -62,10 +129,7 @@ namespace XCEngine.Rendering.Universal
return;
}
if (m_pass == null)
{
CreateInstance();
}
CreateInstance();
m_pass.Configure(
passEvent,

View File

@@ -9,6 +9,8 @@ namespace XCEngine.Rendering.Universal
private bool m_runtimeCreated;
private bool m_isActive = true;
private int m_runtimeStateVersion = 1;
private int m_runtimeStateHash;
private bool m_runtimeStateHashResolved;
private ScriptableRendererData m_owner;
protected ScriptableRendererFeature()
@@ -41,6 +43,7 @@ namespace XCEngine.Rendering.Universal
internal int GetRuntimeStateVersionInstance()
{
SynchronizeRuntimeStateVersion();
return m_runtimeStateVersion;
}
@@ -125,8 +128,47 @@ namespace XCEngine.Rendering.Universal
{
}
protected virtual int ComputeRuntimeStateHash()
{
int hash = 17;
hash =
RuntimeStateHashUtility.Combine(
hash,
m_isActive);
return hash;
}
protected void SetDirty()
{
ApplyDirty(
ComputeRuntimeStateHash());
}
private void SynchronizeRuntimeStateVersion()
{
int runtimeStateHash =
ComputeRuntimeStateHash();
if (!m_runtimeStateHashResolved)
{
m_runtimeStateHash = runtimeStateHash;
m_runtimeStateHashResolved = true;
return;
}
if (runtimeStateHash == m_runtimeStateHash)
{
return;
}
ApplyDirty(runtimeStateHash);
}
private void ApplyDirty(
int runtimeStateHash)
{
m_runtimeStateHash = runtimeStateHash;
m_runtimeStateHashResolved = true;
if (m_runtimeCreated &&
!m_disposed)
{
@@ -152,5 +194,175 @@ namespace XCEngine.Rendering.Universal
}
}
}
internal static class RuntimeStateHashUtility
{
public static int Combine(
int hash,
bool value)
{
return Combine(
hash,
value ? 1 : 0);
}
public static int Combine(
int hash,
byte value)
{
return Combine(
hash,
(int)value);
}
public static int Combine(
int hash,
uint value)
{
unchecked
{
return (hash * 31) + (int)value;
}
}
public static int Combine(
int hash,
int value)
{
unchecked
{
return (hash * 31) + value;
}
}
public static int Combine(
int hash,
float value)
{
return Combine(
hash,
value.GetHashCode());
}
public static int Combine(
int hash,
string value)
{
return Combine(
hash,
value != null
? value.GetHashCode()
: 0);
}
public static int Combine(
int hash,
Vector4 value)
{
hash = Combine(hash, value.X);
hash = Combine(hash, value.Y);
hash = Combine(hash, value.Z);
hash = Combine(hash, value.W);
return hash;
}
public static int Combine(
int hash,
RenderQueueRange value)
{
hash = Combine(hash, value.lowerBound);
hash = Combine(hash, value.upperBound);
return hash;
}
public static int Combine(
int hash,
FilteringSettings value)
{
hash = Combine(hash, value.renderQueueMin);
hash = Combine(hash, value.renderQueueMax);
hash = Combine(hash, value.renderLayerMask);
hash = Combine(hash, value.requireShadowCasting);
hash = Combine(hash, value.requireRenderObjectId);
return hash;
}
public static int Combine(
int hash,
SortingSettings value)
{
return Combine(
hash,
(int)value.sortMode);
}
public static int Combine(
int hash,
RendererListDesc value)
{
hash = Combine(hash, (int)value.type);
hash = Combine(hash, value.filtering);
hash = Combine(hash, value.sorting);
return hash;
}
public static int Combine(
int hash,
DepthState value)
{
hash = Combine(hash, value.writeEnabled);
hash =
Combine(hash, (int)value.compareFunction);
return hash;
}
public static int Combine(
int hash,
StencilFaceState value)
{
hash =
Combine(hash, (int)value.failOperation);
hash =
Combine(hash, (int)value.passOperation);
hash =
Combine(hash, (int)value.depthFailOperation);
hash =
Combine(hash, (int)value.compareFunction);
return hash;
}
public static int Combine(
int hash,
StencilState value)
{
hash = Combine(hash, value.enabled);
hash = Combine(hash, value.readMask);
hash = Combine(hash, value.writeMask);
hash = Combine(hash, value.frontFace);
hash = Combine(hash, value.backFace);
return hash;
}
public static int Combine(
int hash,
RenderStateBlock value)
{
hash = Combine(hash, (int)value.mask);
hash = Combine(hash, value.depthState);
hash = Combine(hash, value.stencilState);
hash = Combine(hash, value.stencilReference);
return hash;
}
public static int Combine(
int hash,
DrawingSettings value)
{
hash = Combine(hash, value.overrideMaterialPath);
hash = Combine(hash, value.shaderPassName);
hash = Combine(hash, value.renderStateBlock);
return hash;
}
}
}