diff --git a/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h b/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h index d2837f3e..dfe446a3 100644 --- a/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h +++ b/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h @@ -43,7 +43,8 @@ enum class RenderGraphTextureAspect : Core::uint8 { enum class RenderGraphTextureViewType : Core::uint8 { RenderTarget = 0, ShaderResource = 1, - DepthStencil = 2 + DepthStencil = 2, + UnorderedAccess = 3 }; struct RenderGraphTextureHandle { diff --git a/engine/src/Rendering/Graph/RenderGraphExecutor.cpp b/engine/src/Rendering/Graph/RenderGraphExecutor.cpp index 1baf2e09..dbe6145c 100644 --- a/engine/src/Rendering/Graph/RenderGraphExecutor.cpp +++ b/engine/src/Rendering/Graph/RenderGraphExecutor.cpp @@ -17,6 +17,7 @@ bool IsDepthFormat(RHI::Format format) { bool IsGraphManagedTransientState(RHI::ResourceStates state) { return state == RHI::ResourceStates::Common || state == RHI::ResourceStates::RenderTarget || + state == RHI::ResourceStates::UnorderedAccess || state == RHI::ResourceStates::DepthWrite || state == RHI::ResourceStates::DepthRead || state == RHI::ResourceStates::PixelShaderResource || @@ -30,6 +31,10 @@ RenderGraphTextureViewType ResolveBarrierViewType(RHI::ResourceStates state) { return RenderGraphTextureViewType::DepthStencil; } + if (state == RHI::ResourceStates::UnorderedAccess) { + return RenderGraphTextureViewType::UnorderedAccess; + } + return state == RHI::ResourceStates::RenderTarget ? RenderGraphTextureViewType::RenderTarget : RenderGraphTextureViewType::ShaderResource; @@ -75,7 +80,11 @@ public: return false; } - if (!CreateTransientTexture(renderContext, texture, m_textureAllocations[textureIndex])) { + if (!CreateTransientTexture( + renderContext, + static_cast(textureIndex), + texture, + m_textureAllocations[textureIndex])) { if (outErrorMessage != nullptr) { *outErrorMessage = Containers::String("RenderGraph failed to allocate transient texture: ") + @@ -114,6 +123,8 @@ public: return allocation.depthStencilView; case RenderGraphTextureViewType::ShaderResource: return allocation.shaderResourceView; + case RenderGraphTextureViewType::UnorderedAccess: + return allocation.unorderedAccessView; default: return nullptr; } @@ -196,6 +207,7 @@ private: RHI::RHIResourceView* renderTargetView = nullptr; RHI::RHIResourceView* depthStencilView = nullptr; RHI::RHIResourceView* shaderResourceView = nullptr; + RHI::RHIResourceView* unorderedAccessView = nullptr; }; bool ShouldGraphManageTransitions(RenderGraphTextureHandle handle) const { @@ -266,6 +278,12 @@ private: allocation.shaderResourceView = nullptr; } + if (allocation.unorderedAccessView != nullptr) { + allocation.unorderedAccessView->Shutdown(); + delete allocation.unorderedAccessView; + allocation.unorderedAccessView = nullptr; + } + if (allocation.texture != nullptr) { allocation.texture->Shutdown(); delete allocation.texture; @@ -273,8 +291,23 @@ private: } } + bool TextureRequiresUnorderedAccess(Core::uint32 textureIndex) const { + for (const CompiledRenderGraph::CompiledPass& pass : m_graph.m_passes) { + for (const CompiledRenderGraph::CompiledTextureAccess& access : pass.accesses) { + if (access.texture.IsValid() && + access.texture.index == textureIndex && + access.requiredState == RHI::ResourceStates::UnorderedAccess) { + return true; + } + } + } + + return false; + } + bool CreateTransientTexture( const RenderContext& renderContext, + Core::uint32 textureIndex, const CompiledRenderGraph::CompiledTexture& texture, TextureAllocation& allocation) { RHI::TextureDesc textureDesc = {}; @@ -302,6 +335,8 @@ private: const bool isDepthTexture = IsDepthFormat(static_cast(texture.desc.format)); + const bool requiresUnorderedAccess = + !isDepthTexture && TextureRequiresUnorderedAccess(textureIndex); if (isDepthTexture) { allocation.depthStencilView = renderContext.device->CreateDepthStencilView(allocation.texture, viewDesc); @@ -325,6 +360,15 @@ private: DestroyTextureAllocation(allocation); return false; } + + if (requiresUnorderedAccess) { + allocation.unorderedAccessView = + renderContext.device->CreateUnorderedAccessView(allocation.texture, viewDesc); + if (allocation.unorderedAccessView == 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 28e92a5b..73c5c866 100644 --- a/tests/Rendering/unit/test_render_graph.cpp +++ b/tests/Rendering/unit/test_render_graph.cpp @@ -28,6 +28,9 @@ struct MockTransientAllocationState { int createShaderViewCalls = 0; int shutdownShaderViewCalls = 0; int destroyShaderViewCalls = 0; + int createUnorderedAccessViewCalls = 0; + int shutdownUnorderedAccessViewCalls = 0; + int destroyUnorderedAccessViewCalls = 0; }; class MockTransientTexture final : public RHITexture { @@ -88,6 +91,8 @@ public: ++m_state->destroyRenderTargetViewCalls; } else if (m_viewType == ResourceViewType::DepthStencil) { ++m_state->destroyDepthViewCalls; + } else if (m_viewType == ResourceViewType::UnorderedAccess) { + ++m_state->destroyUnorderedAccessViewCalls; } else { ++m_state->destroyShaderViewCalls; } @@ -98,6 +103,8 @@ public: ++m_state->shutdownRenderTargetViewCalls; } else if (m_viewType == ResourceViewType::DepthStencil) { ++m_state->shutdownDepthViewCalls; + } else if (m_viewType == ResourceViewType::UnorderedAccess) { + ++m_state->shutdownUnorderedAccessViewCalls; } else { ++m_state->shutdownShaderViewCalls; } @@ -223,8 +230,14 @@ public: } RHIResourceView* CreateUnorderedAccessView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; } - RHIResourceView* CreateUnorderedAccessView(RHITexture*, const ResourceViewDesc&) override { - return nullptr; + RHIResourceView* CreateUnorderedAccessView( + RHITexture*, + const ResourceViewDesc& desc) override { + ++m_state->createUnorderedAccessViewCalls; + return new MockTransientView( + m_state, + ResourceViewType::UnorderedAccess, + static_cast(desc.format)); } const RHICapabilities& GetCapabilities() const override { return m_capabilities; } @@ -721,6 +734,130 @@ TEST(RenderGraph_Test, ExecutesGraphOwnedImportedDepthTransitionsAtGraphBoundari EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::DepthWrite); } +TEST(RenderGraph_Test, ExecutesTransientComputeTransitionsWithUnorderedAccessView) { + RenderGraph graph; + RenderGraphBuilder builder(graph); + + const RenderGraphTextureDesc desc = BuildTestTextureDesc(); + const RenderGraphTextureHandle sceneColor = builder.CreateTransientTexture("SceneColor", desc); + + RHIResourceView* computeWriteView = nullptr; + RHIResourceView* computeReadView = nullptr; + builder.AddComputePass( + "Generate", + [&](RenderGraphPassBuilder& pass) { + pass.WriteTexture(sceneColor); + pass.SetExecuteCallback( + [&](const RenderGraphExecutionContext& executionContext) { + computeWriteView = + executionContext.ResolveTextureView( + sceneColor, + RenderGraphTextureViewType::UnorderedAccess); + EXPECT_NE(computeWriteView, nullptr); + }); + }); + + builder.AddComputePass( + "Consume", + [&](RenderGraphPassBuilder& pass) { + pass.ReadTexture(sceneColor); + pass.SetExecuteCallback( + [&](const RenderGraphExecutionContext& executionContext) { + computeReadView = + executionContext.ResolveTextureView( + sceneColor, + RenderGraphTextureViewType::ShaderResource); + EXPECT_NE(computeReadView, nullptr); + EXPECT_NE(computeReadView, computeWriteView); + }); + }); + + 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, computeWriteView); + EXPECT_EQ(commandList.transitionCalls[0].before, ResourceStates::Common); + EXPECT_EQ(commandList.transitionCalls[0].after, ResourceStates::UnorderedAccess); + EXPECT_EQ(commandList.transitionCalls[1].resource, computeReadView); + EXPECT_EQ(commandList.transitionCalls[1].before, ResourceStates::UnorderedAccess); + EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::GenericRead); + EXPECT_EQ(allocationState->createTextureCalls, 1); + EXPECT_EQ(allocationState->createRenderTargetViewCalls, 1); + EXPECT_EQ(allocationState->createDepthViewCalls, 0); + EXPECT_EQ(allocationState->createShaderViewCalls, 1); + EXPECT_EQ(allocationState->createUnorderedAccessViewCalls, 1); + EXPECT_EQ(allocationState->shutdownTextureCalls, 1); + EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 1); + EXPECT_EQ(allocationState->shutdownDepthViewCalls, 0); + EXPECT_EQ(allocationState->shutdownShaderViewCalls, 1); + EXPECT_EQ(allocationState->shutdownUnorderedAccessViewCalls, 1); + EXPECT_EQ(allocationState->destroyTextureCalls, 1); + EXPECT_EQ(allocationState->destroyRenderTargetViewCalls, 1); + EXPECT_EQ(allocationState->destroyDepthViewCalls, 0); + EXPECT_EQ(allocationState->destroyShaderViewCalls, 1); + EXPECT_EQ(allocationState->destroyUnorderedAccessViewCalls, 1); +} + +TEST(RenderGraph_Test, ExecutesGraphOwnedImportedUnorderedAccessTransitionsAtGraphBoundaries) { + RenderGraph graph; + RenderGraphBuilder builder(graph); + + const RenderGraphTextureDesc desc = BuildTestTextureDesc(); + RenderGraphImportedTextureOptions importedOptions = {}; + importedOptions.initialState = ResourceStates::GenericRead; + importedOptions.finalState = ResourceStates::GenericRead; + importedOptions.graphOwnsTransitions = true; + + MockImportedView importedView( + ResourceViewType::UnorderedAccess, + Format::R8G8B8A8_UNorm); + const RenderGraphTextureHandle importedTexture = builder.ImportTexture( + "ImportedComputeTarget", + desc, + &importedView, + importedOptions); + + builder.AddComputePass( + "WriteImported", + [&](RenderGraphPassBuilder& pass) { + pass.WriteTexture(importedTexture); + }); + + 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, &importedView); + EXPECT_EQ(commandList.transitionCalls[0].before, ResourceStates::GenericRead); + EXPECT_EQ(commandList.transitionCalls[0].after, ResourceStates::UnorderedAccess); + EXPECT_EQ(commandList.transitionCalls[1].resource, &importedView); + EXPECT_EQ(commandList.transitionCalls[1].before, ResourceStates::UnorderedAccess); + EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::GenericRead); +} + TEST(RenderGraph_Test, ExecutesCompiledPassCallbacksInCompiledOrder) { RenderGraph graph; RenderGraphBuilder builder(graph);