Add renderer phase A textured scene path

This commit is contained in:
2026-03-26 20:43:17 +08:00
parent 0921f2a459
commit a78593e7e1
32 changed files with 2455 additions and 9 deletions

View File

@@ -8,6 +8,7 @@ set(COMPONENTS_TEST_SOURCES
test_transform_component.cpp
test_game_object.cpp
test_camera_light_component.cpp
test_mesh_render_components.cpp
)
add_executable(components_tests ${COMPONENTS_TEST_SOURCES})

View File

@@ -6,6 +6,8 @@
#include <XCEngine/Components/ComponentFactoryRegistry.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/LightComponent.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
using namespace XCEngine::Components;
@@ -18,6 +20,8 @@ TEST(ComponentFactoryRegistry_Test, BuiltInTypesAreRegistered) {
EXPECT_TRUE(registry.IsRegistered("Light"));
EXPECT_TRUE(registry.IsRegistered("AudioSource"));
EXPECT_TRUE(registry.IsRegistered("AudioListener"));
EXPECT_TRUE(registry.IsRegistered("MeshFilter"));
EXPECT_TRUE(registry.IsRegistered("MeshRenderer"));
EXPECT_FALSE(registry.IsRegistered("Transform"));
EXPECT_FALSE(registry.IsRegistered("MissingComponent"));
}
@@ -30,6 +34,8 @@ TEST(ComponentFactoryRegistry_Test, CreateBuiltInComponentsByTypeName) {
EXPECT_NE(dynamic_cast<LightComponent*>(registry.CreateComponent(&gameObject, "Light")), nullptr);
EXPECT_NE(dynamic_cast<AudioSourceComponent*>(registry.CreateComponent(&gameObject, "AudioSource")), nullptr);
EXPECT_NE(dynamic_cast<AudioListenerComponent*>(registry.CreateComponent(&gameObject, "AudioListener")), nullptr);
EXPECT_NE(dynamic_cast<MeshFilterComponent*>(registry.CreateComponent(&gameObject, "MeshFilter")), nullptr);
EXPECT_NE(dynamic_cast<MeshRendererComponent*>(registry.CreateComponent(&gameObject, "MeshRenderer")), nullptr);
EXPECT_EQ(registry.CreateComponent(&gameObject, "MissingComponent"), nullptr);
}

View File

