refactor: extract viewport render flow helpers

This commit is contained in:
2026-04-01 21:37:15 +08:00
parent 1fbbf318de
commit 846666bb2c
4 changed files with 438 additions and 40 deletions

View File

@@ -13,6 +13,7 @@ set(EDITOR_TEST_SOURCES
test_viewport_host_surface_utils.cpp
test_viewport_object_id_picker.cpp
test_viewport_render_targets.cpp
test_viewport_render_flow_utils.cpp
test_builtin_icon_layout_utils.cpp
${CMAKE_SOURCE_DIR}/editor/src/Core/UndoManager.cpp
${CMAKE_SOURCE_DIR}/editor/src/Managers/SceneManager.cpp

View File

@@ -0,0 +1,223 @@
#include <gtest/gtest.h>
#include "Viewport/ViewportHostRenderFlowUtils.h"
#include <memory>
#include <string>
namespace {
using XCEngine::Editor::ApplySceneViewportRenderRequestSetup;
using XCEngine::Editor::ApplyViewportFailureStatus;
using XCEngine::Editor::BuildGameViewportRenderFailurePolicy;
using XCEngine::Editor::BuildSceneViewportRenderFailurePolicy;
using XCEngine::Editor::BuildViewportRenderTargetUnavailablePolicy;
using XCEngine::Editor::GameViewportRenderFailure;
using XCEngine::Editor::MarkGameViewportRenderSuccess;
using XCEngine::Editor::MarkSceneViewportRenderSuccess;
using XCEngine::Editor::SceneViewportRenderFailure;
using XCEngine::Editor::ViewportRenderTargets;
using XCEngine::RHI::Format;
using XCEngine::RHI::RHIResourceView;
using XCEngine::RHI::ResourceStates;
using XCEngine::RHI::ResourceViewDimension;
using XCEngine::RHI::ResourceViewType;
using XCEngine::Rendering::RenderPass;
using XCEngine::Rendering::RenderPassContext;
using XCEngine::Rendering::RenderPassSequence;
using XCEngine::Rendering::RenderSurface;
class DummyResourceView final : public RHIResourceView {
public:
explicit DummyResourceView(
ResourceViewType viewType = ResourceViewType::RenderTarget,
Format format = Format::R8G8B8A8_UNorm)
: m_viewType(viewType)
, m_format(format) {
}
void Shutdown() override {
}
void* GetNativeHandle() override {
return nullptr;
}
bool IsValid() const override {
return true;
}
ResourceViewType GetViewType() const override {
return m_viewType;
}
ResourceViewDimension GetDimension() const override {
return ResourceViewDimension::Texture2D;
}
Format GetFormat() const override {
return m_format;
}
private:
ResourceViewType m_viewType = ResourceViewType::RenderTarget;
Format m_format = Format::R8G8B8A8_UNorm;
};
class NoopRenderPass final : public RenderPass {
public:
const char* GetName() const override {
return "NoopRenderPass";
}
bool Execute(const RenderPassContext&) override {
return true;
}
};
TEST(ViewportRenderFlowUtilsTest, BuildFailurePoliciesExposeExpectedStatusAndClearBehavior) {
const auto targetUnavailable = BuildViewportRenderTargetUnavailablePolicy();
EXPECT_STREQ(targetUnavailable.statusText, "Viewport render target is unavailable");
EXPECT_FALSE(targetUnavailable.shouldClear);
EXPECT_TRUE(targetUnavailable.invalidateObjectIdFrame);
const auto missingSceneCamera =
BuildSceneViewportRenderFailurePolicy(SceneViewportRenderFailure::MissingSceneViewCamera);
EXPECT_STREQ(missingSceneCamera.statusText, "Scene view camera is unavailable");
EXPECT_TRUE(missingSceneCamera.shouldClear);
EXPECT_FLOAT_EQ(missingSceneCamera.clearColor.r, 0.18f);
EXPECT_FLOAT_EQ(missingSceneCamera.clearColor.g, 0.07f);
EXPECT_FLOAT_EQ(missingSceneCamera.clearColor.b, 0.07f);
EXPECT_FALSE(missingSceneCamera.setStatusIfEmpty);
const auto sceneRendererFailed =
BuildSceneViewportRenderFailurePolicy(SceneViewportRenderFailure::SceneRendererFailed);
EXPECT_STREQ(sceneRendererFailed.statusText, "Scene renderer failed");
EXPECT_TRUE(sceneRendererFailed.shouldClear);
EXPECT_TRUE(sceneRendererFailed.setStatusIfEmpty);
const auto noGameCamera =
BuildGameViewportRenderFailurePolicy(GameViewportRenderFailure::NoCameraInScene);
EXPECT_STREQ(noGameCamera.statusText, "No camera in scene");
EXPECT_TRUE(noGameCamera.shouldClear);
EXPECT_FLOAT_EQ(noGameCamera.clearColor.r, 0.10f);
EXPECT_FLOAT_EQ(noGameCamera.clearColor.g, 0.09f);
EXPECT_FLOAT_EQ(noGameCamera.clearColor.b, 0.08f);
}
TEST(ViewportRenderFlowUtilsTest, ApplyViewportFailureStatusRespectsSetIfEmptyBehavior) {
std::string statusText;
ApplyViewportFailureStatus(
statusText,
BuildSceneViewportRenderFailurePolicy(SceneViewportRenderFailure::SceneRendererFailed));
EXPECT_EQ(statusText, "Scene renderer failed");
statusText = "Scene object id shader view is unavailable";
ApplyViewportFailureStatus(
statusText,
BuildSceneViewportRenderFailurePolicy(SceneViewportRenderFailure::SceneRendererFailed));
EXPECT_EQ(statusText, "Scene object id shader view is unavailable");
ApplyViewportFailureStatus(
statusText,
BuildGameViewportRenderFailurePolicy(GameViewportRenderFailure::NoActiveScene));
EXPECT_EQ(statusText, "No active scene");
}
TEST(ViewportRenderFlowUtilsTest, ApplySceneRenderRequestSetupAttachesOptionalPassesAndObjectIdSurface) {
DummyResourceView depthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt);
DummyResourceView objectIdView(ResourceViewType::RenderTarget);
ViewportRenderTargets targets = {};
targets.width = 800;
targets.height = 600;
targets.depthView = &depthView;
targets.objectIdView = &objectIdView;
targets.objectIdState = ResourceStates::Common;
RenderPassSequence postPasses;
postPasses.AddPass(std::make_unique<NoopRenderPass>());
XCEngine::Rendering::CameraRenderRequest request = {};
request.surface = RenderSurface(800, 600);
request.surface.SetRenderArea(XCEngine::Math::RectInt(64, 32, 320, 240));
ApplySceneViewportRenderRequestSetup(targets, &postPasses, request);
EXPECT_EQ(request.postScenePasses, &postPasses);
EXPECT_TRUE(request.objectId.IsRequested());
ASSERT_EQ(request.objectId.surface.GetColorAttachments().size(), 1u);
EXPECT_EQ(request.objectId.surface.GetColorAttachments()[0], &objectIdView);
EXPECT_EQ(request.objectId.surface.GetDepthAttachment(), &depthView);
const auto requestArea = request.surface.GetRenderArea();
const auto objectIdArea = request.objectId.surface.GetRenderArea();
EXPECT_EQ(objectIdArea.x, requestArea.x);
EXPECT_EQ(objectIdArea.y, requestArea.y);
EXPECT_EQ(objectIdArea.width, requestArea.width);
EXPECT_EQ(objectIdArea.height, requestArea.height);
}
TEST(ViewportRenderFlowUtilsTest, ApplySceneRenderRequestSetupSkipsUnavailableOptionalAttachments) {
ViewportRenderTargets targets = {};
targets.width = 800;
targets.height = 600;
RenderPassSequence postPasses;
XCEngine::Rendering::CameraRenderRequest request = {};
request.postScenePasses = reinterpret_cast<RenderPassSequence*>(static_cast<uintptr_t>(0x1));
request.objectId.surface = RenderSurface(1, 1);
request.objectId.surface.SetColorAttachment(
reinterpret_cast<RHIResourceView*>(static_cast<uintptr_t>(0x2)));
ApplySceneViewportRenderRequestSetup(targets, &postPasses, request);
EXPECT_EQ(request.postScenePasses, nullptr);
EXPECT_FALSE(request.objectId.IsRequested());
}
TEST(ViewportRenderFlowUtilsTest, MarkSceneRenderSuccessMovesTargetsToShaderResourceState) {
DummyResourceView depthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt);
DummyResourceView objectIdView(ResourceViewType::RenderTarget);
ViewportRenderTargets targets = {};
targets.width = 640;
targets.height = 360;
targets.depthView = &depthView;
targets.objectIdView = &objectIdView;
targets.colorState = ResourceStates::Common;
targets.objectIdState = ResourceStates::Common;
XCEngine::Rendering::CameraRenderRequest request = {};
request.surface = RenderSurface(640, 360);
ApplySceneViewportRenderRequestSetup(targets, nullptr, request);
MarkSceneViewportRenderSuccess(targets, request);
EXPECT_EQ(targets.colorState, ResourceStates::PixelShaderResource);
EXPECT_EQ(targets.objectIdState, ResourceStates::PixelShaderResource);
EXPECT_TRUE(targets.hasValidObjectIdFrame);
ViewportRenderTargets noObjectIdTargets = {};
noObjectIdTargets.colorState = ResourceStates::Common;
noObjectIdTargets.objectIdState = ResourceStates::Common;
XCEngine::Rendering::CameraRenderRequest noObjectIdRequest = {};
MarkSceneViewportRenderSuccess(noObjectIdTargets, noObjectIdRequest);
EXPECT_EQ(noObjectIdTargets.colorState, ResourceStates::PixelShaderResource);
EXPECT_EQ(noObjectIdTargets.objectIdState, ResourceStates::PixelShaderResource);
EXPECT_FALSE(noObjectIdTargets.hasValidObjectIdFrame);
}
TEST(ViewportRenderFlowUtilsTest, MarkGameRenderSuccessClearsObjectIdFrameAndUpdatesColorState) {
ViewportRenderTargets targets = {};
targets.colorState = ResourceStates::Common;
targets.hasValidObjectIdFrame = true;
MarkGameViewportRenderSuccess(targets);
EXPECT_EQ(targets.colorState, ResourceStates::PixelShaderResource);
EXPECT_FALSE(targets.hasValidObjectIdFrame);
}
} // namespace