refactor: generalize renderer builtin post process

This commit is contained in:
2026-04-02 14:49:00 +08:00
parent 697deb4e41
commit ec7a15d85b
17 changed files with 430 additions and 327 deletions

View File

@@ -5,8 +5,8 @@ project(XCEngine_RenderingUnitTests)
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_builtin_post_process_pass_plan.cpp
test_builtin_post_process_pass_sequence_builder.cpp
test_camera_scene_renderer.cpp
test_scene_render_request_planner.cpp
test_scene_render_request_utils.cpp

View File

@@ -0,0 +1,143 @@
#include <gtest/gtest.h>
#include <XCEngine/Rendering/Passes/BuiltinPostProcessPassPlan.h>
#include <vector>
namespace {
using XCEngine::Rendering::Passes::BuildBuiltinPostProcessPassPlan;
using XCEngine::Rendering::Passes::BuiltinPostProcessPassPlanInput;
using XCEngine::Rendering::Passes::BuiltinPostProcessPassStep;
TEST(BuiltinPostProcessPassPlan_Test, ReturnsInvalidPlanWhenNoBuiltinEffectIsAvailable) {
const auto plan = BuildBuiltinPostProcessPassPlan({});
EXPECT_FALSE(plan.valid);
EXPECT_TRUE(plan.steps.empty());
}
TEST(BuiltinPostProcessPassPlan_Test, BuildsGridOnlyPlanWhenNothingIsSelected) {
BuiltinPostProcessPassPlanInput input = {};
input.hasGridOverlay = true;
const auto plan = BuildBuiltinPostProcessPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinPostProcessPassStep>{
BuiltinPostProcessPassStep::ColorToRenderTarget,
BuiltinPostProcessPassStep::InfiniteGrid,
BuiltinPostProcessPassStep::ColorToShaderResource
}));
}
TEST(BuiltinPostProcessPassPlan_Test, BuildsGridAndSelectionOutlinePlanWhenObjectIdShaderViewExists) {
BuiltinPostProcessPassPlanInput input = {};
input.hasGridOverlay = true;
input.hasSelection = true;
input.hasObjectIdShaderView = true;
const auto plan = BuildBuiltinPostProcessPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinPostProcessPassStep>{
BuiltinPostProcessPassStep::ColorToRenderTarget,
BuiltinPostProcessPassStep::InfiniteGrid,
BuiltinPostProcessPassStep::SelectionOutline,
BuiltinPostProcessPassStep::ColorToShaderResource
}));
}
TEST(BuiltinPostProcessPassPlan_Test, SkipsSelectionOutlineWhenObjectIdShaderViewIsMissingAndGridExists) {
BuiltinPostProcessPassPlanInput input = {};
input.hasGridOverlay = true;
input.hasSelection = true;
const auto plan = BuildBuiltinPostProcessPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinPostProcessPassStep>{
BuiltinPostProcessPassStep::ColorToRenderTarget,
BuiltinPostProcessPassStep::InfiniteGrid,
BuiltinPostProcessPassStep::ColorToShaderResource
}));
}
TEST(BuiltinPostProcessPassPlan_Test, BuildsDebugMaskPlanFromObjectIdShaderView) {
BuiltinPostProcessPassPlanInput input = {};
input.hasGridOverlay = true;
input.hasSelection = true;
input.debugSelectionMask = true;
input.hasObjectIdShaderView = true;
const auto plan = BuildBuiltinPostProcessPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinPostProcessPassStep>{
BuiltinPostProcessPassStep::ColorToRenderTarget,
BuiltinPostProcessPassStep::SelectionMaskDebug,
BuiltinPostProcessPassStep::ColorToShaderResource
}));
}
TEST(BuiltinPostProcessPassPlan_Test, DebugMaskPlanFallsBackToTransitionsWhenGridExistsButObjectIdShaderViewIsMissing) {
BuiltinPostProcessPassPlanInput input = {};
input.hasGridOverlay = true;
input.hasSelection = true;
input.debugSelectionMask = true;
const auto plan = BuildBuiltinPostProcessPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinPostProcessPassStep>{
BuiltinPostProcessPassStep::ColorToRenderTarget,
BuiltinPostProcessPassStep::ColorToShaderResource
}));
}
TEST(BuiltinPostProcessPassPlan_Test, BuildsSelectionOnlyOutlinePlanWhenGridOverlayIsUnavailable) {
BuiltinPostProcessPassPlanInput input = {};
input.hasSelection = true;
input.hasObjectIdShaderView = true;
const auto plan = BuildBuiltinPostProcessPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinPostProcessPassStep>{
BuiltinPostProcessPassStep::ColorToRenderTarget,
BuiltinPostProcessPassStep::SelectionOutline,
BuiltinPostProcessPassStep::ColorToShaderResource
}));
}
TEST(BuiltinPostProcessPassPlan_Test, BuildsDebugMaskWithoutGridWhenObjectIdShaderViewExists) {
BuiltinPostProcessPassPlanInput input = {};
input.hasSelection = true;
input.debugSelectionMask = true;
input.hasObjectIdShaderView = true;
const auto plan = BuildBuiltinPostProcessPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinPostProcessPassStep>{
BuiltinPostProcessPassStep::ColorToRenderTarget,
BuiltinPostProcessPassStep::SelectionMaskDebug,
BuiltinPostProcessPassStep::ColorToShaderResource
}));
}
} // namespace

