diff --git a/engine/include/XCEngine/Rendering/Graph/RenderGraphCompiler.h b/engine/include/XCEngine/Rendering/Graph/RenderGraphCompiler.h index f07494f4..cfa49327 100644 --- a/engine/include/XCEngine/Rendering/Graph/RenderGraphCompiler.h +++ b/engine/include/XCEngine/Rendering/Graph/RenderGraphCompiler.h @@ -23,6 +23,9 @@ public: bool TryGetImportedTextureOptions( RenderGraphTextureHandle handle, RenderGraphImportedTextureOptions& outOptions) const; + bool TryGetTextureTransitionPlan( + RenderGraphTextureHandle handle, + RenderGraphTextureTransitionPlan& outPlan) const; private: struct CompiledTextureAccess { @@ -50,6 +53,7 @@ private: std::vector m_passes; std::vector m_textures; std::vector m_textureLifetimes; + std::vector m_textureTransitionPlans; friend class RenderGraphCompiler; friend class RenderGraphExecutor; diff --git a/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h b/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h index 53052d7c..a14ad2fa 100644 --- a/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h +++ b/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h @@ -85,6 +85,16 @@ struct RenderGraphImportedTextureOptions { bool graphOwnsTransitions = false; }; +struct RenderGraphTextureTransitionPlan { + bool graphOwnsTransitions = false; + bool hasFirstAccessState = false; + bool hasLastAccessState = false; + RHI::ResourceStates initialState = RHI::ResourceStates::Common; + RHI::ResourceStates firstAccessState = RHI::ResourceStates::Common; + RHI::ResourceStates lastAccessState = RHI::ResourceStates::Common; + RHI::ResourceStates finalState = RHI::ResourceStates::Common; +}; + struct RenderGraphExecutionContext { const RenderContext& renderContext; const RenderGraphRuntimeResources* runtimeResources = nullptr; diff --git a/engine/src/Rendering/Graph/RenderGraphCompiler.cpp b/engine/src/Rendering/Graph/RenderGraphCompiler.cpp index cc7c6af0..be80a1ae 100644 --- a/engine/src/Rendering/Graph/RenderGraphCompiler.cpp +++ b/engine/src/Rendering/Graph/RenderGraphCompiler.cpp @@ -36,6 +36,7 @@ void CompiledRenderGraph::Reset() { m_passes.clear(); m_textures.clear(); m_textureLifetimes.clear(); + m_textureTransitionPlans.clear(); } const Containers::String& CompiledRenderGraph::GetPassName(size_t index) const { @@ -76,6 +77,17 @@ bool CompiledRenderGraph::TryGetImportedTextureOptions( return true; } +bool CompiledRenderGraph::TryGetTextureTransitionPlan( + RenderGraphTextureHandle handle, + RenderGraphTextureTransitionPlan& outPlan) const { + if (!handle.IsValid() || handle.index >= m_textureTransitionPlans.size()) { + return false; + } + + outPlan = m_textureTransitionPlans[handle.index]; + return true; +} + bool RenderGraphCompiler::Compile( const RenderGraph& graph, CompiledRenderGraph& outCompiledGraph, @@ -89,6 +101,16 @@ bool RenderGraphCompiler::Compile( outErrorMessage); return false; } + + if (texture.kind == RenderGraphTextureKind::Imported && + texture.importedOptions.graphOwnsTransitions && + texture.importedView == nullptr) { + WriteError( + Containers::String("RenderGraph imported texture requires a valid view when graph owns transitions: ") + + texture.name, + outErrorMessage); + return false; + } } const size_t passCount = graph.m_passes.size(); @@ -197,6 +219,7 @@ bool RenderGraphCompiler::Compile( } outCompiledGraph.m_textureLifetimes.resize(textureCount); + outCompiledGraph.m_textureTransitionPlans.resize(textureCount); for (size_t textureIndex = 0u; textureIndex < textureCount; ++textureIndex) { CompiledRenderGraph::CompiledTexture compiledTexture = {}; compiledTexture.name = graph.m_textures[textureIndex].name; @@ -207,6 +230,19 @@ bool RenderGraphCompiler::Compile( outCompiledGraph.m_textures.push_back(std::move(compiledTexture)); outCompiledGraph.m_textureLifetimes[textureIndex].kind = graph.m_textures[textureIndex].kind; + RenderGraphTextureTransitionPlan& transitionPlan = + outCompiledGraph.m_textureTransitionPlans[textureIndex]; + transitionPlan.graphOwnsTransitions = + graph.m_textures[textureIndex].kind == RenderGraphTextureKind::Transient || + graph.m_textures[textureIndex].importedOptions.graphOwnsTransitions; + transitionPlan.initialState = + graph.m_textures[textureIndex].kind == RenderGraphTextureKind::Imported + ? graph.m_textures[textureIndex].importedOptions.initialState + : RHI::ResourceStates::Common; + transitionPlan.finalState = + graph.m_textures[textureIndex].kind == RenderGraphTextureKind::Imported + ? graph.m_textures[textureIndex].importedOptions.finalState + : RHI::ResourceStates::Common; } outCompiledGraph.m_passes.reserve(passCount); @@ -235,11 +271,25 @@ bool RenderGraphCompiler::Compile( for (const RenderGraph::TextureAccess& access : sourcePass.accesses) { RenderGraphTextureLifetime& lifetime = outCompiledGraph.m_textureLifetimes[access.texture.index]; + RenderGraphTextureTransitionPlan& transitionPlan = + outCompiledGraph.m_textureTransitionPlans[access.texture.index]; if (!lifetime.used) { lifetime.used = true; lifetime.firstPassIndex = compiledPassIndex; } lifetime.lastPassIndex = compiledPassIndex; + + const RHI::ResourceStates accessState = + ResolveRequiredState(sourcePass.type, access.mode); + if (!transitionPlan.hasFirstAccessState) { + transitionPlan.hasFirstAccessState = true; + transitionPlan.firstAccessState = accessState; + } + transitionPlan.hasLastAccessState = true; + transitionPlan.lastAccessState = accessState; + if (graph.m_textures[access.texture.index].kind == RenderGraphTextureKind::Transient) { + transitionPlan.finalState = accessState; + } } } diff --git a/tests/Rendering/unit/test_render_graph.cpp b/tests/Rendering/unit/test_render_graph.cpp index 4eb00a56..41b12c1d 100644 --- a/tests/Rendering/unit/test_render_graph.cpp +++ b/tests/Rendering/unit/test_render_graph.cpp @@ -429,6 +429,16 @@ TEST(RenderGraph_Test, PreservesImportedTextureStateContractAcrossCompile) { EXPECT_EQ(resolvedOptions.initialState, ResourceStates::Present); EXPECT_EQ(resolvedOptions.finalState, ResourceStates::PixelShaderResource); EXPECT_TRUE(resolvedOptions.graphOwnsTransitions); + + RenderGraphTextureTransitionPlan transitionPlan = {}; + ASSERT_TRUE(compiledGraph.TryGetTextureTransitionPlan(importedTexture, transitionPlan)); + EXPECT_TRUE(transitionPlan.graphOwnsTransitions); + EXPECT_TRUE(transitionPlan.hasFirstAccessState); + EXPECT_TRUE(transitionPlan.hasLastAccessState); + EXPECT_EQ(transitionPlan.initialState, ResourceStates::Present); + EXPECT_EQ(transitionPlan.firstAccessState, ResourceStates::PixelShaderResource); + EXPECT_EQ(transitionPlan.lastAccessState, ResourceStates::PixelShaderResource); + EXPECT_EQ(transitionPlan.finalState, ResourceStates::PixelShaderResource); } TEST(RenderGraph_Test, ExecutesGraphOwnedImportedTextureTransitionsAtGraphBoundaries) { @@ -495,6 +505,34 @@ TEST(RenderGraph_Test, RejectsTransientTextureReadBeforeWrite) { EXPECT_FALSE(errorMessage.Empty()); } +TEST(RenderGraph_Test, RejectsGraphOwnedImportedTextureWithoutView) { + RenderGraph graph; + RenderGraphBuilder builder(graph); + + const RenderGraphTextureDesc desc = BuildTestTextureDesc(); + RenderGraphImportedTextureOptions importedOptions = {}; + importedOptions.initialState = ResourceStates::Present; + importedOptions.finalState = ResourceStates::Present; + importedOptions.graphOwnsTransitions = true; + + const RenderGraphTextureHandle importedTexture = builder.ImportTexture( + "BackBuffer", + desc, + nullptr, + importedOptions); + + builder.AddRasterPass( + "FinalBlit", + [&](RenderGraphPassBuilder& pass) { + pass.WriteTexture(importedTexture); + }); + + CompiledRenderGraph compiledGraph; + XCEngine::Containers::String errorMessage; + EXPECT_FALSE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)); + EXPECT_FALSE(errorMessage.Empty()); +} + TEST(RenderGraph_Test, ExecutesCompiledPassCallbacksInCompiledOrder) { RenderGraph graph; RenderGraphBuilder builder(graph);