@@ -0,0 +1,125 @@
#include <gtest/gtest.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <sstream>
using namespace XCEngine::Components;
using namespace XCEngine::Resources;
namespace {
Mesh* CreateTestMesh(const char* name, const char* path) {
auto* mesh = new Mesh();
IResource::ConstructParams params = {};
params.name = name;
params.path = path;
params.guid = ResourceGUID::Generate(path);
mesh->Initialize(params);
return mesh;
}
Material* CreateTestMaterial(const char* name, const char* path) {
auto* material = new Material();
IResource::ConstructParams params = {};
params.name = name;
params.path = path;
params.guid = ResourceGUID::Generate(path);
material->Initialize(params);
return material;
}
TEST(MeshFilterComponent_Test, SetMeshCachesResourceAndPath) {
GameObject gameObject("MeshHolder");
auto* component = gameObject.AddComponent<MeshFilterComponent>();
Mesh* mesh = CreateTestMesh("Quad", "Meshes/quad.mesh");
component->SetMesh(mesh);
EXPECT_EQ(component->GetMesh(), mesh);
EXPECT_EQ(component->GetMeshPath(), "Meshes/quad.mesh");
component->ClearMesh();
delete mesh;
}
TEST(MeshFilterComponent_Test, SerializeAndDeserializePreservesPath) {
MeshFilterComponent source;
Mesh* mesh = CreateTestMesh("Quad", "Meshes/serialized.mesh");
source.SetMesh(mesh);
std::stringstream stream;
source.Serialize(stream);
MeshFilterComponent target;
target.Deserialize(stream);
EXPECT_EQ(target.GetMeshPath(), "Meshes/serialized.mesh");
EXPECT_EQ(target.GetMesh(), nullptr);
source.ClearMesh();
delete mesh;
}
TEST(MeshRendererComponent_Test, SetMaterialsKeepsSlotsAndFlags) {
GameObject gameObject("RendererHolder");
auto* component = gameObject.AddComponent<MeshRendererComponent>();
Material* material0 = CreateTestMaterial("M0", "Materials/m0.mat");
Material* material1 = CreateTestMaterial("M1", "Materials/m1.mat");
component->SetMaterial(0, material0);
component->SetMaterial(1, material1);
component->SetCastShadows(false);
component->SetReceiveShadows(false);
component->SetRenderLayer(7);
ASSERT_EQ(component->GetMaterialCount(), 2u);
EXPECT_EQ(component->GetMaterial(0), material0);
EXPECT_EQ(component->GetMaterial(1), material1);
EXPECT_EQ(component->GetMaterialPaths()[0], "Materials/m0.mat");
EXPECT_EQ(component->GetMaterialPaths()[1], "Materials/m1.mat");
EXPECT_FALSE(component->GetCastShadows());
EXPECT_FALSE(component->GetReceiveShadows());
EXPECT_EQ(component->GetRenderLayer(), 7u);
component->ClearMaterials();
delete material0;
delete material1;
}
TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesMaterialPathsAndSettings) {
MeshRendererComponent source;
Material* material0 = CreateTestMaterial("M0", "Materials/serialized0.mat");
Material* material1 = CreateTestMaterial("M1", "Materials/serialized1.mat");
source.SetMaterial(0, material0);
source.SetMaterial(1, material1);
source.SetCastShadows(false);
source.SetReceiveShadows(true);
source.SetRenderLayer(3);
std::stringstream stream;
source.Serialize(stream);
MeshRendererComponent target;
target.Deserialize(stream);
ASSERT_EQ(target.GetMaterialCount(), 2u);
EXPECT_EQ(target.GetMaterial(0), nullptr);
EXPECT_EQ(target.GetMaterial(1), nullptr);
EXPECT_EQ(target.GetMaterialPaths()[0], "Materials/serialized0.mat");
EXPECT_EQ(target.GetMaterialPaths()[1], "Materials/serialized1.mat");
EXPECT_FALSE(target.GetCastShadows());
EXPECT_TRUE(target.GetReceiveShadows());
EXPECT_EQ(target.GetRenderLayer(), 3u);
source.ClearMaterials();
delete material0;
delete material1;
}
} // namespace

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.15)
project(XCEngine_RenderingTests)
add_subdirectory(unit)
add_subdirectory(integration)

View File

@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.15)
project(XCEngine_RenderingIntegrationTests)
add_subdirectory(textured_quad_scene)

View File

