diff --git a/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h b/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h index 92fac902..d2837f3e 100644 --- a/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h +++ b/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h @@ -42,7 +42,8 @@ enum class RenderGraphTextureAspect : Core::uint8 { enum class RenderGraphTextureViewType : Core::uint8 { RenderTarget = 0, - ShaderResource = 1 + ShaderResource = 1, + DepthStencil = 2 }; struct RenderGraphTextureHandle { diff --git a/engine/src/Rendering/Graph/RenderGraphExecutor.cpp b/engine/src/Rendering/Graph/RenderGraphExecutor.cpp index 1f812b33..1baf2e09 100644 --- a/engine/src/Rendering/Graph/RenderGraphExecutor.cpp +++ b/engine/src/Rendering/Graph/RenderGraphExecutor.cpp @@ -8,15 +8,28 @@ namespace XCEngine { namespace Rendering { namespace { +bool IsDepthFormat(RHI::Format format) { + return format == RHI::Format::D16_UNorm || + format == RHI::Format::D24_UNorm_S8_UInt || + format == RHI::Format::D32_Float; +} + bool IsGraphManagedTransientState(RHI::ResourceStates state) { return state == RHI::ResourceStates::Common || state == RHI::ResourceStates::RenderTarget || + state == RHI::ResourceStates::DepthWrite || + state == RHI::ResourceStates::DepthRead || state == RHI::ResourceStates::PixelShaderResource || state == RHI::ResourceStates::GenericRead || state == RHI::ResourceStates::Present; } RenderGraphTextureViewType ResolveBarrierViewType(RHI::ResourceStates state) { + if (state == RHI::ResourceStates::DepthWrite || + state == RHI::ResourceStates::DepthRead) { + return RenderGraphTextureViewType::DepthStencil; + } + return state == RHI::ResourceStates::RenderTarget ? RenderGraphTextureViewType::RenderTarget : RenderGraphTextureViewType::ShaderResource; @@ -71,6 +84,11 @@ public: Reset(); return false; } + + if (m_textureAllocations[textureIndex].texture != nullptr) { + m_textureStates[textureIndex] = + m_textureAllocations[textureIndex].texture->GetState(); + } } return true; @@ -92,6 +110,8 @@ public: switch (viewType) { case RenderGraphTextureViewType::RenderTarget: return allocation.renderTargetView; + case RenderGraphTextureViewType::DepthStencil: + return allocation.depthStencilView; case RenderGraphTextureViewType::ShaderResource: return allocation.shaderResourceView; default: @@ -174,6 +194,7 @@ private: struct TextureAllocation { RHI::RHITexture* texture = nullptr; RHI::RHIResourceView* renderTargetView = nullptr; + RHI::RHIResourceView* depthStencilView = nullptr; RHI::RHIResourceView* shaderResourceView = nullptr; }; @@ -233,6 +254,12 @@ private: allocation.renderTargetView = nullptr; } + if (allocation.depthStencilView != nullptr) { + allocation.depthStencilView->Shutdown(); + delete allocation.depthStencilView; + allocation.depthStencilView = nullptr; + } + if (allocation.shaderResourceView != nullptr) { allocation.shaderResourceView->Shutdown(); delete allocation.shaderResourceView; @@ -273,18 +300,31 @@ private: viewDesc.dimension = RHI::ResourceViewDimension::Texture2D; viewDesc.mipLevel = 0u; - allocation.renderTargetView = - renderContext.device->CreateRenderTargetView(allocation.texture, viewDesc); - if (allocation.renderTargetView == nullptr) { - DestroyTextureAllocation(allocation); - return false; + const bool isDepthTexture = + IsDepthFormat(static_cast(texture.desc.format)); + if (isDepthTexture) { + allocation.depthStencilView = + renderContext.device->CreateDepthStencilView(allocation.texture, viewDesc); + if (allocation.depthStencilView == nullptr) { + DestroyTextureAllocation(allocation); + return false; + } + } else { + allocation.renderTargetView = + renderContext.device->CreateRenderTargetView(allocation.texture, viewDesc); + if (allocation.renderTargetView == nullptr) { + DestroyTextureAllocation(allocation); + return false; + } } - allocation.shaderResourceView = - renderContext.device->CreateShaderResourceView(allocation.texture, viewDesc); - if (allocation.shaderResourceView == nullptr) { - DestroyTextureAllocation(allocation); - return false; + if (!isDepthTexture) { + allocation.shaderResourceView = + renderContext.device->CreateShaderResourceView(allocation.texture, viewDesc); + if (allocation.shaderResourceView == nullptr) { + DestroyTextureAllocation(allocation); + return false; + } } return true; diff --git a/tests/Rendering/unit/test_render_graph.cpp b/tests/Rendering/unit/test_render_graph.cpp index 148eefe5..28e92a5b 100644 --- a/tests/Rendering/unit/test_render_graph.cpp +++ b/tests/Rendering/unit/test_render_graph.cpp @@ -22,6 +22,9 @@ struct MockTransientAllocationState { int createRenderTargetViewCalls = 0; int shutdownRenderTargetViewCalls = 0; int destroyRenderTargetViewCalls = 0; + int createDepthViewCalls = 0; + int shutdownDepthViewCalls = 0; + int destroyDepthViewCalls = 0; int createShaderViewCalls = 0; int shutdownShaderViewCalls = 0; int destroyShaderViewCalls = 0; @@ -83,6 +86,8 @@ public: ~MockTransientView() override { if (m_viewType == ResourceViewType::RenderTarget) { ++m_state->destroyRenderTargetViewCalls; + } else if (m_viewType == ResourceViewType::DepthStencil) { + ++m_state->destroyDepthViewCalls; } else { ++m_state->destroyShaderViewCalls; } @@ -91,6 +96,8 @@ public: void Shutdown() override { if (m_viewType == ResourceViewType::RenderTarget) { ++m_state->shutdownRenderTargetViewCalls; + } else if (m_viewType == ResourceViewType::DepthStencil) { + ++m_state->shutdownDepthViewCalls; } else { ++m_state->shutdownShaderViewCalls; } @@ -110,12 +117,23 @@ private: class MockImportedView final : public RHIResourceView { public: + explicit MockImportedView( + ResourceViewType viewType = ResourceViewType::RenderTarget, + Format format = Format::R8G8B8A8_UNorm) + : m_viewType(viewType) + , m_format(format) { + } + void Shutdown() override {} void* GetNativeHandle() override { return nullptr; } bool IsValid() const override { return true; } - ResourceViewType GetViewType() const override { return ResourceViewType::RenderTarget; } + ResourceViewType GetViewType() const override { return m_viewType; } ResourceViewDimension GetDimension() const override { return ResourceViewDimension::Texture2D; } - Format GetFormat() const override { return Format::R8G8B8A8_UNorm; } + Format GetFormat() const override { return m_format; } + +private: + ResourceViewType m_viewType = ResourceViewType::RenderTarget; + Format m_format = Format::R8G8B8A8_UNorm; }; class MockTransientDevice final : public RHIDevice { @@ -183,7 +201,15 @@ public: static_cast(desc.format)); } - RHIResourceView* CreateDepthStencilView(RHITexture*, const ResourceViewDesc&) override { return nullptr; } + RHIResourceView* CreateDepthStencilView( + RHITexture*, + const ResourceViewDesc& desc) override { + ++m_state->createDepthViewCalls; + return new MockTransientView( + m_state, + ResourceViewType::DepthStencil, + static_cast(desc.format)); + } RHIResourceView* CreateShaderResourceView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; } RHIResourceView* CreateShaderResourceView( @@ -574,6 +600,127 @@ TEST(RenderGraph_Test, TracksDepthAttachmentTransitionPlan) { EXPECT_EQ(transitionPlan.finalState, ResourceStates::DepthWrite); } +TEST(RenderGraph_Test, ExecutesTransientDepthTransitionsWithDepthStencilView) { + RenderGraph graph; + RenderGraphBuilder builder(graph); + + const RenderGraphTextureDesc depthDesc = BuildTestDepthTextureDesc(); + const RenderGraphTextureHandle depthTexture = + builder.CreateTransientTexture("SceneDepth", depthDesc); + + RHIResourceView* depthWriteView = nullptr; + RHIResourceView* depthReadView = nullptr; + builder.AddRasterPass( + "DepthPrepass", + [&](RenderGraphPassBuilder& pass) { + pass.WriteDepthTexture(depthTexture); + pass.SetExecuteCallback( + [&](const RenderGraphExecutionContext& executionContext) { + depthWriteView = + executionContext.ResolveTextureView( + depthTexture, + RenderGraphTextureViewType::DepthStencil); + EXPECT_NE(depthWriteView, nullptr); + }); + }); + + builder.AddRasterPass( + "DepthConsumer", + [&](RenderGraphPassBuilder& pass) { + pass.ReadDepthTexture(depthTexture); + pass.SetExecuteCallback( + [&](const RenderGraphExecutionContext& executionContext) { + depthReadView = + executionContext.ResolveTextureView( + depthTexture, + RenderGraphTextureViewType::DepthStencil); + EXPECT_EQ(depthReadView, depthWriteView); + }); + }); + + CompiledRenderGraph compiledGraph; + XCEngine::Containers::String errorMessage; + ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) + << errorMessage.CStr(); + + auto allocationState = std::make_shared(); + MockTransientDevice device(allocationState); + MockTransientCommandList commandList; + RenderContext renderContext = {}; + renderContext.device = &device; + renderContext.commandList = &commandList; + renderContext.commandQueue = reinterpret_cast(1); + ASSERT_TRUE(RenderGraphExecutor::Execute(compiledGraph, renderContext, &errorMessage)) + << errorMessage.CStr(); + + ASSERT_EQ(commandList.transitionCalls.size(), 2u); + EXPECT_EQ(commandList.transitionCalls[0].resource, depthWriteView); + EXPECT_EQ(commandList.transitionCalls[0].before, ResourceStates::Common); + EXPECT_EQ(commandList.transitionCalls[0].after, ResourceStates::DepthWrite); + EXPECT_EQ(commandList.transitionCalls[1].resource, depthReadView); + EXPECT_EQ(commandList.transitionCalls[1].before, ResourceStates::DepthWrite); + EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::DepthRead); + EXPECT_EQ(allocationState->createTextureCalls, 1); + EXPECT_EQ(allocationState->createRenderTargetViewCalls, 0); + EXPECT_EQ(allocationState->createDepthViewCalls, 1); + EXPECT_EQ(allocationState->createShaderViewCalls, 0); + EXPECT_EQ(allocationState->shutdownTextureCalls, 1); + EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 0); + EXPECT_EQ(allocationState->shutdownDepthViewCalls, 1); + EXPECT_EQ(allocationState->shutdownShaderViewCalls, 0); + EXPECT_EQ(allocationState->destroyTextureCalls, 1); + EXPECT_EQ(allocationState->destroyRenderTargetViewCalls, 0); + EXPECT_EQ(allocationState->destroyDepthViewCalls, 1); + EXPECT_EQ(allocationState->destroyShaderViewCalls, 0); +} + +TEST(RenderGraph_Test, ExecutesGraphOwnedImportedDepthTransitionsAtGraphBoundaries) { + RenderGraph graph; + RenderGraphBuilder builder(graph); + + const RenderGraphTextureDesc depthDesc = BuildTestDepthTextureDesc(); + RenderGraphImportedTextureOptions importedOptions = {}; + importedOptions.initialState = ResourceStates::DepthWrite; + importedOptions.finalState = ResourceStates::DepthWrite; + importedOptions.graphOwnsTransitions = true; + + MockImportedView importedDepthView( + ResourceViewType::DepthStencil, + Format::D24_UNorm_S8_UInt); + const RenderGraphTextureHandle importedDepth = builder.ImportTexture( + "ImportedDepth", + depthDesc, + &importedDepthView, + importedOptions); + + builder.AddRasterPass( + "ReadOnlyDepth", + [&](RenderGraphPassBuilder& pass) { + pass.ReadDepthTexture(importedDepth); + }); + + CompiledRenderGraph compiledGraph; + XCEngine::Containers::String errorMessage; + ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) + << errorMessage.CStr(); + + MockTransientCommandList commandList; + RenderContext renderContext = {}; + renderContext.device = reinterpret_cast(1); + renderContext.commandList = &commandList; + renderContext.commandQueue = reinterpret_cast(1); + ASSERT_TRUE(RenderGraphExecutor::Execute(compiledGraph, renderContext, &errorMessage)) + << errorMessage.CStr(); + + ASSERT_EQ(commandList.transitionCalls.size(), 2u); + EXPECT_EQ(commandList.transitionCalls[0].resource, &importedDepthView); + EXPECT_EQ(commandList.transitionCalls[0].before, ResourceStates::DepthWrite); + EXPECT_EQ(commandList.transitionCalls[0].after, ResourceStates::DepthRead); + EXPECT_EQ(commandList.transitionCalls[1].resource, &importedDepthView); + EXPECT_EQ(commandList.transitionCalls[1].before, ResourceStates::DepthRead); + EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::DepthWrite); +} + TEST(RenderGraph_Test, ExecutesCompiledPassCallbacksInCompiledOrder) { RenderGraph graph; RenderGraphBuilder builder(graph);