diff --git a/engine/include/XCEngine/Rendering/AGENTS.md b/engine/include/XCEngine/Rendering/AGENTS.md index 01401326..804b7130 100644 --- a/engine/include/XCEngine/Rendering/AGENTS.md +++ b/engine/include/XCEngine/Rendering/AGENTS.md @@ -221,6 +221,10 @@ Managed SRP assets 通过 `GraphicsSettings.renderPipelineAsset` 选择,并通 `SceneDrawBackend`。 - Managed resource/version invalidation 是正确性的一部分。如果 managed asset、renderer data 或 feature 修改 runtime state,调用 `SetDirty` 或本地 invalidation helper,确保 native runtime caches 被释放。 + 当前 runtime version 也会兜底 hash `UniversalRenderPipelineAsset` 的 shadow/final-color settings,以及 + `UniversalRendererData` 自身的 main-scene、shadow-caster 和 depth-prepass block settings;新增可序列化 + renderer data/asset settings 时,必须把影响 request、planning、stage manifest 或 pass queue 的字段纳入 + 对应 runtime hash。 ### URP Model @@ -232,6 +236,9 @@ package。 renderer index,然后委托给 renderer data。 - `ScriptableRendererData` 拥有持久 renderer instance 和 renderer feature collection。Renderer data dirty state 会释放 renderer setup cache,并递增 runtime state version。 +- `ScriptableRendererData` 的 runtime state version 不只来自 feature collection。`UniversalRendererData` + 已把 `mainScene`、`shadowCaster` 和 `depthPrepass` block settings 纳入 hash;这些 settings 改变时必须 + 触发 renderer cache 释放和 pass queue/stage manifest 重建。 - `ScriptableRenderer` 按 stage 构建 `m_activePassQueue`。它依次调用 feature `SetupRenderPasses`、feature `AddRenderPasses`,再调用 renderer-owned `AddRenderPasses`;passes 按 `RenderPassEvent` 顺序插入,并归组到 `RendererBlocks`。 @@ -378,6 +385,9 @@ Scene data 每个 camera frame 提取一次,然后由 pipeline 调整。 - URP stage planning 已收口到 renderer active pass queue 派生的 per-`framePlanId` 快照和 stage manifest。 Stage support 和 stage recording 现在消费 planning 阶段保存的快照,关闭了 feature planning hook、 support probe 和 recording 各自重建 pass queue 的重复事实源。 +- URP runtime-state invalidation 已覆盖 asset-level shadow/final-color settings 和 renderer-data-level + main-scene/shadow-caster/depth-prepass block settings。配置变更会通过 runtime resource version 释放 + renderer caches,并让后续 planning 重新生成 pass queue 快照和 stage manifest。 - Public managed RenderGraph raster authoring 已存在;internal fullscreen kernels 仍是 URP implementation details。 - Public managed `SetRenderFunc` 已从 recording-time 调用改为 RenderGraph execution-time 调用; diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index 8714a92d..ac020a62 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -2228,6 +2228,77 @@ namespace Gameplay } } + internal sealed class ManagedUniversalConfigInvalidationProbeAsset + : UniversalRenderPipelineAsset + { + private readonly UniversalRendererData m_rendererData; + + public ManagedUniversalConfigInvalidationProbeAsset() + { + m_rendererData = + ProbeScriptableObjectFactory + .Create(); + rendererDataList = + ProbeScriptableObjectFactory + .CreateRendererDataList( + m_rendererData); + shadows = + UniversalShadowSettings.CreateDefault(); + finalColor = + UniversalFinalColorSettings.CreateDefault(); + } + + public void MutateRendererDataForTest() + { + if (m_rendererData == null) + { + return; + } + + if (m_rendererData.mainScene == null) + { + m_rendererData.mainScene = + UniversalMainSceneData.CreateDefault(); + } + + m_rendererData.mainScene.renderOpaque = + !m_rendererData.mainScene.renderOpaque; + m_rendererData.mainScene.opaquePassEvent = + RenderPassEvent.AfterRenderingOpaques; + + if (m_rendererData.depthPrepass == null) + { + m_rendererData.depthPrepass = + DepthPrepassBlockData.CreateDefault(); + } + + m_rendererData.depthPrepass.enabled = + !m_rendererData.depthPrepass.enabled; + } + + public void MutateAssetSettingsForTest() + { + if (shadows == null) + { + shadows = + UniversalShadowSettings.CreateDefault(); + } + + shadows.supportsMainLightShadows = + !shadows.supportsMainLightShadows; + shadows.mainLightPlanningSettings.mapDimension = 1024u; + + if (finalColor == null) + { + finalColor = + UniversalFinalColorSettings.CreateDefault(); + } + + finalColor.settings.finalColorScale = + new Vector4(0.75f, 0.80f, 0.85f, 1.0f); + } + } + public sealed class ManagedFeaturePassOrderProbeAsset : UniversalRenderPipelineAsset { @@ -2940,6 +3011,58 @@ namespace Gameplay } } + public sealed class ManagedUniversalConfigInvalidationRuntimeSelectionProbe + : MonoBehaviour + { + public void Start() + { + GraphicsSettings.renderPipelineAsset = + ProbeScriptableObjectFactory + .Create(); + } + } + + public sealed class ManagedUniversalConfigInvalidationObservationProbe + : MonoBehaviour + { + public int ObservedRuntimeResourceVersionBeforeMutation; + public int ObservedRuntimeResourceVersionAfterRendererDataMutation; + public int ObservedRuntimeResourceVersionAfterAssetSettingsMutation; + public bool ObservedMutationRan; + + public void Update() + { + if (ObservedMutationRan) + { + return; + } + + ManagedUniversalConfigInvalidationProbeAsset selectedAsset = + GraphicsSettings.renderPipelineAsset + as ManagedUniversalConfigInvalidationProbeAsset; + if (selectedAsset == null) + { + return; + } + + ObservedRuntimeResourceVersionBeforeMutation = + ProbeRuntimeVersionUtility + .GetRuntimeResourceVersion( + selectedAsset); + selectedAsset.MutateRendererDataForTest(); + ObservedRuntimeResourceVersionAfterRendererDataMutation = + ProbeRuntimeVersionUtility + .GetRuntimeResourceVersion( + selectedAsset); + selectedAsset.MutateAssetSettingsForTest(); + ObservedRuntimeResourceVersionAfterAssetSettingsMutation = + ProbeRuntimeVersionUtility + .GetRuntimeResourceVersion( + selectedAsset); + ObservedMutationRan = true; + } + } + public sealed class ManagedFeaturePassOrderRuntimeSelectionProbe : MonoBehaviour { diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs index 6d10c002..9c4f046b 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererData.cs @@ -14,6 +14,8 @@ namespace XCEngine.Rendering.Universal private ScriptableRenderer m_rendererInstance; private bool m_rendererInvalidated; private int m_runtimeStateVersion = 1; + private int m_runtimeStateHash; + private bool m_runtimeStateHashResolved; private int m_rendererFeatureCollectionHash; private bool m_rendererFeatureCollectionHashResolved; @@ -72,7 +74,7 @@ namespace XCEngine.Rendering.Universal internal int GetRuntimeStateVersionInstance() { - SynchronizeRendererFeatureCollectionState(); + SynchronizeRuntimeStateVersion(); return m_runtimeStateVersion; } @@ -191,6 +193,11 @@ namespace XCEngine.Rendering.Universal { } + protected virtual int ComputeRuntimeStateHash() + { + return 17; + } + protected virtual ScriptableRendererFeature[] CreateDefaultRendererFeatures() { @@ -229,6 +236,32 @@ namespace XCEngine.Rendering.Universal protected void SetDirty() { + ApplyDirty( + ComputeRuntimeStateHash()); + } + + private void SynchronizeRuntimeStateVersion() + { + int runtimeStateHash = + ComputeRuntimeStateHash(); + if (!m_runtimeStateHashResolved) + { + m_runtimeStateHash = runtimeStateHash; + m_runtimeStateHashResolved = true; + } + else if (runtimeStateHash != m_runtimeStateHash) + { + ApplyDirty(runtimeStateHash); + } + + SynchronizeRendererFeatureCollectionState(); + } + + private void ApplyDirty( + int runtimeStateHash) + { + m_runtimeStateHash = runtimeStateHash; + m_runtimeStateHashResolved = true; ReleaseRendererSetupCache(); m_rendererInvalidated = true; unchecked diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs index b9b6879e..5ebbd38e 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRendererFeature.cs @@ -410,6 +410,54 @@ namespace XCEngine.Rendering.Universal hash = Combine(hash, value.renderStateBlock); return hash; } + + public static int Combine( + int hash, + FinalColorSettings value) + { + hash = Combine(hash, (int)value.outputTransferMode); + hash = Combine(hash, (int)value.exposureMode); + hash = Combine(hash, value.exposureValue); + hash = Combine(hash, (int)value.toneMappingMode); + hash = Combine(hash, value.finalColorScale); + return hash; + } + + public static int Combine( + int hash, + DirectionalShadowSamplingSettings value) + { + hash = Combine(hash, value.receiverDepthBias); + hash = Combine(hash, value.normalBiasScale); + hash = Combine(hash, value.shadowStrength); + return hash; + } + + public static int Combine( + int hash, + DirectionalShadowCasterBiasSettings value) + { + hash = Combine(hash, value.depthBiasFactor); + hash = Combine(hash, value.depthBiasUnits); + return hash; + } + + public static int Combine( + int hash, + DirectionalShadowPlanningSettings value) + { + hash = Combine(hash, value.mapDimension); + hash = Combine(hash, value.minFocusDistance); + hash = Combine(hash, value.maxFocusDistance); + hash = Combine(hash, value.perspectiveFocusFactor); + hash = Combine(hash, value.orthographicFocusFactor); + hash = Combine(hash, value.minDepthRange); + hash = Combine(hash, value.boundsPadding); + hash = Combine(hash, value.minDepthPadding); + hash = Combine(hash, value.sampling); + hash = Combine(hash, value.casterBias); + return hash; + } } } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs index 0b5cfe68..16805f5f 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipelineAsset.cs @@ -14,6 +14,8 @@ namespace XCEngine.Rendering.Universal public UniversalFinalColorSettings finalColor; private readonly ScriptableRendererDataCollection m_rendererDataCollection; + private int m_assetRuntimeStateHash; + private bool m_assetRuntimeStateHashResolved; private int m_rendererDataRuntimeStateHash; private bool m_rendererDataRuntimeStateHashResolved; @@ -258,6 +260,7 @@ namespace XCEngine.Rendering.Universal protected override void SynchronizeRuntimeResourceVersion() { + SynchronizeAssetRuntimeState(); m_rendererDataCollection.Synchronize( ref rendererDataList, ref defaultRendererIndex, @@ -267,6 +270,40 @@ namespace XCEngine.Rendering.Universal SetDirty); } + private void SynchronizeAssetRuntimeState() + { + int runtimeStateHash = + ComputeAssetRuntimeStateHash(); + if (!m_assetRuntimeStateHashResolved) + { + m_assetRuntimeStateHash = runtimeStateHash; + m_assetRuntimeStateHashResolved = true; + return; + } + + if (runtimeStateHash == m_assetRuntimeStateHash) + { + return; + } + + m_assetRuntimeStateHash = runtimeStateHash; + SetDirty(); + } + + private int ComputeAssetRuntimeStateHash() + { + int hash = 17; + hash = + AppendShadowSettingsRuntimeStateHash( + hash, + GetShadowSettingsInstance()); + hash = + AppendFinalColorRuntimeStateHash( + hash, + GetFinalColorSettingsInstance()); + return hash; + } + protected virtual bool UsesExplicitCameraFrameStagePlanning() { return UsesExplicitFullscreenStagePlanning(); @@ -289,6 +326,43 @@ namespace XCEngine.Rendering.Universal return finalColor; } + private static int AppendShadowSettingsRuntimeStateHash( + int hash, + UniversalShadowSettings settings) + { + hash = + RuntimeStateHashUtility.Combine( + hash, + settings != null); + if (settings == null) + { + return hash; + } + + hash = + RuntimeStateHashUtility.Combine( + hash, + settings.supportsMainLightShadows); + hash = + RuntimeStateHashUtility.Combine( + hash, + settings.mainLightPlanningSettings); + return hash; + } + + private static int AppendFinalColorRuntimeStateHash( + int hash, + UniversalFinalColorSettings settings) + { + hash = + RuntimeStateHashUtility.Combine( + hash, + settings != null); + return settings != null + ? RuntimeStateHashUtility.Combine(hash, settings.settings) + : hash; + } + protected void ResetRendererDataToDefault() { ScriptableRendererDataComposition composition = diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs index 60e965b5..39b1242b 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRendererData.cs @@ -32,6 +32,25 @@ namespace XCEngine.Rendering.Universal return new UniversalRenderer(this); } + protected override int ComputeRuntimeStateHash() + { + int hash = + base.ComputeRuntimeStateHash(); + hash = + AppendMainSceneRuntimeStateHash( + hash, + GetMainSceneInstance()); + hash = + AppendShadowCasterRuntimeStateHash( + hash, + GetShadowCasterBlockInstance()); + hash = + AppendDepthPrepassRuntimeStateHash( + hash, + GetDepthPrepassBlockInstance()); + return hash; + } + protected override ScriptableRendererFeature[] CreateDefaultRendererFeatures() { @@ -71,6 +90,105 @@ namespace XCEngine.Rendering.Universal return depthPrepass; } + + private static int AppendMainSceneRuntimeStateHash( + int hash, + UniversalMainSceneData data) + { + hash = RuntimeStateHashUtility.Combine(hash, data != null); + if (data == null) + { + return hash; + } + + hash = RuntimeStateHashUtility.Combine(hash, data.renderOpaque); + hash = + RuntimeStateHashUtility.Combine( + hash, + (int)data.opaquePassEvent); + hash = + RuntimeStateHashUtility.Combine( + hash, + data.opaqueRendererListDesc); + hash = + RuntimeStateHashUtility.Combine( + hash, + data.opaqueDrawingSettings); + hash = RuntimeStateHashUtility.Combine(hash, data.renderSkybox); + hash = + RuntimeStateHashUtility.Combine( + hash, + (int)data.skyboxPassEvent); + hash = + RuntimeStateHashUtility.Combine( + hash, + data.renderTransparent); + hash = + RuntimeStateHashUtility.Combine( + hash, + (int)data.transparentPassEvent); + hash = + RuntimeStateHashUtility.Combine( + hash, + data.transparentRendererListDesc); + hash = + RuntimeStateHashUtility.Combine( + hash, + data.transparentDrawingSettings); + return hash; + } + + private static int AppendShadowCasterRuntimeStateHash( + int hash, + ShadowCasterBlockData data) + { + hash = RuntimeStateHashUtility.Combine(hash, data != null); + if (data == null) + { + return hash; + } + + hash = RuntimeStateHashUtility.Combine(hash, data.enabled); + hash = + RuntimeStateHashUtility.Combine( + hash, + (int)data.passEvent); + hash = + RuntimeStateHashUtility.Combine( + hash, + data.rendererListDesc); + hash = + RuntimeStateHashUtility.Combine( + hash, + data.drawingSettings); + return hash; + } + + private static int AppendDepthPrepassRuntimeStateHash( + int hash, + DepthPrepassBlockData data) + { + hash = RuntimeStateHashUtility.Combine(hash, data != null); + if (data == null) + { + return hash; + } + + hash = RuntimeStateHashUtility.Combine(hash, data.enabled); + hash = + RuntimeStateHashUtility.Combine( + hash, + (int)data.passEvent); + hash = + RuntimeStateHashUtility.Combine( + hash, + data.rendererListDesc); + hash = + RuntimeStateHashUtility.Combine( + hash, + data.drawingSettings); + return hash; + } } } diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index fb1864f6..1cf91416 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -4303,6 +4303,66 @@ TEST_F( EXPECT_EQ(observedInvalidateRendererCallCount, 1); } +TEST_F( + MonoScriptRuntimeTest, + ManagedUniversalRenderPipelineAssetTracksRendererDataAndAssetSettingMutationsThroughRuntimeVersion) { + Scene* runtimeScene = + CreateScene("ManagedUniversalConfigInvalidationScene"); + GameObject* selectionObject = + runtimeScene->CreateGameObject( + "ManagedUniversalConfigInvalidationSelection"); + ScriptComponent* selectionScript = + AddScript( + selectionObject, + "Gameplay", + "ManagedUniversalConfigInvalidationRuntimeSelectionProbe"); + ASSERT_NE(selectionScript, nullptr); + GameObject* observationObject = + runtimeScene->CreateGameObject( + "ManagedUniversalConfigInvalidationObservation"); + ScriptComponent* observationScript = + AddScript( + observationObject, + "Gameplay", + "ManagedUniversalConfigInvalidationObservationProbe"); + ASSERT_NE(observationScript, nullptr); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + EXPECT_TRUE(runtime->GetLastError().empty()) << runtime->GetLastError(); + + int observedRuntimeResourceVersionBeforeMutation = 0; + int observedRuntimeResourceVersionAfterRendererDataMutation = 0; + int observedRuntimeResourceVersionAfterAssetSettingsMutation = 0; + bool observedMutationRan = false; + EXPECT_TRUE(runtime->TryGetFieldValue( + observationScript, + "ObservedRuntimeResourceVersionBeforeMutation", + observedRuntimeResourceVersionBeforeMutation)); + EXPECT_TRUE(runtime->TryGetFieldValue( + observationScript, + "ObservedRuntimeResourceVersionAfterRendererDataMutation", + observedRuntimeResourceVersionAfterRendererDataMutation)); + EXPECT_TRUE(runtime->TryGetFieldValue( + observationScript, + "ObservedRuntimeResourceVersionAfterAssetSettingsMutation", + observedRuntimeResourceVersionAfterAssetSettingsMutation)); + EXPECT_TRUE(runtime->TryGetFieldValue( + observationScript, + "ObservedMutationRan", + observedMutationRan)); + + EXPECT_TRUE(observedMutationRan); + EXPECT_GT(observedRuntimeResourceVersionBeforeMutation, 0); + EXPECT_EQ( + observedRuntimeResourceVersionAfterRendererDataMutation, + observedRuntimeResourceVersionBeforeMutation + 1); + EXPECT_EQ( + observedRuntimeResourceVersionAfterAssetSettingsMutation, + observedRuntimeResourceVersionAfterRendererDataMutation + 1); +} + TEST_F( MonoScriptRuntimeTest, ManagedRenderPipelineBridgeRebuildsPipelineAfterAssetInvalidation) {