diff --git a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h index 5324366a..3888d907 100644 --- a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h +++ b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h @@ -21,6 +21,8 @@ enum class CameraFrameStage : uint8_t { ShadowCaster, DepthOnly, MainScene, + PostProcess, + FinalOutput, ObjectId, PostScenePasses, OverlayPasses @@ -32,11 +34,13 @@ struct CameraFrameStageInfo { bool runtimeStage = true; }; -inline constexpr std::array kOrderedCameraFrameStages = {{ +inline constexpr std::array kOrderedCameraFrameStages = {{ { CameraFrameStage::PreScenePasses, "PreScenePasses", false }, { CameraFrameStage::ShadowCaster, "ShadowCaster", true }, { CameraFrameStage::DepthOnly, "DepthOnly", true }, { CameraFrameStage::MainScene, "MainScene", true }, + { CameraFrameStage::PostProcess, "PostProcess", true }, + { CameraFrameStage::FinalOutput, "FinalOutput", true }, { CameraFrameStage::ObjectId, "ObjectId", false }, { CameraFrameStage::PostScenePasses, "PostScenePasses", false }, { CameraFrameStage::OverlayPasses, "OverlayPasses", false } @@ -52,6 +56,10 @@ inline constexpr const char* GetCameraFrameStageName(CameraFrameStage stage) { return "DepthOnly"; case CameraFrameStage::MainScene: return "MainScene"; + case CameraFrameStage::PostProcess: + return "PostProcess"; + case CameraFrameStage::FinalOutput: + return "FinalOutput"; case CameraFrameStage::ObjectId: return "ObjectId"; case CameraFrameStage::PostScenePasses: @@ -88,6 +96,14 @@ struct ScenePassRenderRequest { using DepthOnlyRenderRequest = ScenePassRenderRequest; using ShadowCasterRenderRequest = ScenePassRenderRequest; +inline bool HasValidColorTarget(const RenderSurface& surface) { + const std::vector& colorAttachments = surface.GetColorAttachments(); + return !colorAttachments.empty() && + colorAttachments[0] != nullptr && + surface.GetRenderAreaWidth() > 0 && + surface.GetRenderAreaHeight() > 0; +} + struct DirectionalShadowRenderPlan { bool enabled = false; Math::Vector3 lightDirection = Math::Vector3::Back(); @@ -125,6 +141,25 @@ struct ObjectIdRenderRequest { } }; +struct FullscreenPassRenderRequest { + RenderSurface sourceSurface; + RenderSurface destinationSurface; + RenderPassSequence* passes = nullptr; + + bool IsRequested() const { + return passes != nullptr; + } + + bool IsValid() const { + return passes != nullptr && + HasValidColorTarget(sourceSurface) && + HasValidColorTarget(destinationSurface); + } +}; + +using PostProcessRenderRequest = FullscreenPassRenderRequest; +using FinalOutputRenderRequest = FullscreenPassRenderRequest; + struct CameraRenderRequest { const Components::Scene* scene = nullptr; Components::CameraComponent* camera = nullptr; @@ -133,6 +168,8 @@ struct CameraRenderRequest { DepthOnlyRenderRequest depthOnly; ShadowCasterRenderRequest shadowCaster; DirectionalShadowRenderPlan directionalShadow; + PostProcessRenderRequest postProcess; + FinalOutputRenderRequest finalOutput; ObjectIdRenderRequest objectId; float cameraDepth = 0.0f; uint8_t cameraStackOrder = 0; @@ -153,6 +190,10 @@ struct CameraRenderRequest { return depthOnly.IsRequested(); case CameraFrameStage::MainScene: return true; + case CameraFrameStage::PostProcess: + return postProcess.IsRequested(); + case CameraFrameStage::FinalOutput: + return finalOutput.IsRequested(); case CameraFrameStage::ObjectId: return objectId.IsRequested(); case CameraFrameStage::PostScenePasses: @@ -168,6 +209,10 @@ struct CameraRenderRequest { switch (stage) { case CameraFrameStage::PreScenePasses: return preScenePasses; + case CameraFrameStage::PostProcess: + return postProcess.passes; + case CameraFrameStage::FinalOutput: + return finalOutput.passes; case CameraFrameStage::PostScenePasses: return postScenePasses; case CameraFrameStage::OverlayPasses: @@ -192,6 +237,68 @@ struct CameraRenderRequest { return stage == CameraFrameStage::ObjectId ? &objectId : nullptr; } + const RenderSurface& GetMainSceneSurface() const { + if (postProcess.IsRequested()) { + return postProcess.sourceSurface; + } + + if (finalOutput.IsRequested()) { + return finalOutput.sourceSurface; + } + + return surface; + } + + const RenderSurface& GetFinalCompositedSurface() const { + if (finalOutput.IsRequested()) { + return finalOutput.destinationSurface; + } + + if (postProcess.IsRequested()) { + return postProcess.destinationSurface; + } + + return surface; + } + + const RenderSurface* GetOutputSurface(CameraFrameStage stage) const { + switch (stage) { + case CameraFrameStage::PreScenePasses: + case CameraFrameStage::MainScene: + return &GetMainSceneSurface(); + case CameraFrameStage::ShadowCaster: + return shadowCaster.IsRequested() ? &shadowCaster.surface : nullptr; + case CameraFrameStage::DepthOnly: + return depthOnly.IsRequested() ? &depthOnly.surface : nullptr; + case CameraFrameStage::PostProcess: + return postProcess.IsRequested() ? &postProcess.destinationSurface : nullptr; + case CameraFrameStage::FinalOutput: + return finalOutput.IsRequested() ? &finalOutput.destinationSurface : nullptr; + case CameraFrameStage::ObjectId: + return objectId.IsRequested() ? &objectId.surface : nullptr; + case CameraFrameStage::PostScenePasses: + case CameraFrameStage::OverlayPasses: + return &GetFinalCompositedSurface(); + default: + return nullptr; + } + } + + const RenderSurface* GetSourceSurface(CameraFrameStage stage) const { + switch (stage) { + case CameraFrameStage::PostProcess: + return postProcess.IsRequested() ? &postProcess.sourceSurface : nullptr; + case CameraFrameStage::FinalOutput: + return finalOutput.IsRequested() ? &finalOutput.sourceSurface : nullptr; + default: + return nullptr; + } + } + + bool RequiresIntermediateSceneColor() const { + return postProcess.IsRequested() || finalOutput.IsRequested(); + } + bool IsValid() const { return scene != nullptr && camera != nullptr && diff --git a/engine/include/XCEngine/Rendering/RenderPass.h b/engine/include/XCEngine/Rendering/RenderPass.h index 945d7e85..4bbaf863 100644 --- a/engine/include/XCEngine/Rendering/RenderPass.h +++ b/engine/include/XCEngine/Rendering/RenderPass.h @@ -16,6 +16,7 @@ struct RenderPassContext { const RenderContext& renderContext; const RenderSurface& surface; const RenderSceneData& sceneData; + const RenderSurface* sourceSurface = nullptr; }; class RenderPass { diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index 71b29e01..55052a21 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -85,7 +85,8 @@ bool ExecuteScenePassRequest( const RenderPassContext passContext = { context, request.surface, - sceneData + sceneData, + nullptr }; return pass->Execute(passContext); } @@ -106,7 +107,8 @@ bool ExecuteStandalonePass( const RenderPassContext passContext = { context, surface, - sceneData + sceneData, + nullptr }; return pass->Execute(passContext); } @@ -158,6 +160,8 @@ struct CameraFrameExecutionState { RenderPass* depthOnlyPass = nullptr; RenderPass* shadowCasterPass = nullptr; std::unique_ptr preScenePasses; + std::unique_ptr postProcessPasses; + std::unique_ptr finalOutputPasses; std::unique_ptr postScenePasses; std::unique_ptr overlayPasses; }; @@ -171,12 +175,27 @@ bool ExecutePassSequenceStage( return activeSequence->IsReady() && activeSequence->Execute(passContext); } +RenderPassContext BuildFrameStagePassContext( + CameraFrameStage stage, + const CameraRenderRequest& request, + const RenderSceneData& sceneData) { + const RenderSurface* outputSurface = request.GetOutputSurface(stage); + return { + request.context, + outputSurface != nullptr ? *outputSurface : request.surface, + sceneData, + request.GetSourceSurface(stage) + }; +} + bool ExecuteFrameStage( CameraFrameStage stage, const CameraRenderRequest& request, const ShadowCasterRenderRequest& resolvedShadowCaster, - const RenderPassContext& passContext, + const RenderSceneData& sceneData, CameraFrameExecutionState& executionState) { + const RenderPassContext passContext = BuildFrameStagePassContext(stage, request, sceneData); + switch (stage) { case CameraFrameStage::PreScenePasses: return ExecutePassSequenceStage( @@ -189,23 +208,35 @@ bool ExecuteFrameStage( executionState.shadowCasterPass, resolvedShadowCaster, request.context, - passContext.sceneData); + sceneData); case CameraFrameStage::DepthOnly: return ExecuteScenePassRequest( executionState.depthOnlyPass, request.depthOnly, request.context, - passContext.sceneData); + sceneData); case CameraFrameStage::MainScene: return executionState.pipeline != nullptr && - executionState.pipeline->Render(request.context, request.surface, passContext.sceneData); + executionState.pipeline->Render(request.context, passContext.surface, sceneData); + case CameraFrameStage::PostProcess: + return ExecutePassSequenceStage( + executionState.postProcessPasses, + request.GetPassSequence(stage), + request.context, + passContext); + case CameraFrameStage::FinalOutput: + return ExecutePassSequenceStage( + executionState.finalOutputPasses, + request.GetPassSequence(stage), + request.context, + passContext); case CameraFrameStage::ObjectId: return !request.objectId.IsRequested() || ExecuteStandalonePass( executionState.objectIdPass, request.context, request.objectId.surface, - passContext.sceneData); + sceneData); case CameraFrameStage::PostScenePasses: return ExecutePassSequenceStage( executionState.postScenePasses, @@ -244,8 +275,9 @@ RenderDirectionalShadowData BuildDirectionalShadowData( RenderEnvironmentData BuildEnvironmentData(const CameraRenderRequest& request) { RenderEnvironmentData environment = {}; + const RenderSurface& mainSceneSurface = request.GetMainSceneSurface(); if (request.camera == nullptr || - request.surface.GetDepthAttachment() == nullptr || + mainSceneSurface.GetDepthAttachment() == nullptr || !HasRenderClearFlag(request.clearFlags, RenderClearFlags::Color) || !request.camera->IsSkyboxEnabled() || request.camera->GetProjectionType() != Components::CameraProjectionType::Perspective) { @@ -416,11 +448,12 @@ bool CameraRenderer::BuildSceneDataForRequest( const CameraRenderRequest& request, RHI::RHIResourceView* shadowMapView, RenderSceneData& outSceneData) { + const RenderSurface& mainSceneSurface = request.GetMainSceneSurface(); outSceneData = m_sceneExtractor.ExtractForCamera( *request.scene, *request.camera, - request.surface.GetRenderAreaWidth(), - request.surface.GetRenderAreaHeight()); + mainSceneSurface.GetRenderAreaWidth(), + mainSceneSurface.GetRenderAreaHeight()); if (!outSceneData.HasCamera()) { return false; } @@ -443,11 +476,6 @@ bool CameraRenderer::ExecuteRenderPlan( const CameraRenderRequest& request, const ShadowCasterRenderRequest& resolvedShadowCaster, const RenderSceneData& sceneData) { - const RenderPassContext passContext = { - request.context, - request.surface, - sceneData - }; CameraFrameExecutionState executionState = {}; executionState.pipeline = m_pipeline.get(); executionState.objectIdPass = m_objectIdPass.get(); @@ -464,7 +492,7 @@ bool CameraRenderer::ExecuteRenderPlan( stageInfo.stage, request, resolvedShadowCaster, - passContext, + sceneData, executionState)) { return false; } @@ -479,14 +507,23 @@ bool CameraRenderer::Render( return false; } - if (request.surface.GetRenderAreaWidth() == 0 || - request.surface.GetRenderAreaHeight() == 0) { + const RenderSurface& mainSceneSurface = request.GetMainSceneSurface(); + if (mainSceneSurface.GetRenderAreaWidth() == 0 || + mainSceneSurface.GetRenderAreaHeight() == 0) { return false; } if (request.depthOnly.IsRequested() && !request.depthOnly.IsValid()) { return false; } + if (request.postProcess.IsRequested() && + !request.postProcess.IsValid()) { + return false; + } + if (request.finalOutput.IsRequested() && + !request.finalOutput.IsValid()) { + return false; + } if (request.objectId.IsRequested() && !request.objectId.IsValid()) { return false; diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 951030cb..46dbd2b8 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -390,6 +390,11 @@ public: lastWorldPosition = context.sceneData.cameraData.worldPosition; lastSurfaceWidth = context.surface.GetRenderAreaWidth(); lastSurfaceHeight = context.surface.GetRenderAreaHeight(); + lastHasSourceSurface = context.sourceSurface != nullptr; + if (context.sourceSurface != nullptr) { + lastSourceSurfaceWidth = context.sourceSurface->GetRenderAreaWidth(); + lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight(); + } return m_executeResult; } @@ -404,6 +409,9 @@ public: XCEngine::Math::Vector3 lastWorldPosition = XCEngine::Math::Vector3::Zero(); uint32_t lastSurfaceWidth = 0; uint32_t lastSurfaceHeight = 0; + bool lastHasSourceSurface = false; + uint32_t lastSourceSurfaceWidth = 0; + uint32_t lastSourceSurfaceHeight = 0; private: std::shared_ptr m_state; @@ -434,8 +442,15 @@ public: return m_initializeResult; } - bool Execute(const RenderPassContext&) override { + bool Execute(const RenderPassContext& context) override { m_state->eventLog.push_back(m_label); + lastSurfaceWidth = context.surface.GetRenderAreaWidth(); + lastSurfaceHeight = context.surface.GetRenderAreaHeight(); + lastHasSourceSurface = context.sourceSurface != nullptr; + if (context.sourceSurface != nullptr) { + lastSourceSurfaceWidth = context.sourceSurface->GetRenderAreaWidth(); + lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight(); + } return m_executeResult; } @@ -448,6 +463,13 @@ private: const char* m_label = ""; bool m_initializeResult = true; bool m_executeResult = true; + +public: + uint32_t lastSurfaceWidth = 0; + uint32_t lastSurfaceHeight = 0; + bool lastHasSourceSurface = false; + uint32_t lastSourceSurfaceWidth = 0; + uint32_t lastSourceSurfaceHeight = 0; }; RenderContext CreateValidContext() { @@ -464,9 +486,13 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { CameraRenderRequest request; RenderPassSequence prePasses; + RenderPassSequence postProcessPasses; + RenderPassSequence finalOutputPasses; RenderPassSequence postPasses; RenderPassSequence overlayPasses; request.preScenePasses = &prePasses; + request.postProcess.passes = &postProcessPasses; + request.finalOutput.passes = &finalOutputPasses; request.postScenePasses = &postPasses; request.overlayPasses = &overlayPasses; @@ -479,30 +505,48 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { 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)); + request.postProcess.sourceSurface = RenderSurface(256, 128); + request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast(2)); + request.postProcess.destinationSurface = RenderSurface(512, 256); + request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(3)); - ASSERT_EQ(kOrderedCameraFrameStages.size(), 7u); + request.finalOutput.sourceSurface = RenderSurface(512, 256); + request.finalOutput.sourceSurface.SetColorAttachment(reinterpret_cast(4)); + request.finalOutput.destinationSurface = RenderSurface(640, 360); + request.finalOutput.destinationSurface.SetColorAttachment(reinterpret_cast(5)); + + request.objectId.surface = RenderSurface(320, 180); + request.objectId.surface.SetColorAttachment(reinterpret_cast(6)); + request.objectId.surface.SetDepthAttachment(reinterpret_cast(7)); + + ASSERT_EQ(kOrderedCameraFrameStages.size(), 9u); 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_EQ(kOrderedCameraFrameStages[4].stage, CameraFrameStage::PostProcess); + EXPECT_EQ(kOrderedCameraFrameStages[5].stage, CameraFrameStage::FinalOutput); + EXPECT_EQ(kOrderedCameraFrameStages[6].stage, CameraFrameStage::ObjectId); + EXPECT_EQ(kOrderedCameraFrameStages[7].stage, CameraFrameStage::PostScenePasses); + EXPECT_EQ(kOrderedCameraFrameStages[8].stage, CameraFrameStage::OverlayPasses); EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::MainScene), "MainScene"); + EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::PostProcess), "PostProcess"); + EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::FinalOutput), "FinalOutput"); 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::PostProcess)); + EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::FinalOutput)); 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::PostProcess), &postProcessPasses); + EXPECT_EQ(request.GetPassSequence(CameraFrameStage::FinalOutput), &finalOutputPasses); EXPECT_EQ(request.GetPassSequence(CameraFrameStage::PostScenePasses), &postPasses); EXPECT_EQ(request.GetPassSequence(CameraFrameStage::OverlayPasses), &overlayPasses); EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::ShadowCaster), &request.shadowCaster); @@ -510,6 +554,17 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::MainScene), nullptr); EXPECT_EQ(request.GetObjectIdRequest(CameraFrameStage::ObjectId), &request.objectId); EXPECT_EQ(request.GetObjectIdRequest(CameraFrameStage::MainScene), nullptr); + EXPECT_TRUE(request.RequiresIntermediateSceneColor()); + EXPECT_EQ(request.GetMainSceneSurface().GetRenderAreaWidth(), 256u); + EXPECT_EQ(request.GetMainSceneSurface().GetRenderAreaHeight(), 128u); + EXPECT_EQ(request.GetFinalCompositedSurface().GetRenderAreaWidth(), 640u); + EXPECT_EQ(request.GetFinalCompositedSurface().GetRenderAreaHeight(), 360u); + ASSERT_NE(request.GetOutputSurface(CameraFrameStage::PostProcess), nullptr); + EXPECT_EQ(request.GetOutputSurface(CameraFrameStage::PostProcess)->GetRenderAreaWidth(), 512u); + ASSERT_NE(request.GetSourceSurface(CameraFrameStage::PostProcess), nullptr); + EXPECT_EQ(request.GetSourceSurface(CameraFrameStage::PostProcess)->GetRenderAreaWidth(), 256u); + ASSERT_NE(request.GetSourceSurface(CameraFrameStage::FinalOutput), nullptr); + EXPECT_EQ(request.GetSourceSurface(CameraFrameStage::FinalOutput)->GetRenderAreaWidth(), 512u); } TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) { @@ -694,6 +749,74 @@ TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRe "shutdown:pre" })); } +TEST(CameraRenderer_Test, RoutesSceneColorThroughPostProcessAndFinalOutputStages) { + Scene scene("CameraRendererPostProcessScene"); + + 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)); + + auto postProcessPass = std::make_unique(state, "postProcess"); + MockScenePass* postProcessPassRaw = postProcessPass.get(); + RenderPassSequence postProcessPasses; + postProcessPasses.AddPass(std::move(postProcessPass)); + + auto finalOutputPass = std::make_unique(state, "finalOutput"); + MockScenePass* finalOutputPassRaw = finalOutputPass.get(); + RenderPassSequence finalOutputPasses; + finalOutputPasses.AddPass(std::move(finalOutputPass)); + + CameraRenderRequest request; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.surface = RenderSurface(800, 600); + request.surface.SetColorAttachment(reinterpret_cast(1)); + request.cameraDepth = camera->GetDepth(); + + request.postProcess.sourceSurface = RenderSurface(256, 128); + request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast(2)); + request.postProcess.destinationSurface = RenderSurface(512, 256); + request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(3)); + request.postProcess.passes = &postProcessPasses; + + request.finalOutput.sourceSurface = request.postProcess.destinationSurface; + request.finalOutput.destinationSurface = request.surface; + request.finalOutput.passes = &finalOutputPasses; + + ASSERT_TRUE(renderer.Render(request)); + EXPECT_EQ(state->lastSurfaceWidth, 256u); + EXPECT_EQ(state->lastSurfaceHeight, 128u); + EXPECT_EQ(state->lastCameraViewportWidth, 256u); + EXPECT_EQ(state->lastCameraViewportHeight, 128u); + ASSERT_NE(postProcessPassRaw, nullptr); + EXPECT_TRUE(postProcessPassRaw->lastHasSourceSurface); + EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceWidth, 256u); + EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceHeight, 128u); + EXPECT_EQ(postProcessPassRaw->lastSurfaceWidth, 512u); + EXPECT_EQ(postProcessPassRaw->lastSurfaceHeight, 256u); + ASSERT_NE(finalOutputPassRaw, nullptr); + EXPECT_TRUE(finalOutputPassRaw->lastHasSourceSurface); + EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceWidth, 512u); + EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceHeight, 256u); + EXPECT_EQ(finalOutputPassRaw->lastSurfaceWidth, 800u); + EXPECT_EQ(finalOutputPassRaw->lastSurfaceHeight, 600u); + EXPECT_EQ( + state->eventLog, + (std::vector{ + "pipeline", + "init:postProcess", + "postProcess", + "init:finalOutput", + "finalOutput", + "shutdown:finalOutput", + "shutdown:postProcess" })); +} + TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) { Scene scene("CameraRendererFormalFrameStageScene"); @@ -716,6 +839,12 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) { RenderPassSequence prePasses; prePasses.AddPass(std::make_unique(state, "pre")); + RenderPassSequence postProcessPasses; + postProcessPasses.AddPass(std::make_unique(state, "postProcess")); + + RenderPassSequence finalOutputPasses; + finalOutputPasses.AddPass(std::make_unique(state, "finalOutput")); + RenderPassSequence postPasses; postPasses.AddPass(std::make_unique(state, "post")); @@ -727,17 +856,26 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) { request.camera = camera; request.context = CreateValidContext(); request.surface = RenderSurface(320, 180); + request.surface.SetColorAttachment(reinterpret_cast(3)); request.cameraDepth = camera->GetDepth(); request.preScenePasses = &prePasses; + request.postProcess.passes = &postProcessPasses; + request.finalOutput.passes = &finalOutputPasses; 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.postProcess.sourceSurface = RenderSurface(256, 128); + request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast(4)); + request.postProcess.destinationSurface = RenderSurface(320, 180); + request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(5)); + request.finalOutput.sourceSurface = request.postProcess.destinationSurface; + request.finalOutput.destinationSurface = request.surface; request.objectId.surface = RenderSurface(320, 180); - request.objectId.surface.SetColorAttachment(reinterpret_cast(3)); - request.objectId.surface.SetDepthAttachment(reinterpret_cast(4)); + request.objectId.surface.SetColorAttachment(reinterpret_cast(6)); + request.objectId.surface.SetDepthAttachment(reinterpret_cast(7)); ASSERT_TRUE(renderer.Render(request)); EXPECT_EQ( @@ -750,6 +888,10 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) { "init:depthOnly", "depthOnly", "pipeline", + "init:postProcess", + "postProcess", + "init:finalOutput", + "finalOutput", "objectId", "init:post", "post", @@ -757,6 +899,8 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) { "overlay", "shutdown:overlay", "shutdown:post", + "shutdown:finalOutput", + "shutdown:postProcess", "shutdown:pre" })); } @@ -1056,6 +1200,33 @@ TEST(CameraRenderer_Test, StopsRenderingWhenShadowCasterRequestIsInvalid) { EXPECT_TRUE(state->eventLog.empty()); } +TEST(CameraRenderer_Test, StopsRenderingWhenPostProcessRequestIsInvalid) { + Scene scene("CameraRendererInvalidPostProcessScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + + auto state = std::make_shared(); + CameraRenderer renderer(std::make_unique(state)); + + RenderPassSequence postProcessPasses; + postProcessPasses.AddPass(std::make_unique(state, "postProcess")); + + CameraRenderRequest request; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.surface = RenderSurface(320, 180); + request.postProcess.passes = &postProcessPasses; + request.postProcess.sourceSurface = RenderSurface(256, 128); + request.postProcess.destinationSurface = RenderSurface(320, 180); + request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(1)); + + EXPECT_FALSE(renderer.Render(request)); + EXPECT_TRUE(state->eventLog.empty()); +} + TEST(CameraRenderer_Test, ShutsDownInitializedPassesWhenPipelineRenderFails) { Scene scene("CameraRendererFailureScene");