diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 8ad2065f..37516dfd 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -534,6 +534,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Features/BuiltinGaussianSplatPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Features/BuiltinVolumetricPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.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 @@ -594,6 +595,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Shadow/DirectionalShadowRuntime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Execution/SceneRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Pipelines/Internal/BuiltinForwardMainSceneGraphBuilder.h diff --git a/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h b/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h new file mode 100644 index 00000000..f95061ca --- /dev/null +++ b/engine/include/XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#include + +namespace XCEngine { +namespace Rendering { +namespace Pipelines { + +class ScriptableRenderPipelineHost final : public RenderPipeline { +public: + ScriptableRenderPipelineHost(); + explicit ScriptableRenderPipelineHost( + std::unique_ptr mainSceneRenderer); + explicit ScriptableRenderPipelineHost( + std::shared_ptr mainSceneRendererAsset); + ~ScriptableRenderPipelineHost() override; + + using RenderPipeline::Render; + + void SetMainSceneRenderer(std::unique_ptr mainSceneRenderer); + void SetMainSceneRendererAsset( + std::shared_ptr mainSceneRendererAsset); + RenderPipelineRenderer* GetMainSceneRenderer() const { + return m_mainSceneRenderer.get(); + } + const RenderPipelineAsset* GetMainSceneRendererAsset() const { + return m_mainSceneRendererAsset.get(); + } + + bool Initialize(const RenderContext& context) override; + void Shutdown() override; + bool SupportsMainSceneRenderGraph() const override; + bool RecordMainSceneRenderGraph( + const RenderPipelineMainSceneRenderGraphContext& context) override; + bool Render(const FrameExecutionContext& executionContext) override; + bool Render( + const RenderContext& context, + const RenderSurface& surface, + const RenderSceneData& sceneData) override; + +private: + void ResetMainSceneRenderer(std::unique_ptr mainSceneRenderer); + + std::shared_ptr m_mainSceneRendererAsset; + std::unique_ptr m_mainSceneRenderer; +}; + +class ScriptableRenderPipelineHostAsset final : public RenderPipelineAsset { +public: + ScriptableRenderPipelineHostAsset(); + explicit ScriptableRenderPipelineHostAsset( + std::shared_ptr mainSceneRendererAsset); + + std::unique_ptr CreatePipeline() const override; + FinalColorSettings GetDefaultFinalColorSettings() const override; + +private: + std::shared_ptr m_mainSceneRendererAsset; +}; + +} // namespace Pipelines +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/RenderPipeline.h b/engine/include/XCEngine/Rendering/RenderPipeline.h index 76bb99ed..f57b972c 100644 --- a/engine/include/XCEngine/Rendering/RenderPipeline.h +++ b/engine/include/XCEngine/Rendering/RenderPipeline.h @@ -30,9 +30,9 @@ struct RenderPipelineMainSceneRenderGraphContext { RenderGraphBlackboard* blackboard = nullptr; }; -class RenderPipeline { +class RenderPipelineRenderer { public: - virtual ~RenderPipeline() = default; + virtual ~RenderPipelineRenderer() = default; virtual bool Initialize(const RenderContext& context) = 0; virtual void Shutdown() = 0; @@ -55,5 +55,12 @@ public: const RenderSceneData& sceneData) = 0; }; +class RenderPipeline : public RenderPipelineRenderer { +public: + using RenderPipelineRenderer::Render; + + ~RenderPipeline() override = default; +}; + } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index c95e77f2..1de91997 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -7,6 +7,7 @@ #include "Rendering/Passes/BuiltinObjectIdPass.h" #include "Rendering/Passes/BuiltinShadowCasterPass.h" #include "Rendering/Pipelines/BuiltinForwardPipeline.h" +#include "Rendering/Pipelines/ScriptableRenderPipelineHost.h" #include "Rendering/RenderPipelineAsset.h" #include "Rendering/RenderSurface.h" #include "Rendering/Shadow/DirectionalShadowRuntime.h" @@ -19,7 +20,7 @@ namespace { std::shared_ptr CreateDefaultPipelineAsset() { static const std::shared_ptr s_defaultPipelineAsset = - std::make_shared(); + std::make_shared(); return s_defaultPipelineAsset; } @@ -40,7 +41,7 @@ std::unique_ptr CreatePipelineFromAsset( } } - return std::make_unique(); + return std::make_unique(); } Resources::ShaderKeywordSet BuildSceneGlobalShaderKeywords( diff --git a/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp new file mode 100644 index 00000000..b6e4037a --- /dev/null +++ b/engine/src/Rendering/Pipelines/ScriptableRenderPipelineHost.cpp @@ -0,0 +1,150 @@ +#include "Rendering/Pipelines/ScriptableRenderPipelineHost.h" + +#include "Rendering/Pipelines/BuiltinForwardPipeline.h" + +namespace XCEngine { +namespace Rendering { +namespace Pipelines { + +namespace { + +std::shared_ptr CreateDefaultMainSceneRendererAsset() { + static const std::shared_ptr s_defaultRendererAsset = + std::make_shared(); + return s_defaultRendererAsset; +} + +std::unique_ptr CreateMainSceneRendererFromAsset( + const std::shared_ptr& mainSceneRendererAsset) { + const std::shared_ptr resolvedAsset = + mainSceneRendererAsset != nullptr + ? mainSceneRendererAsset + : CreateDefaultMainSceneRendererAsset(); + if (resolvedAsset != nullptr) { + if (std::unique_ptr pipeline = resolvedAsset->CreatePipeline()) { + return pipeline; + } + } + + return std::make_unique(); +} + +} // namespace + +ScriptableRenderPipelineHost::ScriptableRenderPipelineHost() + : ScriptableRenderPipelineHost(CreateDefaultMainSceneRendererAsset()) { +} + +ScriptableRenderPipelineHost::ScriptableRenderPipelineHost( + std::unique_ptr mainSceneRenderer) { + ResetMainSceneRenderer(std::move(mainSceneRenderer)); +} + +ScriptableRenderPipelineHost::ScriptableRenderPipelineHost( + std::shared_ptr mainSceneRendererAsset) + : m_mainSceneRendererAsset( + mainSceneRendererAsset != nullptr + ? std::move(mainSceneRendererAsset) + : CreateDefaultMainSceneRendererAsset()) { + ResetMainSceneRenderer( + CreateMainSceneRendererFromAsset(m_mainSceneRendererAsset)); +} + +ScriptableRenderPipelineHost::~ScriptableRenderPipelineHost() { + Shutdown(); +} + +void ScriptableRenderPipelineHost::SetMainSceneRenderer( + std::unique_ptr mainSceneRenderer) { + m_mainSceneRendererAsset.reset(); + ResetMainSceneRenderer(std::move(mainSceneRenderer)); +} + +void ScriptableRenderPipelineHost::SetMainSceneRendererAsset( + std::shared_ptr mainSceneRendererAsset) { + m_mainSceneRendererAsset = + mainSceneRendererAsset != nullptr + ? std::move(mainSceneRendererAsset) + : CreateDefaultMainSceneRendererAsset(); + ResetMainSceneRenderer( + CreateMainSceneRendererFromAsset(m_mainSceneRendererAsset)); +} + +bool ScriptableRenderPipelineHost::Initialize(const RenderContext& context) { + return m_mainSceneRenderer != nullptr && + m_mainSceneRenderer->Initialize(context); +} + +void ScriptableRenderPipelineHost::Shutdown() { + if (m_mainSceneRenderer != nullptr) { + m_mainSceneRenderer->Shutdown(); + } +} + +bool ScriptableRenderPipelineHost::SupportsMainSceneRenderGraph() const { + return m_mainSceneRenderer != nullptr && + m_mainSceneRenderer->SupportsMainSceneRenderGraph(); +} + +bool ScriptableRenderPipelineHost::RecordMainSceneRenderGraph( + const RenderPipelineMainSceneRenderGraphContext& context) { + return m_mainSceneRenderer != nullptr && + m_mainSceneRenderer->RecordMainSceneRenderGraph(context); +} + +bool ScriptableRenderPipelineHost::Render( + const FrameExecutionContext& executionContext) { + return m_mainSceneRenderer != nullptr && + m_mainSceneRenderer->Render(executionContext); +} + +bool ScriptableRenderPipelineHost::Render( + const RenderContext& context, + const RenderSurface& surface, + const RenderSceneData& sceneData) { + return m_mainSceneRenderer != nullptr && + m_mainSceneRenderer->Render(context, surface, sceneData); +} + +void ScriptableRenderPipelineHost::ResetMainSceneRenderer( + std::unique_ptr mainSceneRenderer) { + if (m_mainSceneRenderer != nullptr) { + m_mainSceneRenderer->Shutdown(); + } + + m_mainSceneRenderer = std::move(mainSceneRenderer); + if (m_mainSceneRenderer == nullptr) { + if (m_mainSceneRendererAsset == nullptr) { + m_mainSceneRendererAsset = CreateDefaultMainSceneRendererAsset(); + } + m_mainSceneRenderer = + CreateMainSceneRendererFromAsset(m_mainSceneRendererAsset); + } +} + +ScriptableRenderPipelineHostAsset::ScriptableRenderPipelineHostAsset() + : ScriptableRenderPipelineHostAsset(CreateDefaultMainSceneRendererAsset()) { +} + +ScriptableRenderPipelineHostAsset::ScriptableRenderPipelineHostAsset( + std::shared_ptr mainSceneRendererAsset) + : m_mainSceneRendererAsset( + mainSceneRendererAsset != nullptr + ? std::move(mainSceneRendererAsset) + : CreateDefaultMainSceneRendererAsset()) { +} + +std::unique_ptr ScriptableRenderPipelineHostAsset::CreatePipeline() const { + return std::make_unique( + m_mainSceneRendererAsset); +} + +FinalColorSettings ScriptableRenderPipelineHostAsset::GetDefaultFinalColorSettings() const { + return m_mainSceneRendererAsset != nullptr + ? m_mainSceneRendererAsset->GetDefaultFinalColorSettings() + : FinalColorSettings{}; +} + +} // namespace Pipelines +} // namespace Rendering +} // namespace XCEngine diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index be61b62f..0cc37587 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -861,6 +862,18 @@ RenderContext CreateValidContext() { return context; } +RenderSceneData CreateSceneDataForCamera( + Scene&, + CameraComponent& camera, + const RenderSurface& surface) { + RenderSceneData sceneData = {}; + sceneData.camera = &camera; + sceneData.cameraData.viewportWidth = surface.GetRenderAreaWidth(); + sceneData.cameraData.viewportHeight = surface.GetRenderAreaHeight(); + sceneData.cameraData.clearFlags = RenderClearFlags::All; + return sceneData; +} + } // namespace TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { @@ -3602,6 +3615,108 @@ TEST(RenderPipelineHost_Test, SortsManualFramePlansByDepthBeforeRendering) { EXPECT_EQ(state->renderedClearFlags[1], RenderClearFlags::None); } +TEST(ScriptableRenderPipelineHost_Test, ForwardsRendererLifetimeAndFrameRendering) { + Scene scene("ScriptableRenderPipelineHostScene"); + + 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 initialRenderer = std::make_unique(initialState); + MockPipeline* initialRendererRaw = initialRenderer.get(); + Pipelines::ScriptableRenderPipelineHost host(std::move(initialRenderer)); + EXPECT_EQ(host.GetMainSceneRenderer(), initialRendererRaw); + + auto replacementRenderer = std::make_unique(replacementState); + MockPipeline* replacementRendererRaw = replacementRenderer.get(); + host.SetMainSceneRenderer(std::move(replacementRenderer)); + + EXPECT_EQ(initialState->shutdownCalls, 1); + EXPECT_EQ(host.GetMainSceneRenderer(), replacementRendererRaw); + + CameraRenderRequest request = {}; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.surface = RenderSurface(800, 600); + request.cameraDepth = camera->GetDepth(); + request.clearFlags = RenderClearFlags::All; + + RenderSceneData sceneData = + CreateSceneDataForCamera(scene, *camera, request.surface); + ASSERT_TRUE(host.Render( + request.context, + request.surface, + sceneData)); + EXPECT_EQ(replacementState->renderCalls, 1); + EXPECT_EQ(replacementState->lastSurfaceWidth, 800u); + EXPECT_EQ(replacementState->lastSurfaceHeight, 600u); + EXPECT_EQ(replacementState->lastCamera, camera); + + replacementState->supportsMainSceneRenderGraph = true; + RenderGraph graph = {}; + RenderGraphBuilder graphBuilder(graph); + RenderGraphBlackboard blackboard = {}; + bool executionSucceeded = true; + RenderSceneData graphSceneData = + CreateSceneDataForCamera(scene, *camera, request.surface); + const RenderPipelineMainSceneRenderGraphContext graphContext = { + graphBuilder, + "MainScene", + request.context, + graphSceneData, + request.surface, + nullptr, + nullptr, + XCEngine::RHI::ResourceStates::Common, + {}, + {}, + &executionSucceeded, + &blackboard + }; + EXPECT_TRUE(host.SupportsMainSceneRenderGraph()); + EXPECT_TRUE(host.RecordMainSceneRenderGraph(graphContext)); + EXPECT_EQ(replacementState->recordMainSceneCalls, 1); + } + + EXPECT_EQ(initialState->shutdownCalls, 1); + EXPECT_EQ(replacementState->shutdownCalls, 1); +} + +TEST(ScriptableRenderPipelineHostAsset_Test, CreatesHostFromRendererAssetAndForwardsDefaults) { + auto assetState = std::make_shared(); + assetState->defaultFinalColorSettings.outputTransferMode = + FinalColorOutputTransferMode::LinearToSRGB; + assetState->defaultFinalColorSettings.exposureMode = + FinalColorExposureMode::Fixed; + assetState->defaultFinalColorSettings.exposureValue = 1.5f; + + Pipelines::ScriptableRenderPipelineHostAsset asset( + std::make_shared(assetState)); + EXPECT_EQ( + asset.GetDefaultFinalColorSettings().outputTransferMode, + FinalColorOutputTransferMode::LinearToSRGB); + EXPECT_EQ( + asset.GetDefaultFinalColorSettings().exposureMode, + FinalColorExposureMode::Fixed); + EXPECT_FLOAT_EQ( + asset.GetDefaultFinalColorSettings().exposureValue, + 1.5f); + + std::unique_ptr pipeline = asset.CreatePipeline(); + ASSERT_NE(pipeline, nullptr); + auto* host = + dynamic_cast(pipeline.get()); + ASSERT_NE(host, nullptr); + EXPECT_NE(host->GetMainSceneRenderer(), nullptr); + EXPECT_EQ(assetState->createCalls, 1); +} + TEST(SceneRenderer_Test, CreatesPipelineInstancesFromPipelineAssetsAndShutsDownReplacedPipelines) { Scene scene("SceneRendererAssetScene");