@@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.15)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
project(rendering_integration_textured_quad_scene)
set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine)
set(PACKAGE_DIR ${CMAKE_SOURCE_DIR}/tests/opengl/package)
get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE)
add_executable(rendering_integration_textured_quad_scene
main.cpp
${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp
${PACKAGE_DIR}/src/glad.c
)
target_include_directories(rendering_integration_textured_quad_scene PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures
${ENGINE_ROOT_DIR}/include
${PACKAGE_DIR}/include
${PROJECT_ROOT_DIR}/engine/src
)
target_link_libraries(rendering_integration_textured_quad_scene PRIVATE
d3d12
dxgi
d3dcompiler
winmm
opengl32
XCEngine
GTest::gtest
)
target_compile_definitions(rendering_integration_textured_quad_scene PRIVATE
UNICODE
_UNICODE
XCENGINE_SUPPORT_OPENGL
XCENGINE_SUPPORT_D3D12
)
add_custom_command(TARGET rendering_integration_textured_quad_scene POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/tests/RHI/integration/compare_ppm.py
$<TARGET_FILE_DIR:rendering_integration_textured_quad_scene>/
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm
$<TARGET_FILE_DIR:rendering_integration_textured_quad_scene>/GT.ppm
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${ENGINE_ROOT_DIR}/third_party/renderdoc/renderdoc.dll
$<TARGET_FILE_DIR:rendering_integration_textured_quad_scene>/
)
include(GoogleTest)
gtest_discover_tests(rendering_integration_textured_quad_scene)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,272 @@
#include <gtest/gtest.h>
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Core/Math/Vector2.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Debug/ConsoleLogSink.h>
#include <XCEngine/Debug/Logger.h>
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/SceneRenderer.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Texture/Texture.h>
#include <XCEngine/Scene/Scene.h>
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
#include <memory>
#include <vector>
using namespace XCEngine::Components;
using namespace XCEngine::Debug;
using namespace XCEngine::Math;
using namespace XCEngine::Rendering;
using namespace XCEngine::Resources;
using namespace XCEngine::RHI;
using namespace XCEngine::RHI::Integration;
namespace {
constexpr const char* kD3D12Screenshot = "textured_quad_scene_d3d12.ppm";
constexpr const char* kOpenGLScreenshot = "textured_quad_scene_opengl.ppm";
Mesh* CreateQuadMesh() {
auto* mesh = new Mesh();
IResource::ConstructParams params = {};
params.name = "TexturedQuad";
params.path = "Tests/Rendering/TexturedQuad.mesh";
params.guid = ResourceGUID::Generate(params.path);
mesh->Initialize(params);
StaticMeshVertex vertices[4] = {};
vertices[0].position = Vector3(-1.0f, -1.0f, 0.0f);
vertices[0].uv0 = Vector2(0.0f, 1.0f);
vertices[1].position = Vector3(-1.0f, 1.0f, 0.0f);
vertices[1].uv0 = Vector2(0.0f, 0.0f);
vertices[2].position = Vector3(1.0f, -1.0f, 0.0f);
vertices[2].uv0 = Vector2(1.0f, 1.0f);
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
vertices[3].uv0 = Vector2(1.0f, 0.0f);
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
4,
sizeof(StaticMeshVertex),
VertexAttribute::Position | VertexAttribute::UV0);
mesh->SetIndexData(indices, sizeof(indices), 6, true);
MeshSection section = {};
section.baseVertex = 0;
section.vertexCount = 4;
section.startIndex = 0;
section.indexCount = 6;
section.materialID = 0;
mesh->AddSection(section);
return mesh;
}
Texture* CreateCheckerTexture() {
auto* texture = new Texture();
IResource::ConstructParams params = {};
params.name = "Checker";
params.path = "Tests/Rendering/Checker.texture";
params.guid = ResourceGUID::Generate(params.path);
texture->Initialize(params);
const unsigned char pixels[16] = {
255, 32, 32, 255,
32, 255, 32, 255,
32, 32, 255, 255,
255, 255, 32, 255
};
texture->Create(
2,
2,
1,
1,
XCEngine::Resources::TextureType::Texture2D,
XCEngine::Resources::TextureFormat::RGBA8_UNORM,
pixels,
sizeof(pixels));
return texture;
}
Material* CreateMaterial(Texture* texture) {
auto* material = new Material();
IResource::ConstructParams params = {};
params.name = "QuadMaterial";
params.path = "Tests/Rendering/Quad.material";
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
return material;
}
const char* GetScreenshotFilename(RHIType backendType) {
return backendType == RHIType::D3D12 ? kD3D12Screenshot : kOpenGLScreenshot;
}
int GetComparisonThreshold(RHIType backendType) {
return backendType == RHIType::OpenGL ? 5 : 0;
}
class TexturedQuadSceneTest : public RHIIntegrationFixture {
protected:
void SetUp() override;
void TearDown() override;
void RenderFrame() override;
private:
RHIResourceView* GetCurrentBackBufferView();
std::unique_ptr<Scene> mScene;
std::unique_ptr<SceneRenderer> mSceneRenderer;
std::vector<RHIResourceView*> mBackBufferViews;
Mesh* mMesh = nullptr;
Material* mMaterial = nullptr;
Texture* mTexture = nullptr;
};
void TexturedQuadSceneTest::SetUp() {
RHIIntegrationFixture::SetUp();
mSceneRenderer = std::make_unique<SceneRenderer>();
mScene = std::make_unique<Scene>("TexturedQuadScene");
mMesh = CreateQuadMesh();
mTexture = CreateCheckerTexture();
mMaterial = CreateMaterial(mTexture);
GameObject* cameraObject = mScene->CreateGameObject("MainCamera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetClearColor(XCEngine::Math::Color(0.08f, 0.08f, 0.10f, 1.0f));
GameObject* quadObject = mScene->CreateGameObject("Quad");
quadObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, 3.0f));
auto* meshFilter = quadObject->AddComponent<MeshFilterComponent>();
auto* meshRenderer = quadObject->AddComponent<MeshRendererComponent>();
meshFilter->SetMesh(ResourceHandle<Mesh>(mMesh));
meshRenderer->SetMaterial(0, ResourceHandle<Material>(mMaterial));
mBackBufferViews.resize(2, nullptr);
}
void TexturedQuadSceneTest::TearDown() {
mSceneRenderer.reset();
for (RHIResourceView*& backBufferView : mBackBufferViews) {
if (backBufferView != nullptr) {
backBufferView->Shutdown();
delete backBufferView;
backBufferView = nullptr;
}
}
mBackBufferViews.clear();
mScene.reset();
delete mMaterial;
mMaterial = nullptr;
delete mMesh;
mMesh = nullptr;
delete mTexture;
mTexture = nullptr;
RHIIntegrationFixture::TearDown();
}
RHIResourceView* TexturedQuadSceneTest::GetCurrentBackBufferView() {
const int backBufferIndex = GetCurrentBackBufferIndex();
if (backBufferIndex < 0) {
return nullptr;
}
if (static_cast<size_t>(backBufferIndex) >= mBackBufferViews.size()) {
mBackBufferViews.resize(static_cast<size_t>(backBufferIndex) + 1, nullptr);
}
if (mBackBufferViews[backBufferIndex] == nullptr) {
ResourceViewDesc viewDesc = {};
viewDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
viewDesc.dimension = ResourceViewDimension::Texture2D;
viewDesc.mipLevel = 0;
mBackBufferViews[backBufferIndex] = GetDevice()->CreateRenderTargetView(GetCurrentBackBuffer(), viewDesc);
}
return mBackBufferViews[backBufferIndex];
}
void TexturedQuadSceneTest::RenderFrame() {
ASSERT_NE(mScene, nullptr);
ASSERT_NE(mSceneRenderer, nullptr);
RHICommandList* commandList = GetCommandList();
ASSERT_NE(commandList, nullptr);
commandList->Reset();
RenderSurface surface(1280, 720);
surface.SetColorAttachment(GetCurrentBackBufferView());
RenderContext renderContext = {};
renderContext.device = GetDevice();
renderContext.commandList = commandList;
renderContext.commandQueue = GetCommandQueue();
renderContext.backendType = GetBackendType();
ASSERT_TRUE(mSceneRenderer->Render(*mScene, nullptr, renderContext, surface));
commandList->Close();
void* commandLists[] = { commandList };
GetCommandQueue()->ExecuteCommandLists(1, commandLists);
}
TEST_P(TexturedQuadSceneTest, RenderTexturedQuadScene) {
RHICommandQueue* commandQueue = GetCommandQueue();
RHISwapChain* swapChain = GetSwapChain();
const int targetFrameCount = 30;
const char* screenshotFilename = GetScreenshotFilename(GetBackendType());
const int comparisonThreshold = GetComparisonThreshold(GetBackendType());
for (int frameCount = 0; frameCount <= targetFrameCount; ++frameCount) {
if (frameCount > 0) {
commandQueue->WaitForPreviousFrame();
}
BeginRender();
RenderFrame();
if (frameCount >= targetFrameCount) {
commandQueue->WaitForIdle();
ASSERT_TRUE(TakeScreenshot(screenshotFilename));
ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast<float>(comparisonThreshold)));
break;
}
swapChain->Present(0, 0);
}
}
} // namespace
INSTANTIATE_TEST_SUITE_P(D3D12, TexturedQuadSceneTest, ::testing::Values(RHIType::D3D12));
INSTANTIATE_TEST_SUITE_P(OpenGL, TexturedQuadSceneTest, ::testing::Values(RHIType::OpenGL));
GTEST_API_ int main(int argc, char** argv) {
Logger::Get().Initialize();
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
Logger::Get().SetMinimumLevel(LogLevel::Debug);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.15)
project(XCEngine_RenderingUnitTests)
set(RENDERING_UNIT_TEST_SOURCES
test_render_scene_extractor.cpp
)
add_executable(rendering_unit_tests ${RENDERING_UNIT_TEST_SOURCES})
if(MSVC)
set_target_properties(rendering_unit_tests PROPERTIES
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
)
endif()
target_link_libraries(rendering_unit_tests PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(rendering_unit_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
)
include(GoogleTest)
gtest_discover_tests(rendering_unit_tests)

View File

@@ -0,0 +1,101 @@
#include <gtest/gtest.h>
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Rendering/RenderSceneExtractor.h>
#include <XCEngine/Scene/Scene.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
using namespace XCEngine::Components;
using namespace XCEngine::Math;
using namespace XCEngine::Rendering;
using namespace XCEngine::Resources;
namespace {
Mesh* CreateTestMesh(const char* path) {
auto* mesh = new Mesh();
IResource::ConstructParams params = {};
params.name = "TestMesh";
params.path = path;
params.guid = ResourceGUID::Generate(path);
mesh->Initialize(params);
return mesh;
}
TEST(RenderSceneExtractor_Test, SelectsHighestDepthPrimaryCameraAndVisibleObjects) {
Scene scene("RenderScene");
GameObject* cameraObjectA = scene.CreateGameObject("CameraA");
auto* cameraA = cameraObjectA->AddComponent<CameraComponent>();
cameraA->SetPrimary(true);
cameraA->SetDepth(0.0f);
GameObject* cameraObjectB = scene.CreateGameObject("CameraB");
auto* cameraB = cameraObjectB->AddComponent<CameraComponent>();
cameraB->SetPrimary(true);
cameraB->SetDepth(5.0f);
cameraObjectB->GetTransform()->SetLocalPosition(Vector3(2.0f, 3.0f, 4.0f));
GameObject* visibleObject = scene.CreateGameObject("VisibleQuad");
visibleObject->GetTransform()->SetLocalPosition(Vector3(1.0f, 2.0f, 3.0f));
auto* meshFilter = visibleObject->AddComponent<MeshFilterComponent>();
visibleObject->AddComponent<MeshRendererComponent>();
Mesh* visibleMesh = CreateTestMesh("Meshes/visible.mesh");
meshFilter->SetMesh(visibleMesh);
GameObject* hiddenObject = scene.CreateGameObject("HiddenQuad");
hiddenObject->SetActive(false);
auto* hiddenMeshFilter = hiddenObject->AddComponent<MeshFilterComponent>();
hiddenObject->AddComponent<MeshRendererComponent>();
Mesh* hiddenMesh = CreateTestMesh("Meshes/hidden.mesh");
hiddenMeshFilter->SetMesh(hiddenMesh);
RenderSceneExtractor extractor;
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 1280, 720);
ASSERT_TRUE(sceneData.HasCamera());
EXPECT_EQ(sceneData.camera, cameraB);
EXPECT_EQ(sceneData.cameraData.viewportWidth, 1280u);
EXPECT_EQ(sceneData.cameraData.viewportHeight, 720u);
EXPECT_EQ(sceneData.cameraData.worldPosition, Vector3(2.0f, 3.0f, 4.0f));
ASSERT_EQ(sceneData.visibleObjects.size(), 1u);
EXPECT_EQ(sceneData.visibleObjects[0].gameObject, visibleObject);
EXPECT_EQ(sceneData.visibleObjects[0].mesh, visibleMesh);
EXPECT_EQ(sceneData.visibleObjects[0].localToWorld.GetTranslation(), Vector3(1.0f, 2.0f, 3.0f));
meshFilter->ClearMesh();
hiddenMeshFilter->ClearMesh();
delete visibleMesh;
delete hiddenMesh;
}
TEST(RenderSceneExtractor_Test, OverrideCameraTakesPriority) {
Scene scene("OverrideScene");
GameObject* primaryObject = scene.CreateGameObject("PrimaryCamera");
auto* primaryCamera = primaryObject->AddComponent<CameraComponent>();
primaryCamera->SetPrimary(true);
primaryCamera->SetDepth(10.0f);
GameObject* overrideObject = scene.CreateGameObject("OverrideCamera");
auto* overrideCamera = overrideObject->AddComponent<CameraComponent>();
overrideCamera->SetPrimary(false);
overrideCamera->SetDepth(-1.0f);
RenderSceneExtractor extractor;
const RenderSceneData sceneData = extractor.Extract(scene, overrideCamera, 640, 480);
ASSERT_TRUE(sceneData.HasCamera());
EXPECT_EQ(sceneData.camera, overrideCamera);
EXPECT_NE(sceneData.camera, primaryCamera);
EXPECT_EQ(sceneData.cameraData.viewportWidth, 640u);
EXPECT_EQ(sceneData.cameraData.viewportHeight, 480u);
}
} // namespace