diff --git a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h index 51865f1d..5324366a 100644 --- a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h +++ b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h @@ -5,6 +5,9 @@ #include #include +#include +#include + namespace XCEngine { namespace Components { class CameraComponent; @@ -13,6 +16,53 @@ class Scene; namespace Rendering { +enum class CameraFrameStage : uint8_t { + PreScenePasses, + ShadowCaster, + DepthOnly, + MainScene, + ObjectId, + PostScenePasses, + OverlayPasses +}; + +struct CameraFrameStageInfo { + CameraFrameStage stage = CameraFrameStage::MainScene; + const char* name = ""; + bool runtimeStage = true; +}; + +inline constexpr std::array kOrderedCameraFrameStages = {{ + { CameraFrameStage::PreScenePasses, "PreScenePasses", false }, + { CameraFrameStage::ShadowCaster, "ShadowCaster", true }, + { CameraFrameStage::DepthOnly, "DepthOnly", true }, + { CameraFrameStage::MainScene, "MainScene", true }, + { CameraFrameStage::ObjectId, "ObjectId", false }, + { CameraFrameStage::PostScenePasses, "PostScenePasses", false }, + { CameraFrameStage::OverlayPasses, "OverlayPasses", false } +}}; + +inline constexpr const char* GetCameraFrameStageName(CameraFrameStage stage) { + switch (stage) { + case CameraFrameStage::PreScenePasses: + return "PreScenePasses"; + case CameraFrameStage::ShadowCaster: + return "ShadowCaster"; + case CameraFrameStage::DepthOnly: + return "DepthOnly"; + case CameraFrameStage::MainScene: + return "MainScene"; + case CameraFrameStage::ObjectId: + return "ObjectId"; + case CameraFrameStage::PostScenePasses: + return "PostScenePasses"; + case CameraFrameStage::OverlayPasses: + return "OverlayPasses"; + default: + return "Unknown"; + } +} + struct ScenePassRenderRequest { RenderSurface surface; RenderClearFlags clearFlags = RenderClearFlags::Depth; @@ -93,6 +143,55 @@ struct CameraRenderRequest { RenderPassSequence* postScenePasses = nullptr; RenderPassSequence* overlayPasses = nullptr; + bool HasFrameStage(CameraFrameStage stage) const { + switch (stage) { + case CameraFrameStage::PreScenePasses: + return preScenePasses != nullptr; + case CameraFrameStage::ShadowCaster: + return shadowCaster.IsRequested() || directionalShadow.IsValid(); + case CameraFrameStage::DepthOnly: + return depthOnly.IsRequested(); + case CameraFrameStage::MainScene: + return true; + case CameraFrameStage::ObjectId: + return objectId.IsRequested(); + case CameraFrameStage::PostScenePasses: + return postScenePasses != nullptr; + case CameraFrameStage::OverlayPasses: + return overlayPasses != nullptr; + default: + return false; + } + } + + RenderPassSequence* GetPassSequence(CameraFrameStage stage) const { + switch (stage) { + case CameraFrameStage::PreScenePasses: + return preScenePasses; + case CameraFrameStage::PostScenePasses: + return postScenePasses; + case CameraFrameStage::OverlayPasses: + return overlayPasses; + default: + return nullptr; + } + } + + const ScenePassRenderRequest* GetScenePassRequest(CameraFrameStage stage) const { + switch (stage) { + case CameraFrameStage::ShadowCaster: + return &shadowCaster; + case CameraFrameStage::DepthOnly: + return &depthOnly; + default: + return nullptr; + } + } + + const ObjectIdRenderRequest* GetObjectIdRequest(CameraFrameStage stage) const { + return stage == CameraFrameStage::ObjectId ? &objectId : nullptr; + } + bool IsValid() const { return scene != nullptr && camera != nullptr && diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index a4e54a12..4ae3a1c9 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -151,6 +151,77 @@ private: bool m_failed = false; }; +struct CameraFrameExecutionState { + RenderPipeline* pipeline = nullptr; + RenderPass* objectIdPass = nullptr; + RenderPass* depthOnlyPass = nullptr; + RenderPass* shadowCasterPass = nullptr; + std::unique_ptr preScenePasses; + std::unique_ptr postScenePasses; + std::unique_ptr overlayPasses; +}; + +bool ExecutePassSequenceStage( + std::unique_ptr& activeSequence, + RenderPassSequence* sequence, + const RenderContext& context, + const RenderPassContext& passContext) { + activeSequence = std::make_unique(sequence, context); + return activeSequence->IsReady() && activeSequence->Execute(passContext); +} + +bool ExecuteFrameStage( + CameraFrameStage stage, + const CameraRenderRequest& request, + const ShadowCasterRenderRequest& resolvedShadowCaster, + const RenderPassContext& passContext, + CameraFrameExecutionState& executionState) { + switch (stage) { + case CameraFrameStage::PreScenePasses: + return ExecutePassSequenceStage( + executionState.preScenePasses, + request.GetPassSequence(stage), + request.context, + passContext); + case CameraFrameStage::ShadowCaster: + return ExecuteScenePassRequest( + executionState.shadowCasterPass, + resolvedShadowCaster, + request.context, + passContext.sceneData); + case CameraFrameStage::DepthOnly: + return ExecuteScenePassRequest( + executionState.depthOnlyPass, + request.depthOnly, + request.context, + passContext.sceneData); + case CameraFrameStage::MainScene: + return executionState.pipeline != nullptr && + executionState.pipeline->Render(request.context, request.surface, passContext.sceneData); + case CameraFrameStage::ObjectId: + return !request.objectId.IsRequested() || + ExecuteStandalonePass( + executionState.objectIdPass, + request.context, + request.objectId.surface, + passContext.sceneData); + case CameraFrameStage::PostScenePasses: + return ExecutePassSequenceStage( + executionState.postScenePasses, + request.GetPassSequence(stage), + request.context, + passContext); + case CameraFrameStage::OverlayPasses: + return ExecutePassSequenceStage( + executionState.overlayPasses, + request.GetPassSequence(stage), + request.context, + passContext); + default: + return false; + } +} + RenderDirectionalShadowData BuildDirectionalShadowData( const DirectionalShadowRenderPlan& plan, RHI::RHIResourceView* shadowMapView) { @@ -352,49 +423,26 @@ bool CameraRenderer::ExecuteRenderPlan( request.surface, sceneData }; + CameraFrameExecutionState executionState = {}; + executionState.pipeline = m_pipeline.get(); + executionState.objectIdPass = m_objectIdPass.get(); + executionState.depthOnlyPass = m_depthOnlyPass.get(); + executionState.shadowCasterPass = m_shadowCasterPass.get(); - ScopedInitializedPassSequence preScenePasses(request.preScenePasses, request.context); - if (!preScenePasses.IsReady() || !preScenePasses.Execute(passContext)) { - return false; - } + for (const CameraFrameStageInfo& stageInfo : kOrderedCameraFrameStages) { + if (!request.HasFrameStage(stageInfo.stage) && + stageInfo.stage != CameraFrameStage::MainScene) { + continue; + } - if (!ExecuteScenePassRequest( - m_shadowCasterPass.get(), - resolvedShadowCaster, - request.context, - sceneData)) { - return false; - } - - if (!ExecuteScenePassRequest( - m_depthOnlyPass.get(), - request.depthOnly, - request.context, - sceneData)) { - return false; - } - - if (!m_pipeline->Render(request.context, request.surface, sceneData)) { - return false; - } - - if (request.objectId.IsRequested() && - !ExecuteStandalonePass( - m_objectIdPass.get(), - request.context, - request.objectId.surface, - sceneData)) { - return false; - } - - ScopedInitializedPassSequence postScenePasses(request.postScenePasses, request.context); - if (!postScenePasses.IsReady() || !postScenePasses.Execute(passContext)) { - return false; - } - - ScopedInitializedPassSequence overlayPasses(request.overlayPasses, request.context); - if (!overlayPasses.IsReady() || !overlayPasses.Execute(passContext)) { - return false; + if (!ExecuteFrameStage( + stageInfo.stage, + request, + resolvedShadowCaster, + passContext, + executionState)) { + return false; + } } return true; diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 86f37e1e..e2828500 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -453,6 +453,58 @@ RenderContext CreateValidContext() { } // namespace +TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { + CameraRenderRequest request; + + RenderPassSequence prePasses; + RenderPassSequence postPasses; + RenderPassSequence overlayPasses; + request.preScenePasses = &prePasses; + request.postScenePasses = &postPasses; + request.overlayPasses = &overlayPasses; + + request.directionalShadow.enabled = true; + request.directionalShadow.mapWidth = 128; + request.directionalShadow.mapHeight = 64; + request.directionalShadow.cameraData.viewportWidth = 128; + request.directionalShadow.cameraData.viewportHeight = 64; + + request.depthOnly.surface = RenderSurface(96, 48); + request.depthOnly.surface.SetDepthAttachment(reinterpret_cast(1)); + + request.objectId.surface = RenderSurface(320, 180); + request.objectId.surface.SetColorAttachment(reinterpret_cast(2)); + request.objectId.surface.SetDepthAttachment(reinterpret_cast(3)); + + ASSERT_EQ(kOrderedCameraFrameStages.size(), 7u); + EXPECT_EQ(kOrderedCameraFrameStages[0].stage, CameraFrameStage::PreScenePasses); + EXPECT_EQ(kOrderedCameraFrameStages[1].stage, CameraFrameStage::ShadowCaster); + EXPECT_EQ(kOrderedCameraFrameStages[2].stage, CameraFrameStage::DepthOnly); + EXPECT_EQ(kOrderedCameraFrameStages[3].stage, CameraFrameStage::MainScene); + EXPECT_EQ(kOrderedCameraFrameStages[4].stage, CameraFrameStage::ObjectId); + EXPECT_EQ(kOrderedCameraFrameStages[5].stage, CameraFrameStage::PostScenePasses); + EXPECT_EQ(kOrderedCameraFrameStages[6].stage, CameraFrameStage::OverlayPasses); + EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::MainScene), "MainScene"); + EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::OverlayPasses), "OverlayPasses"); + + EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::PreScenePasses)); + EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::ShadowCaster)); + EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::DepthOnly)); + EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::MainScene)); + EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::ObjectId)); + EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::PostScenePasses)); + EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::OverlayPasses)); + + EXPECT_EQ(request.GetPassSequence(CameraFrameStage::PreScenePasses), &prePasses); + EXPECT_EQ(request.GetPassSequence(CameraFrameStage::PostScenePasses), &postPasses); + EXPECT_EQ(request.GetPassSequence(CameraFrameStage::OverlayPasses), &overlayPasses); + EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::ShadowCaster), &request.shadowCaster); + EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::DepthOnly), &request.depthOnly); + EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::MainScene), nullptr); + EXPECT_EQ(request.GetObjectIdRequest(CameraFrameStage::ObjectId), &request.objectId); + EXPECT_EQ(request.GetObjectIdRequest(CameraFrameStage::MainScene), nullptr); +} + TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) { Scene scene("CameraRendererScene"); @@ -608,6 +660,72 @@ TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRe "shutdown:pre" })); } +TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) { + Scene scene("CameraRendererFormalFrameStageScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(3.0f); + + auto state = std::make_shared(); + CameraRenderer renderer( + std::make_unique(state), + std::make_unique(state)); + + auto shadowPass = std::make_unique(state, "shadowCaster"); + renderer.SetShadowCasterPass(std::move(shadowPass)); + + auto depthPass = std::make_unique(state, "depthOnly"); + renderer.SetDepthOnlyPass(std::move(depthPass)); + + RenderPassSequence prePasses; + prePasses.AddPass(std::make_unique(state, "pre")); + + RenderPassSequence postPasses; + postPasses.AddPass(std::make_unique(state, "post")); + + RenderPassSequence overlayPasses; + overlayPasses.AddPass(std::make_unique(state, "overlay")); + + 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.overlayPasses = &overlayPasses; + request.shadowCaster.surface = RenderSurface(128, 64); + request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast(1)); + request.depthOnly.surface = RenderSurface(96, 48); + request.depthOnly.surface.SetDepthAttachment(reinterpret_cast(2)); + request.objectId.surface = RenderSurface(320, 180); + request.objectId.surface.SetColorAttachment(reinterpret_cast(3)); + request.objectId.surface.SetDepthAttachment(reinterpret_cast(4)); + + ASSERT_TRUE(renderer.Render(request)); + EXPECT_EQ( + state->eventLog, + (std::vector{ + "init:pre", + "pre", + "init:shadowCaster", + "shadowCaster", + "init:depthOnly", + "depthOnly", + "pipeline", + "objectId", + "init:post", + "post", + "init:overlay", + "overlay", + "shutdown:overlay", + "shutdown:post", + "shutdown:pre" })); +} + TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipeline) { Scene scene("CameraRendererDepthAndShadowScene");