340 lines
13 KiB
C++
340 lines
13 KiB
C++
#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::BuildSceneViewportBuiltinPostProcess;
|
|
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::SceneViewportOverlayData;
|
|
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::BuiltinPostProcessRequest;
|
|
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;
|
|
}
|
|
};
|
|
|
|
SceneViewportOverlayData CreateValidOverlay() {
|
|
SceneViewportOverlayData overlay = {};
|
|
overlay.valid = true;
|
|
overlay.cameraPosition = XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f);
|
|
overlay.cameraForward = XCEngine::Math::Vector3::Forward();
|
|
overlay.cameraRight = XCEngine::Math::Vector3::Right();
|
|
overlay.cameraUp = XCEngine::Math::Vector3::Up();
|
|
overlay.verticalFovDegrees = 70.0f;
|
|
overlay.nearClipPlane = 0.1f;
|
|
overlay.farClipPlane = 500.0f;
|
|
overlay.orbitDistance = 9.0f;
|
|
return overlay;
|
|
}
|
|
|
|
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, BuildSceneViewportBuiltinPostProcessReturnsEmptyRequestWhenOverlayIsInvalid) {
|
|
const auto result = BuildSceneViewportBuiltinPostProcess({}, {}, true, false);
|
|
|
|
EXPECT_FALSE(result.request.IsRequested());
|
|
EXPECT_EQ(result.warningStatusText, nullptr);
|
|
}
|
|
|
|
TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportBuiltinPostProcessConfiguresGridAndOutlineDefaults) {
|
|
const auto result = BuildSceneViewportBuiltinPostProcess(
|
|
CreateValidOverlay(),
|
|
{ 7u, 11u },
|
|
true,
|
|
false);
|
|
|
|
EXPECT_TRUE(result.request.IsRequested());
|
|
EXPECT_TRUE(result.request.gridPassData.valid);
|
|
EXPECT_EQ(result.request.selectedObjectIds.size(), 2u);
|
|
EXPECT_EQ(result.request.selectedObjectIds[0], 7u);
|
|
EXPECT_EQ(result.request.selectedObjectIds[1], 11u);
|
|
EXPECT_FLOAT_EQ(result.request.outlineStyle.outlineColor.r, 1.0f);
|
|
EXPECT_FLOAT_EQ(result.request.outlineStyle.outlineColor.g, 0.4f);
|
|
EXPECT_FLOAT_EQ(result.request.outlineStyle.outlineColor.b, 0.0f);
|
|
EXPECT_FLOAT_EQ(result.request.outlineStyle.outlineWidthPixels, 2.0f);
|
|
EXPECT_FALSE(result.request.outlineStyle.debugSelectionMask);
|
|
EXPECT_EQ(result.warningStatusText, nullptr);
|
|
}
|
|
|
|
TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportBuiltinPostProcessReportsMissingObjectIdShaderViewForSelectionOutline) {
|
|
const auto result = BuildSceneViewportBuiltinPostProcess(
|
|
CreateValidOverlay(),
|
|
{ 42u },
|
|
false,
|
|
false);
|
|
|
|
EXPECT_TRUE(result.request.IsRequested());
|
|
EXPECT_TRUE(result.request.gridPassData.valid);
|
|
EXPECT_EQ(result.request.selectedObjectIds.size(), 1u);
|
|
EXPECT_STREQ(result.warningStatusText, "Scene object id shader view is unavailable");
|
|
}
|
|
|
|
TEST(ViewportRenderFlowUtilsTest, BuildSceneViewportBuiltinPostProcessDoesNotWarnWhenDebugMaskDisablesSelectionOutlineFallback) {
|
|
const auto result = BuildSceneViewportBuiltinPostProcess(
|
|
CreateValidOverlay(),
|
|
{ 42u },
|
|
false,
|
|
true);
|
|
|
|
EXPECT_TRUE(result.request.IsRequested());
|
|
EXPECT_TRUE(result.request.outlineStyle.debugSelectionMask);
|
|
EXPECT_EQ(result.warningStatusText, nullptr);
|
|
}
|
|
|
|
TEST(ViewportRenderFlowUtilsTest, ApplySceneRenderRequestSetupAttachesOptionalPassesAndObjectIdSurface) {
|
|
DummyResourceView depthView(ResourceViewType::DepthStencil, Format::D24_UNorm_S8_UInt);
|
|
DummyResourceView objectIdView(ResourceViewType::RenderTarget);
|
|
DummyResourceView objectIdShaderView(ResourceViewType::ShaderResource);
|
|
|
|
ViewportRenderTargets targets = {};
|
|
targets.width = 800;
|
|
targets.height = 600;
|
|
targets.depthView = &depthView;
|
|
targets.objectIdView = &objectIdView;
|
|
targets.objectIdShaderView = &objectIdShaderView;
|
|
targets.objectIdState = ResourceStates::Common;
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<NoopRenderPass>());
|
|
|
|
BuiltinPostProcessRequest builtinPostProcess = {};
|
|
builtinPostProcess.gridPassData.valid = true;
|
|
builtinPostProcess.selectedObjectIds = { 7u };
|
|
|
|
XCEngine::Rendering::CameraRenderRequest request = {};
|
|
request.surface = RenderSurface(800, 600);
|
|
request.surface.SetRenderArea(XCEngine::Math::RectInt(64, 32, 320, 240));
|
|
|
|
ApplySceneViewportRenderRequestSetup(
|
|
targets,
|
|
&builtinPostProcess,
|
|
&postPasses,
|
|
request);
|
|
|
|
EXPECT_EQ(request.postScenePasses, &postPasses);
|
|
EXPECT_TRUE(request.objectId.IsRequested());
|
|
EXPECT_TRUE(request.builtinPostProcess.IsRequested());
|
|
EXPECT_EQ(request.builtinPostProcess.objectIdTextureView, &objectIdShaderView);
|
|
ASSERT_EQ(request.builtinPostProcess.selectedObjectIds.size(), 1u);
|
|
EXPECT_EQ(request.builtinPostProcess.selectedObjectIds[0], 7u);
|
|
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.builtinPostProcess.gridPassData.valid = true;
|
|
request.objectId.surface.SetColorAttachment(
|
|
reinterpret_cast<RHIResourceView*>(static_cast<uintptr_t>(0x2)));
|
|
|
|
ApplySceneViewportRenderRequestSetup(targets, nullptr, &postPasses, request);
|
|
|
|
EXPECT_EQ(request.postScenePasses, nullptr);
|
|
EXPECT_FALSE(request.objectId.IsRequested());
|
|
EXPECT_FALSE(request.builtinPostProcess.IsRequested());
|
|
}
|
|
|
|
TEST(ViewportRenderFlowUtilsTest, ApplySceneRenderRequestSetupPreservesBuiltinGridFallbackWhenObjectIdShaderViewIsUnavailable) {
|
|
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;
|
|
|
|
const auto builtinPostProcess = BuildSceneViewportBuiltinPostProcess(
|
|
CreateValidOverlay(),
|
|
{ 99u },
|
|
false,
|
|
false);
|
|
|
|
XCEngine::Rendering::CameraRenderRequest request = {};
|
|
request.surface = RenderSurface(800, 600);
|
|
|
|
ApplySceneViewportRenderRequestSetup(
|
|
targets,
|
|
&builtinPostProcess.request,
|
|
nullptr,
|
|
request);
|
|
|
|
EXPECT_TRUE(request.builtinPostProcess.IsRequested());
|
|
EXPECT_EQ(request.builtinPostProcess.objectIdTextureView, nullptr);
|
|
EXPECT_EQ(request.builtinPostProcess.selectedObjectIds.size(), 1u);
|
|
EXPECT_TRUE(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, 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
|