From 9e6c4731864fa290cfd659ce5ac36a444fdbee96 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 20 Apr 2026 01:48:16 +0800 Subject: [PATCH] feat(srp): add managed camera frame planning seam Expose native camera frame planning controls to managed pipeline assets and renderer features. Allow managed planning to override fullscreen stage heuristics while keeping CameraFramePlan as the native execution contract. Add scripting coverage, probe assets, and archive the phase plan after build, test, and editor smoke validation. --- ...lan接缝与Universal规划层计划_2026-04-20.md | 165 +++++++++ .../Rendering/Execution/CameraFramePlan.h | 10 +- .../ManagedScriptableRenderPipelineAsset.h | 4 + .../Scripting/Mono/MonoScriptRuntime.h | 5 + .../Rendering/Execution/CameraFramePlan.cpp | 25 +- .../ManagedScriptableRenderPipelineAsset.cpp | 11 + .../Planning/CameraFramePlanBuilder.cpp | 12 +- .../src/Scripting/Mono/MonoScriptRuntime.cpp | 328 ++++++++++++++++++ managed/CMakeLists.txt | 1 + managed/GameScripts/RenderPipelineApiProbe.cs | 81 +++++ managed/XCEngine.ScriptCore/InternalCalls.cs | 32 ++ .../Core/ScriptableRenderPipelineAsset.cs | 11 + ...ScriptableRenderPipelinePlanningContext.cs | 64 ++++ .../Universal/ScriptableRendererData.cs | 27 ++ .../Universal/ScriptableRendererFeature.cs | 5 + .../Universal/UniversalRenderPipelineAsset.cs | 12 + tests/scripting/test_mono_script_runtime.cpp | 142 +++++++- 17 files changed, 926 insertions(+), 9 deletions(-) create mode 100644 docs/used/SRP_ManagedFramePlan接缝与Universal规划层计划_2026-04-20.md create mode 100644 managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs diff --git a/docs/used/SRP_ManagedFramePlan接缝与Universal规划层计划_2026-04-20.md b/docs/used/SRP_ManagedFramePlan接缝与Universal规划层计划_2026-04-20.md new file mode 100644 index 00000000..4986c74e --- /dev/null +++ b/docs/used/SRP_ManagedFramePlan接缝与Universal规划层计划_2026-04-20.md @@ -0,0 +1,165 @@ +# SRP Managed Frame Plan 接缝与 Universal 规划层计划 2026-04-20 + +## 1. 阶段目标 + +当前 SRP 主线已经有了两条 managed 接缝: + +1. `ConfigureCameraRenderRequest` +2. `RecordStageRenderGraph` + +但真正夹在中间、决定“一帧到底要不要有 post process / final output,以及这些 stage 怎么串”的 +`CameraFramePlan` 仍然完全停留在 native 默认策略里。 + +这一阶段的目标,就是把这条缺口补上: + +1. 给 managed SRP Core 增加正式的 frame-plan planning context +2. 让 `ScriptableRenderPipelineAsset` 能参与 `CameraFramePlan` 配置 +3. 让 Universal 的 `ScriptableRendererData / ScriptableRendererFeature` 能继续把这层规划往下分发 +4. 保持 Render Graph 仍然留在 C++,只把“规划策略”暴露给 C# + +--- + +## 2. 当前问题 + +### 2.1 managed 现在只能改 request,不能改 frame plan + +现在 managed 资产可以在 request 阶段做: + +1. 清理 directional shadow +2. 选择 native backend key +3. 提供 final color 默认值 + +但它还不能做: + +1. 显式请求 post-process stage +2. 显式请求 final-output stage +3. 决定 stage color source +4. 决定 graph-managed output color 的串联关系 + +这导致 managed 管线如果想表达“我需要这两个阶段”,目前只能绕到 +`SupportsStageRenderGraph` 这条被动推断路径。 + +### 2.2 Universal 还没有正式的“规划层”下发通道 + +当前 Universal 已经有: + +`Asset -> RendererData -> Renderer -> Feature -> Pass` + +但只有: + +1. request 配置通道 +2. recording 通道 + +缺少: + +1. plan 配置通道 + +所以 `RendererData` 和 `RendererFeature` 还没法正式参与 stage planning。 + +### 2.3 现在的 fullscreen stage 主要靠 native heuristic 推断 + +当前 `CameraFramePlanBuilder` 里还有一层: + +`ConfigureFullscreenStagesFromPipeline(...)` + +它会根据 pipeline 对 stage 的支持情况补出 fullscreen stage。 + +这条 heuristic 对当前主线有用,但它不是未来 SRP/URP 的最终 authoring 方式。 +真正成熟的方向应该是: + +1. native 负责执行与兜底 +2. managed asset / renderer data / feature 负责更明确的规划策略 + +--- + +## 3. 本阶段方案 + +### 3.1 Core:新增 planning context + +在 managed core 增加: + +1. `ScriptableRenderPipelinePlanningContext` +2. `ScriptableRenderPipelineAsset.ConfigureCameraFramePlan(...)` + +planning context 先只暴露这阶段真正需要的最小能力: + +1. 查询 stage 是否已请求 +2. 查询 stage color source +3. 查询 stage 是否使用 graph-managed output color +4. 请求 fullscreen stage +5. 清理 fullscreen stage + +### 3.2 Native bridge:把 plan 接缝接进 Mono runtime + +在 native/Mono bridge 增加: + +1. planning context state registry +2. managed planning context object 构造 +3. `ManagedRenderPipelineAssetRuntime::ConfigureCameraFramePlan(...)` +4. 对应 internal call + +### 3.3 Universal:补上 renderer data / feature 规划层 + +给 Universal 包增加: + +1. `ScriptableRendererData.ConfigureCameraFramePlanInstance(...)` +2. `ScriptableRendererFeature.ConfigureCameraFramePlan(...)` +3. `UniversalRenderPipelineAsset` 把 planning context 下发到默认 renderer data + +这样 Universal 的 request / plan / record 三层链路就对齐了。 + +--- + +## 4. 实施步骤 + +### Step 1:补 managed core planning API + +目标: + +1. 新增 `ScriptableRenderPipelinePlanningContext` +2. 新增 `ScriptableRenderPipelineAsset.ConfigureCameraFramePlan` +3. 补 internal call 声明 + +### Step 2:补 native planning bridge + +目标: + +1. 扩展 `ManagedRenderPipelineAssetRuntime` +2. `ManagedScriptableRenderPipelineAsset` 调用 managed `ConfigureCameraFramePlan` +3. Mono runtime 能创建并驱动 managed planning context + +### Step 3:把 Universal 接到 planning seam + +目标: + +1. `ScriptableRendererData` 下发 plan 配置 +2. `ScriptableRendererFeature` 能参与 plan 配置 +3. `UniversalRenderPipelineAsset` 使用默认 renderer data 承担这层逻辑 + +### Step 4:补 probe 与回归测试 + +目标: + +1. 更新 API surface probe +2. 新增/更新 managed probe asset,验证 managed plan hook 真正生效 +3. 锁住 Universal feature 的 planning seam + +### Step 5:完整验证与收口 + +目标: + +1. 编译 `rendering_unit_tests`、`scripting_tests`、`XCEditor` +2. 运行相关测试 +3. 运行旧版 `editor/bin/Debug/XCEngine.exe` 至少 10 秒并检查新的 `SceneReady` + +--- + +## 5. 验收标准 + +完成后应满足: + +1. managed `ScriptableRenderPipelineAsset` 可以正式参与 `CameraFramePlan` 配置 +2. `UniversalRenderPipelineAsset -> ScriptableRendererData -> ScriptableRendererFeature` 有完整的 planning 分发链 +3. fullscreen stage 不再只能依赖“pipeline support heuristic”这一条隐式路径 +4. API surface probe 与脚本回归测试锁住新的 public / protected seam +5. 编译、测试、旧版 editor 冒烟全部通过 diff --git a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h index f3b9f700..da1fb43f 100644 --- a/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h +++ b/engine/include/XCEngine/Rendering/Execution/CameraFramePlan.h @@ -12,6 +12,7 @@ namespace Rendering { struct CameraFrameFullscreenStagePlan { bool requested = false; bool usesGraphManagedOutputColor = false; + bool explicitlyConfigured = false; CameraFrameColorSource source = CameraFrameColorSource::ExplicitSurface; }; @@ -58,8 +59,13 @@ struct CameraFramePlan { bool RequestFullscreenStage( CameraFrameStage stage, CameraFrameColorSource source, - bool usesGraphManagedOutputColor = false); - void ClearFullscreenStage(CameraFrameStage stage); + bool usesGraphManagedOutputColor = false, + bool explicitlyConfigured = false); + void ClearFullscreenStage( + CameraFrameStage stage, + bool explicitlyConfigured = false); + bool HasExplicitFullscreenStageConfiguration( + CameraFrameStage stage) const; bool IsPostProcessStageValid() const; bool IsFinalOutputStageValid() const; bool HasFrameStage(CameraFrameStage stage) const; diff --git a/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h index ab545318..7be4c1e2 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h +++ b/engine/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h @@ -10,6 +10,7 @@ namespace XCEngine { namespace Rendering { +struct CameraFramePlan; struct CameraRenderRequest; struct DirectionalShadowPlanningSettings; @@ -52,6 +53,7 @@ public: size_t renderedRequestCount, const DirectionalShadowPlanningSettings& directionalShadowSettings) const override; + void ConfigureCameraFramePlan(CameraFramePlan& plan) const override; FinalColorSettings GetDefaultFinalColorSettings() const override; private: @@ -82,6 +84,8 @@ public: size_t, const DirectionalShadowPlanningSettings&) const { } + virtual void ConfigureCameraFramePlan(CameraFramePlan&) const { + } virtual std::shared_ptr GetPipelineRendererAsset() const { diff --git a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h index 747fce24..0799788d 100644 --- a/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h +++ b/engine/include/XCEngine/Scripting/Mono/MonoScriptRuntime.h @@ -266,6 +266,8 @@ private: MonoObject* CreateManagedScriptableRenderContext(uint64_t nativeHandle); MonoObject* CreateManagedCameraRenderRequestContext( uint64_t nativeHandle); + MonoObject* CreateManagedScriptableRenderPipelinePlanningContext( + uint64_t nativeHandle); MonoObject* GetManagedObject(uint32_t gcHandle) const; MonoMethod* ResolveManagedMethod( MonoClass* monoClass, @@ -306,11 +308,14 @@ private: MonoClass* m_scriptableRenderPipelineClass = nullptr; MonoClass* m_scriptableRenderContextClass = nullptr; MonoClass* m_cameraRenderRequestContextClass = nullptr; + MonoClass* m_scriptableRenderPipelinePlanningContextClass = nullptr; MonoClass* m_serializeFieldAttributeClass = nullptr; MonoMethod* m_gameObjectConstructor = nullptr; MonoMethod* m_scriptableRenderContextConstructor = nullptr; MonoMethod* m_cameraRenderRequestContextConstructor = nullptr; + MonoMethod* m_scriptableRenderPipelinePlanningContextConstructor = + nullptr; MonoClassField* m_managedGameObjectUUIDField = nullptr; MonoClassField* m_gameObjectUUIDField = nullptr; MonoClassField* m_scriptComponentUUIDField = nullptr; diff --git a/engine/src/Rendering/Execution/CameraFramePlan.cpp b/engine/src/Rendering/Execution/CameraFramePlan.cpp index 665cb56f..11231797 100644 --- a/engine/src/Rendering/Execution/CameraFramePlan.cpp +++ b/engine/src/Rendering/Execution/CameraFramePlan.cpp @@ -149,7 +149,8 @@ bool CameraFramePlan::IsFullscreenStageRequested(CameraFrameStage stage) const { bool CameraFramePlan::RequestFullscreenStage( CameraFrameStage stage, CameraFrameColorSource source, - bool usesGraphManagedOutputColor) { + bool usesGraphManagedOutputColor, + bool explicitlyConfigured) { CameraFrameFullscreenStagePlan* const fullscreenStagePlan = GetMutableFullscreenStagePlan(*this, stage); FullscreenPassRenderRequest* const fullscreenRequest = @@ -164,6 +165,9 @@ bool CameraFramePlan::RequestFullscreenStage( stage == CameraFrameStage::PostProcess ? usesGraphManagedOutputColor : false; + fullscreenStagePlan->explicitlyConfigured = + fullscreenStagePlan->explicitlyConfigured || + explicitlyConfigured; if (source != CameraFrameColorSource::ExplicitSurface) { fullscreenRequest->sourceSurface = {}; @@ -184,15 +188,21 @@ bool CameraFramePlan::RequestFullscreenStage( return true; } -void CameraFramePlan::ClearFullscreenStage(CameraFrameStage stage) { +void CameraFramePlan::ClearFullscreenStage( + CameraFrameStage stage, + bool explicitlyConfigured) { if (stage == CameraFrameStage::PostProcess) { ClearOwnedPostProcessSequence(); postProcess = {}; colorChain.postProcess = {}; + colorChain.postProcess.explicitlyConfigured = + explicitlyConfigured; } else if (stage == CameraFrameStage::FinalOutput) { ClearOwnedFinalOutputSequence(); finalOutput = {}; colorChain.finalOutput = {}; + colorChain.finalOutput.explicitlyConfigured = + explicitlyConfigured; } else { return; } @@ -200,6 +210,17 @@ void CameraFramePlan::ClearFullscreenStage(CameraFrameStage stage) { RefreshGraphManagedSceneSurfaceState(); } +bool CameraFramePlan::HasExplicitFullscreenStageConfiguration( + CameraFrameStage stage) const { + if (const CameraFrameFullscreenStagePlan* fullscreenStagePlan = + GetFullscreenStagePlan(stage); + fullscreenStagePlan != nullptr) { + return fullscreenStagePlan->explicitlyConfigured; + } + + return false; +} + bool CameraFramePlan::IsPostProcessStageValid() const { if (!IsFullscreenStageRequested(CameraFrameStage::PostProcess)) { return true; diff --git a/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp b/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp index aa029b17..ffbb8c2d 100644 --- a/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp +++ b/engine/src/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.cpp @@ -100,6 +100,17 @@ void ManagedScriptableRenderPipelineAsset::ConfigureCameraRenderRequest( } } +void ManagedScriptableRenderPipelineAsset::ConfigureCameraFramePlan( + CameraFramePlan& plan) const { + RenderPipelineAsset::ConfigureCameraFramePlan(plan); + + if (const std::shared_ptr runtime = + ResolveManagedAssetRuntime(); + runtime != nullptr) { + runtime->ConfigureCameraFramePlan(plan); + } +} + FinalColorSettings ManagedScriptableRenderPipelineAsset::GetDefaultFinalColorSettings() const { if (const std::shared_ptr runtime = ResolveManagedAssetRuntime(); diff --git a/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp b/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp index 2901f861..538941e8 100644 --- a/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp +++ b/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp @@ -18,6 +18,12 @@ bool UsesExplicitFullscreenSource( void ConfigureFullscreenStagesFromPipeline( CameraFramePlan& plan, const RenderPipeline* pipeline) { + const bool postProcessExplicitlyConfigured = + plan.HasExplicitFullscreenStageConfiguration( + CameraFrameStage::PostProcess); + const bool finalOutputExplicitlyConfigured = + plan.HasExplicitFullscreenStageConfiguration( + CameraFrameStage::FinalOutput); const bool supportsPostProcess = pipeline != nullptr && pipeline->SupportsStageRenderGraph( @@ -33,7 +39,8 @@ void ConfigureFullscreenStagesFromPipeline( plan.finalOutput.IsRequested() || supportsFinalOutput; - if (hasPostProcess && + if (!postProcessExplicitlyConfigured && + hasPostProcess && !UsesExplicitFullscreenSource( plan.postProcess, plan.ResolveStageColorSource( @@ -50,7 +57,8 @@ void ConfigureFullscreenStagesFromPipeline( plan.postProcess, plan.ResolveStageColorSource( CameraFrameStage::PostProcess)); - if (hasFinalOutput && + if (!finalOutputExplicitlyConfigured && + hasFinalOutput && !UsesExplicitFullscreenSource( plan.finalOutput, plan.ResolveStageColorSource( diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 4376ea75..cd6f6fd9 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -111,6 +111,11 @@ struct ManagedCameraRenderRequestContextState { size_t renderedRequestCount = 0u; }; +struct ManagedScriptableRenderPipelinePlanningContextState { + uint64_t handle = 0; + Rendering::CameraFramePlan* plan = nullptr; +}; + uint64_t& GetManagedScriptableRenderContextNextHandle() { static uint64_t nextHandle = 1; return nextHandle; @@ -274,6 +279,92 @@ void UnregisterManagedCameraRenderRequestContextState( GetManagedCameraRenderRequestContextRegistry().erase(handle); } +uint64_t& GetManagedScriptableRenderPipelinePlanningContextNextHandle() { + static uint64_t nextHandle = 1; + return nextHandle; +} + +std::unordered_map& +GetManagedScriptableRenderPipelinePlanningContextRegistry() { + static std::unordered_map< + uint64_t, + ManagedScriptableRenderPipelinePlanningContextState*> registry; + return registry; +} + +ManagedScriptableRenderPipelinePlanningContextState* +FindManagedScriptableRenderPipelinePlanningContextState( + uint64_t handle) { + const auto it = + GetManagedScriptableRenderPipelinePlanningContextRegistry().find( + handle); + return it != + GetManagedScriptableRenderPipelinePlanningContextRegistry().end() + ? it->second + : nullptr; +} + +uint64_t RegisterManagedScriptableRenderPipelinePlanningContextState( + ManagedScriptableRenderPipelinePlanningContextState& state) { + uint64_t handle = + GetManagedScriptableRenderPipelinePlanningContextNextHandle()++; + if (handle == 0) { + handle = + GetManagedScriptableRenderPipelinePlanningContextNextHandle()++; + } + + state.handle = handle; + GetManagedScriptableRenderPipelinePlanningContextRegistry()[handle] = + &state; + return handle; +} + +void UnregisterManagedScriptableRenderPipelinePlanningContextState( + uint64_t handle) { + if (handle == 0) { + return; + } + + GetManagedScriptableRenderPipelinePlanningContextRegistry().erase(handle); +} + +bool TryResolveManagedCameraFrameStage( + int32_t value, + Rendering::CameraFrameStage& outStage) { + switch (value) { + case static_cast(Rendering::CameraFrameStage::PreScenePasses): + outStage = Rendering::CameraFrameStage::PreScenePasses; + return true; + case static_cast(Rendering::CameraFrameStage::ShadowCaster): + outStage = Rendering::CameraFrameStage::ShadowCaster; + return true; + case static_cast(Rendering::CameraFrameStage::DepthOnly): + outStage = Rendering::CameraFrameStage::DepthOnly; + return true; + case static_cast(Rendering::CameraFrameStage::MainScene): + outStage = Rendering::CameraFrameStage::MainScene; + return true; + case static_cast(Rendering::CameraFrameStage::PostProcess): + outStage = Rendering::CameraFrameStage::PostProcess; + return true; + case static_cast(Rendering::CameraFrameStage::FinalOutput): + outStage = Rendering::CameraFrameStage::FinalOutput; + return true; + case static_cast(Rendering::CameraFrameStage::ObjectId): + outStage = Rendering::CameraFrameStage::ObjectId; + return true; + case static_cast(Rendering::CameraFrameStage::PostScenePasses): + outStage = Rendering::CameraFrameStage::PostScenePasses; + return true; + case static_cast(Rendering::CameraFrameStage::OverlayPasses): + outStage = Rendering::CameraFrameStage::OverlayPasses; + return true; + default: + outStage = Rendering::CameraFrameStage::MainScene; + return false; + } +} + void CleanupMonoRootDomainAtExit() { MonoRootState& rootState = GetMonoRootState(); if (!rootState.rootDomain) { @@ -715,6 +806,8 @@ public: size_t renderedRequestCount, const Rendering::DirectionalShadowPlanningSettings& directionalShadowSettings) const override; + void ConfigureCameraFramePlan( + Rendering::CameraFramePlan& plan) const override; bool TryGetDefaultFinalColorSettings( Rendering::FinalColorSettings& settings) const override; @@ -742,6 +835,8 @@ private: MonoMethod* ResolveCreatePipelineMethod(MonoObject* assetObject) const; MonoMethod* ResolveConfigureCameraRenderRequestMethod( MonoObject* assetObject) const; + MonoMethod* ResolveConfigureCameraFramePlanMethod( + MonoObject* assetObject) const; MonoMethod* ResolveGetDefaultFinalColorSettingsMethod( MonoObject* assetObject) const; MonoMethod* ResolveGetPipelineRendererAssetKeyMethod( @@ -756,6 +851,7 @@ private: mutable MonoMethod* m_disposePipelineMethod = nullptr; mutable MonoMethod* m_createPipelineMethod = nullptr; mutable MonoMethod* m_configureCameraRenderRequestMethod = nullptr; + mutable MonoMethod* m_configureCameraFramePlanMethod = nullptr; mutable MonoMethod* m_getDefaultFinalColorSettingsMethod = nullptr; mutable MonoMethod* m_getPipelineRendererAssetKeyMethod = nullptr; mutable MonoMethod* m_releaseRuntimeResourcesMethod = nullptr; @@ -1051,6 +1147,44 @@ void MonoManagedRenderPipelineAssetRuntime::ConfigureCameraRenderRequest( requestContextHandle); } +void MonoManagedRenderPipelineAssetRuntime::ConfigureCameraFramePlan( + Rendering::CameraFramePlan& plan) const { + if (!EnsureManagedAsset()) { + return; + } + + MonoObject* const assetObject = GetManagedAssetObject(); + MonoMethod* const method = + ResolveConfigureCameraFramePlanMethod(assetObject); + if (assetObject == nullptr || method == nullptr) { + return; + } + + ManagedScriptableRenderPipelinePlanningContextState + planningContextState = {}; + planningContextState.plan = &plan; + const uint64_t planningContextHandle = + RegisterManagedScriptableRenderPipelinePlanningContextState( + planningContextState); + MonoObject* const planningContextObject = + m_runtime->CreateManagedScriptableRenderPipelinePlanningContext( + planningContextHandle); + if (planningContextObject == nullptr) { + UnregisterManagedScriptableRenderPipelinePlanningContextState( + planningContextHandle); + return; + } + + void* args[1] = { planningContextObject }; + m_runtime->InvokeManagedMethod( + assetObject, + method, + args, + nullptr); + UnregisterManagedScriptableRenderPipelinePlanningContextState( + planningContextHandle); +} + bool MonoManagedRenderPipelineAssetRuntime::TryGetDefaultFinalColorSettings( Rendering::FinalColorSettings& settings) const { settings = {}; @@ -1268,6 +1402,7 @@ void MonoManagedRenderPipelineAssetRuntime::ReleaseManagedAsset() const { m_releaseRuntimeResourcesMethod = nullptr; m_createPipelineMethod = nullptr; m_configureCameraRenderRequestMethod = nullptr; + m_configureCameraFramePlanMethod = nullptr; m_getDefaultFinalColorSettingsMethod = nullptr; m_getPipelineRendererAssetKeyMethod = nullptr; m_pipelineRendererAsset.reset(); @@ -1345,6 +1480,20 @@ MonoManagedRenderPipelineAssetRuntime::ResolveConfigureCameraRenderRequestMethod return m_configureCameraRenderRequestMethod; } +MonoMethod* +MonoManagedRenderPipelineAssetRuntime::ResolveConfigureCameraFramePlanMethod( + MonoObject* assetObject) const { + if (m_configureCameraFramePlanMethod == nullptr) { + m_configureCameraFramePlanMethod = + m_runtime->ResolveManagedMethod( + assetObject, + "ConfigureCameraFramePlanInstance", + 1); + } + + return m_configureCameraFramePlanMethod; +} + MonoMethod* MonoManagedRenderPipelineAssetRuntime::ResolveGetDefaultFinalColorSettingsMethod( MonoObject* assetObject) const { @@ -3803,6 +3952,115 @@ void InternalCall_Rendering_CameraRenderRequestContext_ClearDirectionalShadow( state->request->directionalShadow = {}; } +mono_bool +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested( + uint64_t nativeHandle, + int32_t stage) { + const ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || state->plan == nullptr) { + return 0; + } + + Rendering::CameraFrameStage resolvedStage = + Rendering::CameraFrameStage::MainScene; + if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) { + return 0; + } + + return state->plan->HasFrameStage(resolvedStage) ? 1 : 0; +} + +int32_t +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetStageColorSource( + uint64_t nativeHandle, + int32_t stage) { + const ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || state->plan == nullptr) { + return static_cast( + Rendering::CameraFrameColorSource::ExplicitSurface); + } + + Rendering::CameraFrameStage resolvedStage = + Rendering::CameraFrameStage::MainScene; + if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) { + return static_cast( + Rendering::CameraFrameColorSource::ExplicitSurface); + } + + return static_cast( + state->plan->ResolveStageColorSource(resolvedStage)); +} + +mono_bool +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_GetStageUsesGraphManagedOutputColor( + uint64_t nativeHandle, + int32_t stage) { + const ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || state->plan == nullptr) { + return 0; + } + + Rendering::CameraFrameStage resolvedStage = + Rendering::CameraFrameStage::MainScene; + if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) { + return 0; + } + + return state->plan->UsesGraphManagedOutputColor(resolvedStage) + ? 1 + : 0; +} + +mono_bool +InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage( + uint64_t nativeHandle, + int32_t stage, + int32_t source, + mono_bool usesGraphManagedOutputColor) { + ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || state->plan == nullptr) { + return 0; + } + + Rendering::CameraFrameStage resolvedStage = + Rendering::CameraFrameStage::MainScene; + if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) { + return 0; + } + + const Rendering::CameraFrameColorSource resolvedSource = + static_cast(source); + return state->plan->RequestFullscreenStage( + resolvedStage, + resolvedSource, + usesGraphManagedOutputColor != 0, + true) + ? 1 + : 0; +} + +void InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage( + uint64_t nativeHandle, + int32_t stage) { + ManagedScriptableRenderPipelinePlanningContextState* const state = + FindManagedScriptableRenderPipelinePlanningContextState(nativeHandle); + if (state == nullptr || state->plan == nullptr) { + return; + } + + Rendering::CameraFrameStage resolvedStage = + Rendering::CameraFrameStage::MainScene; + if (!TryResolveManagedCameraFrameStage(stage, resolvedStage)) { + return; + } + + state->plan->ClearFullscreenStage(resolvedStage, true); +} + void RegisterInternalCalls() { if (GetInternalCallRegistrationState()) { return; @@ -3986,6 +4244,11 @@ 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_RecordSceneInjectionPoint", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_RecordSceneInjectionPoint)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderContext_RecordFullscreenPass", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderContext_RecordFullscreenPass)); + 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)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage)); + mono_add_internal_call("XCEngine.InternalCalls::Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage", reinterpret_cast(&InternalCall_Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedBaseCameraCount)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetRenderedRequestCount", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetRenderedRequestCount)); mono_add_internal_call("XCEngine.InternalCalls::Rendering_CameraRenderRequestContext_GetHasDirectionalShadow", reinterpret_cast(&InternalCall_Rendering_CameraRenderRequestContext_GetHasDirectionalShadow)); @@ -4049,6 +4312,8 @@ void MonoScriptRuntime::Shutdown() { GetManagedScriptableRenderContextNextHandle() = 1; GetManagedCameraRenderRequestContextRegistry().clear(); GetManagedCameraRenderRequestContextNextHandle() = 1; + GetManagedScriptableRenderPipelinePlanningContextRegistry().clear(); + GetManagedScriptableRenderPipelinePlanningContextNextHandle() = 1; ClearManagedInstances(); ClearExternalManagedObjects(); ClearClassCache(); @@ -4066,10 +4331,12 @@ void MonoScriptRuntime::Shutdown() { m_scriptableRenderPipelineClass = nullptr; m_scriptableRenderContextClass = nullptr; m_cameraRenderRequestContextClass = nullptr; + m_scriptableRenderPipelinePlanningContextClass = nullptr; m_serializeFieldAttributeClass = nullptr; m_gameObjectConstructor = nullptr; m_scriptableRenderContextConstructor = nullptr; m_cameraRenderRequestContextConstructor = nullptr; + m_scriptableRenderPipelinePlanningContextConstructor = nullptr; m_managedGameObjectUUIDField = nullptr; m_gameObjectUUIDField = nullptr; m_scriptComponentUUIDField = nullptr; @@ -4863,6 +5130,27 @@ bool MonoScriptRuntime::DiscoverScriptClasses() { return false; } + m_scriptableRenderPipelinePlanningContextClass = mono_class_from_name( + m_coreImage, + kManagedRenderingNamespace, + "ScriptableRenderPipelinePlanningContext"); + if (!m_scriptableRenderPipelinePlanningContextClass) { + SetError( + "Failed to locate the managed ScriptableRenderPipelinePlanningContext type."); + return false; + } + + m_scriptableRenderPipelinePlanningContextConstructor = + mono_class_get_method_from_name( + m_scriptableRenderPipelinePlanningContextClass, + ".ctor", + 1); + if (!m_scriptableRenderPipelinePlanningContextConstructor) { + SetError( + "Failed to locate the managed ScriptableRenderPipelinePlanningContext constructor."); + return false; + } + m_serializeFieldAttributeClass = mono_class_from_name( m_coreImage, m_settings.baseNamespace.c_str(), @@ -5458,6 +5746,46 @@ MonoScriptRuntime::CreateManagedCameraRenderRequestContext( return contextObject; } +MonoObject* +MonoScriptRuntime::CreateManagedScriptableRenderPipelinePlanningContext( + uint64_t nativeHandle) { + if (!m_initialized || + nativeHandle == 0 || + m_scriptableRenderPipelinePlanningContextClass == nullptr || + m_scriptableRenderPipelinePlanningContextConstructor == nullptr) { + return nullptr; + } + + SetCurrentDomain(); + + MonoObject* const contextObject = + mono_object_new( + m_appDomain, + m_scriptableRenderPipelinePlanningContextClass); + if (contextObject == nullptr) { + SetError( + "Mono failed to allocate a managed ScriptableRenderPipelinePlanningContext."); + return nullptr; + } + + void* args[1]; + uint64_t nativeHandleArgument = nativeHandle; + args[0] = &nativeHandleArgument; + + MonoObject* exception = nullptr; + mono_runtime_invoke( + m_scriptableRenderPipelinePlanningContextConstructor, + contextObject, + args, + &exception); + if (exception != nullptr) { + RecordException(exception); + return nullptr; + } + + return contextObject; +} + MonoScriptRuntime::InstanceData* MonoScriptRuntime::FindInstance(const ScriptRuntimeContext& context) { const auto it = m_instances.find(InstanceKey{context.gameObjectUUID, context.scriptComponentUUID}); return it != m_instances.end() ? &it->second : nullptr; diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index 7bb1ec58..913fa20c 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -182,6 +182,7 @@ set(XCENGINE_SCRIPT_CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderContext.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipeline.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Rendering/Core/CameraRenderRequestContext.cs ) diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index e1bebd5b..757f1772 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -899,6 +899,41 @@ namespace Gameplay } } + internal sealed class ManagedFeaturePlannedPostProcessRendererFeature + : ScriptableRendererFeature + { + public override void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + if (context == null || + context.IsStageRequested(CameraFrameStage.PostProcess)) + { + return; + } + + context.RequestFullscreenStage( + CameraFrameStage.PostProcess, + CameraFrameColorSource.MainSceneColor); + } + } + + internal sealed class ManagedFeaturePlannedPostProcessRendererData + : ProbeRendererData + { + protected override ScriptableRenderer CreateProbeRenderer() + { + return new ProbeSceneRenderer(); + } + + protected override ScriptableRendererFeature[] CreateRendererFeatures() + { + return new ScriptableRendererFeature[] + { + new ManagedFeaturePlannedPostProcessRendererFeature() + }; + } + } + internal sealed class ManagedRenderContextCameraDataProbeRendererData : ProbeRendererData { @@ -1206,6 +1241,52 @@ namespace Gameplay } } + public sealed class ManagedFeaturePlannedPostProcessProbeAsset + : UniversalRenderPipelineAsset + { + public ManagedFeaturePlannedPostProcessProbeAsset() + { + rendererDataList = + new ScriptableRendererData[] + { + new ManagedFeaturePlannedPostProcessRendererData() + }; + } + } + + public sealed class ManagedClearedPostProcessRenderPipelineProbeAsset + : UniversalRenderPipelineAsset + { + public ManagedClearedPostProcessRenderPipelineProbeAsset() + { + rendererDataList = + new ScriptableRendererData[] + { + new ManagedPlannedFullscreenRenderPipelineProbeRendererData() + }; + } + + protected override void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + if (context == null) + { + return; + } + + context.ClearFullscreenStage( + CameraFrameStage.PostProcess); + context.RequestFullscreenStage( + CameraFrameStage.FinalOutput, + CameraFrameColorSource.MainSceneColor); + } + + protected override FinalColorSettings GetDefaultFinalColorSettings() + { + return ProbeFinalColorSettingsFactory.Create(); + } + } + internal sealed class ManagedCameraRequestConfiguredRendererData : UniversalRendererData { diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index cd7db4be..0ed74740 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -389,6 +389,38 @@ namespace XCEngine internal static extern int Rendering_ScriptableRenderContext_GetStage( ulong nativeHandle); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool + Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested( + ulong nativeHandle, + int stage); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern int + Rendering_ScriptableRenderPipelinePlanningContext_GetStageColorSource( + ulong nativeHandle, + int stage); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool + Rendering_ScriptableRenderPipelinePlanningContext_GetStageUsesGraphManagedOutputColor( + ulong nativeHandle, + int stage); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool + Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage( + ulong nativeHandle, + int stage, + int source, + bool usesGraphManagedOutputColor); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void + Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage( + ulong nativeHandle, + int stage); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern int Rendering_ScriptableRenderContext_GetStageColorSource( diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs index 4698d900..0a695479 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelineAsset.cs @@ -13,6 +13,12 @@ namespace XCEngine.Rendering ReleaseRuntimeResources(); } + internal void ConfigureCameraFramePlanInstance( + ScriptableRenderPipelinePlanningContext context) + { + ConfigureCameraFramePlan(context); + } + protected virtual ScriptableRenderPipeline CreatePipeline() { return null; @@ -23,6 +29,11 @@ namespace XCEngine.Rendering { } + protected virtual void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + } + protected virtual FinalColorSettings GetDefaultFinalColorSettings() { return FinalColorSettings.CreateDefault(); diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs new file mode 100644 index 00000000..e2955b8e --- /dev/null +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipelinePlanningContext.cs @@ -0,0 +1,64 @@ +using XCEngine; + +namespace XCEngine.Rendering +{ + public sealed class ScriptableRenderPipelinePlanningContext + { + private readonly ulong m_nativeHandle; + + internal ScriptableRenderPipelinePlanningContext( + ulong nativeHandle) + { + m_nativeHandle = nativeHandle; + } + + public bool IsStageRequested( + CameraFrameStage stage) + { + return InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_IsStageRequested( + m_nativeHandle, + (int)stage); + } + + public CameraFrameColorSource GetStageColorSource( + CameraFrameStage stage) + { + return (CameraFrameColorSource)InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_GetStageColorSource( + m_nativeHandle, + (int)stage); + } + + public bool UsesGraphManagedOutputColor( + CameraFrameStage stage) + { + return InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_GetStageUsesGraphManagedOutputColor( + m_nativeHandle, + (int)stage); + } + + public bool RequestFullscreenStage( + CameraFrameStage stage, + CameraFrameColorSource source, + bool usesGraphManagedOutputColor = false) + { + return InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_RequestFullscreenStage( + m_nativeHandle, + (int)stage, + (int)source, + usesGraphManagedOutputColor); + } + + public void ClearFullscreenStage( + CameraFrameStage stage) + { + InternalCalls + .Rendering_ScriptableRenderPipelinePlanningContext_ClearFullscreenStage( + m_nativeHandle, + (int)stage); + } + } +} diff --git a/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererData.cs b/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererData.cs index 751bbfd5..2fa8dfea 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererData.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererData.cs @@ -89,6 +89,28 @@ namespace XCEngine.Rendering.Universal } } + internal void ConfigureCameraFramePlanInstance( + ScriptableRenderPipelinePlanningContext context) + { + ConfigureCameraFramePlan(context); + + ScriptableRendererFeature[] rendererFeatures = + GetRendererFeatures(); + for (int i = 0; i < rendererFeatures.Length; ++i) + { + ScriptableRendererFeature rendererFeature = + rendererFeatures[i]; + if (rendererFeature == null || + !rendererFeature.isActive) + { + continue; + } + + rendererFeature.ConfigureCameraFramePlan( + context); + } + } + protected virtual ScriptableRenderer CreateRenderer() { return null; @@ -99,6 +121,11 @@ namespace XCEngine.Rendering.Universal { } + protected virtual void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + } + protected virtual string GetPipelineRendererAssetKey() { return string.Empty; diff --git a/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererFeature.cs b/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererFeature.cs index a9c58bfd..7ef8e86d 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererFeature.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Universal/ScriptableRendererFeature.cs @@ -33,6 +33,11 @@ namespace XCEngine.Rendering.Universal { } + public virtual void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + } + public virtual void AddRenderPasses( ScriptableRenderer renderer, RenderingData renderingData) diff --git a/managed/XCEngine.ScriptCore/Rendering/Universal/UniversalRenderPipelineAsset.cs b/managed/XCEngine.ScriptCore/Rendering/Universal/UniversalRenderPipelineAsset.cs index 29797908..6d1867fe 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Universal/UniversalRenderPipelineAsset.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Universal/UniversalRenderPipelineAsset.cs @@ -32,6 +32,18 @@ namespace XCEngine.Rendering.Universal } } + protected override void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + ScriptableRendererData resolvedRendererData = + GetDefaultRendererData(); + if (resolvedRendererData != null) + { + resolvedRendererData.ConfigureCameraFramePlanInstance( + context); + } + } + protected override string GetPipelineRendererAssetKey() { ScriptableRendererData resolvedRendererData = diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 227747ac..6a424766 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -1312,9 +1312,9 @@ TEST_F( EXPECT_FALSE(hasUniversalContextRecordShaderVectorFullscreenPassExtension); EXPECT_FALSE(hasUniversalRequestContextHasDirectionalShadowExtension); EXPECT_FALSE(hasUniversalRequestContextClearDirectionalShadowExtension); - EXPECT_FALSE(hasPublicPipelineAssetConfigureCameraFramePlan); - EXPECT_FALSE(hasPlanningContextType); - EXPECT_FALSE(hasRendererFeatureConfigureCameraFramePlan); + EXPECT_TRUE(hasPublicPipelineAssetConfigureCameraFramePlan); + EXPECT_TRUE(hasPlanningContextType); + EXPECT_TRUE(hasRendererFeatureConfigureCameraFramePlan); EXPECT_FALSE(hasRendererRecordingContextType); EXPECT_FALSE(hasRendererCameraRequestContextType); EXPECT_FALSE(hasRendererBackedRenderPipelineAssetType); @@ -3334,6 +3334,142 @@ TEST_F( EXPECT_TRUE(plan.IsFinalOutputStageValid()); } +TEST_F( + MonoScriptRuntimeTest, + ManagedRendererFeatureConfiguresCameraFramePlanThroughPlanningContext) { + Scene* runtimeScene = + CreateScene("ManagedFeatureFramePlanningScene"); + GameObject* cameraObject = runtimeScene->CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + ASSERT_NE(camera, nullptr); + camera->SetPrimary(true); + + TestRenderDevice device; + TestRenderCommandList commandList; + TestRenderCommandQueue commandQueue; + TestRenderResourceView colorView( + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::R8G8B8A8_UNorm); + TestRenderResourceView depthView( + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::D32_Float); + + const XCEngine::Rendering::RenderContext context = + CreateRenderContext( + device, + commandList, + commandQueue); + XCEngine::Rendering::RenderSurface surface(64u, 64u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + descriptor = { + "GameScripts", + "Gameplay", + "ManagedFeaturePlannedPostProcessProbeAsset" + }; + auto asset = + std::make_shared< + XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset>( + descriptor); + + XCEngine::Rendering::CameraRenderRequest request = {}; + request.scene = runtimeScene; + request.camera = camera; + request.context = context; + request.surface = surface; + + XCEngine::Rendering::RenderPipelineHost host(asset); + const std::vector plans = + host.BuildFramePlans({ request }); + + ASSERT_EQ(plans.size(), 1u); + const XCEngine::Rendering::CameraFramePlan& plan = plans[0]; + + EXPECT_TRUE( + plan.IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + EXPECT_EQ( + plan.ResolveStageColorSource( + XCEngine::Rendering::CameraFrameStage::PostProcess), + XCEngine::Rendering::CameraFrameColorSource::MainSceneColor); + EXPECT_FALSE( + plan.UsesGraphManagedOutputColor( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + EXPECT_TRUE(plan.IsPostProcessStageValid()); +} + +TEST_F( + MonoScriptRuntimeTest, + ManagedRenderPipelineAssetPlanningContextCanOverridePipelineStageHeuristic) { + Scene* runtimeScene = + CreateScene("ManagedAssetFramePlanningOverrideScene"); + GameObject* cameraObject = runtimeScene->CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + ASSERT_NE(camera, nullptr); + camera->SetPrimary(true); + + TestRenderDevice device; + TestRenderCommandList commandList; + TestRenderCommandQueue commandQueue; + TestRenderResourceView colorView( + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::R8G8B8A8_UNorm); + TestRenderResourceView depthView( + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::D32_Float); + + const XCEngine::Rendering::RenderContext context = + CreateRenderContext( + device, + commandList, + commandQueue); + XCEngine::Rendering::RenderSurface surface(64u, 64u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + + const XCEngine::Rendering::Pipelines::ManagedRenderPipelineAssetDescriptor + descriptor = { + "GameScripts", + "Gameplay", + "ManagedClearedPostProcessRenderPipelineProbeAsset" + }; + auto asset = + std::make_shared< + XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset>( + descriptor); + + XCEngine::Rendering::CameraRenderRequest request = {}; + request.scene = runtimeScene; + request.camera = camera; + request.context = context; + request.surface = surface; + + XCEngine::Rendering::RenderPipelineHost host(asset); + const std::vector plans = + host.BuildFramePlans({ request }); + + ASSERT_EQ(plans.size(), 1u); + const XCEngine::Rendering::CameraFramePlan& plan = plans[0]; + + EXPECT_FALSE( + plan.IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + EXPECT_TRUE( + plan.IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::FinalOutput)); + EXPECT_EQ( + plan.ResolveStageColorSource( + XCEngine::Rendering::CameraFrameStage::FinalOutput), + XCEngine::Rendering::CameraFrameColorSource::MainSceneColor); + EXPECT_TRUE(plan.IsFinalOutputStageValid()); +} + TEST_F( MonoScriptRuntimeTest, ManagedRenderPipelineAssetConfiguresCameraRequestsThroughRequestContext) {