diff --git a/engine/include/XCEngine/Rendering/SceneRenderRequestUtils.h b/engine/include/XCEngine/Rendering/SceneRenderRequestUtils.h new file mode 100644 index 00000000..e218b2a0 --- /dev/null +++ b/engine/include/XCEngine/Rendering/SceneRenderRequestUtils.h @@ -0,0 +1,133 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace XCEngine { +namespace Components { +class Scene; +} // namespace Components + +namespace Rendering { +namespace SceneRenderRequestUtils { + +inline bool CompareCameraRenderRequests( + const CameraRenderRequest& lhs, + const CameraRenderRequest& rhs) { + if (lhs.cameraStackOrder != rhs.cameraStackOrder) { + return lhs.cameraStackOrder < rhs.cameraStackOrder; + } + + if (lhs.cameraDepth != rhs.cameraDepth) { + return lhs.cameraDepth < rhs.cameraDepth; + } + + return false; +} + +inline bool IsUsableCamera(const Components::CameraComponent* camera) { + return camera != nullptr && + camera->IsEnabled() && + camera->GetGameObject() != nullptr && + camera->GetGameObject()->IsActiveInHierarchy(); +} + +inline bool CompareSceneCamerasForRendering( + const Components::CameraComponent* lhs, + const Components::CameraComponent* rhs) { + if (lhs->GetStackType() != rhs->GetStackType()) { + return static_cast(lhs->GetStackType()) < static_cast(rhs->GetStackType()); + } + + if (lhs->GetDepth() != rhs->GetDepth()) { + return lhs->GetDepth() < rhs->GetDepth(); + } + + return false; +} + +inline void SortSceneCamerasForRendering(std::vector& cameras) { + std::stable_sort( + cameras.begin(), + cameras.end(), + CompareSceneCamerasForRendering); +} + +inline void SortCameraRenderRequests(std::vector& requests) { + std::stable_sort( + requests.begin(), + requests.end(), + CompareCameraRenderRequests); +} + +inline RenderClearFlags ResolveClearFlags( + const Components::CameraComponent& camera, + size_t renderedBaseCameraCount, + size_t renderedRequestCount) { + switch (camera.GetClearMode()) { + case Components::CameraClearMode::ColorAndDepth: + return RenderClearFlags::All; + case Components::CameraClearMode::DepthOnly: + return RenderClearFlags::Depth; + case Components::CameraClearMode::None: + return RenderClearFlags::None; + case Components::CameraClearMode::Auto: + default: + if (camera.GetStackType() == Components::CameraStackType::Overlay) { + return renderedRequestCount == 0 ? RenderClearFlags::All : RenderClearFlags::Depth; + } + + return renderedBaseCameraCount == 0 ? RenderClearFlags::All : RenderClearFlags::Depth; + } +} + +inline Math::RectInt ResolveCameraRenderArea( + const Components::CameraComponent& camera, + const RenderSurface& surface) { + const Math::Rect viewportRect = camera.GetViewportRect(); + const Math::RectInt parentRenderArea = surface.GetRenderArea(); + const float parentWidth = static_cast(std::max(parentRenderArea.width, 0)); + const float parentHeight = static_cast(std::max(parentRenderArea.height, 0)); + + const int32_t left = parentRenderArea.x + + static_cast(std::floor(viewportRect.x * parentWidth)); + const int32_t top = parentRenderArea.y + + static_cast(std::floor(viewportRect.y * parentHeight)); + const int32_t right = parentRenderArea.x + + static_cast(std::ceil((viewportRect.x + viewportRect.width) * parentWidth)); + const int32_t bottom = parentRenderArea.y + + static_cast(std::ceil((viewportRect.y + viewportRect.height) * parentHeight)); + return Math::RectInt(left, top, right - left, bottom - top); +} + +inline bool BuildCameraRenderRequest( + const Components::Scene& scene, + Components::CameraComponent& camera, + const RenderContext& context, + const RenderSurface& surface, + size_t renderedBaseCameraCount, + size_t renderedRequestCount, + CameraRenderRequest& request) { + request = {}; + request.scene = &scene; + request.camera = &camera; + request.context = context; + request.surface = surface; + request.surface.SetRenderArea(ResolveCameraRenderArea(camera, surface)); + request.cameraDepth = camera.GetDepth(); + request.cameraStackOrder = static_cast(camera.GetStackType()); + request.clearFlags = ResolveClearFlags(camera, renderedBaseCameraCount, renderedRequestCount); + + return request.surface.GetRenderAreaWidth() > 0 && + request.surface.GetRenderAreaHeight() > 0; +} + +} // namespace SceneRenderRequestUtils +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/SceneRenderer.cpp b/engine/src/Rendering/SceneRenderer.cpp index 266c660f..cd267a8f 100644 --- a/engine/src/Rendering/SceneRenderer.cpp +++ b/engine/src/Rendering/SceneRenderer.cpp @@ -1,78 +1,14 @@ #include "Rendering/SceneRenderer.h" +#include "Rendering/SceneRenderRequestUtils.h" -#include "Components/CameraComponent.h" #include "Components/GameObject.h" #include "Scene/Scene.h" #include -#include namespace XCEngine { namespace Rendering { -namespace { - -bool CompareCameraRenderRequest(const CameraRenderRequest& lhs, const CameraRenderRequest& rhs) { - if (lhs.cameraStackOrder != rhs.cameraStackOrder) { - return lhs.cameraStackOrder < rhs.cameraStackOrder; - } - - if (lhs.cameraDepth != rhs.cameraDepth) { - return lhs.cameraDepth < rhs.cameraDepth; - } - - return false; -} - -bool IsUsableCamera(const Components::CameraComponent* camera) { - return camera != nullptr && - camera->IsEnabled() && - camera->GetGameObject() != nullptr && - camera->GetGameObject()->IsActiveInHierarchy(); -} - -RenderClearFlags ResolveClearFlags( - const Components::CameraComponent& camera, - size_t renderedBaseCameraCount, - size_t renderedRequestCount) { - switch (camera.GetClearMode()) { - case Components::CameraClearMode::ColorAndDepth: - return RenderClearFlags::All; - case Components::CameraClearMode::DepthOnly: - return RenderClearFlags::Depth; - case Components::CameraClearMode::None: - return RenderClearFlags::None; - case Components::CameraClearMode::Auto: - default: - if (camera.GetStackType() == Components::CameraStackType::Overlay) { - return renderedRequestCount == 0 ? RenderClearFlags::All : RenderClearFlags::Depth; - } - - return renderedBaseCameraCount == 0 ? RenderClearFlags::All : RenderClearFlags::Depth; - } -} - -Math::RectInt ResolveCameraRenderArea( - const Components::CameraComponent& camera, - const RenderSurface& surface) { - const Math::Rect viewportRect = camera.GetViewportRect(); - const Math::RectInt parentRenderArea = surface.GetRenderArea(); - const float parentWidth = static_cast(std::max(parentRenderArea.width, 0)); - const float parentHeight = static_cast(std::max(parentRenderArea.height, 0)); - - const int32_t left = parentRenderArea.x + - static_cast(std::floor(viewportRect.x * parentWidth)); - const int32_t top = parentRenderArea.y + - static_cast(std::floor(viewportRect.y * parentHeight)); - const int32_t right = parentRenderArea.x + - static_cast(std::ceil((viewportRect.x + viewportRect.width) * parentWidth)); - const int32_t bottom = parentRenderArea.y + - static_cast(std::ceil((viewportRect.y + viewportRect.height) * parentHeight)); - return Math::RectInt(left, top, right - left, bottom - top); -} - -} // namespace - SceneRenderer::SceneRenderer() = default; SceneRenderer::SceneRenderer(std::unique_ptr pipeline) @@ -99,7 +35,7 @@ std::vector SceneRenderer::BuildRenderRequests( std::vector requests; std::vector cameras; - if (IsUsableCamera(overrideCamera)) { + if (SceneRenderRequestUtils::IsUsableCamera(overrideCamera)) { cameras.push_back(overrideCamera); } else { cameras = scene.FindObjectsOfType(); @@ -108,40 +44,24 @@ std::vector SceneRenderer::BuildRenderRequests( cameras.begin(), cameras.end(), [](const Components::CameraComponent* camera) { - return !IsUsableCamera(camera); + return !SceneRenderRequestUtils::IsUsableCamera(camera); }), cameras.end()); } - std::stable_sort( - cameras.begin(), - cameras.end(), - [](const Components::CameraComponent* lhs, const Components::CameraComponent* rhs) { - if (lhs->GetStackType() != rhs->GetStackType()) { - return static_cast(lhs->GetStackType()) < static_cast(rhs->GetStackType()); - } - - if (lhs->GetDepth() != rhs->GetDepth()) { - return lhs->GetDepth() < rhs->GetDepth(); - } - - return false; - }); + SceneRenderRequestUtils::SortSceneCamerasForRendering(cameras); size_t renderedBaseCameraCount = 0; for (Components::CameraComponent* camera : cameras) { - CameraRenderRequest request; - request.scene = &scene; - request.camera = camera; - request.context = context; - request.surface = surface; - request.surface.SetRenderArea(ResolveCameraRenderArea(*camera, surface)); - request.cameraDepth = camera->GetDepth(); - request.cameraStackOrder = static_cast(camera->GetStackType()); - request.clearFlags = ResolveClearFlags(*camera, renderedBaseCameraCount, requests.size()); - if (request.surface.GetRenderAreaWidth() > 0 && - request.surface.GetRenderAreaHeight() > 0) { + if (SceneRenderRequestUtils::BuildCameraRenderRequest( + scene, + *camera, + context, + surface, + renderedBaseCameraCount, + requests.size(), + request)) { requests.push_back(request); if (camera->GetStackType() == Components::CameraStackType::Base) { ++renderedBaseCameraCount; @@ -168,10 +88,7 @@ bool SceneRenderer::Render(const std::vector& requests) { } std::vector sortedRequests = requests; - std::stable_sort( - sortedRequests.begin(), - sortedRequests.end(), - CompareCameraRenderRequest); + SceneRenderRequestUtils::SortCameraRenderRequests(sortedRequests); bool rendered = false; for (const CameraRenderRequest& request : sortedRequests) { diff --git a/tests/Rendering/unit/CMakeLists.txt b/tests/Rendering/unit/CMakeLists.txt index af236005..17f787a0 100644 --- a/tests/Rendering/unit/CMakeLists.txt +++ b/tests/Rendering/unit/CMakeLists.txt @@ -7,6 +7,7 @@ set(RENDERING_UNIT_TEST_SOURCES test_builtin_forward_pipeline.cpp test_builtin_scene_view_post_pass_plan.cpp test_camera_scene_renderer.cpp + test_scene_render_request_utils.cpp test_render_scene_utility.cpp test_render_scene_extractor.cpp ) diff --git a/tests/Rendering/unit/test_scene_render_request_utils.cpp b/tests/Rendering/unit/test_scene_render_request_utils.cpp new file mode 100644 index 00000000..7998d201 --- /dev/null +++ b/tests/Rendering/unit/test_scene_render_request_utils.cpp @@ -0,0 +1,213 @@ +#include + +#include +#include +#include + +#include + +using namespace XCEngine::Components; +using namespace XCEngine::Rendering; + +namespace { + +RenderContext CreateValidContext() { + RenderContext context; + context.device = reinterpret_cast(1); + context.commandList = reinterpret_cast(1); + context.commandQueue = reinterpret_cast(1); + return context; +} + +} // namespace + +TEST(SceneRenderRequestUtils_Test, RejectsNullDisabledAndInactiveCameras) { + Scene scene("SceneRenderRequestUtilsUsableCameraScene"); + + GameObject* activeObject = scene.CreateGameObject("ActiveCamera"); + auto* activeCamera = activeObject->AddComponent(); + + GameObject* disabledObject = scene.CreateGameObject("DisabledCamera"); + auto* disabledCamera = disabledObject->AddComponent(); + disabledCamera->SetEnabled(false); + + GameObject* inactiveObject = scene.CreateGameObject("InactiveCamera"); + auto* inactiveCamera = inactiveObject->AddComponent(); + inactiveObject->SetActive(false); + + EXPECT_FALSE(SceneRenderRequestUtils::IsUsableCamera(nullptr)); + EXPECT_TRUE(SceneRenderRequestUtils::IsUsableCamera(activeCamera)); + EXPECT_FALSE(SceneRenderRequestUtils::IsUsableCamera(disabledCamera)); + EXPECT_FALSE(SceneRenderRequestUtils::IsUsableCamera(inactiveCamera)); +} + +TEST(SceneRenderRequestUtils_Test, SortsSceneCamerasByStackDepthAndKeepsStableTieOrder) { + Scene scene("SceneRenderRequestUtilsSortSceneCameras"); + + GameObject* lateBaseObject = scene.CreateGameObject("LateBase"); + auto* lateBase = lateBaseObject->AddComponent(); + lateBase->SetDepth(10.0f); + lateBase->SetStackType(CameraStackType::Base); + + GameObject* firstTieObject = scene.CreateGameObject("FirstTie"); + auto* firstTie = firstTieObject->AddComponent(); + firstTie->SetDepth(2.0f); + firstTie->SetStackType(CameraStackType::Base); + + GameObject* secondTieObject = scene.CreateGameObject("SecondTie"); + auto* secondTie = secondTieObject->AddComponent(); + secondTie->SetDepth(2.0f); + secondTie->SetStackType(CameraStackType::Base); + + GameObject* overlayObject = scene.CreateGameObject("Overlay"); + auto* overlay = overlayObject->AddComponent(); + overlay->SetDepth(-50.0f); + overlay->SetStackType(CameraStackType::Overlay); + + std::vector cameras = { overlay, lateBase, secondTie, firstTie }; + SceneRenderRequestUtils::SortSceneCamerasForRendering(cameras); + + ASSERT_EQ(cameras.size(), 4u); + EXPECT_EQ(cameras[0], secondTie); + EXPECT_EQ(cameras[1], firstTie); + EXPECT_EQ(cameras[2], lateBase); + EXPECT_EQ(cameras[3], overlay); +} + +TEST(SceneRenderRequestUtils_Test, SortsRequestsByPriorityAndKeepsStableTieOrder) { + Scene scene("SceneRenderRequestUtilsSortRequests"); + + GameObject* firstObject = scene.CreateGameObject("First"); + auto* firstCamera = firstObject->AddComponent(); + + GameObject* secondObject = scene.CreateGameObject("Second"); + auto* secondCamera = secondObject->AddComponent(); + + CameraRenderRequest overlayRequest; + overlayRequest.scene = &scene; + overlayRequest.camera = secondCamera; + overlayRequest.context = CreateValidContext(); + overlayRequest.surface = RenderSurface(320, 180); + overlayRequest.cameraDepth = 100.0f; + overlayRequest.cameraStackOrder = 1; + + CameraRenderRequest secondTieRequest = overlayRequest; + secondTieRequest.camera = secondCamera; + secondTieRequest.cameraDepth = 5.0f; + secondTieRequest.cameraStackOrder = 0; + + CameraRenderRequest firstTieRequest = overlayRequest; + firstTieRequest.camera = firstCamera; + firstTieRequest.cameraDepth = 5.0f; + firstTieRequest.cameraStackOrder = 0; + + std::vector requests = { overlayRequest, secondTieRequest, firstTieRequest }; + SceneRenderRequestUtils::SortCameraRenderRequests(requests); + + ASSERT_EQ(requests.size(), 3u); + EXPECT_EQ(requests[0].camera, secondCamera); + EXPECT_EQ(requests[1].camera, firstCamera); + EXPECT_EQ(requests[2].camera, secondCamera); + EXPECT_EQ(requests[2].cameraStackOrder, 1u); +} + +TEST(SceneRenderRequestUtils_Test, ResolvesClearFlagsForExplicitAndAutoModes) { + Scene scene("SceneRenderRequestUtilsClearFlags"); + + GameObject* baseObject = scene.CreateGameObject("Base"); + auto* baseCamera = baseObject->AddComponent(); + baseCamera->SetStackType(CameraStackType::Base); + + GameObject* overlayObject = scene.CreateGameObject("Overlay"); + auto* overlayCamera = overlayObject->AddComponent(); + overlayCamera->SetStackType(CameraStackType::Overlay); + + baseCamera->SetClearMode(CameraClearMode::ColorAndDepth); + EXPECT_EQ( + SceneRenderRequestUtils::ResolveClearFlags(*baseCamera, 0u, 0u), + RenderClearFlags::All); + + baseCamera->SetClearMode(CameraClearMode::DepthOnly); + EXPECT_EQ( + SceneRenderRequestUtils::ResolveClearFlags(*baseCamera, 0u, 0u), + RenderClearFlags::Depth); + + baseCamera->SetClearMode(CameraClearMode::None); + EXPECT_EQ( + SceneRenderRequestUtils::ResolveClearFlags(*baseCamera, 0u, 0u), + RenderClearFlags::None); + + baseCamera->SetClearMode(CameraClearMode::Auto); + EXPECT_EQ( + SceneRenderRequestUtils::ResolveClearFlags(*baseCamera, 0u, 0u), + RenderClearFlags::All); + EXPECT_EQ( + SceneRenderRequestUtils::ResolveClearFlags(*baseCamera, 1u, 1u), + RenderClearFlags::Depth); + + overlayCamera->SetClearMode(CameraClearMode::Auto); + EXPECT_EQ( + SceneRenderRequestUtils::ResolveClearFlags(*overlayCamera, 0u, 0u), + RenderClearFlags::All); + EXPECT_EQ( + SceneRenderRequestUtils::ResolveClearFlags(*overlayCamera, 0u, 1u), + RenderClearFlags::Depth); +} + +TEST(SceneRenderRequestUtils_Test, ComposesCameraViewportWithinNestedSurfaceRenderArea) { + Scene scene("SceneRenderRequestUtilsViewport"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + 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)); + + const XCEngine::Math::RectInt renderArea = + SceneRenderRequestUtils::ResolveCameraRenderArea(*camera, surface); + EXPECT_EQ(renderArea.x, 200); + EXPECT_EQ(renderArea.y, 80); + EXPECT_EQ(renderArea.width, 200); + EXPECT_EQ(renderArea.height, 120); +} + +TEST(SceneRenderRequestUtils_Test, BuildsRequestMetadataAndRejectsZeroSizedRenderAreas) { + Scene scene("SceneRenderRequestUtilsBuildRequest"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetDepth(3.0f); + camera->SetStackType(CameraStackType::Overlay); + camera->SetClearMode(CameraClearMode::Auto); + camera->SetViewportRect(XCEngine::Math::Rect(0.25f, 0.5f, 0.5f, 0.25f)); + + CameraRenderRequest request; + ASSERT_TRUE(SceneRenderRequestUtils::BuildCameraRenderRequest( + scene, + *camera, + CreateValidContext(), + RenderSurface(800, 600), + 1u, + 1u, + request)); + EXPECT_EQ(request.scene, &scene); + EXPECT_EQ(request.camera, camera); + EXPECT_FLOAT_EQ(request.cameraDepth, 3.0f); + EXPECT_EQ(request.cameraStackOrder, 1u); + EXPECT_EQ(request.clearFlags, RenderClearFlags::Depth); + EXPECT_EQ(request.surface.GetRenderArea().x, 200); + EXPECT_EQ(request.surface.GetRenderArea().y, 300); + EXPECT_EQ(request.surface.GetRenderArea().width, 400); + EXPECT_EQ(request.surface.GetRenderArea().height, 150); + + camera->SetViewportRect(XCEngine::Math::Rect(0.5f, 0.5f, 0.0f, 1.0f)); + EXPECT_FALSE(SceneRenderRequestUtils::BuildCameraRenderRequest( + scene, + *camera, + CreateValidContext(), + RenderSurface(800, 600), + 0u, + 0u, + request)); +}