diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 67b821e8..8ad2065f 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -516,6 +516,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSurface.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeaturePass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/SceneRenderFeatureHost.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Execution/RenderPipelineHost.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Caches/RenderResourceCache.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Caches/DirectionalShadowSurfaceCache.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Shadow/DirectionalShadowData.h @@ -535,6 +536,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraFramePlan.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/CameraRenderer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/RenderPipelineHost.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/Internal/CameraFrameRenderGraphExecution.h ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/Internal/CameraFrameRenderGraphExecution.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/Internal/CameraFrameRenderGraphRecordingSession.h diff --git a/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h b/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h new file mode 100644 index 00000000..87f7bd29 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine { +namespace Rendering { + +class CameraFramePlanBuilder; + +class RenderPipelineHost { +public: + RenderPipelineHost(); + explicit RenderPipelineHost(std::unique_ptr pipeline); + explicit RenderPipelineHost(std::shared_ptr pipelineAsset); + ~RenderPipelineHost(); + + void SetPipeline(std::unique_ptr pipeline); + void SetPipelineAsset(std::shared_ptr pipelineAsset); + RenderPipeline* GetPipeline() const { return m_cameraRenderer.GetPipeline(); } + const RenderPipelineAsset* GetPipelineAsset() const { return m_cameraRenderer.GetPipelineAsset(); } + + std::vector BuildFramePlans( + const std::vector& requests); + + bool Render(const CameraFramePlan& plan); + bool Render(const std::vector& plans); + +private: + CameraRenderer m_cameraRenderer; + std::unique_ptr m_framePlanBuilder; +}; + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h index 5d533f1f..5d915c04 100644 --- a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h +++ b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h @@ -1,10 +1,8 @@ #pragma once -#include -#include +#include #include -#include #include namespace XCEngine { @@ -15,9 +13,6 @@ class Scene; namespace Rendering { -class CameraFramePlanBuilder; -class RenderPipelineAsset; - class SceneRenderer { public: SceneRenderer(); @@ -27,8 +22,8 @@ public: void SetPipeline(std::unique_ptr pipeline); void SetPipelineAsset(std::shared_ptr pipelineAsset); - RenderPipeline* GetPipeline() const { return m_cameraRenderer.GetPipeline(); } - const RenderPipelineAsset* GetPipelineAsset() const { return m_cameraRenderer.GetPipelineAsset(); } + RenderPipeline* GetPipeline() const { return m_pipelineHost.GetPipeline(); } + const RenderPipelineAsset* GetPipelineAsset() const { return m_pipelineHost.GetPipelineAsset(); } std::vector BuildFramePlans( const Components::Scene& scene, @@ -45,8 +40,7 @@ public: const RenderSurface& surface); SceneRenderRequestPlanner m_requestPlanner; - CameraRenderer m_cameraRenderer; - std::unique_ptr m_framePlanBuilder; + RenderPipelineHost m_pipelineHost; }; } // namespace Rendering diff --git a/engine/src/Rendering/Execution/RenderPipelineHost.cpp b/engine/src/Rendering/Execution/RenderPipelineHost.cpp new file mode 100644 index 00000000..0257404f --- /dev/null +++ b/engine/src/Rendering/Execution/RenderPipelineHost.cpp @@ -0,0 +1,86 @@ +#include "Rendering/Execution/RenderPipelineHost.h" + +#include "Rendering/Planning/CameraFramePlanBuilder.h" +#include "Rendering/Planning/SceneRenderRequestUtils.h" + +namespace XCEngine { +namespace Rendering { + +namespace { + +bool CompareCameraFramePlans( + const CameraFramePlan& lhs, + const CameraFramePlan& rhs) { + return SceneRenderRequestUtils::CompareCameraRenderRequests( + lhs.request, + rhs.request); +} + +} // namespace + +RenderPipelineHost::RenderPipelineHost() + : m_framePlanBuilder(std::make_unique()) { +} + +RenderPipelineHost::RenderPipelineHost(std::unique_ptr pipeline) + : m_cameraRenderer(std::move(pipeline)) + , m_framePlanBuilder(std::make_unique()) { +} + +RenderPipelineHost::RenderPipelineHost(std::shared_ptr pipelineAsset) + : m_cameraRenderer(std::move(pipelineAsset)) + , m_framePlanBuilder(std::make_unique()) { +} + +RenderPipelineHost::~RenderPipelineHost() = default; + +void RenderPipelineHost::SetPipeline(std::unique_ptr pipeline) { + m_cameraRenderer.SetPipeline(std::move(pipeline)); +} + +void RenderPipelineHost::SetPipelineAsset(std::shared_ptr pipelineAsset) { + m_cameraRenderer.SetPipelineAsset(std::move(pipelineAsset)); +} + +std::vector RenderPipelineHost::BuildFramePlans( + const std::vector& requests) { + return m_framePlanBuilder != nullptr + ? m_framePlanBuilder->BuildPlans(requests, GetPipelineAsset()) + : std::vector(); +} + +bool RenderPipelineHost::Render(const CameraFramePlan& plan) { + return m_cameraRenderer.Render(plan); +} + +bool RenderPipelineHost::Render(const std::vector& plans) { + if (plans.empty()) { + return false; + } + + for (const CameraFramePlan& plan : plans) { + if (!plan.IsValid()) { + return false; + } + } + + std::vector sortedPlans = plans; + std::stable_sort( + sortedPlans.begin(), + sortedPlans.end(), + CompareCameraFramePlans); + + bool rendered = false; + for (const CameraFramePlan& plan : sortedPlans) { + if (!m_cameraRenderer.Render(plan)) { + return false; + } + + rendered = true; + } + + return rendered; +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Execution/SceneRenderer.cpp b/engine/src/Rendering/Execution/SceneRenderer.cpp index e5b955dd..5a8a2e96 100644 --- a/engine/src/Rendering/Execution/SceneRenderer.cpp +++ b/engine/src/Rendering/Execution/SceneRenderer.cpp @@ -1,46 +1,30 @@ #include "Rendering/Execution/SceneRenderer.h" #include "Components/CameraComponent.h" -#include "Rendering/Planning/CameraFramePlanBuilder.h" -#include "Rendering/Planning/SceneRenderRequestUtils.h" +#include "Rendering/Execution/RenderPipelineHost.h" namespace XCEngine { namespace Rendering { -namespace { - -bool CompareCameraFramePlans( - const CameraFramePlan& lhs, - const CameraFramePlan& rhs) { - return SceneRenderRequestUtils::CompareCameraRenderRequests( - lhs.request, - rhs.request); -} - -} // namespace - -SceneRenderer::SceneRenderer() - : m_framePlanBuilder(std::make_unique()) { +SceneRenderer::SceneRenderer() { } SceneRenderer::SceneRenderer(std::unique_ptr pipeline) - : m_cameraRenderer(std::move(pipeline)) - , m_framePlanBuilder(std::make_unique()) { + : m_pipelineHost(std::move(pipeline)) { } SceneRenderer::SceneRenderer(std::shared_ptr pipelineAsset) - : m_cameraRenderer(std::move(pipelineAsset)) - , m_framePlanBuilder(std::make_unique()) { + : m_pipelineHost(std::move(pipelineAsset)) { } SceneRenderer::~SceneRenderer() = default; void SceneRenderer::SetPipeline(std::unique_ptr pipeline) { - m_cameraRenderer.SetPipeline(std::move(pipeline)); + m_pipelineHost.SetPipeline(std::move(pipeline)); } void SceneRenderer::SetPipelineAsset(std::shared_ptr pipelineAsset) { - m_cameraRenderer.SetPipelineAsset(std::move(pipelineAsset)); + m_pipelineHost.SetPipelineAsset(std::move(pipelineAsset)); } std::vector SceneRenderer::BuildFramePlans( @@ -50,42 +34,15 @@ std::vector SceneRenderer::BuildFramePlans( const RenderSurface& surface) { const std::vector requests = m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface); - return m_framePlanBuilder != nullptr - ? m_framePlanBuilder->BuildPlans(requests, GetPipelineAsset()) - : std::vector(); + return m_pipelineHost.BuildFramePlans(requests); } bool SceneRenderer::Render(const CameraFramePlan& plan) { - return m_cameraRenderer.Render(plan); + return m_pipelineHost.Render(plan); } bool SceneRenderer::Render(const std::vector& plans) { - if (plans.empty()) { - return false; - } - - for (const CameraFramePlan& plan : plans) { - if (!plan.IsValid()) { - return false; - } - } - - std::vector sortedPlans = plans; - std::stable_sort( - sortedPlans.begin(), - sortedPlans.end(), - CompareCameraFramePlans); - - bool rendered = false; - for (const CameraFramePlan& plan : sortedPlans) { - if (!m_cameraRenderer.Render(plan)) { - return false; - } - - rendered = true; - } - - return rendered; + return m_pipelineHost.Render(plans); } bool SceneRenderer::Render( diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 0c8c8d21..be61b62f 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -3468,6 +3469,139 @@ TEST(SceneRenderer_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) EXPECT_EQ(replacementState->shutdownCalls, 1); } +TEST(RenderPipelineHost_Test, BuildsFramePlansFromRequestsUsingPipelineAssetDefaults) { + Scene scene("RenderPipelineHostBuildPlans"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(2.0f); + + FinalColorOverrideSettings cameraOverrides = {}; + cameraOverrides.overrideExposureValue = true; + cameraOverrides.exposureValue = 1.8f; + camera->SetFinalColorOverrides(cameraOverrides); + + auto assetState = std::make_shared(); + assetState->defaultFinalColorSettings.outputTransferMode = + FinalColorOutputTransferMode::LinearToSRGB; + assetState->defaultFinalColorSettings.exposureMode = + FinalColorExposureMode::Fixed; + assetState->defaultFinalColorSettings.exposureValue = 1.25f; + + CameraRenderRequest request = {}; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.surface = RenderSurface(640, 360); + request.cameraDepth = camera->GetDepth(); + request.clearFlags = RenderClearFlags::All; + + RenderPipelineHost host(std::make_shared(assetState)); + const std::vector plans = + host.BuildFramePlans({ request }); + + ASSERT_EQ(plans.size(), 1u); + const CameraFramePlan& plan = plans[0]; + EXPECT_TRUE(plan.finalColorPolicy.hasPipelineDefaults); + EXPECT_TRUE(plan.finalColorPolicy.hasCameraOverrides); + EXPECT_EQ( + plan.finalColorPolicy.outputTransferMode, + FinalColorOutputTransferMode::LinearToSRGB); + EXPECT_EQ( + plan.finalColorPolicy.exposureMode, + FinalColorExposureMode::Fixed); + EXPECT_FLOAT_EQ(plan.finalColorPolicy.exposureValue, 1.8f); +} + +TEST(RenderPipelineHost_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) { + Scene scene("RenderPipelineHostScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(2.0f); + + auto initialState = std::make_shared(); + auto replacementState = std::make_shared(); + + { + auto initialPipeline = std::make_unique(initialState); + MockPipeline* initialPipelineRaw = initialPipeline.get(); + RenderPipelineHost host(std::move(initialPipeline)); + EXPECT_EQ(host.GetPipeline(), initialPipelineRaw); + + auto replacementPipeline = std::make_unique(replacementState); + MockPipeline* replacementPipelineRaw = replacementPipeline.get(); + host.SetPipeline(std::move(replacementPipeline)); + + EXPECT_EQ(initialState->shutdownCalls, 1); + EXPECT_EQ(host.GetPipeline(), replacementPipelineRaw); + + CameraRenderRequest request = {}; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.surface = RenderSurface(800, 600); + request.cameraDepth = camera->GetDepth(); + request.clearFlags = RenderClearFlags::All; + + const std::vector plans = + host.BuildFramePlans({ request }); + ASSERT_EQ(plans.size(), 1u); + ASSERT_TRUE(host.Render(plans)); + 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(RenderPipelineHost_Test, SortsManualFramePlansByDepthBeforeRendering) { + Scene scene("RenderPipelineHostManualRequests"); + + GameObject* farCameraObject = scene.CreateGameObject("FarCamera"); + auto* farCamera = farCameraObject->AddComponent(); + farCamera->SetPrimary(true); + farCamera->SetDepth(10.0f); + + GameObject* nearCameraObject = scene.CreateGameObject("NearCamera"); + auto* nearCamera = nearCameraObject->AddComponent(); + nearCamera->SetPrimary(false); + nearCamera->SetDepth(1.0f); + + auto state = std::make_shared(); + RenderPipelineHost host(std::make_unique(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 plans = + host.BuildFramePlans({ farRequest, nearRequest }); + ASSERT_TRUE(host.Render(plans)); + 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); +} + TEST(SceneRenderer_Test, CreatesPipelineInstancesFromPipelineAssetsAndShutsDownReplacedPipelines) { Scene scene("SceneRendererAssetScene");