View File

@@ -0,0 +1,109 @@
#include <gtest/gtest.h>
#include <XCEngine/Rendering/Passes/BuiltinPostProcessPassSequenceBuilder.h>
namespace {
using XCEngine::Rendering::Passes::BuiltinPostProcessPassSequenceBuilder;
using XCEngine::Rendering::Passes::BuiltinPostProcessPassSequenceInput;
using XCEngine::Rendering::Passes::ObjectIdOutlineStyle;
using XCEngine::Rendering::RenderPassSequence;
TEST(BuiltinPostProcessPassSequenceBuilder_Test, ReturnsInvalidSequenceWhenNoBuiltinEffectIsRequested) {
BuiltinPostProcessPassSequenceBuilder builder;
RenderPassSequence sequence;
const auto result = builder.Build({}, sequence);
EXPECT_FALSE(result.valid);
EXPECT_FALSE(result.missingObjectIdTextureViewForSelection);
EXPECT_EQ(sequence.GetPassCount(), 0u);
}
TEST(BuiltinPostProcessPassSequenceBuilder_Test, BuildsGridOnlySequenceWhenNothingIsSelected) {
BuiltinPostProcessPassSequenceBuilder builder;
RenderPassSequence sequence;
BuiltinPostProcessPassSequenceInput 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(BuiltinPostProcessPassSequenceBuilder_Test, BuildsGridAndSelectionOutlineSequenceWhenObjectIdTextureViewExists) {
BuiltinPostProcessPassSequenceBuilder builder;
RenderPassSequence sequence;
BuiltinPostProcessPassSequenceInput input = {};
input.gridPassData.valid = true;
input.objectIdTextureView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(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(BuiltinPostProcessPassSequenceBuilder_Test, ReportsMissingObjectIdTextureViewAndFallsBackToGridOnlySequence) {
BuiltinPostProcessPassSequenceBuilder builder;
RenderPassSequence sequence;
BuiltinPostProcessPassSequenceInput 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(BuiltinPostProcessPassSequenceBuilder_Test, BuildsDebugMaskSequenceWhenRequested) {
BuiltinPostProcessPassSequenceBuilder builder;
RenderPassSequence sequence;
BuiltinPostProcessPassSequenceInput input = {};
input.gridPassData.valid = true;
input.objectIdTextureView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(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);
}
TEST(BuiltinPostProcessPassSequenceBuilder_Test, BuildsSelectionOnlyOutlineSequenceWhenGridDataIsUnavailable) {
BuiltinPostProcessPassSequenceBuilder builder;
RenderPassSequence sequence;
BuiltinPostProcessPassSequenceInput input = {};
input.objectIdTextureView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1);
input.selectedObjectIds = { 13u };
const auto result = builder.Build(input, sequence);
EXPECT_TRUE(result.valid);
EXPECT_FALSE(result.missingObjectIdTextureViewForSelection);
EXPECT_EQ(sequence.GetPassCount(), 3u);
}
TEST(BuiltinPostProcessPassSequenceBuilder_Test, ReportsInvalidSelectionOnlySequenceWhenObjectIdTextureViewIsMissing) {
BuiltinPostProcessPassSequenceBuilder builder;
RenderPassSequence sequence;
BuiltinPostProcessPassSequenceInput input = {};
input.selectedObjectIds = { 13u };
const auto result = builder.Build(input, sequence);
EXPECT_FALSE(result.valid);
EXPECT_TRUE(result.missingObjectIdTextureViewForSelection);
EXPECT_EQ(sequence.GetPassCount(), 0u);
}
} // namespace

View File

@@ -1,108 +0,0 @@
#include <gtest/gtest.h>
#include <XCEngine/Rendering/Passes/BuiltinSceneViewPostPassPlan.h>
#include <vector>
namespace {
using XCEngine::Rendering::Passes::BuildBuiltinSceneViewPostPassPlan;
using XCEngine::Rendering::Passes::BuiltinSceneViewPostPassPlanInput;
using XCEngine::Rendering::Passes::BuiltinSceneViewPostPassStep;
TEST(BuiltinSceneViewPostPassPlan_Test, ReturnsInvalidPlanWhenOverlayIsUnavailable) {
const auto plan = BuildBuiltinSceneViewPostPassPlan({});
EXPECT_FALSE(plan.valid);
EXPECT_TRUE(plan.steps.empty());
}
TEST(BuiltinSceneViewPostPassPlan_Test, BuildsGridOnlyPlanWhenNothingIsSelected) {
BuiltinSceneViewPostPassPlanInput input = {};
input.overlayValid = true;
const auto plan = BuildBuiltinSceneViewPostPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinSceneViewPostPassStep>{
BuiltinSceneViewPostPassStep::ColorToRenderTarget,
BuiltinSceneViewPostPassStep::InfiniteGrid,
BuiltinSceneViewPostPassStep::ColorToShaderResource
}));
}
TEST(BuiltinSceneViewPostPassPlan_Test, BuildsSelectionOutlinePlanWhenObjectIdShaderViewExists) {
BuiltinSceneViewPostPassPlanInput input = {};
input.overlayValid = true;
input.hasSelection = true;
input.hasObjectIdShaderView = true;
const auto plan = BuildBuiltinSceneViewPostPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinSceneViewPostPassStep>{
BuiltinSceneViewPostPassStep::ColorToRenderTarget,
BuiltinSceneViewPostPassStep::InfiniteGrid,
BuiltinSceneViewPostPassStep::SelectionOutline,
BuiltinSceneViewPostPassStep::ColorToShaderResource
}));
}
TEST(BuiltinSceneViewPostPassPlan_Test, SkipsSelectionOutlineWhenObjectIdShaderViewIsMissing) {
BuiltinSceneViewPostPassPlanInput input = {};
input.overlayValid = true;
input.hasSelection = true;
const auto plan = BuildBuiltinSceneViewPostPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinSceneViewPostPassStep>{
BuiltinSceneViewPostPassStep::ColorToRenderTarget,
BuiltinSceneViewPostPassStep::InfiniteGrid,
BuiltinSceneViewPostPassStep::ColorToShaderResource
}));
}
TEST(BuiltinSceneViewPostPassPlan_Test, BuildsDebugMaskPlanFromObjectIdShaderView) {
BuiltinSceneViewPostPassPlanInput input = {};
input.overlayValid = true;
input.hasSelection = true;
input.debugSelectionMask = true;
input.hasObjectIdShaderView = true;
const auto plan = BuildBuiltinSceneViewPostPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinSceneViewPostPassStep>{
BuiltinSceneViewPostPassStep::ColorToRenderTarget,
BuiltinSceneViewPostPassStep::SelectionMaskDebug,
BuiltinSceneViewPostPassStep::ColorToShaderResource
}));
}
TEST(BuiltinSceneViewPostPassPlan_Test, DebugMaskPlanFallsBackWhenObjectIdShaderViewIsMissing) {
BuiltinSceneViewPostPassPlanInput input = {};
input.overlayValid = true;
input.hasSelection = true;
input.debugSelectionMask = true;
const auto plan = BuildBuiltinSceneViewPostPassPlan(input);
ASSERT_TRUE(plan.valid);
EXPECT_EQ(
plan.steps,
(std::vector<BuiltinSceneViewPostPassStep>{
BuiltinSceneViewPostPassStep::ColorToRenderTarget,
BuiltinSceneViewPostPassStep::ColorToShaderResource
}));
}
} // namespace

