From 0d3851204fdb5b8fe645f2be4001e383727e71aa Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 2 Apr 2026 04:42:35 +0800 Subject: [PATCH] refactor: move scene view post passes into rendering --- editor/src/Viewport/ViewportHostService.h | 223 ++---------------- engine/CMakeLists.txt | 2 + .../BuiltinSceneViewPostPassSequenceBuilder.h | 59 +++++ ...uiltinSceneViewPostPassSequenceBuilder.cpp | 214 +++++++++++++++++ tests/Rendering/unit/CMakeLists.txt | 1 + ..._scene_view_post_pass_sequence_builder.cpp | 82 +++++++ 6 files changed, 383 insertions(+), 198 deletions(-) create mode 100644 engine/include/XCEngine/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.h create mode 100644 engine/src/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.cpp create mode 100644 tests/Rendering/unit/test_builtin_scene_view_post_pass_sequence_builder.cpp diff --git a/editor/src/Viewport/ViewportHostService.h b/editor/src/Viewport/ViewportHostService.h index 803eae56..aefe08da 100644 --- a/editor/src/Viewport/ViewportHostService.h +++ b/editor/src/Viewport/ViewportHostService.h @@ -13,13 +13,13 @@ #include #include +#include #include #include #include #include #include -#include -#include +#include #include #include #include @@ -27,10 +27,8 @@ #include #include -#include #include #include -#include #include namespace XCEngine { @@ -40,35 +38,6 @@ namespace { constexpr bool kDebugSceneSelectionMask = false; -class LambdaRenderPass final : public Rendering::RenderPass { -public: - using ExecuteCallback = std::function; - - LambdaRenderPass(std::string name, ExecuteCallback executeCallback) - : m_name(std::move(name)) - , m_executeCallback(std::move(executeCallback)) { - } - - const char* GetName() const override { - return m_name.c_str(); - } - - bool Execute(const Rendering::RenderPassContext& context) override { - return m_executeCallback != nullptr && m_executeCallback(context); - } - -private: - std::string m_name; - ExecuteCallback m_executeCallback; -}; - -template -std::unique_ptr MakeLambdaRenderPass(const char* name, Callback&& callback) { - return std::make_unique( - name, - LambdaRenderPass::ExecuteCallback(std::forward(callback))); -} - Rendering::Passes::InfiniteGridPassData BuildInfiniteGridPassData( const SceneViewportOverlayData& overlay) { Rendering::Passes::InfiniteGridPassData data = {}; @@ -123,8 +92,7 @@ public: m_sceneViewLastRenderContext = {}; m_device = nullptr; m_backend = nullptr; - m_sceneGridPass.Shutdown(); - m_sceneSelectionOutlinePass.Shutdown(); + m_sceneViewPostPassBuilder.Shutdown(); m_sceneRenderer.reset(); } @@ -444,176 +412,32 @@ private: policy.clearColor.a); } - void AddSceneColorToRenderTargetPass( - ViewportEntry& entry, - Rendering::RenderPassSequence& outPostPasses) { - outPostPasses.AddPass(MakeLambdaRenderPass( - "SceneColorToRenderTarget", - [&entry](const Rendering::RenderPassContext& context) { - context.renderContext.commandList->TransitionBarrier( - entry.renderTargets.colorView, - context.surface.GetColorStateAfter(), - RHI::ResourceStates::RenderTarget); - entry.renderTargets.colorState = RHI::ResourceStates::RenderTarget; - return true; - })); - } - - void AddSceneInfiniteGridPass( - ViewportEntry& entry, - const SceneViewportOverlayData& overlay, - Rendering::RenderPassSequence& outPostPasses) { - outPostPasses.AddPass(MakeLambdaRenderPass( - "SceneInfiniteGrid", - [this, overlay, &entry](const Rendering::RenderPassContext& context) { - const bool rendered = m_sceneGridPass.Render( - context.renderContext, - context.surface, - BuildInfiniteGridPassData(overlay)); - if (!rendered) { - SetViewportStatusIfEmpty(entry.statusText, "Scene grid pass failed"); - } - return rendered; - })); - } - - void AddSceneSelectionOutlinePass( - ViewportEntry& entry, - const std::vector& selectedObjectIds, - Rendering::RenderPassSequence& outPostPasses) { - outPostPasses.AddPass(MakeLambdaRenderPass( - "SceneSelectionOutline", - [this, &entry, selectedObjectIds](const Rendering::RenderPassContext& context) { - const bool rendered = m_sceneSelectionOutlinePass.Render( - context.renderContext, - context.surface, - entry.renderTargets.objectIdShaderView, - selectedObjectIds, - Rendering::Passes::ObjectIdOutlineStyle{ - Math::Color(1.0f, 0.4f, 0.0f, 1.0f), - 2.0f, - false - }); - if (!rendered) { - SetViewportStatusIfEmpty(entry.statusText, "Scene selection outline pass failed"); - } - return rendered; - })); - } - - void AddSceneColorToShaderResourcePass( - ViewportEntry& entry, - Rendering::RenderPassSequence& outPostPasses) { - outPostPasses.AddPass(MakeLambdaRenderPass( - "SceneColorToShaderResource", - [&entry](const Rendering::RenderPassContext& context) { - context.renderContext.commandList->TransitionBarrier( - entry.renderTargets.colorView, - RHI::ResourceStates::RenderTarget, - context.surface.GetColorStateAfter()); - entry.renderTargets.colorState = context.surface.GetColorStateAfter(); - return true; - })); - } - - void AddSceneSelectionMaskDebugPass( - ViewportEntry& entry, - const std::vector& selectedObjectIds, - Rendering::RenderPassSequence& outPostPasses) { - outPostPasses.AddPass(MakeLambdaRenderPass( - "SceneSelectionMaskDebug", - [this, &entry, selectedObjectIds]( - const Rendering::RenderPassContext& context) { - const float debugClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; - RHI::RHIResourceView* colorView = entry.renderTargets.colorView; - context.renderContext.commandList->SetRenderTargets( - 1, - &colorView, - entry.renderTargets.depthView); - context.renderContext.commandList->ClearRenderTarget(colorView, debugClearColor); - - const bool rendered = m_sceneSelectionOutlinePass.Render( - context.renderContext, - context.surface, - entry.renderTargets.objectIdShaderView, - selectedObjectIds, - Rendering::Passes::ObjectIdOutlineStyle{ - Math::Color(1.0f, 0.4f, 0.0f, 1.0f), - 2.0f, - true - }); - if (!rendered) { - SetViewportStatusIfEmpty(entry.statusText, "Scene selection mask debug pass failed"); - } - return rendered; - })); - } - - void AddSceneViewPostPassStep( - Rendering::Passes::BuiltinSceneViewPostPassStep step, - ViewportEntry& entry, - const SceneViewportOverlayData& overlay, - const std::vector& selectedObjectIds, - Rendering::RenderPassSequence& outPostPasses) { - switch (step) { - case Rendering::Passes::BuiltinSceneViewPostPassStep::ColorToRenderTarget: - AddSceneColorToRenderTargetPass(entry, outPostPasses); - break; - case Rendering::Passes::BuiltinSceneViewPostPassStep::InfiniteGrid: - AddSceneInfiniteGridPass(entry, overlay, outPostPasses); - break; - case Rendering::Passes::BuiltinSceneViewPostPassStep::SelectionOutline: - AddSceneSelectionOutlinePass(entry, selectedObjectIds, outPostPasses); - break; - case Rendering::Passes::BuiltinSceneViewPostPassStep::ColorToShaderResource: - AddSceneColorToShaderResourcePass(entry, outPostPasses); - break; - case Rendering::Passes::BuiltinSceneViewPostPassStep::SelectionMaskDebug: - AddSceneSelectionMaskDebugPass( - entry, - selectedObjectIds, - outPostPasses); - break; - default: - break; - } - } - bool BuildSceneViewPostPassSequence( ViewportEntry& entry, const SceneViewportOverlayData& overlay, const std::vector& selectedObjectIds, Rendering::RenderPassSequence& outPostPasses) { - const bool hasSelection = !selectedObjectIds.empty(); - const bool hasObjectIdShaderView = entry.renderTargets.objectIdShaderView != nullptr; + Rendering::Passes::ObjectIdOutlineStyle outlineStyle = {}; + outlineStyle.outlineColor = Math::Color(1.0f, 0.4f, 0.0f, 1.0f); + outlineStyle.outlineWidthPixels = 2.0f; + outlineStyle.debugSelectionMask = kDebugSceneSelectionMask; - if (hasSelection && - !kDebugSceneSelectionMask && - !hasObjectIdShaderView) { + const Rendering::Passes::BuiltinSceneViewPostPassSequenceBuildResult result = + m_sceneViewPostPassBuilder.Build( + { + BuildInfiniteGridPassData(overlay), + entry.renderTargets.objectIdShaderView, + selectedObjectIds, + outlineStyle + }, + outPostPasses); + + if (result.missingObjectIdTextureViewForSelection && + !kDebugSceneSelectionMask) { SetViewportStatusIfEmpty(entry.statusText, "Scene object id shader view is unavailable"); } - const Rendering::Passes::BuiltinSceneViewPostPassPlan plan = - Rendering::Passes::BuildBuiltinSceneViewPostPassPlan({ - overlay.valid, - hasSelection, - kDebugSceneSelectionMask, - hasObjectIdShaderView - }); - if (!plan.valid) { - return false; - } - - for (const Rendering::Passes::BuiltinSceneViewPostPassStep step : plan.steps) { - AddSceneViewPostPassStep( - step, - entry, - overlay, - selectedObjectIds, - outPostPasses); - } - - return true; + return result.valid; } void BuildSceneViewportRenderState( @@ -693,6 +517,10 @@ private: } MarkSceneViewportRenderSuccess(entry.renderTargets, requests[0]); + const Core::uint32 pendingAsyncLoads = Resources::ResourceManager::Get().GetAsyncPendingCount(); + if (pendingAsyncLoads > 0) { + entry.statusText = "Loading scene assets... (" + std::to_string(pendingAsyncLoads) + ")"; + } return true; } @@ -829,8 +657,7 @@ private: Rendering::RenderContext m_sceneViewLastRenderContext = {}; std::array m_entries = {}; SceneViewCameraState m_sceneViewCamera; - Rendering::Passes::BuiltinInfiniteGridPass m_sceneGridPass; - Rendering::Passes::BuiltinObjectIdOutlinePass m_sceneSelectionOutlinePass; + Rendering::Passes::BuiltinSceneViewPostPassSequenceBuilder m_sceneViewPostPassBuilder; }; } // namespace Editor diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 0cd7ac7e..a214d282 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -358,11 +358,13 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinSceneViewPostPassPlan.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/CameraRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSurface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSceneExtractor.cpp diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.h b/engine/include/XCEngine/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.h new file mode 100644 index 00000000..46ca384a --- /dev/null +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace XCEngine { +namespace Rendering { +namespace Passes { + +struct BuiltinSceneViewPostPassSequenceInput { + InfiniteGridPassData gridPassData = {}; + RHI::RHIResourceView* objectIdTextureView = nullptr; + std::vector selectedObjectIds = {}; + ObjectIdOutlineStyle outlineStyle = {}; +}; + +struct BuiltinSceneViewPostPassSequenceBuildResult { + bool valid = false; + bool missingObjectIdTextureViewForSelection = false; +}; + +class BuiltinSceneViewPostPassSequenceBuilder { +public: + ~BuiltinSceneViewPostPassSequenceBuilder(); + + void Shutdown(); + + BuiltinSceneViewPostPassSequenceBuildResult Build( + const BuiltinSceneViewPostPassSequenceInput& input, + RenderPassSequence& outSequence); + +private: + void AddColorToRenderTargetPass(RenderPassSequence& outSequence); + void AddInfiniteGridPass( + const InfiniteGridPassData& gridPassData, + RenderPassSequence& outSequence); + void AddSelectionOutlinePass( + RHI::RHIResourceView* objectIdTextureView, + const std::vector& selectedObjectIds, + const ObjectIdOutlineStyle& outlineStyle, + RenderPassSequence& outSequence); + void AddColorToShaderResourcePass(RenderPassSequence& outSequence); + void AddSelectionMaskDebugPass( + RHI::RHIResourceView* objectIdTextureView, + const std::vector& selectedObjectIds, + const ObjectIdOutlineStyle& outlineStyle, + RenderPassSequence& outSequence); + + BuiltinInfiniteGridPass m_gridPass; + BuiltinObjectIdOutlinePass m_outlinePass; +}; + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.cpp b/engine/src/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.cpp new file mode 100644 index 00000000..decc974f --- /dev/null +++ b/engine/src/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.cpp @@ -0,0 +1,214 @@ +#include "Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.h" + +#include "Rendering/Passes/BuiltinSceneViewPostPassPlan.h" + +#include +#include + +#include +#include +#include +#include + +namespace XCEngine { +namespace Rendering { +namespace Passes { + +namespace { + +class LambdaRenderPass final : public RenderPass { +public: + using ExecuteCallback = std::function; + + LambdaRenderPass(std::string name, ExecuteCallback executeCallback) + : m_name(std::move(name)) + , m_executeCallback(std::move(executeCallback)) { + } + + const char* GetName() const override { + return m_name.c_str(); + } + + bool Execute(const RenderPassContext& context) override { + return m_executeCallback != nullptr && m_executeCallback(context); + } + +private: + std::string m_name; + ExecuteCallback m_executeCallback; +}; + +template +std::unique_ptr MakeLambdaRenderPass(const char* name, Callback&& callback) { + return std::make_unique( + name, + LambdaRenderPass::ExecuteCallback(std::forward(callback))); +} + +} // namespace + +BuiltinSceneViewPostPassSequenceBuilder::~BuiltinSceneViewPostPassSequenceBuilder() { + Shutdown(); +} + +void BuiltinSceneViewPostPassSequenceBuilder::Shutdown() { + m_outlinePass.Shutdown(); + m_gridPass.Shutdown(); +} + +BuiltinSceneViewPostPassSequenceBuildResult BuiltinSceneViewPostPassSequenceBuilder::Build( + const BuiltinSceneViewPostPassSequenceInput& input, + RenderPassSequence& outSequence) { + BuiltinSceneViewPostPassSequenceBuildResult result = {}; + const bool hasSelection = !input.selectedObjectIds.empty(); + const bool hasObjectIdTextureView = input.objectIdTextureView != nullptr; + + if (hasSelection && !hasObjectIdTextureView) { + result.missingObjectIdTextureViewForSelection = true; + } + + const BuiltinSceneViewPostPassPlan plan = BuildBuiltinSceneViewPostPassPlan({ + input.gridPassData.valid, + hasSelection, + input.outlineStyle.debugSelectionMask, + hasObjectIdTextureView + }); + if (!plan.valid) { + return result; + } + + result.valid = true; + for (const BuiltinSceneViewPostPassStep step : plan.steps) { + switch (step) { + case BuiltinSceneViewPostPassStep::ColorToRenderTarget: + AddColorToRenderTargetPass(outSequence); + break; + case BuiltinSceneViewPostPassStep::InfiniteGrid: + AddInfiniteGridPass(input.gridPassData, outSequence); + break; + case BuiltinSceneViewPostPassStep::SelectionOutline: + AddSelectionOutlinePass( + input.objectIdTextureView, + input.selectedObjectIds, + input.outlineStyle, + outSequence); + break; + case BuiltinSceneViewPostPassStep::ColorToShaderResource: + AddColorToShaderResourcePass(outSequence); + break; + case BuiltinSceneViewPostPassStep::SelectionMaskDebug: + AddSelectionMaskDebugPass( + input.objectIdTextureView, + input.selectedObjectIds, + input.outlineStyle, + outSequence); + break; + default: + break; + } + } + + return result; +} + +void BuiltinSceneViewPostPassSequenceBuilder::AddColorToRenderTargetPass( + RenderPassSequence& outSequence) { + outSequence.AddPass(MakeLambdaRenderPass( + "SceneColorToRenderTarget", + [](const RenderPassContext& context) { + const std::vector& colorAttachments = context.surface.GetColorAttachments(); + if (colorAttachments.empty() || colorAttachments[0] == nullptr || context.renderContext.commandList == nullptr) { + return false; + } + + context.renderContext.commandList->TransitionBarrier( + colorAttachments[0], + context.surface.GetColorStateAfter(), + RHI::ResourceStates::RenderTarget); + return true; + })); +} + +void BuiltinSceneViewPostPassSequenceBuilder::AddInfiniteGridPass( + const InfiniteGridPassData& gridPassData, + RenderPassSequence& outSequence) { + outSequence.AddPass(MakeLambdaRenderPass( + "SceneInfiniteGrid", + [this, gridPassData](const RenderPassContext& context) { + return m_gridPass.Render( + context.renderContext, + context.surface, + gridPassData); + })); +} + +void BuiltinSceneViewPostPassSequenceBuilder::AddSelectionOutlinePass( + RHI::RHIResourceView* objectIdTextureView, + const std::vector& selectedObjectIds, + const ObjectIdOutlineStyle& outlineStyle, + RenderPassSequence& outSequence) { + outSequence.AddPass(MakeLambdaRenderPass( + "SceneSelectionOutline", + [this, objectIdTextureView, selectedObjectIds, outlineStyle](const RenderPassContext& context) { + return m_outlinePass.Render( + context.renderContext, + context.surface, + objectIdTextureView, + selectedObjectIds, + outlineStyle); + })); +} + +void BuiltinSceneViewPostPassSequenceBuilder::AddColorToShaderResourcePass( + RenderPassSequence& outSequence) { + outSequence.AddPass(MakeLambdaRenderPass( + "SceneColorToShaderResource", + [](const RenderPassContext& context) { + const std::vector& colorAttachments = context.surface.GetColorAttachments(); + if (colorAttachments.empty() || colorAttachments[0] == nullptr || context.renderContext.commandList == nullptr) { + return false; + } + + context.renderContext.commandList->TransitionBarrier( + colorAttachments[0], + RHI::ResourceStates::RenderTarget, + context.surface.GetColorStateAfter()); + return true; + })); +} + +void BuiltinSceneViewPostPassSequenceBuilder::AddSelectionMaskDebugPass( + RHI::RHIResourceView* objectIdTextureView, + const std::vector& selectedObjectIds, + const ObjectIdOutlineStyle& outlineStyle, + RenderPassSequence& outSequence) { + outSequence.AddPass(MakeLambdaRenderPass( + "SceneSelectionMaskDebug", + [this, objectIdTextureView, selectedObjectIds, outlineStyle](const RenderPassContext& context) { + const std::vector& colorAttachments = context.surface.GetColorAttachments(); + if (colorAttachments.empty() || colorAttachments[0] == nullptr || context.renderContext.commandList == nullptr) { + return false; + } + + const float debugClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + RHI::RHIResourceView* colorView = colorAttachments[0]; + context.renderContext.commandList->SetRenderTargets( + 1, + &colorView, + context.surface.GetDepthAttachment()); + context.renderContext.commandList->ClearRenderTarget(colorView, debugClearColor); + + ObjectIdOutlineStyle debugStyle = outlineStyle; + debugStyle.debugSelectionMask = true; + return m_outlinePass.Render( + context.renderContext, + context.surface, + objectIdTextureView, + selectedObjectIds, + debugStyle); + })); +} + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/tests/Rendering/unit/CMakeLists.txt b/tests/Rendering/unit/CMakeLists.txt index 11396bd1..fd06caf5 100644 --- a/tests/Rendering/unit/CMakeLists.txt +++ b/tests/Rendering/unit/CMakeLists.txt @@ -6,6 +6,7 @@ set(RENDERING_UNIT_TEST_SOURCES test_render_pass.cpp test_builtin_forward_pipeline.cpp test_builtin_scene_view_post_pass_plan.cpp + test_builtin_scene_view_post_pass_sequence_builder.cpp test_camera_scene_renderer.cpp test_scene_render_request_planner.cpp test_scene_render_request_utils.cpp diff --git a/tests/Rendering/unit/test_builtin_scene_view_post_pass_sequence_builder.cpp b/tests/Rendering/unit/test_builtin_scene_view_post_pass_sequence_builder.cpp new file mode 100644 index 00000000..abeef138 --- /dev/null +++ b/tests/Rendering/unit/test_builtin_scene_view_post_pass_sequence_builder.cpp @@ -0,0 +1,82 @@ +#include + +#include + +namespace { + +using XCEngine::Rendering::Passes::BuiltinSceneViewPostPassSequenceBuilder; +using XCEngine::Rendering::Passes::BuiltinSceneViewPostPassSequenceInput; +using XCEngine::Rendering::Passes::ObjectIdOutlineStyle; +using XCEngine::Rendering::RenderPassSequence; + +TEST(BuiltinSceneViewPostPassSequenceBuilder_Test, ReturnsInvalidSequenceWhenGridDataIsUnavailable) { + BuiltinSceneViewPostPassSequenceBuilder builder; + RenderPassSequence sequence; + + const auto result = builder.Build({}, sequence); + + EXPECT_FALSE(result.valid); + EXPECT_FALSE(result.missingObjectIdTextureViewForSelection); + EXPECT_EQ(sequence.GetPassCount(), 0u); +} + +TEST(BuiltinSceneViewPostPassSequenceBuilder_Test, BuildsGridOnlySequenceWhenNothingIsSelected) { + BuiltinSceneViewPostPassSequenceBuilder builder; + RenderPassSequence sequence; + BuiltinSceneViewPostPassSequenceInput input = {}; + input.gridPassData.valid = true; + + const auto result = builder.Build(input, sequence); + + EXPECT_TRUE(result.valid); + EXPECT_FALSE(result.missingObjectIdTextureViewForSelection); + EXPECT_EQ(sequence.GetPassCount(), 3u); +} + +TEST(BuiltinSceneViewPostPassSequenceBuilder_Test, BuildsSelectionOutlineSequenceWhenObjectIdTextureViewExists) { + BuiltinSceneViewPostPassSequenceBuilder builder; + RenderPassSequence sequence; + BuiltinSceneViewPostPassSequenceInput input = {}; + input.gridPassData.valid = true; + input.objectIdTextureView = reinterpret_cast(1); + input.selectedObjectIds = { 7u, 11u }; + + const auto result = builder.Build(input, sequence); + + EXPECT_TRUE(result.valid); + EXPECT_FALSE(result.missingObjectIdTextureViewForSelection); + EXPECT_EQ(sequence.GetPassCount(), 4u); +} + +TEST(BuiltinSceneViewPostPassSequenceBuilder_Test, ReportsMissingObjectIdTextureViewAndFallsBackToGridOnlySequence) { + BuiltinSceneViewPostPassSequenceBuilder builder; + RenderPassSequence sequence; + BuiltinSceneViewPostPassSequenceInput input = {}; + input.gridPassData.valid = true; + input.selectedObjectIds = { 42u }; + + const auto result = builder.Build(input, sequence); + + EXPECT_TRUE(result.valid); + EXPECT_TRUE(result.missingObjectIdTextureViewForSelection); + EXPECT_EQ(sequence.GetPassCount(), 3u); +} + +TEST(BuiltinSceneViewPostPassSequenceBuilder_Test, BuildsDebugMaskSequenceWhenRequested) { + BuiltinSceneViewPostPassSequenceBuilder builder; + RenderPassSequence sequence; + BuiltinSceneViewPostPassSequenceInput input = {}; + input.gridPassData.valid = true; + input.objectIdTextureView = reinterpret_cast(1); + input.selectedObjectIds = { 5u }; + input.outlineStyle = ObjectIdOutlineStyle{}; + input.outlineStyle.debugSelectionMask = true; + + const auto result = builder.Build(input, sequence); + + EXPECT_TRUE(result.valid); + EXPECT_FALSE(result.missingObjectIdTextureViewForSelection); + EXPECT_EQ(sequence.GetPassCount(), 3u); +} + +} // namespace