diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 2b40c4a4..d6443437 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -507,6 +507,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Builtin/BuiltinPassMetadataUtils.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/CameraFramePlan.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/CameraStackFramePlan.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/CameraFrameStage.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/CameraRenderer.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/DirectionalShadowExecutionState.h @@ -575,6 +576,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraFramePlan.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraStackFramePlan.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/RenderPipelineHost.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/SceneRenderSequence.cpp diff --git a/engine/include/XCEngine/Rendering/AGENTS.md b/engine/include/XCEngine/Rendering/AGENTS.md index 021b00f6..196ceeaa 100644 --- a/engine/include/XCEngine/Rendering/AGENTS.md +++ b/engine/include/XCEngine/Rendering/AGENTS.md @@ -62,6 +62,9 @@ Unity 兼容的公开命名、对象所有权和扩展点。 具体后端头。 - 新增 camera-stack、renderer-index、shadow、depth、post-process 或 final output 行为时,不要绕过 `CameraFramePlanBuilder` 或 `SceneRenderRequestPlanner`。 +- 修改 camera stack 行为时,要保持 `CameraRenderRequest.cameraStackId`、`CameraStackFramePlan`、 + `RenderPipelineHost::BuildStackFramePlans` 和 `SceneRenderer::BuildStackFramePlans` 同步。 + `CameraFramePlan` 是每个 camera 的叶子计划;`CameraStackFramePlan` 是顶层渲染生命周期根。 - 不要在 native 长生命周期对象里保存 raw managed object pointers 或 managed context handles。 需要持久化时,通过现有 external managed object handle 系统 retain。 - Managed URP public APIs 不要依赖 `CameraFrameGraph` 或 `NativeSceneRecorder` 这类 native @@ -97,8 +100,9 @@ Unity 兼容的公开命名、对象所有权和扩展点。 built-in forward pipeline policy,而不是作为 URP-declared work 的 backend executor。 - 目标所有权模型是:URP 决定渲染什么、何时渲染、哪些 renderer lists/passes/features 激活,以及 哪些 stages 存在;native 只执行这些声明,不夹带 built-in pipeline policy。 -- `CameraFramePlanBuilder` 现在只负责从 request 构造 `CameraFramePlan` 并委托所选 - `RenderPipelineAsset::ConfigureCameraFramePlan`。它不再在 asset hook 之后追加通用 legacy +- `CameraFramePlanBuilder` 现在负责从 request 构造 per-camera `CameraFramePlan`,再按 + `CameraRenderRequest.cameraStackId` 收口为 `CameraStackFramePlan`。它委托所选 + `RenderPipelineAsset::ConfigureCameraFramePlan` 配置叶子计划,不再在 asset hook 之后追加通用 legacy fullscreen stage heuristic;默认 native fullscreen/final-output 行为应留在 native asset policy 内部。 - Hidden fallback 很危险。如果 managed URP stage 声明支持但无法 record,失败必须可见。不要静默用 default built-in path 画同一个 stage,然后称之为 URP。 @@ -132,10 +136,12 @@ Native renderer 现在是 camera frame planner 加 RenderGraph executor,manage 叠在 native scene draw backend 之上。 - `SceneRenderer` 是 scene-level convenience entry point。它的公开管线选择根是 `RenderPipelineAsset`; - 它通过 `SceneRenderRequestPlanner` 收集 camera requests,通过 `RenderPipelineHost` 构建 frame plans, - 然后渲染排序后的 camera plans。 + 它通过 `SceneRenderRequestPlanner` 收集 camera requests,通过 `RenderPipelineHost` 构建 + `CameraStackFramePlan`,然后以 camera stack 为根渲染。旧的 `BuildFramePlans`/flat-plan `Render` + API 只是兼容包装。 - `RenderPipelineHost` 拥有一个 `CameraFramePlanBuilder` 和一个 `CameraRenderer`。它是从 - `CameraRenderRequest` 到 `CameraFramePlan` 的常规 native bridge,并沿用同一个 asset selection root。 + `CameraRenderRequest` 到 `CameraStackFramePlan` 的常规 native bridge,并沿用同一个 asset selection root。 + Stack 渲染完成后由所选 `RenderPipeline` 接收 `FinishCameraStackRendering`,用于 URP stack 生命周期收尾。 - `CameraRenderer` 拥有 asset 创建出的所选 `RenderPipeline`,提取 `RenderSceneData`,解析 directional shadow execution,把全部 frame stages 记录进 native `RenderGraph`,编译 graph 并执行。 - `RenderPipelineAsset` 是 native asset contract。它创建 pipeline,配置 pipeline 实例,配置 request @@ -265,6 +271,9 @@ package。 - `RendererFramePlan` 不得只是 pass 对象引用列表。`ScriptableRenderPass.CreateFramePlanSnapshot` 是冻结 pass 状态的边界;新增 mutable pass state 时必须确认它能被 frame-plan snapshot 捕获,避免后续 camera planning 改写前一个 camera 的 recording。 +- `OnFinishCameraStackRendering` 是 camera-stack 生命周期回调,不是 per-stage 或 per-camera-frame 回调。 + `ScriptableRenderer` 可以在 `RecordRendererFromFramePlan` 时登记待收尾的 `RendererFramePlan`,但只能由 + native `CameraStackFramePlan` 渲染完成后的 `FinishCameraStackRendering` 触发实际回调。 - URP features 的 `AddRenderPasses` 是 per-camera 声明点,不是 per-stage 回调。不要通过 stage 数据分阶段重复 enqueue;`AddRenderPasses`/`SetupRenderPasses` 必须使用 `RendererPassQueueData`, 而不是 recording 阶段的 `RenderingData`。pass 所属 stage 应由 `RenderPassEvent -> RendererBlock` @@ -377,6 +386,8 @@ Scene data 每个 camera frame 提取一次,然后由 pipeline 调整。 authoring 还未公开。 - `UniversalPostProcessBlock` 仍保留 post-process source promotion helper;实际 post-process stage 由 active pass queue 中的 features/passes 声明。 +- 当前 camera stack 归属仍来自 `SceneRenderRequestPlanner` 对 base/overlay 请求流的 `cameraStackId` + 分组;引擎相机数据模型还没有 Unity `cameraStack` 那种显式 per-base overlay 列表。 - Render-pipeline selection 已切到 `AssetRef` 作为根身份,并且 managed SRP descriptor 已保存 ScriptableObject graph snapshot;完整 editor/importer `.asset` 持久化和 sub-asset authoring 仍未完成。 - 当前 shadow support 是单个 main directional shadow path,没有 cascades。 @@ -411,6 +422,10 @@ Scene data 每个 camera frame 提取一次,然后由 pipeline 调整。 - URP stage planning 已收口到 renderer active pass queue 派生的 per-`framePlanId` `RendererFramePlan` 和 stage manifest。Stage support 和 stage recording 现在消费 planning 阶段保存的同一份冻结 plan, 关闭了 feature planning hook、support probe、recording 和各 stage 分别重建 pass queue 的重复事实源。 +- 顶层 native orchestration 已从 flat camera frame list 收口为 `CameraStackFramePlan`。`SceneRenderer` + 和 `RenderPipelineHost` 的真实渲染入口以 camera stack 为根;flat `CameraFramePlan` APIs 保留为兼容包装。 +- URP `OnFinishCameraStackRendering` 已改为 stack-end 语义:recording 阶段只登记待收尾 pass,native + `CameraStackFramePlan` 渲染完成后再通过 pipeline/stage-recorder bridge 触发。 - `CameraFramePlanBuilder` 已移除对 fullscreen stages 的通用尾部自动补齐;selected `RenderPipelineAsset` 现在是 native plan policy 的唯一入口,managed URP path 不再保留第二个 fullscreen stage planner。 diff --git a/engine/include/XCEngine/Rendering/Execution/CameraStackFramePlan.h b/engine/include/XCEngine/Rendering/Execution/CameraStackFramePlan.h new file mode 100644 index 00000000..d573b112 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Execution/CameraStackFramePlan.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine { +namespace Rendering { + +struct CameraStackFramePlan { + uint64_t stackPlanId = 0u; + uint64_t cameraStackId = 0u; + std::vector cameraPlans = {}; + + static CameraStackFramePlan FromCameraPlans( + uint64_t cameraStackId, + std::vector cameraPlans); + + bool IsValid() const; + bool HasBaseCamera() const; + bool IsOverlayOnly() const; + const CameraFramePlan* GetBaseCameraPlan() const; + const CameraFramePlan* GetSortKeyPlan() const; +}; + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h b/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h index fbeeb402..e539279e 100644 --- a/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h +++ b/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -22,9 +23,13 @@ public: std::vector BuildFramePlans( const std::vector& requests); + std::vector BuildStackFramePlans( + const std::vector& requests); bool Render(const CameraFramePlan& plan); bool Render(const std::vector& plans); + bool Render(const CameraStackFramePlan& stackPlan); + bool Render(const std::vector& stackPlans); private: CameraRenderer m_cameraRenderer; diff --git a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h index 424b706f..54104e93 100644 --- a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h +++ b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -28,9 +29,16 @@ public: Components::CameraComponent* overrideCamera, const RenderContext& context, const RenderSurface& surface); + std::vector BuildStackFramePlans( + const Components::Scene& scene, + Components::CameraComponent* overrideCamera, + const RenderContext& context, + const RenderSurface& surface); bool Render(const CameraFramePlan& plan); bool Render(const std::vector& plans); + bool Render(const CameraStackFramePlan& stackPlan); + bool Render(const std::vector& stackPlans); bool Render( const Components::Scene& scene, Components::CameraComponent* overrideCamera, diff --git a/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h b/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h index dc7b1934..0faccf78 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h +++ b/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h @@ -60,6 +60,8 @@ public: const RenderContext& context, const RenderSurface& surface, const RenderSceneData& sceneData) override; + void FinishCameraStackRendering( + const CameraStackFramePlan& stackPlan) override; void ConfigureRenderSceneData( const CameraFramePlan& plan, RenderSceneData& sceneData) const override; diff --git a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h index ca31f2f4..3e2fbf87 100644 --- a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h +++ b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h @@ -27,6 +27,7 @@ struct CameraRenderRequest { FinalOutputRenderRequest finalOutput; ResolvedFinalColorPolicy finalColorPolicy = {}; ObjectIdRenderRequest objectId; + uint64_t cameraStackId = 0u; float cameraDepth = 0.0f; uint8_t cameraStackOrder = 0; int32_t rendererIndex = -1; diff --git a/engine/include/XCEngine/Rendering/RenderPipeline.h b/engine/include/XCEngine/Rendering/RenderPipeline.h index 3f0184c6..0d905c8e 100644 --- a/engine/include/XCEngine/Rendering/RenderPipeline.h +++ b/engine/include/XCEngine/Rendering/RenderPipeline.h @@ -23,6 +23,7 @@ namespace Rendering { class RenderGraphBuilder; struct CameraFramePlan; +struct CameraStackFramePlan; struct DirectionalShadowExecutionState; struct DirectionalShadowSurfaceAllocation; @@ -91,6 +92,8 @@ public: const RenderPipelineStageRenderGraphContext&) { return false; } + virtual void FinishCameraStackRendering( + const CameraStackFramePlan&) {} }; class RenderPipelineBackend { @@ -142,6 +145,8 @@ public: const CameraFramePlan& plan, const DirectionalShadowSurfaceAllocation& shadowAllocation, DirectionalShadowExecutionState& shadowState) const; + virtual void FinishCameraStackRendering( + const CameraStackFramePlan&) {} void SetCameraFrameStandalonePass( CameraFrameStage stage, diff --git a/engine/src/Rendering/Execution/CameraStackFramePlan.cpp b/engine/src/Rendering/Execution/CameraStackFramePlan.cpp new file mode 100644 index 00000000..6681f8e5 --- /dev/null +++ b/engine/src/Rendering/Execution/CameraStackFramePlan.cpp @@ -0,0 +1,89 @@ +#include + +#include +#include + +namespace XCEngine { +namespace Rendering { + +namespace { + +uint64_t AllocateCameraStackFramePlanId() { + static std::atomic s_nextStackPlanId{ 1u }; + uint64_t stackPlanId = + s_nextStackPlanId.fetch_add( + 1u, + std::memory_order_relaxed); + if (stackPlanId == 0u) { + stackPlanId = + s_nextStackPlanId.fetch_add( + 1u, + std::memory_order_relaxed); + } + + return stackPlanId; +} + +bool IsBaseCameraPlan(const CameraFramePlan& plan) { + return plan.request.cameraStackOrder == 0u; +} + +} // namespace + +CameraStackFramePlan CameraStackFramePlan::FromCameraPlans( + uint64_t cameraStackId, + std::vector cameraPlans) { + CameraStackFramePlan stackPlan = {}; + stackPlan.stackPlanId = AllocateCameraStackFramePlanId(); + stackPlan.cameraStackId = cameraStackId; + stackPlan.cameraPlans = std::move(cameraPlans); + return stackPlan; +} + +bool CameraStackFramePlan::IsValid() const { + if (stackPlanId == 0u || + cameraPlans.empty()) { + return false; + } + + for (const CameraFramePlan& plan : cameraPlans) { + if (!plan.IsValid()) { + return false; + } + } + + return true; +} + +bool CameraStackFramePlan::HasBaseCamera() const { + return GetBaseCameraPlan() != nullptr; +} + +bool CameraStackFramePlan::IsOverlayOnly() const { + return !cameraPlans.empty() && + !HasBaseCamera(); +} + +const CameraFramePlan* CameraStackFramePlan::GetBaseCameraPlan() const { + for (const CameraFramePlan& plan : cameraPlans) { + if (IsBaseCameraPlan(plan)) { + return &plan; + } + } + + return nullptr; +} + +const CameraFramePlan* CameraStackFramePlan::GetSortKeyPlan() const { + if (const CameraFramePlan* const basePlan = GetBaseCameraPlan(); + basePlan != nullptr) { + return basePlan; + } + + return cameraPlans.empty() + ? nullptr + : &cameraPlans.front(); +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Execution/RenderPipelineHost.cpp b/engine/src/Rendering/Execution/RenderPipelineHost.cpp index 30816370..23e61049 100644 --- a/engine/src/Rendering/Execution/RenderPipelineHost.cpp +++ b/engine/src/Rendering/Execution/RenderPipelineHost.cpp @@ -3,6 +3,9 @@ #include "Rendering/Planning/CameraFramePlanBuilder.h" #include "Rendering/Planning/SceneRenderRequestUtils.h" +#include +#include + namespace XCEngine { namespace Rendering { @@ -16,6 +19,23 @@ bool CompareCameraFramePlans( rhs.request); } +bool CompareCameraStackFramePlans( + const CameraStackFramePlan& lhs, + const CameraStackFramePlan& rhs) { + const CameraFramePlan* const lhsSortKeyPlan = + lhs.GetSortKeyPlan(); + const CameraFramePlan* const rhsSortKeyPlan = + rhs.GetSortKeyPlan(); + if (lhsSortKeyPlan == nullptr || + rhsSortKeyPlan == nullptr) { + return false; + } + + return CompareCameraFramePlans( + *lhsSortKeyPlan, + *rhsSortKeyPlan); +} + } // namespace RenderPipelineHost::RenderPipelineHost() @@ -42,8 +62,22 @@ std::vector RenderPipelineHost::BuildFramePlans( : std::vector(); } +std::vector RenderPipelineHost::BuildStackFramePlans( + const std::vector& requests) { + return m_framePlanBuilder != nullptr + ? m_framePlanBuilder->BuildStackPlans( + requests, + GetPipelineAsset()) + : std::vector(); +} + bool RenderPipelineHost::Render(const CameraFramePlan& plan) { - return m_cameraRenderer.Render(plan); + std::vector plans = {}; + plans.push_back(plan); + return Render( + CameraStackFramePlan::FromCameraPlans( + plan.request.cameraStackId, + std::move(plans))); } bool RenderPipelineHost::Render(const std::vector& plans) { @@ -63,8 +97,20 @@ bool RenderPipelineHost::Render(const std::vector& plans) { sortedPlans.end(), CompareCameraFramePlans); + return Render( + m_framePlanBuilder != nullptr + ? m_framePlanBuilder->BuildStackPlansFromFramePlans( + std::move(sortedPlans)) + : std::vector()); +} + +bool RenderPipelineHost::Render(const CameraStackFramePlan& stackPlan) { + if (!stackPlan.IsValid()) { + return false; + } + bool rendered = false; - for (const CameraFramePlan& plan : sortedPlans) { + for (const CameraFramePlan& plan : stackPlan.cameraPlans) { if (!m_cameraRenderer.Render(plan)) { return false; } @@ -72,6 +118,44 @@ bool RenderPipelineHost::Render(const std::vector& plans) { rendered = true; } + if (rendered) { + if (RenderPipeline* const pipeline = GetPipeline(); + pipeline != nullptr) { + pipeline->FinishCameraStackRendering(stackPlan); + } + } + + return rendered; +} + +bool RenderPipelineHost::Render(const std::vector& stackPlans) { + if (stackPlans.empty()) { + return false; + } + + for (const CameraStackFramePlan& stackPlan : stackPlans) { + if (!stackPlan.IsValid()) { + return false; + } + } + + std::vector sortedStackPlans = + stackPlans; + std::stable_sort( + sortedStackPlans.begin(), + sortedStackPlans.end(), + CompareCameraStackFramePlans); + + bool rendered = false; + for (const CameraStackFramePlan& stackPlan : + sortedStackPlans) { + if (!Render(stackPlan)) { + return false; + } + + rendered = true; + } + return rendered; } diff --git a/engine/src/Rendering/Execution/SceneRenderer.cpp b/engine/src/Rendering/Execution/SceneRenderer.cpp index a677281d..0fb0e3d5 100644 --- a/engine/src/Rendering/Execution/SceneRenderer.cpp +++ b/engine/src/Rendering/Execution/SceneRenderer.cpp @@ -34,6 +34,21 @@ std::vector SceneRenderer::BuildFramePlans( return m_pipelineHost.BuildFramePlans(requests); } +std::vector SceneRenderer::BuildStackFramePlans( + const Components::Scene& scene, + Components::CameraComponent* overrideCamera, + const RenderContext& context, + const RenderSurface& surface) { + const std::vector requests = + m_requestPlanner.BuildRequests( + scene, + overrideCamera, + context, + surface, + m_pipelineHost.GetPipelineAsset()); + return m_pipelineHost.BuildStackFramePlans(requests); +} + bool SceneRenderer::Render(const CameraFramePlan& plan) { return m_pipelineHost.Render(plan); } @@ -42,12 +57,20 @@ bool SceneRenderer::Render(const std::vector& plans) { return m_pipelineHost.Render(plans); } +bool SceneRenderer::Render(const CameraStackFramePlan& stackPlan) { + return m_pipelineHost.Render(stackPlan); +} + +bool SceneRenderer::Render(const std::vector& stackPlans) { + return m_pipelineHost.Render(stackPlans); +} + bool SceneRenderer::Render( const Components::Scene& scene, Components::CameraComponent* overrideCamera, const RenderContext& context, const RenderSurface& surface) { - return Render(BuildFramePlans(scene, overrideCamera, context, surface)); + return Render(BuildStackFramePlans(scene, overrideCamera, context, surface)); } } // namespace Rendering diff --git a/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp index 1238aec9..d1be5778 100644 --- a/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp +++ b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp @@ -193,6 +193,13 @@ bool ScriptableRenderPipelineHost::Render( m_pipelineBackend->Render(context, surface, sceneData); } +void ScriptableRenderPipelineHost::FinishCameraStackRendering( + const CameraStackFramePlan& stackPlan) { + if (m_stageRecorder != nullptr) { + m_stageRecorder->FinishCameraStackRendering(stackPlan); + } +} + void ScriptableRenderPipelineHost::ConfigureRenderSceneData( const CameraFramePlan& plan, RenderSceneData& sceneData) const { diff --git a/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp b/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp index 1feb3033..f12ff138 100644 --- a/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp +++ b/engine/src/Rendering/Planning/CameraFramePlanBuilder.cpp @@ -2,6 +2,8 @@ #include "Rendering/RenderPipelineAsset.h" +#include + namespace XCEngine { namespace Rendering { @@ -13,6 +15,19 @@ std::vector CameraFramePlanBuilder::BuildPlans( return plans; } +std::vector CameraFramePlanBuilder::BuildStackPlans( + const std::vector& requests, + const RenderPipelineAsset* pipelineAsset) { + std::vector plans = CreatePlansFromRequests(requests); + ConfigurePlans(plans, pipelineAsset); + return CreateStackPlansFromPlans(std::move(plans)); +} + +std::vector CameraFramePlanBuilder::BuildStackPlansFromFramePlans( + std::vector plans) const { + return CreateStackPlansFromPlans(std::move(plans)); +} + std::vector CameraFramePlanBuilder::CreatePlansFromRequests( const std::vector& requests) const { std::vector plans = {}; @@ -24,6 +39,43 @@ std::vector CameraFramePlanBuilder::CreatePlansFromRequests( return plans; } +std::vector CameraFramePlanBuilder::CreateStackPlansFromPlans( + std::vector plans) const { + std::vector stackPlans = {}; + if (plans.empty()) { + return stackPlans; + } + + stackPlans.reserve(plans.size()); + + for (CameraFramePlan& plan : plans) { + const uint64_t cameraStackId = plan.request.cameraStackId; + if (cameraStackId != 0u) { + bool appendedToExistingStack = false; + for (CameraStackFramePlan& stackPlan : stackPlans) { + if (stackPlan.cameraStackId == cameraStackId) { + stackPlan.cameraPlans.push_back(std::move(plan)); + appendedToExistingStack = true; + break; + } + } + + if (appendedToExistingStack) { + continue; + } + } + + std::vector stackCameraPlans = {}; + stackCameraPlans.push_back(std::move(plan)); + stackPlans.push_back( + CameraStackFramePlan::FromCameraPlans( + cameraStackId, + std::move(stackCameraPlans))); + } + + return stackPlans; +} + void CameraFramePlanBuilder::ConfigurePlans( std::vector& plans, const RenderPipelineAsset* pipelineAsset) const { diff --git a/engine/src/Rendering/Planning/CameraFramePlanBuilder.h b/engine/src/Rendering/Planning/CameraFramePlanBuilder.h index 28a2515c..cf9e0eda 100644 --- a/engine/src/Rendering/Planning/CameraFramePlanBuilder.h +++ b/engine/src/Rendering/Planning/CameraFramePlanBuilder.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -19,10 +20,17 @@ public: std::vector BuildPlans( const std::vector& requests, const RenderPipelineAsset* pipelineAsset); + std::vector BuildStackPlans( + const std::vector& requests, + const RenderPipelineAsset* pipelineAsset); + std::vector BuildStackPlansFromFramePlans( + std::vector plans) const; private: std::vector CreatePlansFromRequests( const std::vector& requests) const; + std::vector CreateStackPlansFromPlans( + std::vector plans) const; void ConfigurePlans( std::vector& plans, const RenderPipelineAsset* pipelineAsset) const; diff --git a/engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp b/engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp index f57848c4..ce0371d2 100644 --- a/engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp +++ b/engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp @@ -56,6 +56,8 @@ std::vector SceneRenderRequestPlanner::BuildRequests( CollectCameras(scene, overrideCamera); size_t renderedBaseCameraCount = 0; + uint64_t nextCameraStackId = 1u; + uint64_t activeCameraStackId = 0u; for (Components::CameraComponent* camera : cameras) { CameraRenderRequest request; if (!SceneRenderRequestUtils::BuildCameraRenderRequest( @@ -69,6 +71,12 @@ std::vector SceneRenderRequestPlanner::BuildRequests( continue; } + if (camera->GetStackType() == Components::CameraStackType::Base || + activeCameraStackId == 0u) { + activeCameraStackId = nextCameraStackId++; + } + request.cameraStackId = activeCameraStackId; + if (pipelineAsset != nullptr) { pipelineAsset->ConfigureCameraRenderRequest( request, diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index a0d300a1..6d05519a 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -12,6 +12,7 @@ #include "Physics/PhysicsWorld.h" #include "Rendering/Caches/DirectionalShadowSurfaceCache.h" #include "Rendering/Execution/CameraFramePlan.h" +#include "Rendering/Execution/CameraStackFramePlan.h" #include "Rendering/Execution/DirectionalShadowExecutionState.h" #include "Rendering/Execution/CameraFrameRenderGraphFrameData.h" #include "Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.h" @@ -2134,6 +2135,7 @@ public: m_supportsStageContextualMethod = nullptr; m_supportsStageMethod = nullptr; m_recordStageMethod = nullptr; + m_finishCameraStackRenderingMethod = nullptr; m_resolvedPipelineHandle = 0; m_boundSceneDrawBackend = nullptr; } @@ -2322,6 +2324,35 @@ public: return flushed; } + void FinishCameraStackRendering( + const Rendering::CameraStackFramePlan& stackPlan) override { + if (!IsRuntimeAlive()) { + return; + } + + MonoObject* const pipelineObject = GetManagedPipelineObject(); + MonoMethod* const method = + ResolveFinishCameraStackRenderingMethod(pipelineObject); + if (pipelineObject == nullptr || + method == nullptr) { + return; + } + + for (const Rendering::CameraFramePlan& plan : + stackPlan.cameraPlans) { + int32_t rendererIndex = plan.request.rendererIndex; + uint64_t framePlanId = plan.framePlanId; + void* args[2] = { + &rendererIndex, + &framePlanId }; + m_runtime->InvokeManagedMethod( + pipelineObject, + method, + args, + nullptr); + } + } + private: struct ManagedRenderFuncExecutionState { std::shared_ptr @@ -2415,6 +2446,7 @@ private: m_supportsStageContextualMethod = nullptr; m_supportsStageMethod = nullptr; m_recordStageMethod = nullptr; + m_finishCameraStackRenderingMethod = nullptr; m_resolvedPipelineHandle = pipelineHandle; } @@ -2471,6 +2503,19 @@ private: return m_recordStageMethod; } + MonoMethod* ResolveFinishCameraStackRenderingMethod( + MonoObject* pipelineObject) const { + if (m_finishCameraStackRenderingMethod == nullptr) { + m_finishCameraStackRenderingMethod = + m_runtime->ResolveManagedMethod( + pipelineObject, + "FinishCameraStackRenderingInstance", + 2); + } + + return m_finishCameraStackRenderingMethod; + } + bool FlushManagedRasterPasses( const Rendering::RenderPipelineStageRenderGraphContext& context, ManagedScriptableRenderContextState& managedContextState) { @@ -2653,6 +2698,7 @@ private: mutable MonoMethod* m_supportsStageContextualMethod = nullptr; mutable MonoMethod* m_supportsStageMethod = nullptr; mutable MonoMethod* m_recordStageMethod = nullptr; + mutable MonoMethod* m_finishCameraStackRenderingMethod = nullptr; mutable uint32_t m_resolvedPipelineHandle = 0; std::vector> m_fullscreenPassPool = {}; Rendering::SceneDrawBackend* m_boundSceneDrawBackend = nullptr; diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs index d2e943e4..dda9614c 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/ScriptableRenderer.cs @@ -46,6 +46,12 @@ namespace XCEngine.Rendering.Universal private readonly Dictionary m_framePlans = new Dictionary(); + private readonly List + m_pendingCameraStackFinishPasses = + new List(); + private readonly List + m_pendingCameraStackFinishFramePlans = + new List(); private readonly CommandBuffer m_finishCameraStackCommandBuffer = new CommandBuffer("ScriptableRenderer.FinishCameraStack"); private bool m_disposed; @@ -76,6 +82,8 @@ namespace XCEngine.Rendering.Universal m_activePassQueue.Clear(); m_rendererBlocks.Clear(); m_framePlans.Clear(); + m_pendingCameraStackFinishPasses.Clear(); + m_pendingCameraStackFinishFramePlans.Clear(); m_disposed = true; } @@ -185,6 +193,12 @@ namespace XCEngine.Rendering.Universal return RecordRenderer(context); } + internal void FinishCameraStackRenderingInstance( + RendererRecordingContext context) + { + FinishCameraStackRendering(context); + } + protected virtual bool SupportsRendererRecording( RendererRecordingContext context) { @@ -267,7 +281,10 @@ namespace XCEngine.Rendering.Universal } if (recorded) { - FinishCameraStackRendering(); + QueueCameraStackFinish( + m_activePassQueue, + 0UL); + FinishCameraStackRendering(null); } return recorded; @@ -814,7 +831,7 @@ namespace XCEngine.Rendering.Universal } if (recorded) { - FinishCameraStackRendering(); + QueueCameraStackFinish(framePlan); } return recorded; @@ -897,12 +914,71 @@ namespace XCEngine.Rendering.Universal m_rendererBlocks.Clear(); } - private void FinishCameraStackRendering() + private void QueueCameraStackFinish( + RendererFramePlan framePlan) { - for (int i = 0; i < m_activePassQueue.Count; ++i) + if (framePlan == null) + { + return; + } + + QueueCameraStackFinish( + framePlan.activePassQueue, + framePlan.framePlanId); + } + + private void QueueCameraStackFinish( + IList renderPasses, + ulong framePlanId) + { + if (renderPasses == null || + renderPasses.Count == 0) + { + return; + } + + if (framePlanId != 0UL && + m_pendingCameraStackFinishFramePlans.Contains( + framePlanId)) + { + return; + } + + if (framePlanId != 0UL) + { + m_pendingCameraStackFinishFramePlans.Add( + framePlanId); + } + + for (int i = 0; i < renderPasses.Count; ++i) { ScriptableRenderPass renderPass = - m_activePassQueue[i]; + renderPasses[i]; + if (renderPass != null) + { + m_pendingCameraStackFinishPasses.Add( + renderPass); + } + } + } + + private void FinishCameraStackRendering( + RendererRecordingContext context) + { + if (context != null && + context.framePlanId != 0UL && + !m_pendingCameraStackFinishFramePlans.Contains( + context.framePlanId)) + { + return; + } + + for (int i = 0; + i < m_pendingCameraStackFinishPasses.Count; + ++i) + { + ScriptableRenderPass renderPass = + m_pendingCameraStackFinishPasses[i]; if (renderPass == null) { continue; @@ -911,6 +987,9 @@ namespace XCEngine.Rendering.Universal renderPass.OnFinishCameraStackRendering( m_finishCameraStackCommandBuffer); } + + m_pendingCameraStackFinishPasses.Clear(); + m_pendingCameraStackFinishFramePlans.Clear(); } protected virtual void ReleaseRuntimeResources() diff --git a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs index d6ad16bd..01a41eaf 100644 --- a/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs +++ b/managed/XCEngine.RenderPipelines.Universal/Rendering/Universal/UniversalRenderPipeline.cs @@ -63,6 +63,24 @@ namespace XCEngine.Rendering.Universal renderer.RecordRendererInstance(recordingContext); } + protected override void FinishCameraStackRendering( + int rendererIndex, + ulong framePlanId) + { + ScriptableRenderer renderer = + ResolveRenderer(rendererIndex); + if (renderer == null) + { + return; + } + + renderer.FinishCameraStackRenderingInstance( + new RendererRecordingContext( + CameraFrameStage.MainScene, + rendererIndex, + framePlanId)); + } + private ScriptableRenderer ResolveRenderer( int rendererIndex) { diff --git a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipeline.cs b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipeline.cs index 7ad4f888..c4019902 100644 --- a/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipeline.cs +++ b/managed/XCEngine.ScriptCore/Rendering/Core/ScriptableRenderPipeline.cs @@ -21,6 +21,15 @@ namespace XCEngine.Rendering m_disposed = true; } + internal void FinishCameraStackRenderingInstance( + int rendererIndex, + ulong framePlanId) + { + FinishCameraStackRendering( + rendererIndex, + framePlanId); + } + protected virtual bool SupportsStageRenderGraph( CameraFrameStage stage) { @@ -50,6 +59,12 @@ namespace XCEngine.Rendering return false; } + protected virtual void FinishCameraStackRendering( + int rendererIndex, + ulong framePlanId) + { + } + protected virtual void Dispose( bool disposing) { diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 2ef5c21d..96b3d6b1 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -71,6 +71,7 @@ struct MockPipelineState { int recordPostProcessCalls = 0; int recordFinalOutputCalls = 0; int executeRecordedMainSceneCalls = 0; + int finishCameraStackRenderingCalls = 0; bool renderResult = true; bool supportsMainSceneRenderGraph = false; bool supportsPostProcessRenderGraph = false; @@ -112,6 +113,7 @@ struct MockPipelineState { std::vector renderedCameras; std::vector renderedClearFlags; std::vector renderedClearColors; + std::vector finishedCameraStackSizes; std::vector eventLog; }; @@ -764,6 +766,13 @@ public: return m_state->renderResult; } + void FinishCameraStackRendering( + const CameraStackFramePlan& stackPlan) override { + ++m_state->finishCameraStackRenderingCalls; + m_state->finishedCameraStackSizes.push_back( + stackPlan.cameraPlans.size()); + } + private: std::shared_ptr m_state; }; @@ -3420,15 +3429,65 @@ TEST(SceneRenderer_Test, RendersBaseCamerasBeforeOverlayCamerasAndResolvesAutoCl ASSERT_EQ(plans.size(), 3u); EXPECT_EQ(plans[0].request.camera, earlyBaseCamera); EXPECT_EQ(plans[0].request.cameraStackOrder, 0u); + EXPECT_NE(plans[0].request.cameraStackId, 0u); EXPECT_EQ(plans[0].request.clearFlags, RenderClearFlags::All); EXPECT_EQ(plans[1].request.camera, lateBaseCamera); EXPECT_EQ(plans[1].request.cameraStackOrder, 0u); + EXPECT_NE(plans[1].request.cameraStackId, 0u); + EXPECT_NE( + plans[0].request.cameraStackId, + plans[1].request.cameraStackId); EXPECT_EQ(plans[1].request.clearFlags, RenderClearFlags::Depth); EXPECT_EQ(plans[2].request.camera, overlayCamera); EXPECT_EQ(plans[2].request.cameraStackOrder, 1u); + EXPECT_EQ( + plans[2].request.cameraStackId, + plans[1].request.cameraStackId); EXPECT_EQ(plans[2].request.clearFlags, RenderClearFlags::Depth); } +TEST(SceneRenderer_Test, BuildsRootCameraStackFramePlansFromSceneRequests) { + Scene scene("SceneRendererCameraStackFramePlanScene"); + + GameObject* firstBaseCameraObject = scene.CreateGameObject("FirstBaseCamera"); + auto* firstBaseCamera = firstBaseCameraObject->AddComponent(); + firstBaseCamera->SetDepth(1.0f); + firstBaseCamera->SetStackType(CameraStackType::Base); + + GameObject* secondBaseCameraObject = scene.CreateGameObject("SecondBaseCamera"); + auto* secondBaseCamera = secondBaseCameraObject->AddComponent(); + secondBaseCamera->SetDepth(2.0f); + secondBaseCamera->SetStackType(CameraStackType::Base); + + GameObject* overlayCameraObject = scene.CreateGameObject("OverlayCamera"); + auto* overlayCamera = overlayCameraObject->AddComponent(); + overlayCamera->SetDepth(3.0f); + overlayCamera->SetStackType(CameraStackType::Overlay); + + SceneRenderer renderer; + const std::vector stackPlans = + renderer.BuildStackFramePlans( + scene, + nullptr, + CreateValidContext(), + RenderSurface(640, 360)); + + ASSERT_EQ(stackPlans.size(), 2u); + ASSERT_EQ(stackPlans[0].cameraPlans.size(), 1u); + EXPECT_EQ(stackPlans[0].cameraPlans[0].request.camera, firstBaseCamera); + EXPECT_TRUE(stackPlans[0].HasBaseCamera()); + EXPECT_FALSE(stackPlans[0].IsOverlayOnly()); + + ASSERT_EQ(stackPlans[1].cameraPlans.size(), 2u); + EXPECT_EQ(stackPlans[1].cameraPlans[0].request.camera, secondBaseCamera); + EXPECT_EQ(stackPlans[1].cameraPlans[1].request.camera, overlayCamera); + EXPECT_TRUE(stackPlans[1].HasBaseCamera()); + EXPECT_FALSE(stackPlans[1].IsOverlayOnly()); + EXPECT_EQ( + stackPlans[1].cameraPlans[1].request.cameraStackId, + stackPlans[1].cameraPlans[0].request.cameraStackId); +} + TEST(SceneRenderer_Test, PreservesSceneTraversalOrderForEqualPriorityCameras) { Scene scene("SceneRendererStableSceneOrder"); @@ -4531,6 +4590,55 @@ TEST(RenderPipelineHost_Test, ForwardsPipelineAssetLifetimeAndRenderCallsToCamer EXPECT_EQ(replacementState->shutdownCalls, 1); } +TEST(RenderPipelineHost_Test, RendersCameraStackFramePlansAsSingleStackLifetime) { + Scene scene("RenderPipelineHostStackPlans"); + + GameObject* baseCameraObject = scene.CreateGameObject("BaseCamera"); + auto* baseCamera = baseCameraObject->AddComponent(); + baseCamera->SetDepth(1.0f); + baseCamera->SetStackType(CameraStackType::Base); + + GameObject* overlayCameraObject = scene.CreateGameObject("OverlayCamera"); + auto* overlayCamera = overlayCameraObject->AddComponent(); + overlayCamera->SetDepth(2.0f); + overlayCamera->SetStackType(CameraStackType::Overlay); + + CameraRenderRequest baseRequest = {}; + baseRequest.scene = &scene; + baseRequest.camera = baseCamera; + baseRequest.context = CreateValidContext(); + baseRequest.surface = RenderSurface(800, 600); + baseRequest.cameraStackId = 42u; + baseRequest.cameraDepth = baseCamera->GetDepth(); + baseRequest.cameraStackOrder = 0u; + baseRequest.clearFlags = RenderClearFlags::All; + + CameraRenderRequest overlayRequest = baseRequest; + overlayRequest.camera = overlayCamera; + overlayRequest.cameraDepth = overlayCamera->GetDepth(); + overlayRequest.cameraStackOrder = 1u; + overlayRequest.clearFlags = RenderClearFlags::Depth; + + auto state = std::make_shared(); + RenderPipelineHost host(MakeMockPipelineAsset(state)); + + const std::vector stackPlans = + host.BuildStackFramePlans({ baseRequest, overlayRequest }); + ASSERT_EQ(stackPlans.size(), 1u); + ASSERT_EQ(stackPlans[0].cameraPlans.size(), 2u); + EXPECT_EQ(stackPlans[0].cameraStackId, 42u); + EXPECT_EQ(stackPlans[0].cameraPlans[0].request.camera, baseCamera); + EXPECT_EQ(stackPlans[0].cameraPlans[1].request.camera, overlayCamera); + + ASSERT_TRUE(host.Render(stackPlans)); + ASSERT_EQ(state->renderedCameras.size(), 2u); + EXPECT_EQ(state->renderedCameras[0], baseCamera); + EXPECT_EQ(state->renderedCameras[1], overlayCamera); + EXPECT_EQ(state->finishCameraStackRenderingCalls, 1); + ASSERT_EQ(state->finishedCameraStackSizes.size(), 1u); + EXPECT_EQ(state->finishedCameraStackSizes[0], 2u); +} + TEST(RenderPipelineHost_Test, SortsManualFramePlansByDepthBeforeRendering) { Scene scene("RenderPipelineHostManualRequests");