refactor(srp): synchronize urp renderer feature runtime state
This commit is contained in:
@@ -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 主线代码与计划文档
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user