View File

@@ -1,82 +0,0 @@
#include <gtest/gtest.h>
#include <XCEngine/Rendering/Passes/BuiltinSceneViewPostPassSequenceBuilder.h>
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<XCEngine::RHI::RHIResourceView*>(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<XCEngine::RHI::RHIResourceView*>(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

View File

@@ -438,8 +438,8 @@ TEST(CameraRenderer_Test, ShutsDownSequencesWhenPostPassInitializationFails) {
"shutdown:pre" }));
}
TEST(CameraRenderer_Test, RejectsBuiltinSceneViewPostProcessThatCannotProduceFreshObjectIdData) {
Scene scene("CameraRendererSceneViewPostProcessValidationScene");
TEST(CameraRenderer_Test, RejectsBuiltinPostProcessThatCannotProduceFreshObjectIdData) {
Scene scene("CameraRendererBuiltinPostProcessValidationScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
@@ -455,8 +455,8 @@ TEST(CameraRenderer_Test, RejectsBuiltinSceneViewPostProcessThatCannotProduceFre
request.context = CreateValidContext();
request.surface = RenderSurface(512, 512);
request.cameraDepth = camera->GetDepth();
request.builtinSceneViewPostProcess.gridPassData.valid = true;
request.builtinSceneViewPostProcess.objectIdTextureView =
request.builtinPostProcess.gridPassData.valid = true;
request.builtinPostProcess.objectIdTextureView =
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1);
EXPECT_FALSE(renderer.Render(request));