729 lines
27 KiB
C++
729 lines
27 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <XCEngine/Components/CameraComponent.h>
|
|
#include <XCEngine/Rendering/CameraRenderer.h>
|
|
#include <XCEngine/Rendering/ObjectIdPass.h>
|
|
#include <XCEngine/Rendering/RenderPipelineAsset.h>
|
|
#include <XCEngine/Rendering/RenderSurface.h>
|
|
#include <XCEngine/Rendering/SceneRenderer.h>
|
|
#include <XCEngine/Scene/Scene.h>
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace XCEngine::Components;
|
|
using namespace XCEngine::Rendering;
|
|
|
|
namespace {
|
|
|
|
struct MockPipelineState {
|
|
int initializeCalls = 0;
|
|
int shutdownCalls = 0;
|
|
int renderCalls = 0;
|
|
bool renderResult = true;
|
|
uint32_t lastSurfaceWidth = 0;
|
|
uint32_t lastSurfaceHeight = 0;
|
|
int32_t lastRenderAreaX = 0;
|
|
int32_t lastRenderAreaY = 0;
|
|
int32_t lastRenderAreaWidth = 0;
|
|
int32_t lastRenderAreaHeight = 0;
|
|
uint32_t lastCameraViewportWidth = 0;
|
|
uint32_t lastCameraViewportHeight = 0;
|
|
CameraComponent* lastCamera = nullptr;
|
|
size_t lastVisibleItemCount = 0;
|
|
RenderClearFlags lastClearFlags = RenderClearFlags::All;
|
|
std::vector<CameraComponent*> renderedCameras;
|
|
std::vector<RenderClearFlags> renderedClearFlags;
|
|
std::vector<std::string> eventLog;
|
|
};
|
|
|
|
struct MockPipelineAssetState {
|
|
int createCalls = 0;
|
|
std::shared_ptr<MockPipelineState> lastCreatedPipelineState;
|
|
};
|
|
|
|
class MockPipeline final : public RenderPipeline {
|
|
public:
|
|
explicit MockPipeline(std::shared_ptr<MockPipelineState> state)
|
|
: m_state(std::move(state)) {
|
|
}
|
|
|
|
bool Initialize(const RenderContext&) override {
|
|
++m_state->initializeCalls;
|
|
return true;
|
|
}
|
|
|
|
void Shutdown() override {
|
|
++m_state->shutdownCalls;
|
|
}
|
|
|
|
bool Render(
|
|
const RenderContext&,
|
|
const RenderSurface& surface,
|
|
const RenderSceneData& sceneData) override {
|
|
m_state->eventLog.push_back("pipeline");
|
|
++m_state->renderCalls;
|
|
m_state->lastSurfaceWidth = surface.GetWidth();
|
|
m_state->lastSurfaceHeight = surface.GetHeight();
|
|
const XCEngine::Math::RectInt renderArea = surface.GetRenderArea();
|
|
m_state->lastRenderAreaX = renderArea.x;
|
|
m_state->lastRenderAreaY = renderArea.y;
|
|
m_state->lastRenderAreaWidth = renderArea.width;
|
|
m_state->lastRenderAreaHeight = renderArea.height;
|
|
m_state->lastCamera = sceneData.camera;
|
|
m_state->lastCameraViewportWidth = sceneData.cameraData.viewportWidth;
|
|
m_state->lastCameraViewportHeight = sceneData.cameraData.viewportHeight;
|
|
m_state->lastVisibleItemCount = sceneData.visibleItems.size();
|
|
m_state->lastClearFlags = sceneData.cameraData.clearFlags;
|
|
m_state->renderedCameras.push_back(sceneData.camera);
|
|
m_state->renderedClearFlags.push_back(sceneData.cameraData.clearFlags);
|
|
return m_state->renderResult;
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineState> m_state;
|
|
};
|
|
|
|
class MockPipelineAsset final : public RenderPipelineAsset {
|
|
public:
|
|
explicit MockPipelineAsset(std::shared_ptr<MockPipelineAssetState> state)
|
|
: m_state(std::move(state)) {
|
|
}
|
|
|
|
std::unique_ptr<RenderPipeline> CreatePipeline() const override {
|
|
++m_state->createCalls;
|
|
m_state->lastCreatedPipelineState = std::make_shared<MockPipelineState>();
|
|
return std::make_unique<MockPipeline>(m_state->lastCreatedPipelineState);
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineAssetState> m_state;
|
|
};
|
|
|
|
class MockObjectIdPass final : public ObjectIdPass {
|
|
public:
|
|
MockObjectIdPass(
|
|
std::shared_ptr<MockPipelineState> state,
|
|
bool renderResult = true)
|
|
: m_state(std::move(state))
|
|
, m_renderResult(renderResult) {
|
|
}
|
|
|
|
bool Render(
|
|
const RenderContext&,
|
|
const RenderSurface&,
|
|
const RenderSceneData&) override {
|
|
m_state->eventLog.push_back("objectId");
|
|
return m_renderResult;
|
|
}
|
|
|
|
void Shutdown() override {
|
|
m_state->eventLog.push_back("shutdown:objectId");
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineState> m_state;
|
|
bool m_renderResult = true;
|
|
};
|
|
|
|
class TrackingPass final : public RenderPass {
|
|
public:
|
|
TrackingPass(
|
|
std::shared_ptr<MockPipelineState> state,
|
|
const char* label,
|
|
bool initializeResult = true,
|
|
bool executeResult = true)
|
|
: m_state(std::move(state))
|
|
, m_label(label)
|
|
, m_initializeResult(initializeResult)
|
|
, m_executeResult(executeResult) {
|
|
}
|
|
|
|
const char* GetName() const override {
|
|
return m_label;
|
|
}
|
|
|
|
bool Initialize(const RenderContext&) override {
|
|
m_state->eventLog.push_back(std::string("init:") + m_label);
|
|
return m_initializeResult;
|
|
}
|
|
|
|
bool Execute(const RenderPassContext&) override {
|
|
m_state->eventLog.push_back(m_label);
|
|
return m_executeResult;
|
|
}
|
|
|
|
void Shutdown() override {
|
|
m_state->eventLog.push_back(std::string("shutdown:") + m_label);
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineState> m_state;
|
|
const char* m_label = "";
|
|
bool m_initializeResult = true;
|
|
bool m_executeResult = true;
|
|
};
|
|
|
|
RenderContext CreateValidContext() {
|
|
RenderContext context;
|
|
context.device = reinterpret_cast<XCEngine::RHI::RHIDevice*>(1);
|
|
context.commandList = reinterpret_cast<XCEngine::RHI::RHICommandList*>(1);
|
|
context.commandQueue = reinterpret_cast<XCEngine::RHI::RHICommandQueue*>(1);
|
|
return context;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) {
|
|
Scene scene("CameraRendererScene");
|
|
|
|
GameObject* primaryCameraObject = scene.CreateGameObject("PrimaryCamera");
|
|
auto* primaryCamera = primaryCameraObject->AddComponent<CameraComponent>();
|
|
primaryCamera->SetPrimary(true);
|
|
primaryCamera->SetDepth(10.0f);
|
|
|
|
GameObject* overrideCameraObject = scene.CreateGameObject("OverrideCamera");
|
|
auto* overrideCamera = overrideCameraObject->AddComponent<CameraComponent>();
|
|
overrideCamera->SetPrimary(false);
|
|
overrideCamera->SetDepth(-1.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = overrideCamera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(640, 480);
|
|
request.cameraDepth = overrideCamera->GetDepth();
|
|
request.clearFlags = RenderClearFlags::None;
|
|
|
|
ASSERT_TRUE(renderer.Render(request));
|
|
EXPECT_EQ(state->renderCalls, 1);
|
|
EXPECT_EQ(state->lastSurfaceWidth, 640u);
|
|
EXPECT_EQ(state->lastSurfaceHeight, 480u);
|
|
EXPECT_EQ(state->lastRenderAreaX, 0);
|
|
EXPECT_EQ(state->lastRenderAreaY, 0);
|
|
EXPECT_EQ(state->lastRenderAreaWidth, 640);
|
|
EXPECT_EQ(state->lastRenderAreaHeight, 480);
|
|
EXPECT_EQ(state->lastCameraViewportWidth, 640u);
|
|
EXPECT_EQ(state->lastCameraViewportHeight, 480u);
|
|
EXPECT_EQ(state->lastCamera, overrideCamera);
|
|
EXPECT_NE(state->lastCamera, primaryCamera);
|
|
EXPECT_EQ(state->lastVisibleItemCount, 0u);
|
|
EXPECT_EQ(state->lastClearFlags, RenderClearFlags::None);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ExecutesInjectedPreAndPostPassSequencesAroundPipelineRender) {
|
|
Scene scene("CameraRendererPassScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postScenePasses = &postPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(request));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:pre",
|
|
"pre",
|
|
"pipeline",
|
|
"init:post",
|
|
"post",
|
|
"shutdown:post",
|
|
"shutdown:pre" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRequested) {
|
|
Scene scene("CameraRendererObjectIdPassScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(state),
|
|
std::make_unique<MockObjectIdPass>(state));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postScenePasses = &postPasses;
|
|
request.objectId.surface = RenderSurface(320, 180);
|
|
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
|
|
ASSERT_TRUE(renderer.Render(request));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:pre",
|
|
"pre",
|
|
"pipeline",
|
|
"objectId",
|
|
"init:post",
|
|
"post",
|
|
"shutdown:post",
|
|
"shutdown:pre" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ShutsDownInitializedPassesWhenPipelineRenderFails) {
|
|
Scene scene("CameraRendererFailureScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
state->renderResult = false;
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
|
|
EXPECT_FALSE(renderer.Render(request));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{ "init:pre", "pre", "pipeline", "shutdown:pre" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, StopsRenderingWhenObjectIdPassFails) {
|
|
Scene scene("CameraRendererObjectIdFailureScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(state),
|
|
std::make_unique<MockObjectIdPass>(state, false));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postScenePasses = &postPasses;
|
|
request.objectId.surface = RenderSurface(320, 180);
|
|
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
|
|
EXPECT_FALSE(renderer.Render(request));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{ "init:pre", "pre", "pipeline", "objectId", "shutdown:pre" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ShutsDownSequencesWhenPostPassInitializationFails) {
|
|
Scene scene("CameraRendererPostPassInitFailureScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(4.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post", false, true));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(512, 512);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postScenePasses = &postPasses;
|
|
|
|
EXPECT_FALSE(renderer.Render(request));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:pre",
|
|
"pre",
|
|
"pipeline",
|
|
"init:post",
|
|
"shutdown:post",
|
|
"shutdown:pre" }));
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, BuildsSortedRequestsForAllUsableCamerasAndHonorsOverrideCamera) {
|
|
Scene scene("SceneRendererRequestScene");
|
|
|
|
GameObject* lowCameraObject = scene.CreateGameObject("LowCamera");
|
|
auto* lowCamera = lowCameraObject->AddComponent<CameraComponent>();
|
|
lowCamera->SetPrimary(true);
|
|
lowCamera->SetDepth(1.0f);
|
|
|
|
GameObject* highCameraObject = scene.CreateGameObject("HighCamera");
|
|
auto* highCamera = highCameraObject->AddComponent<CameraComponent>();
|
|
highCamera->SetPrimary(true);
|
|
highCamera->SetDepth(5.0f);
|
|
highCamera->SetClearMode(CameraClearMode::None);
|
|
|
|
SceneRenderer renderer;
|
|
const RenderContext context = CreateValidContext();
|
|
const RenderSurface surface(320, 180);
|
|
|
|
const std::vector<CameraRenderRequest> defaultRequests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
ASSERT_EQ(defaultRequests.size(), 2u);
|
|
EXPECT_EQ(defaultRequests[0].camera, lowCamera);
|
|
EXPECT_EQ(defaultRequests[0].cameraDepth, 1.0f);
|
|
EXPECT_EQ(defaultRequests[0].clearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(defaultRequests[0].surface.GetWidth(), 320u);
|
|
EXPECT_EQ(defaultRequests[0].surface.GetHeight(), 180u);
|
|
EXPECT_EQ(defaultRequests[1].camera, highCamera);
|
|
EXPECT_EQ(defaultRequests[1].cameraDepth, 5.0f);
|
|
EXPECT_EQ(defaultRequests[1].clearFlags, RenderClearFlags::None);
|
|
|
|
const std::vector<CameraRenderRequest> overrideRequests =
|
|
renderer.BuildRenderRequests(scene, lowCamera, context, surface);
|
|
ASSERT_EQ(overrideRequests.size(), 1u);
|
|
EXPECT_EQ(overrideRequests[0].camera, lowCamera);
|
|
EXPECT_EQ(overrideRequests[0].clearFlags, RenderClearFlags::All);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, RendersBaseCamerasBeforeOverlayCamerasAndResolvesAutoClearPerStackType) {
|
|
Scene scene("SceneRendererCameraStackScene");
|
|
|
|
GameObject* lateBaseCameraObject = scene.CreateGameObject("LateBaseCamera");
|
|
auto* lateBaseCamera = lateBaseCameraObject->AddComponent<CameraComponent>();
|
|
lateBaseCamera->SetDepth(10.0f);
|
|
lateBaseCamera->SetStackType(CameraStackType::Base);
|
|
|
|
GameObject* earlyBaseCameraObject = scene.CreateGameObject("EarlyBaseCamera");
|
|
auto* earlyBaseCamera = earlyBaseCameraObject->AddComponent<CameraComponent>();
|
|
earlyBaseCamera->SetDepth(1.0f);
|
|
earlyBaseCamera->SetStackType(CameraStackType::Base);
|
|
|
|
GameObject* overlayCameraObject = scene.CreateGameObject("OverlayCamera");
|
|
auto* overlayCamera = overlayCameraObject->AddComponent<CameraComponent>();
|
|
overlayCamera->SetDepth(-10.0f);
|
|
overlayCamera->SetStackType(CameraStackType::Overlay);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(requests.size(), 3u);
|
|
EXPECT_EQ(requests[0].camera, earlyBaseCamera);
|
|
EXPECT_EQ(requests[0].cameraStackOrder, 0u);
|
|
EXPECT_EQ(requests[0].clearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(requests[1].camera, lateBaseCamera);
|
|
EXPECT_EQ(requests[1].cameraStackOrder, 0u);
|
|
EXPECT_EQ(requests[1].clearFlags, RenderClearFlags::Depth);
|
|
EXPECT_EQ(requests[2].camera, overlayCamera);
|
|
EXPECT_EQ(requests[2].cameraStackOrder, 1u);
|
|
EXPECT_EQ(requests[2].clearFlags, RenderClearFlags::Depth);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, FallsBackToColorClearForFirstOverlayCameraWhenNoBaseCameraExists) {
|
|
Scene scene("SceneRendererOverlayOnlyScene");
|
|
|
|
GameObject* firstOverlayObject = scene.CreateGameObject("FirstOverlay");
|
|
auto* firstOverlay = firstOverlayObject->AddComponent<CameraComponent>();
|
|
firstOverlay->SetDepth(1.0f);
|
|
firstOverlay->SetStackType(CameraStackType::Overlay);
|
|
|
|
GameObject* secondOverlayObject = scene.CreateGameObject("SecondOverlay");
|
|
auto* secondOverlay = secondOverlayObject->AddComponent<CameraComponent>();
|
|
secondOverlay->SetDepth(2.0f);
|
|
secondOverlay->SetStackType(CameraStackType::Overlay);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), RenderSurface(320, 180));
|
|
|
|
ASSERT_EQ(requests.size(), 2u);
|
|
EXPECT_EQ(requests[0].camera, firstOverlay);
|
|
EXPECT_EQ(requests[0].clearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(requests[1].camera, secondOverlay);
|
|
EXPECT_EQ(requests[1].clearFlags, RenderClearFlags::Depth);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, HonorsExplicitOverrideCameraClearMode) {
|
|
Scene scene("SceneRendererOverrideClearModeScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetClearMode(CameraClearMode::DepthOnly);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, camera, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
EXPECT_EQ(requests[0].camera, camera);
|
|
EXPECT_EQ(requests[0].clearFlags, RenderClearFlags::Depth);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ResolvesNormalizedCameraViewportRectToPerRequestRenderArea) {
|
|
Scene scene("SceneRendererViewportRectScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.25f, 0.1f, 0.5f, 0.4f));
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), RenderSurface(800, 600));
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const XCEngine::Math::RectInt renderArea = requests[0].surface.GetRenderArea();
|
|
EXPECT_EQ(renderArea.x, 200);
|
|
EXPECT_EQ(renderArea.y, 60);
|
|
EXPECT_EQ(renderArea.width, 400);
|
|
EXPECT_EQ(renderArea.height, 240);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ComposesCameraViewportRectWithinExistingSurfaceRenderArea) {
|
|
Scene scene("SceneRendererNestedViewportRectScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.25f, 0.1f, 0.5f, 0.4f));
|
|
|
|
RenderSurface surface(800, 600);
|
|
surface.SetRenderArea(XCEngine::Math::RectInt(100, 50, 400, 300));
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), surface);
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const XCEngine::Math::RectInt renderArea = requests[0].surface.GetRenderArea();
|
|
EXPECT_EQ(renderArea.x, 200);
|
|
EXPECT_EQ(renderArea.y, 80);
|
|
EXPECT_EQ(renderArea.width, 200);
|
|
EXPECT_EQ(renderArea.height, 120);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, PreservesExistingSurfaceRenderAreaForFullViewportCamera) {
|
|
Scene scene("SceneRendererFullViewportNestedSurfaceScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.0f, 0.0f, 1.0f, 1.0f));
|
|
|
|
RenderSurface surface(1024, 768);
|
|
surface.SetRenderArea(XCEngine::Math::RectInt(80, 120, 320, 240));
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), surface);
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const XCEngine::Math::RectInt renderArea = requests[0].surface.GetRenderArea();
|
|
EXPECT_EQ(renderArea.x, 80);
|
|
EXPECT_EQ(renderArea.y, 120);
|
|
EXPECT_EQ(renderArea.width, 320);
|
|
EXPECT_EQ(renderArea.height, 240);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, UsesResolvedRenderAreaForCameraViewportDimensions) {
|
|
Scene scene("CameraRendererViewportRectScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.125f, 0.25f, 0.5f, 0.5f));
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(640, 480);
|
|
request.surface.SetRenderArea(XCEngine::Math::RectInt(80, 120, 320, 240));
|
|
|
|
ASSERT_TRUE(renderer.Render(request));
|
|
EXPECT_EQ(state->lastRenderAreaX, 80);
|
|
EXPECT_EQ(state->lastRenderAreaY, 120);
|
|
EXPECT_EQ(state->lastRenderAreaWidth, 320);
|
|
EXPECT_EQ(state->lastRenderAreaHeight, 240);
|
|
EXPECT_EQ(state->lastCameraViewportWidth, 320u);
|
|
EXPECT_EQ(state->lastCameraViewportHeight, 240u);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) {
|
|
Scene scene("SceneRendererScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto initialState = std::make_shared<MockPipelineState>();
|
|
auto replacementState = std::make_shared<MockPipelineState>();
|
|
|
|
{
|
|
auto initialPipeline = std::make_unique<MockPipeline>(initialState);
|
|
MockPipeline* initialPipelineRaw = initialPipeline.get();
|
|
SceneRenderer renderer(std::move(initialPipeline));
|
|
EXPECT_EQ(renderer.GetPipeline(), initialPipelineRaw);
|
|
|
|
auto replacementPipeline = std::make_unique<MockPipeline>(replacementState);
|
|
MockPipeline* replacementPipelineRaw = replacementPipeline.get();
|
|
renderer.SetPipeline(std::move(replacementPipeline));
|
|
|
|
EXPECT_EQ(initialState->shutdownCalls, 1);
|
|
EXPECT_EQ(renderer.GetPipeline(), replacementPipelineRaw);
|
|
|
|
const RenderSurface surface(800, 600);
|
|
ASSERT_TRUE(renderer.Render(scene, nullptr, CreateValidContext(), surface));
|
|
EXPECT_EQ(replacementState->renderCalls, 1);
|
|
EXPECT_EQ(replacementState->lastSurfaceWidth, 800u);
|
|
EXPECT_EQ(replacementState->lastSurfaceHeight, 600u);
|
|
EXPECT_EQ(replacementState->lastCamera, camera);
|
|
}
|
|
|
|
EXPECT_EQ(initialState->shutdownCalls, 1);
|
|
EXPECT_EQ(replacementState->shutdownCalls, 1);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, CreatesPipelineInstancesFromPipelineAssetsAndShutsDownReplacedPipelines) {
|
|
Scene scene("SceneRendererAssetScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto initialAssetState = std::make_shared<MockPipelineAssetState>();
|
|
auto replacementAssetState = std::make_shared<MockPipelineAssetState>();
|
|
|
|
{
|
|
SceneRenderer renderer(std::make_shared<MockPipelineAsset>(initialAssetState));
|
|
ASSERT_NE(renderer.GetPipeline(), nullptr);
|
|
ASSERT_NE(renderer.GetPipelineAsset(), nullptr);
|
|
EXPECT_EQ(initialAssetState->createCalls, 1);
|
|
|
|
const RenderSurface surface(800, 600);
|
|
ASSERT_TRUE(renderer.Render(scene, nullptr, CreateValidContext(), surface));
|
|
ASSERT_NE(initialAssetState->lastCreatedPipelineState, nullptr);
|
|
EXPECT_EQ(initialAssetState->lastCreatedPipelineState->renderCalls, 1);
|
|
EXPECT_EQ(initialAssetState->lastCreatedPipelineState->lastCamera, camera);
|
|
|
|
renderer.SetPipelineAsset(std::make_shared<MockPipelineAsset>(replacementAssetState));
|
|
ASSERT_NE(initialAssetState->lastCreatedPipelineState, nullptr);
|
|
EXPECT_EQ(initialAssetState->lastCreatedPipelineState->shutdownCalls, 1);
|
|
EXPECT_EQ(replacementAssetState->createCalls, 1);
|
|
|
|
ASSERT_TRUE(renderer.Render(scene, nullptr, CreateValidContext(), surface));
|
|
ASSERT_NE(replacementAssetState->lastCreatedPipelineState, nullptr);
|
|
EXPECT_EQ(replacementAssetState->lastCreatedPipelineState->renderCalls, 1);
|
|
EXPECT_EQ(replacementAssetState->lastCreatedPipelineState->lastCamera, camera);
|
|
}
|
|
|
|
ASSERT_NE(replacementAssetState->lastCreatedPipelineState, nullptr);
|
|
EXPECT_EQ(replacementAssetState->lastCreatedPipelineState->shutdownCalls, 1);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, SortsManualCameraRequestsByDepthBeforeRendering) {
|
|
Scene scene("SceneRendererManualRequests");
|
|
|
|
GameObject* farCameraObject = scene.CreateGameObject("FarCamera");
|
|
auto* farCamera = farCameraObject->AddComponent<CameraComponent>();
|
|
farCamera->SetPrimary(true);
|
|
farCamera->SetDepth(10.0f);
|
|
|
|
GameObject* nearCameraObject = scene.CreateGameObject("NearCamera");
|
|
auto* nearCamera = nearCameraObject->AddComponent<CameraComponent>();
|
|
nearCamera->SetPrimary(false);
|
|
nearCamera->SetDepth(1.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
SceneRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest farRequest;
|
|
farRequest.scene = &scene;
|
|
farRequest.camera = farCamera;
|
|
farRequest.context = CreateValidContext();
|
|
farRequest.surface = RenderSurface(800, 600);
|
|
farRequest.cameraDepth = farCamera->GetDepth();
|
|
farRequest.cameraStackOrder = 1;
|
|
farRequest.clearFlags = RenderClearFlags::None;
|
|
|
|
CameraRenderRequest nearRequest = farRequest;
|
|
nearRequest.camera = nearCamera;
|
|
nearRequest.cameraDepth = nearCamera->GetDepth();
|
|
nearRequest.cameraStackOrder = 0;
|
|
nearRequest.clearFlags = RenderClearFlags::Depth;
|
|
|
|
const std::vector<CameraRenderRequest> requests = { farRequest, nearRequest };
|
|
ASSERT_TRUE(renderer.Render(requests));
|
|
ASSERT_EQ(state->renderedCameras.size(), 2u);
|
|
ASSERT_EQ(state->renderedClearFlags.size(), 2u);
|
|
EXPECT_EQ(state->renderedCameras[0], nearCamera);
|
|
EXPECT_EQ(state->renderedClearFlags[0], RenderClearFlags::Depth);
|
|
EXPECT_EQ(state->renderedCameras[1], farCamera);
|
|
EXPECT_EQ(state->renderedClearFlags[1], RenderClearFlags::None);
|
|
}
|