diff --git a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h index 35c65b72..05e23e86 100644 --- a/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h +++ b/engine/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include @@ -41,11 +41,6 @@ class RenderSurface; namespace Pipelines { } // namespace Pipelines -namespace Passes { -class BuiltinGaussianSplatPass; -class BuiltinVolumetricPass; -} // namespace Passes - namespace Pipelines { class BuiltinForwardPipeline : public RenderPipeline { @@ -57,8 +52,15 @@ public: static RHI::InputLayoutDesc BuildInputLayout(); + void AddForwardSceneFeaturePass(std::unique_ptr featurePass); + size_t GetForwardSceneFeaturePassCount() const; + SceneRenderFeaturePass* GetForwardSceneFeaturePass(size_t index) const; + 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, @@ -66,8 +68,6 @@ public: const RenderSceneData& sceneData) override; private: - using ForwardSceneFeaturePassArray = std::array; - struct OwnedDescriptorSet { RHI::RHIDescriptorPool* pool = nullptr; RHI::RHIDescriptorSet* set = nullptr; @@ -244,7 +244,7 @@ private: Core::uint32 sampleCount = 1; Core::uint32 sampleQuality = 0; - bool operator==(const SkyboxPipelineStateKey& other) const { + bool operator==(const SkyboxPipelineStateKey& other) const { return renderTargetCount == other.renderTargetCount && renderTargetFormats == other.renderTargetFormats && depthStencilFormat == other.depthStencilFormat && @@ -271,19 +271,12 @@ private: }; bool EnsureInitialized(const RenderContext& context); - ForwardSceneFeaturePassArray CollectForwardSceneFeaturePasses() const; - bool InitializeForwardSceneFeaturePasses(const RenderContext& context); - void ShutdownForwardSceneFeaturePasses(); - bool PrepareForwardSceneFeaturePasses( - const FrameExecutionContext& executionContext) const; - bool ExecuteForwardSceneFeaturePasses( - const ScenePhaseExecutionContext& executionContext) const; ScenePhaseExecutionContext BuildScenePhaseExecutionContext( const FrameExecutionContext& executionContext, ScenePhase scenePhase) const; DrawSettings BuildDrawSettings(ScenePhase scenePhase) const; bool ExecuteForwardScene(const FrameExecutionContext& executionContext); - bool ExecuteScenePhase(const ScenePhaseExecutionContext& executionContext); + bool ExecuteBuiltinScenePhase(const ScenePhaseExecutionContext& executionContext); bool CreatePipelineResources(const RenderContext& context); void DestroyPipelineResources(); static bool TryResolveSurfacePassType( @@ -332,8 +325,12 @@ private: const BuiltinPassResourceBindingDesc& binding); static LightingConstants BuildLightingConstants(const RenderLightingData& lightingData); static AdditionalLightConstants BuildAdditionalLightConstants(const RenderAdditionalLightData& lightData); + static bool ShouldSampleMainDirectionalShadowMap(const RenderSceneData& sceneData); bool HasSkybox(const RenderSceneData& sceneData) const; bool HasProceduralSkybox(const RenderSceneData& sceneData) const; + bool ExecuteForwardSceneFrame( + const FrameExecutionContext& executionContext, + bool manageMainDirectionalShadowTransitions); bool BeginForwardScenePass(const RenderPassContext& context); void EndForwardScenePass(const RenderPassContext& context); bool ExecuteForwardOpaquePass(const ScenePhaseExecutionContext& context); @@ -377,8 +374,7 @@ private: OwnedDescriptorSet m_skyboxSamplerSet = {}; RHI::RHIResourceView* m_skyboxBoundPanoramicTextureView = nullptr; RHI::RHIResourceView* m_skyboxBoundCubemapTextureView = nullptr; - std::unique_ptr m_gaussianSplatPass; - std::unique_ptr m_volumetricPass; + SceneRenderFeatureHost m_forwardSceneFeatureHost; }; class BuiltinForwardPipelineAsset final : public RenderPipelineAsset { diff --git a/engine/include/XCEngine/Rendering/RenderPipeline.h b/engine/include/XCEngine/Rendering/RenderPipeline.h index 26a9e939..5133cdcb 100644 --- a/engine/include/XCEngine/Rendering/RenderPipeline.h +++ b/engine/include/XCEngine/Rendering/RenderPipeline.h @@ -1,13 +1,33 @@ #pragma once +#include #include #include +#include #include +#include + +#include namespace XCEngine { namespace Rendering { -class RenderSurface; +class RenderGraphBuilder; + +struct RenderPipelineMainSceneRenderGraphContext { + RenderGraphBuilder& graphBuilder; + Containers::String passName = {}; + const RenderContext& renderContext; + const RenderSceneData& sceneData; + RenderSurface surfaceTemplate = {}; + const RenderSurface* sourceSurface = nullptr; + RHI::RHIResourceView* sourceColorView = nullptr; + RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common; + std::vector colorTargets = {}; + RenderGraphTextureHandle depthTarget = {}; + RenderGraphTextureHandle mainDirectionalShadowTexture = {}; + bool* executionSucceeded = nullptr; +}; class RenderPipeline { public: @@ -15,6 +35,13 @@ public: virtual bool Initialize(const RenderContext& context) = 0; virtual void Shutdown() = 0; + virtual bool SupportsMainSceneRenderGraph() const { + return false; + } + virtual bool RecordMainSceneRenderGraph( + const RenderPipelineMainSceneRenderGraphContext&) { + return false; + } virtual bool Render(const FrameExecutionContext& executionContext) { return Render( executionContext.renderContext, diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index cf1a268c..fd8dd0f3 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -774,6 +774,7 @@ bool ExecuteRenderGraphPlan( RenderGraphImportedTextureRegistry importedTextures = {}; bool stageExecutionSucceeded = true; + RenderGraphTextureHandle mainDirectionalShadowTexture = {}; for (const CameraFrameStageInfo& stageInfo : kOrderedCameraFrameStages) { if (!plan.HasFrameStage(stageInfo.stage) && stageInfo.stage != CameraFrameStage::MainScene) { @@ -891,6 +892,11 @@ bool ExecuteRenderGraphPlan( RenderGraphSurfaceImportUsage::Output, ShouldGraphOwnStageColorTransitions(stage), ShouldGraphOwnStageDepthTransitions(stage)); + if (stage == CameraFrameStage::ShadowCaster && + shadowState.HasShadowSampling() && + outputSurface.depthTexture.IsValid()) { + mainDirectionalShadowTexture = outputSurface.depthTexture; + } const RenderSurface stageSurfaceTemplate = stagePassContext.surface; const bool hasStageSourceSurface = stagePassContext.sourceSurface != nullptr; const RenderSurface stageSourceSurfaceTemplate = @@ -900,6 +906,32 @@ bool ExecuteRenderGraphPlan( const RHI::RHIResourceView* const stageSourceColorView = stagePassContext.sourceColorView; const RHI::ResourceStates stageSourceColorState = stagePassContext.sourceColorState; + if (stage == CameraFrameStage::MainScene && + executionState.pipeline != nullptr && + executionState.pipeline->SupportsMainSceneRenderGraph()) { + const RenderPipelineMainSceneRenderGraphContext mainSceneContext = { + graphBuilder, + stageName, + plan.request.context, + sceneData, + stageSurfaceTemplate, + hasStageSourceSurface ? &stageSourceSurfaceTemplate : nullptr, + const_cast(stageSourceColorView), + stageSourceColorState, + outputSurface.colorTextures, + outputSurface.depthTexture, + mainDirectionalShadowTexture, + &stageExecutionSucceeded + }; + if (!executionState.pipeline->RecordMainSceneRenderGraph(mainSceneContext)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "CameraRenderer::Render failed: RenderPipeline::RecordMainSceneRenderGraph returned false"); + return false; + } + continue; + } + graphBuilder.AddRasterPass( stageName, [&, sourceSurface, outputSurface, stage, stageSurfaceTemplate, hasStageSourceSurface, stageSourceSurfaceTemplate, stageSourceColorView, stageSourceColorState]( diff --git a/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp new file mode 100644 index 00000000..8b324659 --- /dev/null +++ b/engine/src/Rendering/Pipelines/Internal/BuiltinForwardPipelineFrame.cpp @@ -0,0 +1,319 @@ +#include "Rendering/Pipelines/BuiltinForwardPipeline.h" + +#include "Debug/Logger.h" +#include "Rendering/Graph/RenderGraph.h" +#include "RHI/RHICommandList.h" +#include "Rendering/Internal/RenderSurfacePipelineUtils.h" +#include "Rendering/RenderSurface.h" + +namespace XCEngine { +namespace Rendering { +namespace Pipelines { +namespace { + +bool IsDepthFormat(RHI::Format format) { + return format == RHI::Format::D24_UNorm_S8_UInt || + format == RHI::Format::D32_Float; +} + +void TransitionMainDirectionalShadowForSampling( + const RenderContext& context, + const RenderSceneData& sceneData) { + context.commandList->TransitionBarrier( + sceneData.lighting.mainDirectionalShadow.shadowMap, + RHI::ResourceStates::DepthWrite, + RHI::ResourceStates::PixelShaderResource); +} + +void RestoreMainDirectionalShadowAfterSampling( + const RenderContext& context, + const RenderSceneData& sceneData) { + context.commandList->TransitionBarrier( + sceneData.lighting.mainDirectionalShadow.shadowMap, + RHI::ResourceStates::PixelShaderResource, + RHI::ResourceStates::DepthWrite); +} + +std::vector CollectSurfaceColorAttachments(const RenderSurface& surface) { + std::vector renderTargets; + const Core::uint32 colorAttachmentCount = + ::XCEngine::Rendering::Internal::ResolveSurfaceColorAttachmentCount(surface); + renderTargets.reserve(colorAttachmentCount); + for (Core::uint32 attachmentIndex = 0; attachmentIndex < colorAttachmentCount; ++attachmentIndex) { + RHI::RHIResourceView* renderTarget = surface.GetColorAttachments()[attachmentIndex]; + if (renderTarget == nullptr) { + break; + } + + renderTargets.push_back(renderTarget); + } + + return renderTargets; +} + +RenderSurface BuildGraphManagedForwardSceneSurface(const RenderSurface& templateSurface) { + RenderSurface surface = templateSurface; + surface.SetAutoTransitionEnabled(false); + return surface; +} + +} // namespace + +bool BuiltinForwardPipeline::ShouldSampleMainDirectionalShadowMap(const RenderSceneData& sceneData) { + return sceneData.lighting.HasMainDirectionalShadow() && + sceneData.lighting.mainDirectionalShadow.shadowMap != nullptr && + IsDepthFormat(sceneData.lighting.mainDirectionalShadow.shadowMap->GetFormat()); +} + +bool BuiltinForwardPipeline::SupportsMainSceneRenderGraph() const { + return true; +} + +bool BuiltinForwardPipeline::RecordMainSceneRenderGraph( + const RenderPipelineMainSceneRenderGraphContext& context) { + const Containers::String passName = context.passName; + const RenderContext* const renderContext = &context.renderContext; + const RenderSceneData* const sceneData = &context.sceneData; + const RenderSurface surfaceTemplate = + BuildGraphManagedForwardSceneSurface(context.surfaceTemplate); + const RenderSurface* const sourceSurface = context.sourceSurface; + RHI::RHIResourceView* const sourceColorView = context.sourceColorView; + const RHI::ResourceStates sourceColorState = context.sourceColorState; + const std::vector colorTargets = context.colorTargets; + const RenderGraphTextureHandle depthTarget = context.depthTarget; + const RenderGraphTextureHandle mainDirectionalShadowTexture = + context.mainDirectionalShadowTexture; + bool* const executionSucceeded = context.executionSucceeded; + + context.graphBuilder.AddRasterPass( + passName, + [this, + surfaceTemplate, + renderContext, + sceneData, + sourceSurface, + sourceColorView, + sourceColorState, + colorTargets, + depthTarget, + mainDirectionalShadowTexture, + executionSucceeded]( + RenderGraphPassBuilder& passBuilder) { + for (RenderGraphTextureHandle colorTarget : colorTargets) { + if (colorTarget.IsValid()) { + passBuilder.WriteTexture(colorTarget); + } + } + + if (depthTarget.IsValid()) { + passBuilder.WriteDepthTexture(depthTarget); + } + if (mainDirectionalShadowTexture.IsValid()) { + passBuilder.ReadTexture(mainDirectionalShadowTexture); + } + + passBuilder.SetExecuteCallback( + [this, + surfaceTemplate, + renderContext, + sceneData, + sourceSurface, + sourceColorView, + sourceColorState, + mainDirectionalShadowTexture, + executionSucceeded]( + const RenderGraphExecutionContext&) { + if (executionSucceeded != nullptr && !(*executionSucceeded)) { + return; + } + + const FrameExecutionContext executionContext( + *renderContext, + surfaceTemplate, + *sceneData, + sourceSurface, + sourceColorView, + sourceColorState); + const bool renderResult = + ExecuteForwardSceneFrame( + executionContext, + !mainDirectionalShadowTexture.IsValid()); + if (executionSucceeded != nullptr) { + *executionSucceeded = renderResult; + } + }); + }); + + return true; +} + +bool BuiltinForwardPipeline::Render( + const FrameExecutionContext& executionContext) { + return ExecuteForwardSceneFrame(executionContext, true); +} + +bool BuiltinForwardPipeline::ExecuteForwardSceneFrame( + const FrameExecutionContext& executionContext, + bool manageMainDirectionalShadowTransitions) { + if (!Initialize(executionContext.renderContext)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline::Render failed: Initialize returned false"); + return false; + } + + if (!m_forwardSceneFeatureHost.Prepare(executionContext)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline::Render failed: SceneRenderFeatureHost::Prepare returned false"); + return false; + } + + const RenderPassContext passContext = BuildRenderPassContext(executionContext); + + if (!BeginForwardScenePass(passContext)) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinForwardPipeline::Render failed: BeginForwardScenePass returned false"); + return false; + } + + const bool sampledDirectionalShadow = + manageMainDirectionalShadowTransitions && + ShouldSampleMainDirectionalShadowMap(executionContext.sceneData); + if (sampledDirectionalShadow) { + TransitionMainDirectionalShadowForSampling( + executionContext.renderContext, + executionContext.sceneData); + } + + const bool renderResult = ExecuteForwardScene(executionContext); + + if (sampledDirectionalShadow) { + RestoreMainDirectionalShadowAfterSampling( + executionContext.renderContext, + executionContext.sceneData); + } + EndForwardScenePass(passContext); + + return renderResult; +} + +bool BuiltinForwardPipeline::BeginForwardScenePass(const RenderPassContext& passContext) { + const RenderContext& context = passContext.renderContext; + const RenderSurface& surface = passContext.surface; + const RenderSceneData& sceneData = passContext.sceneData; + RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment(); + + std::vector renderTargets = CollectSurfaceColorAttachments(surface); + if (renderTargets.empty()) { + return false; + } + + const Math::RectInt renderArea = surface.GetRenderArea(); + if (renderArea.width <= 0 || renderArea.height <= 0) { + return false; + } + + RHI::RHICommandList* commandList = context.commandList; + if (surface.IsAutoTransitionEnabled()) { + for (RHI::RHIResourceView* renderTarget : renderTargets) { + if (renderTarget != nullptr) { + commandList->TransitionBarrier( + renderTarget, + surface.GetColorStateBefore(), + RHI::ResourceStates::RenderTarget); + } + } + + if (depthAttachment != nullptr) { + commandList->TransitionBarrier( + depthAttachment, + surface.GetDepthStateBefore(), + RHI::ResourceStates::DepthWrite); + } + } + + commandList->SetRenderTargets( + static_cast(renderTargets.size()), + renderTargets.data(), + depthAttachment); + + const RHI::Viewport viewport = { + static_cast(renderArea.x), + static_cast(renderArea.y), + static_cast(renderArea.width), + static_cast(renderArea.height), + 0.0f, + 1.0f + }; + const RHI::Rect scissorRect = { + renderArea.x, + renderArea.y, + renderArea.x + renderArea.width, + renderArea.y + renderArea.height + }; + const RHI::Rect clearRects[] = { scissorRect }; + commandList->SetViewport(viewport); + commandList->SetScissorRect(scissorRect); + + const Math::Color clearColor = surface.HasClearColorOverride() + ? surface.GetClearColorOverride() + : sceneData.cameraData.clearColor; + const float clearValues[4] = { clearColor.r, clearColor.g, clearColor.b, clearColor.a }; + if (HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Color) && + !HasSkybox(sceneData)) { + for (RHI::RHIResourceView* renderTarget : renderTargets) { + if (renderTarget != nullptr) { + commandList->ClearRenderTarget(renderTarget, clearValues, 1, clearRects); + } + } + } + if (depthAttachment != nullptr && + HasRenderClearFlag(sceneData.cameraData.clearFlags, RenderClearFlags::Depth)) { + commandList->ClearDepthStencil(depthAttachment, 1.0f, 0, 1, clearRects); + } + + commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); + return true; +} + +bool BuiltinForwardPipeline::Render( + const RenderContext& context, + const RenderSurface& surface, + const RenderSceneData& sceneData) { + return Render(FrameExecutionContext(context, surface, sceneData)); +} + +void BuiltinForwardPipeline::EndForwardScenePass(const RenderPassContext& passContext) { + const RenderContext& context = passContext.renderContext; + const RenderSurface& surface = passContext.surface; + RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment(); + std::vector renderTargets = CollectSurfaceColorAttachments(surface); + RHI::RHICommandList* commandList = context.commandList; + + commandList->EndRenderPass(); + if (!surface.IsAutoTransitionEnabled()) { + return; + } + + for (RHI::RHIResourceView* renderTarget : renderTargets) { + if (renderTarget != nullptr) { + commandList->TransitionBarrier( + renderTarget, + RHI::ResourceStates::RenderTarget, + surface.GetColorStateAfter()); + } + } + + if (depthAttachment != nullptr) { + commandList->TransitionBarrier( + depthAttachment, + RHI::ResourceStates::DepthWrite, + surface.GetDepthStateAfter()); + } +} + +} // namespace Pipelines +} // namespace Rendering +} // namespace XCEngine diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index eefe0c25..be53ebd7 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -2,23 +2,33 @@ #include "Rendering/Internal/ShaderVariantUtils.h" +#include #include +#include #include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include #include #include +#include +#include +#include +#define private public #include +#undef private #include #include #include #include #include +#include #include #include #include #include #include +#include +#include using namespace XCEngine::Rendering::Pipelines; using namespace XCEngine::Rendering::Passes; @@ -130,6 +140,344 @@ private: XCEngine::RHI::Format m_format; }; +class MockForwardPipelineLayout final : public XCEngine::RHI::RHIPipelineLayout { +public: + explicit MockForwardPipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc& desc) + : m_desc(desc) { + } + + bool Initialize(const XCEngine::RHI::RHIPipelineLayoutDesc& desc) override { + m_desc = desc; + return true; + } + + void Shutdown() override { + m_shutdownCalled = true; + } + + void* GetNativeHandle() override { + return nullptr; + } + + bool m_shutdownCalled = false; + +private: + XCEngine::RHI::RHIPipelineLayoutDesc m_desc = {}; +}; + +class MockForwardTexture final : public XCEngine::RHI::RHITexture { +public: + explicit MockForwardTexture(const XCEngine::RHI::TextureDesc& desc) + : m_width(desc.width) + , m_height(desc.height) + , m_depth(desc.depth) + , m_mipLevels(desc.mipLevels) + , m_format(static_cast(desc.format)) + , m_textureType(static_cast(desc.textureType)) { + } + + uint32_t GetWidth() const override { return m_width; } + uint32_t GetHeight() const override { return m_height; } + uint32_t GetDepth() const override { return m_depth; } + uint32_t GetMipLevels() const override { return m_mipLevels; } + XCEngine::RHI::Format GetFormat() const override { return m_format; } + XCEngine::RHI::TextureType GetTextureType() const override { return m_textureType; } + XCEngine::RHI::ResourceStates GetState() const override { return m_state; } + void SetState(XCEngine::RHI::ResourceStates state) override { m_state = state; } + void* GetNativeHandle() override { return nullptr; } + const std::string& GetName() const override { return m_name; } + void SetName(const std::string& name) override { m_name = name; } + void Shutdown() override {} + +private: + uint32_t m_width = 0u; + uint32_t m_height = 0u; + uint32_t m_depth = 0u; + uint32_t m_mipLevels = 0u; + XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown; + XCEngine::RHI::TextureType m_textureType = XCEngine::RHI::TextureType::Texture2D; + XCEngine::RHI::ResourceStates m_state = XCEngine::RHI::ResourceStates::Common; + std::string m_name; +}; + +class MockForwardSampler final : public XCEngine::RHI::RHISampler { +public: + void Shutdown() override {} + void Bind(unsigned int) override {} + void Unbind(unsigned int) override {} + void* GetNativeHandle() override { return nullptr; } + unsigned int GetID() override { return 0u; } +}; + +class MockForwardCommandList final : public XCEngine::RHI::RHICommandList { +public: + void Shutdown() override {} + void Reset() override {} + void Close() override {} + + void TransitionBarrier( + XCEngine::RHI::RHIResourceView*, + XCEngine::RHI::ResourceStates, + XCEngine::RHI::ResourceStates) override { + } + + void BeginRenderPass( + XCEngine::RHI::RHIRenderPass*, + XCEngine::RHI::RHIFramebuffer*, + const XCEngine::RHI::Rect&, + uint32_t, + const XCEngine::RHI::ClearValue*) override { + } + + void EndRenderPass() override { + ++endRenderPassCalls; + } + + void SetShader(XCEngine::RHI::RHIShader*) override {} + void SetPipelineState(XCEngine::RHI::RHIPipelineState*) override {} + void SetGraphicsDescriptorSets( + uint32_t, + uint32_t, + XCEngine::RHI::RHIDescriptorSet**, + XCEngine::RHI::RHIPipelineLayout*) override { + } + void SetComputeDescriptorSets( + uint32_t, + uint32_t, + XCEngine::RHI::RHIDescriptorSet**, + XCEngine::RHI::RHIPipelineLayout*) override { + } + void SetPrimitiveTopology(XCEngine::RHI::PrimitiveTopology) override {} + void SetViewport(const XCEngine::RHI::Viewport&) override {} + void SetViewports(uint32_t, const XCEngine::RHI::Viewport*) override {} + void SetScissorRect(const XCEngine::RHI::Rect&) override {} + void SetScissorRects(uint32_t, const XCEngine::RHI::Rect*) override {} + void SetRenderTargets( + uint32_t, + XCEngine::RHI::RHIResourceView**, + XCEngine::RHI::RHIResourceView*) override { + } + void SetStencilRef(uint8_t) override {} + void SetBlendFactor(const float[4]) override {} + void SetVertexBuffers( + uint32_t, + uint32_t, + XCEngine::RHI::RHIResourceView**, + const uint64_t*, + const uint32_t*) override { + } + void SetIndexBuffer(XCEngine::RHI::RHIResourceView*, uint64_t) override {} + void Draw(uint32_t, uint32_t, uint32_t, uint32_t) override {} + void DrawIndexed(uint32_t, uint32_t, uint32_t, int32_t, uint32_t) override {} + void Clear(float, float, float, float, uint32_t) override {} + void ClearRenderTarget( + XCEngine::RHI::RHIResourceView*, + const float[4], + uint32_t, + const XCEngine::RHI::Rect*) override { + } + void ClearDepthStencil( + XCEngine::RHI::RHIResourceView*, + float, + uint8_t, + uint32_t, + const XCEngine::RHI::Rect*) override { + } + void CopyResource(XCEngine::RHI::RHIResourceView*, XCEngine::RHI::RHIResourceView*) override {} + void Dispatch(uint32_t, uint32_t, uint32_t) override {} + + size_t endRenderPassCalls = 0u; +}; + +class MockForwardCommandQueue final : public XCEngine::RHI::RHICommandQueue { +public: + void Shutdown() override {} + void ExecuteCommandLists(uint32_t, void**) override {} + void Signal(XCEngine::RHI::RHIFence*, uint64_t) override {} + void Wait(XCEngine::RHI::RHIFence*, uint64_t) override {} + uint64_t GetCompletedValue() override { return 0u; } + void WaitForIdle() override {} + XCEngine::RHI::CommandQueueType GetType() const override { return XCEngine::RHI::CommandQueueType::Direct; } + uint64_t GetTimestampFrequency() const override { return 0u; } + void* GetNativeHandle() override { return nullptr; } + void WaitForPreviousFrame() override {} + uint64_t GetCurrentFrame() const override { return 0u; } +}; + +class MockForwardFailureDevice final : public XCEngine::RHI::RHIDevice { +public: + bool Initialize(const XCEngine::RHI::RHIDeviceDesc&) override { return true; } + void Shutdown() override {} + + XCEngine::RHI::RHIBuffer* CreateBuffer(const XCEngine::RHI::BufferDesc&) override { return nullptr; } + XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc& desc) override { + ++createTextureCalls; + return new MockForwardTexture(desc); + } + XCEngine::RHI::RHITexture* CreateTexture( + const XCEngine::RHI::TextureDesc& desc, + const void*, + size_t, + uint32_t) override { + return CreateTexture(desc); + } + + XCEngine::RHI::RHISwapChain* CreateSwapChain( + const XCEngine::RHI::SwapChainDesc&, + XCEngine::RHI::RHICommandQueue*) override { return nullptr; } + XCEngine::RHI::RHICommandList* CreateCommandList(const XCEngine::RHI::CommandListDesc&) override { return nullptr; } + XCEngine::RHI::RHICommandQueue* CreateCommandQueue(const XCEngine::RHI::CommandQueueDesc&) override { return nullptr; } + XCEngine::RHI::RHIShader* CreateShader(const XCEngine::RHI::ShaderCompileDesc&) override { return nullptr; } + + XCEngine::RHI::RHIPipelineState* CreatePipelineState(const XCEngine::RHI::GraphicsPipelineDesc& desc) override { + ++createPipelineStateCalls; + lastPipelineDesc = desc; + return nullptr; + } + + XCEngine::RHI::RHIPipelineLayout* CreatePipelineLayout( + const XCEngine::RHI::RHIPipelineLayoutDesc& desc) override { + ++createPipelineLayoutCalls; + return new MockForwardPipelineLayout(desc); + } + + XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; } + XCEngine::RHI::RHISampler* CreateSampler(const XCEngine::RHI::SamplerDesc&) override { + ++createSamplerCalls; + return new MockForwardSampler(); + } + XCEngine::RHI::RHIRenderPass* CreateRenderPass( + uint32_t, + const XCEngine::RHI::AttachmentDesc*, + const XCEngine::RHI::AttachmentDesc*) override { return nullptr; } + XCEngine::RHI::RHIFramebuffer* CreateFramebuffer( + XCEngine::RHI::RHIRenderPass*, + uint32_t, + uint32_t, + uint32_t, + XCEngine::RHI::RHIResourceView**, + XCEngine::RHI::RHIResourceView*) override { return nullptr; } + XCEngine::RHI::RHIDescriptorPool* CreateDescriptorPool(const XCEngine::RHI::DescriptorPoolDesc&) override { return nullptr; } + XCEngine::RHI::RHIDescriptorSet* CreateDescriptorSet( + XCEngine::RHI::RHIDescriptorPool*, + const XCEngine::RHI::DescriptorSetLayoutDesc&) override { return nullptr; } + XCEngine::RHI::RHIResourceView* CreateVertexBufferView( + XCEngine::RHI::RHIBuffer*, + const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; } + XCEngine::RHI::RHIResourceView* CreateIndexBufferView( + XCEngine::RHI::RHIBuffer*, + const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; } + XCEngine::RHI::RHIResourceView* CreateRenderTargetView( + XCEngine::RHI::RHITexture*, + const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; } + XCEngine::RHI::RHIResourceView* CreateDepthStencilView( + XCEngine::RHI::RHITexture*, + const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; } + XCEngine::RHI::RHIResourceView* CreateShaderResourceView( + XCEngine::RHI::RHIBuffer*, + const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; } + XCEngine::RHI::RHIResourceView* CreateShaderResourceView( + XCEngine::RHI::RHITexture*, + const XCEngine::RHI::ResourceViewDesc& desc) override { + ++createShaderResourceViewCalls; + return new TestResourceView( + XCEngine::RHI::ResourceViewType::ShaderResource, + desc.dimension, + static_cast(desc.format)); + } + XCEngine::RHI::RHIResourceView* CreateUnorderedAccessView( + XCEngine::RHI::RHIBuffer*, + const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; } + XCEngine::RHI::RHIResourceView* CreateUnorderedAccessView( + XCEngine::RHI::RHITexture*, + const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; } + + const XCEngine::RHI::RHICapabilities& GetCapabilities() const override { return m_capabilities; } + const XCEngine::RHI::RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; } + void* GetNativeDevice() override { return nullptr; } + + size_t createPipelineLayoutCalls = 0u; + size_t createPipelineStateCalls = 0u; + size_t createSamplerCalls = 0u; + size_t createTextureCalls = 0u; + size_t createShaderResourceViewCalls = 0u; + XCEngine::RHI::GraphicsPipelineDesc lastPipelineDesc = {}; + +private: + XCEngine::RHI::RHICapabilities m_capabilities = {}; + XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {}; +}; + +class TestSceneRenderFeaturePass final : public SceneRenderFeaturePass { +public: + explicit TestSceneRenderFeaturePass( + SceneRenderInjectionPoint injectionPoint = SceneRenderInjectionPoint::BeforeTransparent, + bool isActive = true, + std::vector* eventLog = nullptr, + const char* label = "TestSceneRenderFeaturePass") + : m_injectionPoint(injectionPoint) + , m_isActive(isActive) + , m_eventLog(eventLog) + , m_label(label != nullptr ? label : "TestSceneRenderFeaturePass") { + } + + const char* GetName() const override { + return m_label.c_str(); + } + + SceneRenderInjectionPoint GetInjectionPoint() const override { + return m_injectionPoint; + } + + bool Initialize(const RenderContext&) override { + ++initializeCallCount; + RecordEvent("Initialize"); + return true; + } + + void Shutdown() override { + ++shutdownCallCount; + RecordEvent("Shutdown"); + } + + bool IsActive(const RenderSceneData&) const override { + ++isActiveCallCount; + return m_isActive; + } + + bool Prepare(const FrameExecutionContext&) override { + ++prepareCallCount; + RecordEvent("Prepare"); + return true; + } + + bool Execute(const RenderPassContext&) override { + ++executeCallCount; + RecordEvent("Execute"); + return true; + } + + size_t initializeCallCount = 0u; + mutable size_t isActiveCallCount = 0u; + size_t prepareCallCount = 0u; + size_t executeCallCount = 0u; + size_t shutdownCallCount = 0u; + +private: + void RecordEvent(const char* suffix) { + if (m_eventLog == nullptr || suffix == nullptr) { + return; + } + + m_eventLog->push_back(m_label + ":" + suffix); + } + + SceneRenderInjectionPoint m_injectionPoint; + bool m_isActive = true; + std::vector* m_eventLog = nullptr; + std::string m_label; +}; + } // namespace TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) { @@ -187,6 +535,254 @@ TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVert EXPECT_EQ(color.alignedByteOffset, static_cast(offsetof(StaticMeshVertex, color))); } +TEST(BuiltinForwardPipeline_Test, RecordsMainSceneGraphPassWithSampledShadowDependency) { + BuiltinForwardPipeline pipeline; + EXPECT_TRUE(pipeline.SupportsMainSceneRenderGraph()); + + RenderGraph graph = {}; + RenderGraphBuilder graphBuilder(graph); + + RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 320u; + colorDesc.height = 180u; + colorDesc.format = static_cast(Format::R8G8B8A8_UNorm); + colorDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + colorDesc.sampleCount = 1u; + + RenderGraphTextureDesc depthDesc = {}; + depthDesc.width = 320u; + depthDesc.height = 180u; + depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); + depthDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); + depthDesc.sampleCount = 1u; + + TestResourceView colorView( + ResourceViewType::RenderTarget, + ResourceViewDimension::Texture2D, + Format::R8G8B8A8_UNorm); + TestResourceView depthView( + ResourceViewType::DepthStencil, + ResourceViewDimension::Texture2D, + Format::D24_UNorm_S8_UInt); + TestResourceView shadowView( + ResourceViewType::DepthStencil, + ResourceViewDimension::Texture2D, + Format::D24_UNorm_S8_UInt); + + const RenderGraphImportedTextureOptions graphManagedImport = { + ResourceStates::Common, + ResourceStates::Common, + true + }; + const RenderGraphTextureHandle colorTarget = + graphBuilder.ImportTexture("MainColor", colorDesc, &colorView, graphManagedImport); + const RenderGraphTextureHandle depthTarget = + graphBuilder.ImportTexture("MainDepth", depthDesc, &depthView, graphManagedImport); + const RenderGraphTextureHandle shadowTarget = + graphBuilder.ImportTexture("ShadowMap", depthDesc, &shadowView, graphManagedImport); + + RenderSurface surface(320u, 180u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + + RenderContext renderContext = {}; + RenderSceneData sceneData = {}; + bool executionSucceeded = true; + const RenderPipelineMainSceneRenderGraphContext context = { + graphBuilder, + "MainScene", + renderContext, + sceneData, + surface, + nullptr, + nullptr, + ResourceStates::Common, + { colorTarget }, + depthTarget, + shadowTarget, + &executionSucceeded + }; + + ASSERT_TRUE(pipeline.RecordMainSceneRenderGraph(context)); + + CompiledRenderGraph compiledGraph = {}; + String errorMessage; + ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) + << errorMessage.CStr(); + EXPECT_EQ(compiledGraph.GetPassCount(), 1u); + EXPECT_STREQ(compiledGraph.GetPassName(0).CStr(), "MainScene"); + EXPECT_EQ(compiledGraph.GetPassType(0), RenderGraphPassType::Raster); + + RenderGraphTextureTransitionPlan shadowPlan = {}; + ASSERT_TRUE(compiledGraph.TryGetTextureTransitionPlan(shadowTarget, shadowPlan)); + EXPECT_TRUE(shadowPlan.graphOwnsTransitions); + EXPECT_TRUE(shadowPlan.hasFirstAccessState); + EXPECT_EQ(shadowPlan.firstAccessState, ResourceStates::PixelShaderResource); + EXPECT_EQ(shadowPlan.lastAccessState, ResourceStates::PixelShaderResource); + + RenderGraphTextureTransitionPlan depthPlan = {}; + ASSERT_TRUE(compiledGraph.TryGetTextureTransitionPlan(depthTarget, depthPlan)); + EXPECT_EQ(depthPlan.firstAccessState, ResourceStates::DepthWrite); + EXPECT_EQ(depthPlan.lastAccessState, ResourceStates::DepthWrite); +} + +TEST(SceneRenderFeaturePass_Test, SupportsExplicitInjectionPointContract) { + TestSceneRenderFeaturePass feature(SceneRenderInjectionPoint::AfterOpaque); + + EXPECT_EQ(feature.GetInjectionPoint(), SceneRenderInjectionPoint::AfterOpaque); + EXPECT_TRUE(feature.SupportsInjectionPoint(SceneRenderInjectionPoint::AfterOpaque)); + EXPECT_FALSE(feature.SupportsInjectionPoint(SceneRenderInjectionPoint::BeforeTransparent)); +} + +TEST(BuiltinForwardPipeline_Test, RegistersBuiltinDefaultForwardSceneFeatures) { + BuiltinForwardPipeline pipeline; + + ASSERT_EQ(pipeline.GetForwardSceneFeaturePassCount(), 2u); + ASSERT_NE(pipeline.GetForwardSceneFeaturePass(0u), nullptr); + ASSERT_NE(pipeline.GetForwardSceneFeaturePass(1u), nullptr); + EXPECT_STREQ(pipeline.GetForwardSceneFeaturePass(0u)->GetName(), "BuiltinGaussianSplatPass"); + EXPECT_STREQ(pipeline.GetForwardSceneFeaturePass(1u)->GetName(), "BuiltinVolumetricPass"); + EXPECT_EQ( + pipeline.GetForwardSceneFeaturePass(0u)->GetInjectionPoint(), + SceneRenderInjectionPoint::BeforeTransparent); + EXPECT_EQ( + pipeline.GetForwardSceneFeaturePass(1u)->GetInjectionPoint(), + SceneRenderInjectionPoint::BeforeTransparent); +} + +TEST(SceneRenderFeatureHost_Test, FiltersByInjectionPointAndShutsDownInReverseOrder) { + SceneRenderFeatureHost host; + std::vector eventLog = {}; + + auto beforeOpaque = std::make_unique( + SceneRenderInjectionPoint::BeforeOpaque, + true, + &eventLog, + "BeforeOpaque"); + auto afterTransparent = std::make_unique( + SceneRenderInjectionPoint::AfterTransparent, + true, + &eventLog, + "AfterTransparent"); + auto inactive = std::make_unique( + SceneRenderInjectionPoint::BeforeOpaque, + false, + &eventLog, + "Inactive"); + + TestSceneRenderFeaturePass* beforeOpaqueRaw = beforeOpaque.get(); + TestSceneRenderFeaturePass* afterTransparentRaw = afterTransparent.get(); + TestSceneRenderFeaturePass* inactiveRaw = inactive.get(); + + host.AddFeaturePass(std::move(beforeOpaque)); + host.AddFeaturePass(std::move(afterTransparent)); + host.AddFeaturePass(std::move(inactive)); + + RenderContext renderContext = {}; + RenderSurface surface(64u, 64u); + RenderSceneData sceneData = {}; + const FrameExecutionContext executionContext(renderContext, surface, sceneData); + + ASSERT_EQ(host.GetFeaturePassCount(), 3u); + EXPECT_TRUE(host.Initialize(renderContext)); + EXPECT_TRUE(host.Prepare(executionContext)); + EXPECT_TRUE(host.Execute(executionContext, SceneRenderInjectionPoint::BeforeOpaque)); + EXPECT_TRUE(host.Execute(executionContext, SceneRenderInjectionPoint::AfterTransparent)); + + EXPECT_EQ(beforeOpaqueRaw->initializeCallCount, 1u); + EXPECT_EQ(afterTransparentRaw->initializeCallCount, 1u); + EXPECT_EQ(inactiveRaw->initializeCallCount, 1u); + EXPECT_EQ(beforeOpaqueRaw->prepareCallCount, 1u); + EXPECT_EQ(afterTransparentRaw->prepareCallCount, 1u); + EXPECT_EQ(inactiveRaw->prepareCallCount, 0u); + EXPECT_EQ(beforeOpaqueRaw->executeCallCount, 1u); + EXPECT_EQ(afterTransparentRaw->executeCallCount, 1u); + EXPECT_EQ(inactiveRaw->executeCallCount, 0u); + + host.Shutdown(); + + EXPECT_EQ(beforeOpaqueRaw->shutdownCallCount, 1u); + EXPECT_EQ(afterTransparentRaw->shutdownCallCount, 1u); + EXPECT_EQ(inactiveRaw->shutdownCallCount, 1u); + ASSERT_GE(eventLog.size(), 7u); + EXPECT_EQ(eventLog[eventLog.size() - 1u], "BeforeOpaque:Shutdown"); + EXPECT_EQ(eventLog[eventLog.size() - 2u], "AfterTransparent:Shutdown"); + EXPECT_EQ(eventLog[eventLog.size() - 3u], "Inactive:Shutdown"); +} + +TEST(BuiltinForwardPipeline_Test, RegistersAdditionalForwardSceneFeaturePasses) { + BuiltinForwardPipeline pipeline; + const size_t initialFeaturePassCount = pipeline.GetForwardSceneFeaturePassCount(); + + auto featurePass = std::make_unique( + SceneRenderInjectionPoint::AfterTransparent); + TestSceneRenderFeaturePass* featurePassRaw = featurePass.get(); + pipeline.AddForwardSceneFeaturePass(std::move(featurePass)); + + ASSERT_EQ(pipeline.GetForwardSceneFeaturePassCount(), initialFeaturePassCount + 1u); + EXPECT_EQ(pipeline.GetForwardSceneFeaturePass(initialFeaturePassCount), featurePassRaw); + ASSERT_NE(pipeline.GetForwardSceneFeaturePass(initialFeaturePassCount), nullptr); + EXPECT_EQ( + pipeline.GetForwardSceneFeaturePass(initialFeaturePassCount)->GetInjectionPoint(), + SceneRenderInjectionPoint::AfterTransparent); + EXPECT_EQ(pipeline.GetForwardSceneFeaturePass(initialFeaturePassCount + 1u), nullptr); +} + +TEST(BuiltinForwardPipeline_Test, PropagatesForwardDrawFailureWhenPipelineStateCreationFails) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + shader->m_guid = ResourceGUID(); + + Material material; + material.SetShader(ResourceHandle(shader)); + + VisibleRenderItem visibleItem = {}; + visibleItem.material = &material; + visibleItem.renderQueue = static_cast(MaterialRenderQueue::Geometry); + + RenderSceneData sceneData = {}; + sceneData.visibleItems.push_back(visibleItem); + + TestResourceView color( + ResourceViewType::RenderTarget, + ResourceViewDimension::Texture2D, + Format::R8G8B8A8_UNorm); + RenderSurface surface(64u, 64u); + surface.SetColorAttachment(&color); + + MockForwardFailureDevice device; + MockForwardCommandList commandList; + MockForwardCommandQueue commandQueue; + RenderContext context = {}; + context.device = &device; + context.commandList = &commandList; + context.commandQueue = &commandQueue; + context.backendType = RHIType::D3D12; + + XCEngine::Debug::Logger::Get().SetCategoryEnabled(XCEngine::Debug::LogCategory::Rendering, false); + { + BuiltinForwardPipeline pipeline; + pipeline.m_forwardSceneFeatureHost.m_featurePasses.clear(); + + EXPECT_FALSE(pipeline.Render(context, surface, sceneData)); + EXPECT_EQ(device.createPipelineLayoutCalls, 1u); + EXPECT_EQ(device.createPipelineStateCalls, 1u); + EXPECT_EQ(device.createSamplerCalls, 2u); + EXPECT_EQ(device.createTextureCalls, 2u); + EXPECT_EQ(device.createShaderResourceViewCalls, 2u); + EXPECT_EQ(commandList.endRenderPassCalls, 1u); + EXPECT_EQ(static_cast(device.lastPipelineDesc.renderTargetFormats[0]), Format::R8G8B8A8_UNorm); + } + ResourceManager::Get().UnloadAll(); + ResourceManager::Get().Shutdown(); + XCEngine::Debug::Logger::Get().SetCategoryEnabled(XCEngine::Debug::LogCategory::Rendering, true); +} + TEST(RenderSurfacePipelineUtils_Test, ResolvesContiguousSurfaceAttachmentFormatsIntoPipelineDesc) { TestResourceView color0( ResourceViewType::RenderTarget, diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index a5413f11..1772808b 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 @@ -28,7 +29,11 @@ struct MockPipelineState { int initializeCalls = 0; int shutdownCalls = 0; int renderCalls = 0; + int recordMainSceneCalls = 0; + int executeRecordedMainSceneCalls = 0; bool renderResult = true; + bool supportsMainSceneRenderGraph = false; + bool recordMainSceneResult = true; bool lastSurfaceAutoTransitionEnabled = true; uint32_t lastSurfaceWidth = 0; uint32_t lastSurfaceHeight = 0; @@ -51,6 +56,7 @@ struct MockPipelineState { RenderEnvironmentMode lastEnvironmentMode = RenderEnvironmentMode::None; bool lastHasSkybox = false; const XCEngine::Resources::Material* lastSkyboxMaterial = nullptr; + bool lastRecordedMainSceneShadowHandleValid = false; std::vector renderedCameras; std::vector renderedClearFlags; std::vector renderedClearColors; @@ -399,6 +405,107 @@ public: ++m_state->shutdownCalls; } + bool SupportsMainSceneRenderGraph() const override { + return m_state->supportsMainSceneRenderGraph; + } + + bool RecordMainSceneRenderGraph( + const RenderPipelineMainSceneRenderGraphContext& context) override { + ++m_state->recordMainSceneCalls; + m_state->lastRecordedMainSceneShadowHandleValid = + context.mainDirectionalShadowTexture.IsValid(); + if (!m_state->recordMainSceneResult) { + return false; + } + + const RenderSceneData* const sceneData = &context.sceneData; + RenderSurface surface = context.surfaceTemplate; + surface.SetAutoTransitionEnabled(false); + const std::vector colorTargets = context.colorTargets; + const RenderGraphTextureHandle depthTarget = context.depthTarget; + const RenderGraphTextureHandle mainDirectionalShadowTexture = + context.mainDirectionalShadowTexture; + bool* const executionSucceeded = context.executionSucceeded; + const std::shared_ptr state = m_state; + + context.graphBuilder.AddRasterPass( + context.passName, + [state, + surface, + sceneData, + colorTargets, + depthTarget, + mainDirectionalShadowTexture, + executionSucceeded]( + RenderGraphPassBuilder& passBuilder) { + for (RenderGraphTextureHandle colorTarget : colorTargets) { + if (colorTarget.IsValid()) { + passBuilder.WriteTexture(colorTarget); + } + } + if (depthTarget.IsValid()) { + passBuilder.WriteDepthTexture(depthTarget); + } + if (mainDirectionalShadowTexture.IsValid()) { + passBuilder.ReadTexture(mainDirectionalShadowTexture); + } + + passBuilder.SetExecuteCallback( + [state, + surface, + sceneData, + executionSucceeded]( + const RenderGraphExecutionContext&) { + if (executionSucceeded != nullptr && !(*executionSucceeded)) { + return; + } + + state->eventLog.push_back("pipelineGraph"); + ++state->executeRecordedMainSceneCalls; + state->lastSurfaceAutoTransitionEnabled = + surface.IsAutoTransitionEnabled(); + state->lastSurfaceWidth = surface.GetWidth(); + state->lastSurfaceHeight = surface.GetHeight(); + const XCEngine::Math::RectInt renderArea = surface.GetRenderArea(); + state->lastRenderAreaX = renderArea.x; + state->lastRenderAreaY = renderArea.y; + state->lastRenderAreaWidth = renderArea.width; + state->lastRenderAreaHeight = renderArea.height; + state->lastCamera = sceneData->camera; + state->lastCameraViewportWidth = sceneData->cameraData.viewportWidth; + state->lastCameraViewportHeight = sceneData->cameraData.viewportHeight; + state->lastVisibleItemCount = sceneData->visibleItems.size(); + state->lastClearFlags = sceneData->cameraData.clearFlags; + state->lastClearColor = sceneData->cameraData.clearColor; + state->lastHasMainDirectionalShadow = + sceneData->lighting.HasMainDirectionalShadow(); + state->lastShadowMap = sceneData->lighting.mainDirectionalShadow.shadowMap; + state->lastShadowViewProjection = + sceneData->lighting.mainDirectionalShadow.viewProjection; + state->lastShadowMapMetrics = + sceneData->lighting.mainDirectionalShadow.mapMetrics; + state->lastShadowSampling = + sceneData->lighting.mainDirectionalShadow.sampling; + state->lastHasMainDirectionalShadowKeyword = + XCEngine::Resources::ShaderKeywordSetContains( + sceneData->globalShaderKeywords, + "XC_MAIN_LIGHT_SHADOWS"); + state->lastEnvironmentMode = sceneData->environment.mode; + state->lastHasSkybox = sceneData->environment.HasSkybox(); + state->lastSkyboxMaterial = + sceneData->environment.materialSkybox.material; + state->renderedCameras.push_back(sceneData->camera); + state->renderedClearFlags.push_back(sceneData->cameraData.clearFlags); + state->renderedClearColors.push_back(sceneData->cameraData.clearColor); + if (executionSucceeded != nullptr) { + *executionSucceeded = state->renderResult; + } + }); + }); + + return true; + } + bool Render( const RenderContext&, const RenderSurface& surface, @@ -1452,6 +1559,37 @@ TEST(CameraRenderer_Test, UsesGraphManagedSurfaceForMainSceneWhenOutputAttachmen EXPECT_EQ(state->lastSurfaceHeight, 180u); } +TEST(CameraRenderer_Test, UsesPipelineRecordedMainSceneWhenSupported) { + Scene scene("CameraRendererPipelineRecordedMainScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(3.0f); + + auto state = std::make_shared(); + state->supportsMainSceneRenderGraph = true; + CameraRenderer renderer(std::make_unique(state)); + + CameraRenderRequest request; + request.scene = &scene; + request.camera = camera; + request.context = CreateValidContext(); + request.surface = RenderSurface(320, 180); + request.surface.SetColorAttachment(reinterpret_cast(1)); + request.surface.SetDepthAttachment(reinterpret_cast(2)); + request.cameraDepth = camera->GetDepth(); + + ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); + EXPECT_EQ(state->recordMainSceneCalls, 1); + EXPECT_EQ(state->executeRecordedMainSceneCalls, 1); + EXPECT_EQ(state->renderCalls, 0); + EXPECT_EQ(state->eventLog, (std::vector{ "pipelineGraph" })); + EXPECT_FALSE(state->lastSurfaceAutoTransitionEnabled); + EXPECT_EQ(state->lastSurfaceWidth, 320u); + EXPECT_EQ(state->lastSurfaceHeight, 180u); +} + TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) { Scene scene("CameraRendererAutoDirectionalShadowScene"); @@ -1540,6 +1678,59 @@ TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) { EXPECT_EQ(allocationState->destroyTextureCalls, 1); } +TEST(CameraRenderer_Test, PassesShadowDependencyToPipelineRecordedMainScene) { + Scene scene("CameraRendererPipelineRecordedShadowDependency"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(2.0f); + + auto pipelineState = std::make_shared(); + pipelineState->supportsMainSceneRenderGraph = true; + auto allocationState = std::make_shared(); + MockShadowDevice device(allocationState); + + RenderContext context = CreateValidContext(); + context.device = &device; + + CameraRenderer renderer( + std::make_unique(pipelineState), + std::make_unique(pipelineState)); + + auto shadowPass = std::make_unique(pipelineState, "shadowCaster"); + renderer.SetShadowCasterPass(std::move(shadowPass)); + + CameraRenderRequest request; + request.scene = &scene; + request.camera = camera; + request.context = context; + request.surface = RenderSurface(320, 180); + request.surface.SetColorAttachment(reinterpret_cast(1)); + request.surface.SetDepthAttachment(reinterpret_cast(2)); + 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); + request.directionalShadow.cameraData.viewProjection = + XCEngine::Math::Matrix4x4::Translation(XCEngine::Math::Vector3(11.0f, 12.0f, 13.0f)); + + ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); + EXPECT_EQ(pipelineState->recordMainSceneCalls, 1); + EXPECT_EQ(pipelineState->executeRecordedMainSceneCalls, 1); + EXPECT_TRUE(pipelineState->lastRecordedMainSceneShadowHandleValid); + EXPECT_EQ( + pipelineState->eventLog, + (std::vector{ + "init:shadowCaster", + "shadowCaster", + "pipelineGraph" })); +} + TEST(CameraRenderer_Test, ReusesDirectionalShadowSurfaceWhenPlanMatches) { Scene scene("CameraRendererDirectionalShadowReuseScene");