From 66d968c3cedb588e657986d2efb76af872ee811e Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 27 Apr 2026 15:21:57 +0800 Subject: [PATCH] Tighten URP pass queue snapshot ownership --- engine/include/XCEngine/Rendering/AGENTS.md | 15 +- .../Rendering/Execution/CameraFramePlan.h | 2 + .../XCEngine/Rendering/RenderPipeline.h | 3 + .../Rendering/Execution/CameraFramePlan.cpp | 19 + .../Rendering/Execution/CameraRenderer.cpp | 3 +- .../CameraFrameGraph/PassRecorder.cpp | 3 +- .../CameraFrameGraph/StageContract.cpp | 2 + .../CameraFrameGraph/StageDispatch.cpp | 3 +- .../Pipelines/NativeSceneRecorder.cpp | 4 + .../ScriptableRenderPipelineHost.cpp | 3 +- .../src/Scripting/Mono/MonoScriptRuntime.cpp | 72 +++- managed/GameScripts/RenderPipelineApiProbe.cs | 76 ++++ .../ScriptableRenderContextApiSurfaceProbe.cs | 31 +- .../Universal/RendererRecordingContext.cs | 17 +- .../Rendering/Universal/RenderingData.cs | 23 +- .../Rendering/Universal/ScriptableRenderer.cs | 394 ++++++++++++++++-- .../Universal/UniversalRenderPipeline.cs | 14 +- managed/XCEngine.ScriptCore/InternalCalls.cs | 10 + .../Rendering/Core/ScriptableRenderContext.cs | 5 + .../Core/ScriptableRenderPipeline.cs | 10 + ...ScriptableRenderPipelinePlanningContext.cs | 5 + tests/scripting/test_mono_script_runtime.cpp | 186 ++++++++- 22 files changed, 849 insertions(+), 51 deletions(-) diff --git a/engine/include/XCEngine/Rendering/AGENTS.md b/engine/include/XCEngine/Rendering/AGENTS.md index 3b28615d..cf117be8 100644 --- a/engine/include/XCEngine/Rendering/AGENTS.md +++ b/engine/include/XCEngine/Rendering/AGENTS.md @@ -67,6 +67,10 @@ Unity 兼容的公开命名、对象所有权和扩展点。 resource versions 必须保持一致。 - 修改 renderer-index 行为时,要一起更新 `CameraRenderRequest.rendererIndex`、stage support contexts、 `RendererRecordingContext`、managed probes 和 Mono bridge calls。 +- 修改 URP stage planning、stage support 或 recording 行为时,要保持 `CameraFramePlan.framePlanId`、 + `RenderPipelineStageSupportContext.framePlanId`、`RenderPipelineStageRenderGraphContext.framePlanId`、 + `ScriptableRenderPipelinePlanningContext.framePlanId` 和 `ScriptableRenderContext.framePlanId` 同步。 + `framePlanId` 是 per-camera-frame plan 的托管快照键,不是 renderer 或 camera 的长期身份。 - 新增 public managed rendering type 时,将它加入 `managed/CMakeLists.txt`,并在对应 managed probe 里覆盖 API presence。 @@ -213,6 +217,12 @@ package。 - URP stage planning 以 `ScriptableRenderer` 的 active pass queue 为最终事实源。`ConfigureCameraFramePlan` 仍是兼容和高级策略 hook,但它不能单独声明 shadow、depth、post 或 final-output stage;没有被 pass queue 覆盖到的 side/fullscreen stage 必须在最终 plan 中清掉。 +- `ConfigurePassQueueCameraFramePlanInstance` 必须在 planning 阶段为当前 renderer 和 `framePlanId` + 生成一次 per-stage pass queue 快照,并用该快照派生 stage manifest。`SupportsStageRenderGraph` + 和 `RecordStageRenderGraph` 在 `framePlanId != 0` 时必须消费这个快照,不得重新运行 + feature `SetupRenderPasses`、feature `AddRenderPasses` 或 renderer-owned `AddRenderPasses` 来重建队列。 + 找不到匹配 `framePlanId` 和 renderer index 的快照时必须返回不支持或 record 失败,让上层暴露错误; + 不要退回 legacy queue rebuild 或 built-in fallback。 - `RendererBlock` 将 pass events 映射到 camera stages:shadow caster、depth prepass、main opaque、main skybox、main transparent、post process 和 final output。 - `UniversalRenderer` 拥有具体 blocks: @@ -329,8 +339,9 @@ Scene data 每个 camera frame 提取一次,然后由 pipeline 调整。 RenderGraph。 - URP 现在已有 renderer data、renderer features、renderer pass queueing、renderer blocks、renderer-index resolution 和 per-stage recording。 -- URP stage planning 已收口到 renderer active pass queue 派生的 stage manifest,关闭了 feature planning hook - 与实际 pass queue 分离的重复事实源。 +- URP stage planning 已收口到 renderer active pass queue 派生的 per-`framePlanId` 快照和 stage manifest。 + Stage support 和 stage recording 现在消费 planning 阶段保存的快照,关闭了 feature planning hook、 + support probe 和 recording 各自重建 pass queue 的重复事实源。 - Public managed RenderGraph raster authoring 已存在;internal fullscreen kernels 仍是 URP implementation details。 - Public `ScriptableRenderPass.RecordRenderGraph(RenderGraph, ContextContainer)` 通过 `RenderingData`、 diff --git a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h index d8eab454..cd694ff3 100644 --- a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h +++ b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h @@ -4,6 +4,7 @@ #include #include +#include #include namespace XCEngine { @@ -33,6 +34,7 @@ struct CameraFramePlan { static RenderSurface BuildCameraDepthOnlySurfaceTemplate( const RenderSurface& surface); + uint64_t framePlanId = 0u; CameraRenderRequest request = {}; ShadowCasterRenderRequest shadowCaster = {}; DirectionalShadowRenderPlan directionalShadow = {}; diff --git a/engine/include/XCEngine/Rendering/RenderPipeline.h b/engine/include/XCEngine/Rendering/RenderPipeline.h index 2499d5e9..3f0184c6 100644 --- a/engine/include/XCEngine/Rendering/RenderPipeline.h +++ b/engine/include/XCEngine/Rendering/RenderPipeline.h @@ -15,6 +15,7 @@ #include #include +#include #include namespace XCEngine { @@ -59,11 +60,13 @@ struct RenderPipelineStageRenderGraphContext { bool usesGraphManagedOutputColor = false; DirectionalShadowRenderPlan directionalShadowPlan = {}; int32_t rendererIndex = -1; + uint64_t framePlanId = 0u; }; struct RenderPipelineStageSupportContext { CameraFrameStage stage = CameraFrameStage::MainScene; int32_t rendererIndex = -1; + uint64_t framePlanId = 0u; }; class RenderPipelineBackend; diff --git a/engine/src/Rendering/Execution/CameraFramePlan.cpp b/engine/src/Rendering/Execution/CameraFramePlan.cpp index bc6c5edf..3501af41 100644 --- a/engine/src/Rendering/Execution/CameraFramePlan.cpp +++ b/engine/src/Rendering/Execution/CameraFramePlan.cpp @@ -1,10 +1,28 @@ #include +#include + namespace XCEngine { namespace Rendering { namespace { +uint64_t AllocateCameraFramePlanId() { + static std::atomic s_nextFramePlanId{ 1u }; + uint64_t framePlanId = + s_nextFramePlanId.fetch_add( + 1u, + std::memory_order_relaxed); + if (framePlanId == 0u) { + framePlanId = + s_nextFramePlanId.fetch_add( + 1u, + std::memory_order_relaxed); + } + + return framePlanId; +} + CameraFrameFullscreenStagePlan* GetMutableFullscreenStagePlan( CameraFramePlan& plan, CameraFrameStage stage) { @@ -59,6 +77,7 @@ RenderSurface CameraFramePlan::BuildCameraDepthOnlySurfaceTemplate( CameraFramePlan CameraFramePlan::FromRequest(const CameraRenderRequest& request) { CameraFramePlan plan = {}; + plan.framePlanId = AllocateCameraFramePlanId(); plan.request = request; plan.shadowCaster = request.shadowCaster; plan.directionalShadow = request.directionalShadow; diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index 65032fc5..21c72e48 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -49,7 +49,8 @@ RenderPipelineStageSupportContext BuildStageSupportContext( CameraFrameStage stage) { return RenderPipelineStageSupportContext{ stage, - plan.request.rendererIndex }; + plan.request.rendererIndex, + plan.framePlanId }; } } // namespace diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/PassRecorder.cpp b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/PassRecorder.cpp index b1d7a1bc..19ff91bf 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/PassRecorder.cpp +++ b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/PassRecorder.cpp @@ -18,7 +18,8 @@ RenderPipelineStageSupportContext BuildStageSupportContext( CameraFrameStage stage) { return RenderPipelineStageSupportContext{ stage, - context.plan.request.rendererIndex }; + context.plan.request.rendererIndex, + context.plan.framePlanId }; } } // namespace diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageContract.cpp b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageContract.cpp index 8cabcc5d..2c455ad5 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageContract.cpp +++ b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageContract.cpp @@ -603,6 +603,8 @@ RenderPipelineStageRenderGraphContext BuildCameraFramePipelineStageRenderGraphCo context.plan.directionalShadow; pipelineContext.rendererIndex = context.plan.request.rendererIndex; + pipelineContext.framePlanId = + context.plan.framePlanId; return pipelineContext; } diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageDispatch.cpp b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageDispatch.cpp index a0317f87..19247efc 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageDispatch.cpp +++ b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/StageDispatch.cpp @@ -30,7 +30,8 @@ RenderPipelineStageSupportContext BuildStageSupportContext( const CameraFrameRenderGraphStageContext& context) { return RenderPipelineStageSupportContext{ stageState.stage, - context.plan.request.rendererIndex }; + context.plan.request.rendererIndex, + context.plan.framePlanId }; } bool AllowsCameraFrameStageFallback( diff --git a/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp b/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp index 16247a0f..f845099c 100644 --- a/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp +++ b/engine/src/Rendering/Pipelines/NativeSceneRecorder.cpp @@ -437,6 +437,10 @@ NativeSceneRecorder::BuildGraphContext() const { m_context.usesGraphManagedOutputColor; context.directionalShadowPlan = m_context.directionalShadowPlan; + context.rendererIndex = + m_context.rendererIndex; + context.framePlanId = + m_context.framePlanId; return context; } diff --git a/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp index 3f331344..1238aec9 100644 --- a/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp +++ b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp @@ -145,7 +145,8 @@ bool ScriptableRenderPipelineHost::RecordStageRenderGraph( if (m_stageRecorder != nullptr) { const RenderPipelineStageSupportContext supportContext = { context.stage, - context.rendererIndex }; + context.rendererIndex, + context.framePlanId }; return m_stageRecorder->SupportsStageRenderGraph(supportContext) && m_stageRecorder->RecordStageRenderGraph(context); } diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 60c53ea8..2afbb982 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -1796,6 +1796,7 @@ public: m_ownedSceneDrawBackend->Shutdown(); } m_ownedSharedPipelineBackendAsset.reset(); + m_supportsStageContextualWithFramePlanMethod = nullptr; m_supportsStageContextualMethod = nullptr; m_supportsStageMethod = nullptr; m_recordStageMethod = nullptr; @@ -1813,7 +1814,8 @@ public: return SupportsStageRenderGraph( Rendering::RenderPipelineStageSupportContext{ stage, - -1 }); + -1, + 0u }); } bool SupportsStageRenderGraph( @@ -1828,6 +1830,36 @@ public: return false; } + MonoMethod* const contextualWithFramePlanMethod = + ResolveSupportsStageContextualWithFramePlanMethod( + pipelineObject); + if (contextualWithFramePlanMethod != nullptr) { + int32_t managedStage = + static_cast(context.stage); + int32_t rendererIndex = + context.rendererIndex; + uint64_t framePlanId = + context.framePlanId; + void* args[3] = { + &managedStage, + &rendererIndex, + &framePlanId }; + MonoObject* result = nullptr; + if (!m_runtime->InvokeManagedMethod( + pipelineObject, + contextualWithFramePlanMethod, + args, + &result)) { + return false; + } + + bool supportsStage = false; + return TryUnboxManagedBoolean( + result, + supportsStage) && + supportsStage; + } + MonoMethod* const contextualMethod = ResolveSupportsStageContextualMethod( pipelineObject); @@ -1964,6 +1996,7 @@ private: } if (pipelineHandle != m_resolvedPipelineHandle) { + m_supportsStageContextualWithFramePlanMethod = nullptr; m_supportsStageContextualMethod = nullptr; m_supportsStageMethod = nullptr; m_recordStageMethod = nullptr; @@ -1973,6 +2006,19 @@ private: return m_runtime->GetManagedObject(pipelineHandle); } + MonoMethod* ResolveSupportsStageContextualWithFramePlanMethod( + MonoObject* pipelineObject) const { + if (m_supportsStageContextualWithFramePlanMethod == nullptr) { + m_supportsStageContextualWithFramePlanMethod = + m_runtime->ResolveManagedMethod( + pipelineObject, + "SupportsStageRenderGraphContextual", + 3); + } + + return m_supportsStageContextualWithFramePlanMethod; + } + MonoMethod* ResolveSupportsStageContextualMethod( MonoObject* pipelineObject) const { if (m_supportsStageContextualMethod == nullptr) { @@ -2159,6 +2205,7 @@ private: std::shared_ptr m_assetRuntime; MonoScriptRuntime* m_runtime = nullptr; + mutable MonoMethod* m_supportsStageContextualWithFramePlanMethod = nullptr; mutable MonoMethod* m_supportsStageContextualMethod = nullptr; mutable MonoMethod* m_supportsStageMethod = nullptr; mutable MonoMethod* m_recordStageMethod = nullptr; @@ -4703,6 +4750,16 @@ int32_t InternalCall_Rendering_ScriptableRenderContext_GetRendererIndex( : -1; } +uint64_t InternalCall_Rendering_ScriptableRenderContext_GetFramePlanId( + uint64_t nativeHandle) { + const ManagedScriptableRenderContextState* const state = + FindManagedScriptableRenderContextState(nativeHandle); + return state != nullptr && + state->graphContext != nullptr + ? state->graphContext->framePlanId + : 0u; +} + int32_t InternalCall_Rendering_ScriptableRenderContext_GetSourceColorTextureHandle( uint64_t nativeHandle) { const ManagedScriptableRenderContextState* const state = @@ -6235,6 +6292,17 @@ InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetRendererIndex( : -1; } +uint64_t +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetFramePlanId( + uint64_t nativeHandle) { + const ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + return state != nullptr && + state->plan != nullptr + ? state->plan->framePlanId + : 0u; +} + mono_bool InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested( uint64_t nativeHandle, @@ -6597,6 +6665,7 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::Rendering_GetRenderPipelineAsset", reinterpret_cast(&InternalCall_Rendering_GetRenderPipelineAsset)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_GetStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_GetStage)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_GetRendererIndex", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_GetRendererIndex)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_GetFramePlanId", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_GetFramePlanId)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_GetSourceColorTextureHandle", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_GetSourceColorTextureHandle)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_GetPrimaryColorTargetHandle", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_GetPrimaryColorTargetHandle)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_GetDepthTargetHandle", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_GetDepthTargetHandle)); @@ -6668,6 +6737,7 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_RecordScenePhase", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_RecordScenePhase)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_DrawRenderersByDesc", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_DrawRenderersByDesc)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetRendererIndex", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetRendererIndex)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetFramePlanId", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetFramePlanId)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetStageColorSource", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetStageColorSource)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_GetStageUsesGraphManagedOutputColor", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetStageUsesGraphManagedOutputColor)); diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index 4e978a2a..786437cf 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -1687,6 +1687,69 @@ namespace Gameplay } } + internal sealed class ManagedPassQueueSnapshotProbeFeature + : ScriptableRendererFeature + { + private readonly FullscreenPass m_pass = + new FullscreenPass( + RenderPassEvent.BeforeRenderingPostProcessing, + "ManagedPassQueueSnapshot.PostProcess"); + private ulong m_enqueuedFramePlanId; + private bool m_enqueuedForCurrentFramePlan; + + public override void AddRenderPasses( + ScriptableRenderer renderer, + RenderingData renderingData) + { + if (renderer == null || + renderingData == null || + !renderingData.isPostProcessStage) + { + return; + } + + if (renderingData.framePlanId == 0UL) + { + renderer.EnqueuePass(m_pass); + return; + } + + if (renderingData.framePlanId != m_enqueuedFramePlanId) + { + m_enqueuedFramePlanId = + renderingData.framePlanId; + m_enqueuedForCurrentFramePlan = false; + } + + if (m_enqueuedForCurrentFramePlan) + { + return; + } + + renderer.EnqueuePass(m_pass); + m_enqueuedForCurrentFramePlan = true; + } + } + + internal sealed class ManagedPassQueueSnapshotProbeRendererData + : ProbeRendererData + { + public ManagedPassQueueSnapshotProbeRendererData() + : base(false) + { + rendererFeatures = + ProbeScriptableObjectFactory + .CreateRendererFeatureList( + ProbeScriptableObjectFactory + .Create()); + } + + protected override ScriptableRenderer CreateProbeRenderer() + { + return new ProbeSceneRenderer(); + } + } + internal sealed class ManagedRenderContextCameraDataProbeRendererData : ProbeRendererData { @@ -2287,6 +2350,19 @@ namespace Gameplay } } + public sealed class ManagedPassQueueSnapshotProbeAsset + : UniversalRenderPipelineAsset + { + public ManagedPassQueueSnapshotProbeAsset() + { + rendererDataList = + ProbeScriptableObjectFactory + .CreateRendererDataList( + ProbeScriptableObjectFactory + .Create()); + } + } + public sealed class ManagedInvalidFullscreenStagePlanningProbeAsset : UniversalRenderPipelineAsset { diff --git a/managed/GameScripts/ScriptableRenderContextApiSurfaceProbe.cs b/managed/GameScripts/ScriptableRenderContextApiSurfaceProbe.cs index 9a3876f7..a3e7cd46 100644 --- a/managed/GameScripts/ScriptableRenderContextApiSurfaceProbe.cs +++ b/managed/GameScripts/ScriptableRenderContextApiSurfaceProbe.cs @@ -34,6 +34,7 @@ namespace Gameplay public bool HasPublicContextEnvironmentData; public bool HasPublicContextFinalColorData; public bool HasPublicContextStageColorData; + public bool HasPublicContextFramePlanId; public bool HasPublicRequestContextHasDirectionalShadow; public bool HasPublicRequestContextClearDirectionalShadow; public bool HasUniversalContextRecordSceneExtension; @@ -49,8 +50,10 @@ namespace Gameplay public bool HasScriptableObjectCreateInstance; public bool HasRenderPipelineAssetScriptableObjectBase; public bool HasPlanningContextType; + public bool HasPublicPlanningContextFramePlanId; public bool HasRendererFeatureConfigureCameraFramePlan; public bool HasRendererRecordingContextType; + public bool HasPublicRendererRecordingContextFramePlanId; public bool HasRendererCameraRequestContextType; public bool HasRendererBackedRenderPipelineAssetType; public bool HasRendererBackedRenderPipelineType; @@ -109,6 +112,7 @@ namespace Gameplay public bool HasRenderGraphType; public bool HasRenderGraphAddRasterPass; public bool HasRenderPassPublicRecordRenderGraph; + public bool HasPublicRenderingDataFramePlanId; public bool HasRenderPassProtectedRecordColorScaleFullscreenPass; public bool HasRenderPassProtectedRecordShaderVectorFullscreenPass; public bool HasRenderPassProtectedRecordFinalColorFullscreenPass; @@ -172,6 +176,8 @@ namespace Gameplay typeof(ScriptableRenderContext); System.Type requestContextType = typeof(CameraRenderRequestContext); + System.Type planningContextType = + typeof(ScriptableRenderPipelinePlanningContext); System.Type pipelineAssetType = typeof(ScriptableRenderPipelineAsset); System.Type scriptableObjectType = @@ -187,6 +193,10 @@ namespace Gameplay typeof(ScriptableRendererData); System.Type rendererType = typeof(ScriptableRenderer); + System.Type rendererRecordingContextType = + typeof(RendererRecordingContext); + System.Type renderingDataType = + typeof(RenderingData); System.Type renderPassType = typeof(ScriptableRenderPass); System.Type commandBufferType = @@ -334,6 +344,10 @@ namespace Gameplay contextType.GetProperty( "stageColorData", PublicInstanceMethodFlags) != null; + HasPublicContextFramePlanId = + contextType.GetProperty( + "framePlanId", + PublicInstanceMethodFlags) != null; HasPublicRequestContextHasDirectionalShadow = requestContextType.GetProperty( "hasDirectionalShadow", @@ -400,7 +414,11 @@ namespace Gameplay scriptableObjectType != null && pipelineAssetType.BaseType == scriptableObjectType; HasPlanningContextType = - typeof(ScriptableRenderPipelinePlanningContext) != null; + planningContextType != null; + HasPublicPlanningContextFramePlanId = + planningContextType.GetProperty( + "framePlanId", + PublicInstanceMethodFlags) != null; HasRendererFeatureConfigureCameraFramePlan = HasMethod( rendererFeatureType, @@ -409,8 +427,11 @@ namespace Gameplay BindingFlags.Public | BindingFlags.NonPublic); HasRendererRecordingContextType = - universalAssembly.GetType( - "XCEngine.Rendering.Universal.RendererRecordingContext") != null; + rendererRecordingContextType != null; + HasPublicRendererRecordingContextFramePlanId = + rendererRecordingContextType.GetProperty( + "framePlanId", + PublicInstanceMethodFlags) != null; HasRendererCameraRequestContextType = universalAssembly.GetType( "XCEngine.Rendering.Universal.RendererCameraRequestContext") != null; @@ -664,6 +685,10 @@ namespace Gameplay typeof(ContextContainer) }, null) != null; + HasPublicRenderingDataFramePlanId = + renderingDataType.GetProperty( + "framePlanId", + PublicInstanceMethodFlags) != null; HasRenderPassProtectedRecordColorScaleFullscreenPass = renderPassType.GetMethod( "RecordColorScaleFullscreenPass", diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererRecordingContext.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererRecordingContext.cs index cc389500..0efa9de0 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererRecordingContext.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RendererRecordingContext.cs @@ -19,11 +19,23 @@ namespace XCEngine.Rendering.Universal internal RendererRecordingContext( CameraFrameStage stage, int rendererIndex) + : this( + stage, + rendererIndex, + 0UL) + { + } + + internal RendererRecordingContext( + CameraFrameStage stage, + int rendererIndex, + ulong framePlanId) : this( null, new RenderingData( stage, - rendererIndex)) + rendererIndex, + framePlanId)) { } @@ -56,6 +68,9 @@ namespace XCEngine.Rendering.Universal public int rendererIndex => m_renderingData.rendererIndex; + public ulong framePlanId => + m_renderingData.framePlanId; + internal ScriptableRenderContext renderContext => m_renderContext; } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs index 52736610..af338989 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/RenderingData.cs @@ -18,6 +18,18 @@ namespace XCEngine.Rendering.Universal : this( stage, rendererIndex, + 0UL) + { + } + + internal RenderingData( + CameraFrameStage stage, + int rendererIndex, + ulong framePlanId) + : this( + stage, + rendererIndex, + framePlanId, CameraData.Default, LightingData.Default, ShadowData.Default, @@ -30,11 +42,13 @@ namespace XCEngine.Rendering.Universal internal static RenderingData CreatePlanning( CameraFrameStage stage, int rendererIndex, - bool finalColorRequiresProcessing) + bool finalColorRequiresProcessing, + ulong framePlanId) { return new RenderingData( stage, rendererIndex, + framePlanId, CameraData.Default, LightingData.Default, ShadowData.Default, @@ -56,6 +70,9 @@ namespace XCEngine.Rendering.Universal context != null ? context.rendererIndex : -1, + context != null + ? context.framePlanId + : 0UL, context != null ? RenderingDataResolver.ResolveCameraData(context) : CameraData.Default, @@ -80,6 +97,7 @@ namespace XCEngine.Rendering.Universal private RenderingData( CameraFrameStage stage, int rendererIndex, + ulong framePlanId, CameraData cameraData, LightingData lightingData, ShadowData shadowData, @@ -89,6 +107,7 @@ namespace XCEngine.Rendering.Universal { this.stage = stage; this.rendererIndex = rendererIndex; + this.framePlanId = framePlanId; this.cameraData = cameraData ?? CameraData.Default; this.lightingData = lightingData ?? LightingData.Default; @@ -106,6 +125,8 @@ namespace XCEngine.Rendering.Universal public int rendererIndex { get; } + public ulong framePlanId { get; } + public CameraData cameraData { get; } public LightingData lightingData { get; } diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs index 628f7de7..82058b72 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs @@ -7,6 +7,8 @@ namespace XCEngine.Rendering.Universal { public abstract class ScriptableRenderer { + private const int kMaxFramePlanSnapshotCount = 64; + private struct PassQueueStageManifest { public bool shadowCaster; @@ -15,12 +17,77 @@ namespace XCEngine.Rendering.Universal public bool finalOutput; } + private sealed class PassQueueStageSnapshot + { + public PassQueueStageSnapshot( + CameraFrameStage stage, + List activePassQueue, + RendererBlocks rendererBlocks) + { + this.stage = stage; + this.activePassQueue = + activePassQueue ?? + new List(); + this.rendererBlocks = + rendererBlocks ?? new RendererBlocks(); + } + + public readonly CameraFrameStage stage; + public readonly List activePassQueue; + public readonly RendererBlocks rendererBlocks; + } + + private sealed class PassQueueFrameSnapshot + { + private readonly Dictionary< + CameraFrameStage, + PassQueueStageSnapshot> m_stageSnapshots = + new Dictionary< + CameraFrameStage, + PassQueueStageSnapshot>(); + + public PassQueueFrameSnapshot( + ulong framePlanId, + int rendererIndex) + { + this.framePlanId = framePlanId; + this.rendererIndex = rendererIndex; + } + + public readonly ulong framePlanId; + public readonly int rendererIndex; + public PassQueueStageManifest manifest; + + public void SetStageSnapshot( + PassQueueStageSnapshot snapshot) + { + if (snapshot == null) + { + return; + } + + m_stageSnapshots[snapshot.stage] = snapshot; + } + + public bool TryGetStageSnapshot( + CameraFrameStage stage, + out PassQueueStageSnapshot snapshot) + { + return m_stageSnapshots.TryGetValue( + stage, + out snapshot); + } + } + private readonly List m_features = new List(); private readonly List m_activePassQueue = new List(); private readonly RendererBlocks m_rendererBlocks = new RendererBlocks(); + private readonly Dictionary + m_framePlanSnapshots = + new Dictionary(); private readonly CommandBuffer m_finishCameraStackCommandBuffer = new CommandBuffer("ScriptableRenderer.FinishCameraStack"); private bool m_isBuildingPassQueue; @@ -53,6 +120,7 @@ namespace XCEngine.Rendering.Universal m_features.Clear(); m_activePassQueue.Clear(); m_rendererBlocks.Clear(); + m_framePlanSnapshots.Clear(); m_disposed = true; } @@ -134,9 +202,10 @@ namespace XCEngine.Rendering.Universal return; } - PassQueueStageManifest manifest = - BuildPassQueueStageManifest(context); - ApplyPassQueueStageManifest(context, manifest); + PassQueueFrameSnapshot snapshot = + BuildPassQueueFrameSnapshot(context); + ApplyPassQueueStageManifest(context, snapshot.manifest); + StorePassQueueFrameSnapshot(snapshot); ClearPassQueue(); } @@ -177,8 +246,30 @@ namespace XCEngine.Rendering.Universal RenderingData renderingData = context.renderingData; + PassQueueStageSnapshot snapshot; + if (TryGetPassQueueStageSnapshot( + context, + out snapshot)) + { + return SupportsRendererRecordingFromSnapshot( + context, + snapshot); + } + + if (context.framePlanId != 0UL) + { + return false; + } + BuildPassQueue(renderingData); - return SupportsRendererStage(context); + try + { + return SupportsRendererStage(context); + } + finally + { + ClearPassQueue(); + } } protected virtual bool RecordRenderer( @@ -192,24 +283,46 @@ namespace XCEngine.Rendering.Universal RenderingData renderingData = context.renderingData; - BuildPassQueue(renderingData); - bool recorded; - using (ContextContainer frameData = - ScriptableRenderPass.BuildRenderGraphFrameData( - renderingData)) + PassQueueStageSnapshot snapshot; + if (TryGetPassQueueStageSnapshot( + context, + out snapshot)) { - recorded = - RecordRendererStage( - context, - new RenderGraph(context.renderContext), - frameData); - } - if (recorded) - { - FinishCameraStackRendering(); + return RecordRendererFromSnapshot( + context, + snapshot); } - return recorded; + if (context.framePlanId != 0UL) + { + return false; + } + + BuildPassQueue(renderingData); + try + { + bool recorded; + using (ContextContainer frameData = + ScriptableRenderPass.BuildRenderGraphFrameData( + renderingData)) + { + recorded = + RecordRendererStage( + context, + new RenderGraph(context.renderContext), + frameData); + } + if (recorded) + { + FinishCameraStackRendering(); + } + + return recorded; + } + finally + { + ClearPassQueue(); + } } protected internal virtual bool SupportsStageRenderGraph( @@ -296,6 +409,14 @@ namespace XCEngine.Rendering.Universal return m_rendererBlocks.HasPasses(block); } + private static bool HasRendererBlock( + RendererBlocks rendererBlocks, + RendererBlock block) + { + return rendererBlocks != null && + rendererBlocks.HasPasses(block); + } + protected virtual bool SupportsRendererStage( RendererRecordingContext context) { @@ -309,76 +430,152 @@ namespace XCEngine.Rendering.Universal private bool HasRendererStageBlocks( CameraFrameStage stage) + { + return HasRendererStageBlocks( + stage, + m_rendererBlocks); + } + + private static bool HasRendererStageBlocks( + CameraFrameStage stage, + RendererBlocks rendererBlocks) { switch (stage) { case CameraFrameStage.ShadowCaster: return HasRendererBlock( + rendererBlocks, RendererBlock.ShadowCaster); case CameraFrameStage.DepthOnly: return HasRendererBlock( + rendererBlocks, RendererBlock.DepthPrepass); case CameraFrameStage.MainScene: return HasRendererBlock( + rendererBlocks, RendererBlock.MainOpaque) || HasRendererBlock( + rendererBlocks, RendererBlock.MainSkybox) || HasRendererBlock( + rendererBlocks, RendererBlock.MainTransparent); case CameraFrameStage.PostProcess: return HasRendererBlock( + rendererBlocks, RendererBlock.PostProcess); case CameraFrameStage.FinalOutput: return HasRendererBlock( + rendererBlocks, RendererBlock.FinalOutput); default: return false; } } - private PassQueueStageManifest BuildPassQueueStageManifest( + private PassQueueFrameSnapshot BuildPassQueueFrameSnapshot( ScriptableRenderPipelinePlanningContext context) { bool finalColorRequiresProcessing = context.HasFinalColorProcessing(); int rendererIndex = context.rendererIndex; + ulong framePlanId = context.framePlanId; - return new PassQueueStageManifest + PassQueueFrameSnapshot snapshot = + new PassQueueFrameSnapshot( + framePlanId, + rendererIndex); + + snapshot.SetStageSnapshot( + BuildPassQueueStageSnapshot( + CameraFrameStage.ShadowCaster, + rendererIndex, + finalColorRequiresProcessing, + framePlanId)); + snapshot.SetStageSnapshot( + BuildPassQueueStageSnapshot( + CameraFrameStage.DepthOnly, + rendererIndex, + finalColorRequiresProcessing, + framePlanId)); + snapshot.SetStageSnapshot( + BuildPassQueueStageSnapshot( + CameraFrameStage.MainScene, + rendererIndex, + finalColorRequiresProcessing, + framePlanId)); + snapshot.SetStageSnapshot( + BuildPassQueueStageSnapshot( + CameraFrameStage.PostProcess, + rendererIndex, + finalColorRequiresProcessing, + framePlanId)); + snapshot.SetStageSnapshot( + BuildPassQueueStageSnapshot( + CameraFrameStage.FinalOutput, + rendererIndex, + finalColorRequiresProcessing, + framePlanId)); + + PassQueueStageSnapshot stageSnapshot; + snapshot.manifest = new PassQueueStageManifest { shadowCaster = - HasPassQueueStage( + snapshot.TryGetStageSnapshot( CameraFrameStage.ShadowCaster, - rendererIndex, - finalColorRequiresProcessing), + out stageSnapshot) && + HasRendererStageBlocks( + CameraFrameStage.ShadowCaster, + stageSnapshot.rendererBlocks), depthOnly = - HasPassQueueStage( + snapshot.TryGetStageSnapshot( CameraFrameStage.DepthOnly, - rendererIndex, - finalColorRequiresProcessing), + out stageSnapshot) && + HasRendererStageBlocks( + CameraFrameStage.DepthOnly, + stageSnapshot.rendererBlocks), postProcess = - HasPassQueueStage( + snapshot.TryGetStageSnapshot( CameraFrameStage.PostProcess, - rendererIndex, - finalColorRequiresProcessing), + out stageSnapshot) && + HasRendererStageBlocks( + CameraFrameStage.PostProcess, + stageSnapshot.rendererBlocks), finalOutput = - HasPassQueueStage( + snapshot.TryGetStageSnapshot( CameraFrameStage.FinalOutput, - rendererIndex, - finalColorRequiresProcessing) + out stageSnapshot) && + HasRendererStageBlocks( + CameraFrameStage.FinalOutput, + stageSnapshot.rendererBlocks) }; + + return snapshot; } - private bool HasPassQueueStage( + private PassQueueStageSnapshot BuildPassQueueStageSnapshot( CameraFrameStage stage, int rendererIndex, - bool finalColorRequiresProcessing) + bool finalColorRequiresProcessing, + ulong framePlanId) { BuildPassQueue( RenderingData.CreatePlanning( stage, rendererIndex, - finalColorRequiresProcessing)); - return HasRendererStageBlocks(stage); + finalColorRequiresProcessing, + framePlanId)); + + List activePassQueue = + new List( + m_activePassQueue); + RendererBlocks rendererBlocks = + new RendererBlocks(); + rendererBlocks.Build(activePassQueue); + return new PassQueueStageSnapshot( + stage, + activePassQueue, + rendererBlocks); } private static void ApplyPassQueueStageManifest( @@ -631,6 +828,129 @@ namespace XCEngine.Rendering.Universal return true; } + private void StorePassQueueFrameSnapshot( + PassQueueFrameSnapshot snapshot) + { + if (snapshot == null || + snapshot.framePlanId == 0UL) + { + return; + } + + if (!m_framePlanSnapshots.ContainsKey( + snapshot.framePlanId) && + m_framePlanSnapshots.Count >= + kMaxFramePlanSnapshotCount) + { + ulong oldestFramePlanId = 0UL; + bool hasOldestFramePlanId = false; + foreach (ulong framePlanId in + m_framePlanSnapshots.Keys) + { + oldestFramePlanId = framePlanId; + hasOldestFramePlanId = true; + break; + } + + if (hasOldestFramePlanId) + { + m_framePlanSnapshots.Remove( + oldestFramePlanId); + } + } + + m_framePlanSnapshots[snapshot.framePlanId] = + snapshot; + } + + private bool TryGetPassQueueStageSnapshot( + RendererRecordingContext context, + out PassQueueStageSnapshot stageSnapshot) + { + stageSnapshot = null; + if (context == null || + context.framePlanId == 0UL) + { + return false; + } + + PassQueueFrameSnapshot frameSnapshot; + if (!m_framePlanSnapshots.TryGetValue( + context.framePlanId, + out frameSnapshot) || + frameSnapshot == null || + frameSnapshot.rendererIndex != + context.rendererIndex) + { + return false; + } + + return frameSnapshot.TryGetStageSnapshot( + context.stage, + out stageSnapshot); + } + + private bool SupportsRendererRecordingFromSnapshot( + RendererRecordingContext context, + PassQueueStageSnapshot snapshot) + { + LoadPassQueueStageSnapshot(snapshot); + try + { + return SupportsRendererStage(context); + } + finally + { + ClearPassQueue(); + } + } + + private bool RecordRendererFromSnapshot( + RendererRecordingContext context, + PassQueueStageSnapshot snapshot) + { + LoadPassQueueStageSnapshot(snapshot); + try + { + bool recorded; + using (ContextContainer frameData = + ScriptableRenderPass.BuildRenderGraphFrameData( + context.renderingData)) + { + recorded = + RecordRendererStage( + context, + new RenderGraph(context.renderContext), + frameData); + } + if (recorded) + { + FinishCameraStackRendering(); + } + + return recorded; + } + finally + { + ClearPassQueue(); + } + } + + private void LoadPassQueueStageSnapshot( + PassQueueStageSnapshot snapshot) + { + ClearPassQueue(); + if (snapshot == null || + snapshot.activePassQueue == null) + { + return; + } + + m_activePassQueue.AddRange( + snapshot.activePassQueue); + m_rendererBlocks.Build(m_activePassQueue); + } + private void BuildPassQueue( RenderingData renderingData) { diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs index e4a071e4..d6ad16bd 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs @@ -25,6 +25,17 @@ namespace XCEngine.Rendering.Universal protected override bool SupportsStageRenderGraphContextual( CameraFrameStage stage, int rendererIndex) + { + return SupportsStageRenderGraphContextual( + stage, + rendererIndex, + 0UL); + } + + protected override bool SupportsStageRenderGraphContextual( + CameraFrameStage stage, + int rendererIndex, + ulong framePlanId) { ScriptableRenderer renderer = ResolveRenderer(rendererIndex); @@ -32,7 +43,8 @@ namespace XCEngine.Rendering.Universal renderer.SupportsRendererRecordingInstance( new RendererRecordingContext( stage, - rendererIndex)); + rendererIndex, + framePlanId)); } protected override bool RecordStageRenderGraph( diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index 709e73d4..8c8a7408 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -432,6 +432,11 @@ namespace XCEngine Rendering_ScriptableRenderContext_GetRendererIndex( ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern ulong + Rendering_ScriptableRenderContext_GetFramePlanId( + ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern int Rendering_ScriptableRenderContext_GetSourceColorTextureHandle( @@ -557,6 +562,11 @@ namespace XCEngine Rendering_ScriptableRenderPipelinePlanningContext_GetRendererIndex( ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern ulong + Rendering_ScriptableRenderPipelinePlanningContext_GetFramePlanId( + ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested( diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs index 48faf985..0da99d7b 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs @@ -23,6 +23,11 @@ namespace XCEngine.Rendering .Rendering_ScriptableRenderContext_GetRendererIndex( m_nativeHandle); + public ulong framePlanId => + InternalCalls + .Rendering_ScriptableRenderContext_GetFramePlanId( + m_nativeHandle); + public RenderGraphTextureHandle sourceColorTexture => RenderGraphTextureHandle.FromNativeIndex( InternalCalls diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipeline.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipeline.cs index 004beace..7ad4f888 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipeline.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipeline.cs @@ -34,6 +34,16 @@ namespace XCEngine.Rendering return SupportsStageRenderGraph(stage); } + protected virtual bool SupportsStageRenderGraphContextual( + CameraFrameStage stage, + int rendererIndex, + ulong framePlanId) + { + return SupportsStageRenderGraphContextual( + stage, + rendererIndex); + } + protected virtual bool RecordStageRenderGraph( ScriptableRenderContext context) { diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs index 927a153d..bd815e26 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs @@ -20,6 +20,11 @@ namespace XCEngine.Rendering .Rendering_ScriptableRenderPipelinePlanningContext_GetRendererIndex( m_nativeHandle); + public ulong framePlanId => + InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_GetFramePlanId( + m_nativeHandle); + public bool IsStageRequested( CameraFrameStage stage) { diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 40d27705..3a3b2bc0 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -807,6 +807,7 @@ TEST_F( 0 })) << runtime->GetLastError(); + const XCEngine::Rendering::RenderContext renderContext = {}; XCEngine::Rendering::RenderGraph graph; XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph); XCEngine::Rendering::RenderGraphTextureDesc colorDesc = {}; @@ -1435,6 +1436,7 @@ TEST_F( bool hasPublicContextEnvironmentData = false; bool hasPublicContextFinalColorData = false; bool hasPublicContextStageColorData = false; + bool hasPublicContextFramePlanId = false; bool hasPublicRequestContextHasDirectionalShadow = false; bool hasPublicRequestContextClearDirectionalShadow = false; bool hasUniversalContextRecordSceneExtension = false; @@ -1447,8 +1449,10 @@ TEST_F( bool hasPipelineAssetSetDirty = false; bool hasPipelineAssetGetRuntimeResourceVersion = false; bool hasPlanningContextType = false; + bool hasPublicPlanningContextFramePlanId = false; bool hasRendererFeatureConfigureCameraFramePlan = false; bool hasRendererRecordingContextType = false; + bool hasPublicRendererRecordingContextFramePlanId = false; bool hasRendererCameraRequestContextType = false; bool hasRendererBackedRenderPipelineAssetType = false; bool hasRendererBackedRenderPipelineType = false; @@ -1473,6 +1477,7 @@ TEST_F( bool hasRenderGraphRasterContextCommandBuffer = false; bool hasSceneRenderPhaseType = false; bool hasSceneRenderInjectionPointType = false; + bool hasPublicRenderingDataFramePlanId = false; bool hasRenderPassProtectedRecordColorScaleFullscreenPass = false; bool hasRenderPassProtectedRecordShaderVectorFullscreenPass = false; bool hasRenderPassProtectedRecordFinalColorFullscreenPass = false; @@ -1537,6 +1542,10 @@ TEST_F( selectionScript, "HasPublicContextStageColorData", hasPublicContextStageColorData)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "HasPublicContextFramePlanId", + hasPublicContextFramePlanId)); EXPECT_TRUE(runtime->TryGetFieldValue( selectionScript, "HasPublicRequestContextHasDirectionalShadow", @@ -1585,6 +1594,10 @@ TEST_F( selectionScript, "HasPlanningContextType", hasPlanningContextType)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "HasPublicPlanningContextFramePlanId", + hasPublicPlanningContextFramePlanId)); EXPECT_TRUE(runtime->TryGetFieldValue( selectionScript, "HasRendererFeatureConfigureCameraFramePlan", @@ -1593,6 +1606,10 @@ TEST_F( selectionScript, "HasRendererRecordingContextType", hasRendererRecordingContextType)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "HasPublicRendererRecordingContextFramePlanId", + hasPublicRendererRecordingContextFramePlanId)); EXPECT_TRUE(runtime->TryGetFieldValue( selectionScript, "HasRendererCameraRequestContextType", @@ -1689,6 +1706,10 @@ TEST_F( selectionScript, "HasSceneRenderInjectionPointType", hasSceneRenderInjectionPointType)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "HasPublicRenderingDataFramePlanId", + hasPublicRenderingDataFramePlanId)); EXPECT_TRUE(runtime->TryGetFieldValue( selectionScript, "HasRenderPassProtectedRecordColorScaleFullscreenPass", @@ -1717,6 +1738,7 @@ TEST_F( EXPECT_FALSE(hasPublicContextEnvironmentData); EXPECT_FALSE(hasPublicContextFinalColorData); EXPECT_FALSE(hasPublicContextStageColorData); + EXPECT_TRUE(hasPublicContextFramePlanId); EXPECT_FALSE(hasPublicRequestContextHasDirectionalShadow); EXPECT_FALSE(hasPublicRequestContextClearDirectionalShadow); EXPECT_FALSE(hasUniversalContextRecordSceneExtension); @@ -1729,8 +1751,10 @@ TEST_F( EXPECT_TRUE(hasPipelineAssetSetDirty); EXPECT_TRUE(hasPipelineAssetGetRuntimeResourceVersion); EXPECT_TRUE(hasPlanningContextType); + EXPECT_TRUE(hasPublicPlanningContextFramePlanId); EXPECT_TRUE(hasRendererFeatureConfigureCameraFramePlan); EXPECT_TRUE(hasRendererRecordingContextType); + EXPECT_TRUE(hasPublicRendererRecordingContextFramePlanId); EXPECT_TRUE(hasRendererCameraRequestContextType); EXPECT_FALSE(hasRendererBackedRenderPipelineAssetType); EXPECT_FALSE(hasRendererBackedRenderPipelineType); @@ -1755,6 +1779,7 @@ TEST_F( EXPECT_TRUE(hasRenderGraphRasterContextCommandBuffer); EXPECT_FALSE(hasSceneRenderPhaseType); EXPECT_FALSE(hasSceneRenderInjectionPointType); + EXPECT_TRUE(hasPublicRenderingDataFramePlanId); EXPECT_FALSE(hasRenderPassProtectedRecordColorScaleFullscreenPass); EXPECT_FALSE(hasRenderPassProtectedRecordShaderVectorFullscreenPass); EXPECT_FALSE(hasRenderPassProtectedRecordFinalColorFullscreenPass); @@ -4626,8 +4651,17 @@ TEST_F( XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset>( descriptor); + TestRenderDevice device; + TestRenderCommandList commandList; + TestRenderCommandQueue commandQueue; + const XCEngine::Rendering::RenderContext renderContext = + CreateRenderContext( + device, + commandList, + commandQueue); + XCEngine::Rendering::CameraRenderRequest request = {}; - request.context = {}; + request.context = renderContext; request.surface = XCEngine::Rendering::RenderSurface(64u, 64u); XCTest::TestRenderResourceView colorAttachmentView( XCEngine::RHI::ResourceViewType::RenderTarget, @@ -4673,6 +4707,156 @@ TEST_F( EXPECT_TRUE(plan.IsFinalOutputStageValid()); } +TEST_F( + MonoScriptRuntimeTest, + ManagedRendererPassQueueSnapshotDrivesSupportAndRecording) { + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { + "GameScripts", + "Gameplay", + "ManagedPassQueueSnapshotProbeAsset" + }; + + auto asset = + std::make_shared< + XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset>( + descriptor); + + TestRenderDevice device; + TestRenderCommandList commandList; + TestRenderCommandQueue commandQueue; + const XCEngine::Rendering::RenderContext renderContext = + CreateRenderContext( + device, + commandList, + commandQueue); + + XCEngine::Rendering::CameraRenderRequest request = {}; + request.context = renderContext; + request.surface = XCEngine::Rendering::RenderSurface(64u, 64u); + TestRenderResourceView colorAttachmentView( + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::R8G8B8A8_UNorm); + TestRenderResourceView depthAttachmentView( + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::D32_Float); + request.surface.SetColorAttachment( + &colorAttachmentView); + request.surface.SetDepthAttachment( + &depthAttachmentView); + + XCEngine::Rendering::RenderPipelineHost host(asset); + const std::vector plans = + host.BuildFramePlans({ request }); + + ASSERT_EQ(plans.size(), 1u); + const XCEngine::Rendering::CameraFramePlan& plan = plans[0]; + ASSERT_NE(plan.framePlanId, 0u); + ASSERT_TRUE( + plan.IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + + auto* pipelineHost = + dynamic_cast( + host.GetPipeline()); + ASSERT_NE(pipelineHost, nullptr); + ASSERT_TRUE( + pipelineHost->Initialize( + renderContext)) + << runtime->GetLastError(); + + const XCEngine::Rendering::RenderPipelineStageSupportContext + supportContext = { + XCEngine::Rendering::CameraFrameStage::PostProcess, + plan.request.rendererIndex, + plan.framePlanId + }; + EXPECT_TRUE( + pipelineHost->SupportsStageRenderGraph( + supportContext)) + << runtime->GetLastError(); + + const XCEngine::Rendering::RenderPipelineStageSupportContext + missingSnapshotSupportContext = { + XCEngine::Rendering::CameraFrameStage::PostProcess, + plan.request.rendererIndex, + plan.framePlanId + 1000u + }; + EXPECT_FALSE( + pipelineHost->SupportsStageRenderGraph( + missingSnapshotSupportContext)); + + XCEngine::Rendering::RenderGraph graph; + XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph); + XCEngine::Rendering::RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 64u; + colorDesc.height = 64u; + colorDesc.format = + static_cast( + XCEngine::RHI::Format::R8G8B8A8_UNorm); + TestRenderResourceView sourceColorView( + XCEngine::RHI::ResourceViewType::ShaderResource, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::R8G8B8A8_UNorm); + const XCEngine::Rendering::RenderGraphTextureHandle sourceColor = + graphBuilder.ImportTexture( + "ManagedPassQueueSnapshotSource", + colorDesc, + &sourceColorView, + {}); + const XCEngine::Rendering::RenderGraphTextureHandle outputColor = + graphBuilder.CreateTransientTexture( + "ManagedPassQueueSnapshotOutput", + colorDesc); + + const XCEngine::Rendering::RenderSceneData sceneData = {}; + const XCEngine::Rendering::RenderSurface surface(64u, 64u); + bool executionSucceeded = true; + XCEngine::Rendering::RenderGraphBlackboard blackboard = {}; + XCEngine::Rendering::EmplaceCameraFrameRenderGraphFrameData(blackboard) + .resources.mainScene.color = sourceColor; + XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = { + graphBuilder, + "ManagedPassQueueSnapshot", + XCEngine::Rendering::CameraFrameStage::PostProcess, + renderContext, + sceneData, + surface, + nullptr, + nullptr, + XCEngine::RHI::ResourceStates::Common, + {}, + { outputColor }, + {}, + {}, + &executionSucceeded, + &blackboard + }; + graphContext.rendererIndex = plan.request.rendererIndex; + graphContext.framePlanId = plan.framePlanId; + + EXPECT_TRUE( + pipelineHost->RecordStageRenderGraph( + graphContext)) + << runtime->GetLastError(); + + XCEngine::Rendering::CompiledRenderGraph compiledGraph = {}; + XCEngine::Containers::String errorMessage; + ASSERT_TRUE( + XCEngine::Rendering::RenderGraphCompiler::Compile( + graph, + compiledGraph, + &errorMessage)) + << errorMessage.CStr(); + ASSERT_EQ(compiledGraph.GetPassCount(), 1u); + EXPECT_STREQ( + compiledGraph.GetPassName(0).CStr(), + "ManagedPassQueueSnapshot.PostProcess"); + + pipelineHost->Shutdown(); +} + TEST_F( MonoScriptRuntimeTest, ManagedPlanningContextRejectsNonFullscreenStageRequests) {