diff --git a/engine/include/XCEngine/Rendering/RenderPipeline.h b/engine/include/XCEngine/Rendering/RenderPipeline.h index 7e416628..dbf4d55e 100644 --- a/engine/include/XCEngine/Rendering/RenderPipeline.h +++ b/engine/include/XCEngine/Rendering/RenderPipeline.h @@ -19,6 +19,13 @@ namespace Rendering { class RenderGraphBuilder; struct CameraFramePlan; +struct DirectionalShadowExecutionState; +struct DirectionalShadowSurfaceAllocation; + +void ApplyDefaultRenderPipelineDirectionalShadowExecutionPolicy( + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState); struct RenderPipelineStageRenderGraphContext { RenderGraphBuilder& graphBuilder; @@ -88,6 +95,10 @@ public: virtual void ConfigureRenderSceneData( const CameraFramePlan& plan, RenderSceneData& sceneData) const; + virtual bool ConfigureDirectionalShadowExecutionState( + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState) const; void SetCameraFrameStandalonePass( CameraFrameStage stage, diff --git a/engine/include/XCEngine/Rendering/Shadow/DirectionalShadowRuntime.h b/engine/include/XCEngine/Rendering/Shadow/DirectionalShadowRuntime.h index ea3e2ebc..3d618f12 100644 --- a/engine/include/XCEngine/Rendering/Shadow/DirectionalShadowRuntime.h +++ b/engine/include/XCEngine/Rendering/Shadow/DirectionalShadowRuntime.h @@ -7,6 +7,7 @@ namespace XCEngine { namespace Rendering { struct CameraFramePlan; +class RenderPipeline; class DirectionalShadowRuntime { public: @@ -17,6 +18,7 @@ public: bool ResolveExecutionState( const CameraFramePlan& plan, + const RenderPipeline& pipeline, DirectionalShadowExecutionState& outShadowState); private: diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index 4a90ec36..c371eb78 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -182,7 +182,10 @@ bool CameraRenderer::Render( DirectionalShadowExecutionState shadowState = {}; if (m_directionalShadowRuntime == nullptr || - !m_directionalShadowRuntime->ResolveExecutionState(plan, shadowState)) { + !m_directionalShadowRuntime->ResolveExecutionState( + plan, + *m_pipeline, + shadowState)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "CameraRenderer::Render failed: DirectionalShadowRuntime::ResolveExecutionState returned false"); diff --git a/engine/src/Rendering/RenderPipeline.cpp b/engine/src/Rendering/RenderPipeline.cpp index 2315ac90..f3b1e326 100644 --- a/engine/src/Rendering/RenderPipeline.cpp +++ b/engine/src/Rendering/RenderPipeline.cpp @@ -1,7 +1,11 @@ #include +#include +#include + #include "Components/CameraComponent.h" #include "Rendering/Execution/CameraFramePlan.h" +#include "Rendering/Shadow/DirectionalShadowData.h" namespace XCEngine { namespace Rendering { @@ -54,5 +58,35 @@ void RenderPipeline::ConfigureRenderSceneData( BuildDefaultEnvironmentData(plan); } +void ApplyDefaultRenderPipelineDirectionalShadowExecutionPolicy( + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState) { + shadowState.shadowCasterRequest.surface = shadowAllocation.surface; + shadowState.shadowCasterRequest.clearFlags = RenderClearFlags::Depth; + if (!shadowState.shadowCasterRequest.hasCameraDataOverride) { + shadowState.shadowCasterRequest.hasCameraDataOverride = true; + shadowState.shadowCasterRequest.cameraDataOverride = + plan.directionalShadow.cameraData; + } + + shadowState.shadowData = + BuildRenderDirectionalShadowData( + plan.directionalShadow, + shadowAllocation.depthShaderView); +} + +bool RenderPipeline::ConfigureDirectionalShadowExecutionState( + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState) const { + ApplyDefaultRenderPipelineDirectionalShadowExecutionPolicy( + plan, + shadowAllocation, + shadowState); + return shadowState.shadowCasterRequest.IsValid() && + shadowState.shadowData.IsValid(); +} + } // namespace Rendering } // namespace XCEngine diff --git a/engine/src/Rendering/Shadow/DirectionalShadowRuntime.cpp b/engine/src/Rendering/Shadow/DirectionalShadowRuntime.cpp index f8ed43d5..baa4c0cd 100644 --- a/engine/src/Rendering/Shadow/DirectionalShadowRuntime.cpp +++ b/engine/src/Rendering/Shadow/DirectionalShadowRuntime.cpp @@ -1,13 +1,14 @@ #include "Rendering/Shadow/DirectionalShadowRuntime.h" #include "Rendering/Execution/CameraFramePlan.h" -#include "Rendering/Shadow/DirectionalShadowData.h" +#include namespace XCEngine { namespace Rendering { bool DirectionalShadowRuntime::ResolveExecutionState( const CameraFramePlan& plan, + const RenderPipeline& pipeline, DirectionalShadowExecutionState& outShadowState) { outShadowState = {}; outShadowState.shadowCasterRequest = plan.shadowCaster; @@ -26,18 +27,10 @@ bool DirectionalShadowRuntime::ResolveExecutionState( return false; } - outShadowState.shadowCasterRequest.surface = shadowAllocation->surface; - outShadowState.shadowCasterRequest.clearFlags = RenderClearFlags::Depth; - if (!outShadowState.shadowCasterRequest.hasCameraDataOverride) { - outShadowState.shadowCasterRequest.hasCameraDataOverride = true; - outShadowState.shadowCasterRequest.cameraDataOverride = plan.directionalShadow.cameraData; - } - - outShadowState.shadowData = - BuildRenderDirectionalShadowData( - plan.directionalShadow, - shadowAllocation->depthShaderView); - return true; + return pipeline.ConfigureDirectionalShadowExecutionState( + plan, + *shadowAllocation, + outShadowState); } } // namespace Rendering diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index a901a706..d08bf133 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -51,6 +51,7 @@ struct MockPipelineState { int initializeCalls = 0; int shutdownCalls = 0; int configureRenderSceneDataCalls = 0; + int configureDirectionalShadowExecutionStateCalls = 0; int renderCalls = 0; int recordMainSceneCalls = 0; int executeRecordedMainSceneCalls = 0; @@ -86,6 +87,10 @@ struct MockPipelineState { bool lastBlackboardMainDirectionalShadowValid = false; bool lastBlackboardObjectIdColorValid = false; std::function configureRenderSceneData = {}; + std::function configureDirectionalShadowExecutionState = {}; std::vector renderedCameras; std::vector renderedClearFlags; std::vector renderedClearColors; @@ -453,6 +458,24 @@ public: RenderPipeline::ConfigureRenderSceneData(plan, sceneData); } + bool ConfigureDirectionalShadowExecutionState( + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState) const override { + ++m_state->configureDirectionalShadowExecutionStateCalls; + if (m_state->configureDirectionalShadowExecutionState) { + return m_state->configureDirectionalShadowExecutionState( + plan, + shadowAllocation, + shadowState); + } + + return RenderPipeline::ConfigureDirectionalShadowExecutionState( + plan, + shadowAllocation, + shadowState); + } + void Shutdown() override { ++m_state->shutdownCalls; ShutdownCameraFrameStandalonePasses(); @@ -2399,6 +2422,88 @@ TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) { EXPECT_EQ(allocationState->destroyTextureCalls, 1); } +TEST(CameraRenderer_Test, AllowsPipelineToOverrideAutoAllocatedDirectionalShadowExecutionState) { + Scene scene("CameraRendererDirectionalShadowExecutionPolicyScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(2.0f); + + auto pipelineState = std::make_shared(); + pipelineState->configureDirectionalShadowExecutionState = + []( + const CameraFramePlan& plan, + const DirectionalShadowSurfaceAllocation& shadowAllocation, + DirectionalShadowExecutionState& shadowState) { + ApplyDefaultRenderPipelineDirectionalShadowExecutionPolicy( + plan, + shadowAllocation, + shadowState); + shadowState.shadowCasterRequest.clearFlags = RenderClearFlags::All; + shadowState.shadowCasterRequest.hasCameraDataOverride = true; + shadowState.shadowCasterRequest.cameraDataOverride = + plan.directionalShadow.cameraData; + shadowState.shadowCasterRequest.cameraDataOverride.worldPosition = + XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f); + shadowState.shadowData.sampling.settings.shadowStrength = 0.25f; + return true; + }; + auto allocationState = std::make_shared(); + MockShadowDevice device(allocationState); + + RenderContext context = CreateValidContext(); + context.device = &device; + + { + auto pipeline = std::make_unique(pipelineState); + InstallStandaloneStagePass( + *pipeline, + CameraFrameStage::ObjectId, + pipelineState); + MockScenePass* shadowPassRaw = + InstallStandaloneStagePass( + *pipeline, + CameraFrameStage::ShadowCaster, + pipelineState, + "shadowCaster"); + CameraRenderer renderer(std::move(pipeline)); + + CameraRenderRequest request; + request.scene = &scene; + request.camera = camera; + request.context = context; + request.surface = RenderSurface(320, 180); + request.cameraDepth = camera->GetDepth(); + request.directionalShadow.enabled = true; + request.directionalShadow.mapWidth = 256; + request.directionalShadow.mapHeight = 128; + request.directionalShadow.cameraData.viewportWidth = 256; + request.directionalShadow.cameraData.viewportHeight = 128; + request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth; + request.directionalShadow.cameraData.worldPosition = + XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f); + + ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); + EXPECT_EQ( + pipelineState->eventLog, + (std::vector{ + "init:shadowCaster", + "shadowCaster", + "pipeline" })); + EXPECT_EQ( + pipelineState->configureDirectionalShadowExecutionStateCalls, + 1); + EXPECT_EQ(shadowPassRaw->lastClearFlags, RenderClearFlags::All); + EXPECT_EQ( + shadowPassRaw->lastWorldPosition, + XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f)); + EXPECT_FLOAT_EQ( + pipelineState->lastShadowSampling.settings.shadowStrength, + 0.25f); + } +} + TEST(CameraRenderer_Test, PassesShadowDependencyToPipelineRecordedMainScene) { Scene scene("CameraRendererPipelineRecordedShadowDependency");