diff --git a/engine/include/XCEngine/Rendering/AGENTS.md b/engine/include/XCEngine/Rendering/AGENTS.md index 36ba042a..ece2b098 100644 --- a/engine/include/XCEngine/Rendering/AGENTS.md +++ b/engine/include/XCEngine/Rendering/AGENTS.md @@ -143,6 +143,7 @@ native RenderGraph 当前支持: - 执行阶段通过 `RenderGraphExecutionContext` 解析 view 和 texture desc。 - Graph-managed surface 必须关闭 `RenderSurface::autoTransition`,让 graph 统一负责 transition。 - imported surface 的 transition ownership 只能通过 `RenderGraphImportedTextureOptions` 表达,不要在 pass 内外重复 barrier。 +- imported texture 的资源身份是 `RHITexture*`,不是某个 primary view。需要 graph 管理 transition 时,必须通过 `RenderGraphImportedTextureDesc.texture` 显式给出资源;`primaryView` 只能作为默认访问 view。surface import registry 应优先按 texture 合并,只有拿不到 texture 的非 graph-owned legacy/view-only import 才能退回按 view 区分。 - fullscreen chain 中间输出优先使用 transient texture,不要把后处理链硬绑到 backbuffer。 ## 6. Builtin Forward diff --git a/engine/include/XCEngine/Rendering/Graph/RenderGraph.h b/engine/include/XCEngine/Rendering/Graph/RenderGraph.h index 11f7d1d7..356cb650 100644 --- a/engine/include/XCEngine/Rendering/Graph/RenderGraph.h +++ b/engine/include/XCEngine/Rendering/Graph/RenderGraph.h @@ -83,6 +83,10 @@ public: void Reset(); + RenderGraphTextureHandle ImportTexture( + const Containers::String& name, + const RenderGraphImportedTextureDesc& importedDesc); + RenderGraphTextureHandle ImportTexture( const Containers::String& name, const RenderGraphTextureDesc& desc, diff --git a/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h b/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h index 3c1ab073..f790954e 100644 --- a/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h +++ b/engine/include/XCEngine/Rendering/Graph/RenderGraphTypes.h @@ -92,6 +92,13 @@ struct RenderGraphImportedTextureOptions { bool graphOwnsTransitions = false; }; +struct RenderGraphImportedTextureDesc { + RenderGraphTextureDesc textureDesc = {}; + RHI::RHITexture* texture = nullptr; + RHI::RHIResourceView* primaryView = nullptr; + RenderGraphImportedTextureOptions options = {}; +}; + struct RenderGraphTextureTransitionPlan { bool graphOwnsTransitions = false; bool hasFirstAccessState = false; diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.cpp b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.cpp index ea770190..6838e8cc 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.cpp +++ b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.cpp @@ -77,15 +77,25 @@ RenderGraphTextureHandle ImportRenderGraphTexture( return {}; } - const auto existing = registry.find(view); + RHI::RHITexture* const texture = view->GetTextureResource(); + RenderGraphImportedTextureRegistryKey registryKey = {}; + registryKey.texture = texture; + registryKey.view = texture == nullptr ? view : nullptr; + + const auto existing = registry.find(registryKey); if (existing != registry.end()) { builder.MergeImportedTextureOptions(existing->second, importedOptions); return existing->second; } + RenderGraphImportedTextureDesc importedDesc = {}; + importedDesc.textureDesc = desc; + importedDesc.texture = texture; + importedDesc.primaryView = view; + importedDesc.options = importedOptions; const RenderGraphTextureHandle handle = - builder.ImportTexture(name, desc, view, importedOptions); - registry.emplace(view, handle); + builder.ImportTexture(name, importedDesc); + registry.emplace(registryKey, handle); return handle; } diff --git a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.h b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.h index 134ab28b..443270d7 100644 --- a/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.h +++ b/engine/src/Rendering/Execution/Internal/CameraFrameGraph/SurfaceUtils.h @@ -5,11 +5,13 @@ #include #include +#include #include #include namespace XCEngine { namespace RHI { +class RHITexture; class RHIResourceView; } // namespace RHI @@ -30,8 +32,27 @@ enum class RenderGraphSurfaceAccessMode { ColorDepth = 1 }; +struct RenderGraphImportedTextureRegistryKey { + RHI::RHITexture* texture = nullptr; + RHI::RHIResourceView* view = nullptr; + + bool operator==(const RenderGraphImportedTextureRegistryKey& other) const { + return texture == other.texture && view == other.view; + } +}; + +struct RenderGraphImportedTextureRegistryKeyHash { + size_t operator()(const RenderGraphImportedTextureRegistryKey& key) const { + return std::hash{}(key.texture) ^ + (std::hash{}(key.view) << 1u); + } +}; + using RenderGraphImportedTextureRegistry = - std::unordered_map; + std::unordered_map< + RenderGraphImportedTextureRegistryKey, + RenderGraphTextureHandle, + RenderGraphImportedTextureRegistryKeyHash>; enum class RenderGraphSurfaceImportUsage { Source = 0, diff --git a/engine/src/Rendering/Graph/RenderGraph.cpp b/engine/src/Rendering/Graph/RenderGraph.cpp index 7c64b895..a76089f2 100644 --- a/engine/src/Rendering/Graph/RenderGraph.cpp +++ b/engine/src/Rendering/Graph/RenderGraph.cpp @@ -79,17 +79,14 @@ void RenderGraphBuilder::Reset() { RenderGraphTextureHandle RenderGraphBuilder::ImportTexture( const Containers::String& name, - const RenderGraphTextureDesc& desc, - RHI::RHIResourceView* importedView, - const RenderGraphImportedTextureOptions& importedOptions) { + const RenderGraphImportedTextureDesc& importedDesc) { RenderGraph::TextureResource resource = {}; resource.name = name; - resource.desc = desc; + resource.desc = importedDesc.textureDesc; resource.kind = RenderGraphTextureKind::Imported; - resource.importedView = importedView; - resource.importedTexture = - importedView != nullptr ? importedView->GetTextureResource() : nullptr; - resource.importedOptions = importedOptions; + resource.importedView = importedDesc.primaryView; + resource.importedTexture = importedDesc.texture; + resource.importedOptions = importedDesc.options; m_graph.m_textures.push_back(resource); RenderGraphTextureHandle handle = {}; @@ -97,6 +94,20 @@ RenderGraphTextureHandle RenderGraphBuilder::ImportTexture( return handle; } +RenderGraphTextureHandle RenderGraphBuilder::ImportTexture( + const Containers::String& name, + const RenderGraphTextureDesc& desc, + RHI::RHIResourceView* importedView, + const RenderGraphImportedTextureOptions& importedOptions) { + RenderGraphImportedTextureDesc importedDesc = {}; + importedDesc.textureDesc = desc; + importedDesc.texture = + importedView != nullptr ? importedView->GetTextureResource() : nullptr; + importedDesc.primaryView = importedView; + importedDesc.options = importedOptions; + return ImportTexture(name, importedDesc); +} + RenderGraphTextureHandle RenderGraphBuilder::CreateTransientTexture( const Containers::String& name, const RenderGraphTextureDesc& desc) { diff --git a/engine/src/Rendering/Graph/RenderGraphCompiler.cpp b/engine/src/Rendering/Graph/RenderGraphCompiler.cpp index 4df32bdf..0376407b 100644 --- a/engine/src/Rendering/Graph/RenderGraphCompiler.cpp +++ b/engine/src/Rendering/Graph/RenderGraphCompiler.cpp @@ -117,7 +117,7 @@ bool RenderGraphCompiler::Compile( texture.importedOptions.graphOwnsTransitions && texture.importedTexture == nullptr) { WriteError( - Containers::String("RenderGraph imported texture requires a texture-backed view when graph owns transitions: ") + + Containers::String("RenderGraph graph-owned imported texture requires an explicit RHI texture resource: ") + texture.name, outErrorMessage); return false; diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index d048576e..dd90e18f 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -1,6 +1,7 @@ #include #include "Rendering/Internal/ShaderVariantUtils.h" +#include "Rendering/Pipelines/Internal/BuiltinForwardSceneSetup.h" #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include #define private public #include @@ -102,6 +104,35 @@ void AppendDefaultBuiltinPassResources(ShaderPass& pass) { } } +class TestResourceViewBackingTexture final : public XCEngine::RHI::RHITexture { +public: + explicit TestResourceViewBackingTexture( + XCEngine::RHI::Format format, + XCEngine::RHI::TextureType textureType = XCEngine::RHI::TextureType::Texture2D) + : m_format(format) + , m_textureType(textureType) { + } + + uint32_t GetWidth() const override { return 1u; } + uint32_t GetHeight() const override { return 1u; } + uint32_t GetDepth() const override { return 1u; } + uint32_t GetMipLevels() const override { return 1u; } + 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: + 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 TestResourceView final : public XCEngine::RHI::RHIResourceView { public: TestResourceView( @@ -136,10 +167,16 @@ public: return m_format; } + XCEngine::RHI::RHITexture* GetTextureResource() const override { + return m_texture.get(); + } + private: XCEngine::RHI::ResourceViewType m_viewType; XCEngine::RHI::ResourceViewDimension m_dimension; XCEngine::RHI::Format m_format; + std::unique_ptr m_texture = + std::make_unique(m_format); }; class MockForwardPipelineLayout final : public XCEngine::RHI::RHIPipelineLayout { @@ -1188,6 +1225,8 @@ TEST(SceneRenderFeaturePass_Test, ReadsSourceColorTextureAndCopiesSourceSurfaceT TEST(BuiltinForwardPipeline_Test, RegistersBuiltinDefaultForwardSceneFeatures) { BuiltinForwardPipeline pipeline; + XCEngine::Rendering::Pipelines::Internal::ConfigureBuiltinForwardSceneDrawBackend( + pipeline); ASSERT_EQ(pipeline.GetForwardSceneFeaturePassCount(), 2u); ASSERT_NE(pipeline.GetForwardSceneFeaturePass(0u), nullptr); @@ -1414,6 +1453,8 @@ TEST(BuiltinForwardPipeline_Test, RegistersAdditionalForwardSceneFeaturePasses) TEST(BuiltinForwardPipeline_Test, RecordsActiveFeatureInjectionPassesIntoMainSceneGraph) { BuiltinForwardPipeline pipeline; + XCEngine::Rendering::Pipelines::Internal::ConfigureBuiltinForwardSceneDrawBackend( + pipeline); RenderGraph graph = {}; RenderGraphBuilder graphBuilder(graph); diff --git a/tests/Rendering/unit/test_camera_frame_graph_stage_contract.cpp b/tests/Rendering/unit/test_camera_frame_graph_stage_contract.cpp index e7637fc5..e9a41701 100644 --- a/tests/Rendering/unit/test_camera_frame_graph_stage_contract.cpp +++ b/tests/Rendering/unit/test_camera_frame_graph_stage_contract.cpp @@ -188,11 +188,11 @@ RenderGraphTextureDesc BuildTestRenderGraphTextureDesc() { RenderGraphTextureHandle ImportTestTexture( RenderGraphBuilder& graphBuilder, size_t viewId) { - return graphBuilder.ImportTexture( - "Imported", - BuildTestRenderGraphTextureDesc(), - reinterpret_cast(viewId), - {}); + RenderGraphImportedTextureDesc importedDesc = {}; + importedDesc.textureDesc = BuildTestRenderGraphTextureDesc(); + importedDesc.primaryView = + reinterpret_cast(viewId); + return graphBuilder.ImportTexture("Imported", importedDesc); } RenderSurface CreateColorSurface( @@ -483,6 +483,7 @@ TEST(CameraFrameRenderGraphStageContract_Test, ResolvesScenePassRequestsFromRunt testContext.plan.request.depthOnly.surface = RenderSurface(320, 180); testContext.plan.request.depthOnly.surface.SetDepthAttachment( reinterpret_cast(142)); + testContext.plan.RequestDepthOnlyStage(true); const CameraFrameRenderGraphStageContext context = testContext.BuildStageContext(); @@ -1534,6 +1535,7 @@ TEST(CameraFrameRenderGraphStageContract_Test, ExecutesDepthOnlyFallbackPassUsin testContext.plan.request.depthOnly.surface.SetDepthAttachment( reinterpret_cast(501)); testContext.plan.request.depthOnly.clearFlags = RenderClearFlags::Color; + testContext.plan.RequestDepthOnlyStage(true); testContext.sceneData.cameraData.viewportWidth = 111u; CameraFrameStageGraphBuildState stageState = {}; diff --git a/tests/Rendering/unit/test_render_graph.cpp b/tests/Rendering/unit/test_render_graph.cpp index 73c5c866..8d278eb7 100644 --- a/tests/Rendering/unit/test_render_graph.cpp +++ b/tests/Rendering/unit/test_render_graph.cpp @@ -122,13 +122,50 @@ private: Format m_format = Format::Unknown; }; +class MockImportedTexture final : public RHITexture { +public: + explicit MockImportedTexture( + Format format = Format::R8G8B8A8_UNorm, + uint32_t width = 1280u, + uint32_t height = 720u) + : m_format(format) + , m_width(width) + , m_height(height) { + } + + uint32_t GetWidth() const override { return m_width; } + uint32_t GetHeight() const override { return m_height; } + uint32_t GetDepth() const override { return 1u; } + uint32_t GetMipLevels() const override { return 1u; } + Format GetFormat() const override { return m_format; } + TextureType GetTextureType() const override { return TextureType::Texture2D; } + ResourceStates GetState() const override { return m_stateValue; } + void SetState(ResourceStates state) override { m_stateValue = 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: + Format m_format = Format::Unknown; + uint32_t m_width = 0u; + uint32_t m_height = 0u; + ResourceStates m_stateValue = ResourceStates::Common; + std::string m_name; +}; + class MockImportedView final : public RHIResourceView { public: explicit MockImportedView( ResourceViewType viewType = ResourceViewType::RenderTarget, - Format format = Format::R8G8B8A8_UNorm) + Format format = Format::R8G8B8A8_UNorm, + RHITexture* texture = nullptr) : m_viewType(viewType) - , m_format(format) { + , m_format(format) + , m_ownedTexture(texture == nullptr + ? std::make_unique(format) + : nullptr) + , m_texture(texture != nullptr ? texture : m_ownedTexture.get()) { } void Shutdown() override {} @@ -137,10 +174,13 @@ public: ResourceViewType GetViewType() const override { return m_viewType; } ResourceViewDimension GetDimension() const override { return ResourceViewDimension::Texture2D; } Format GetFormat() const override { return m_format; } + RHITexture* GetTextureResource() const override { return m_texture; } private: ResourceViewType m_viewType = ResourceViewType::RenderTarget; Format m_format = Format::R8G8B8A8_UNorm; + std::unique_ptr m_ownedTexture; + RHITexture* m_texture = nullptr; }; class MockTransientDevice final : public RHIDevice { @@ -396,14 +436,16 @@ TEST(RenderGraph_Test, OrdersImportedTextureHazardsAcrossFullscreenStyleChain) { RenderGraphBuilder builder(graph); const RenderGraphTextureDesc desc = BuildTestTextureDesc(); + MockImportedView sceneColorView; + MockImportedView postColorView; const RenderGraphTextureHandle sceneColor = builder.ImportTexture( "SceneColor", desc, - reinterpret_cast(1)); + &sceneColorView); const RenderGraphTextureHandle postColor = builder.ImportTexture( "PostColor", desc, - reinterpret_cast(2)); + &postColorView); builder.AddRasterPass( "MainScene", @@ -457,10 +499,13 @@ TEST(RenderGraph_Test, PreservesImportedTextureStateContractAcrossCompile) { importedOptions.finalState = ResourceStates::PixelShaderResource; importedOptions.graphOwnsTransitions = true; + MockImportedView importedView( + ResourceViewType::ShaderResource, + Format::R8G8B8A8_UNorm); const RenderGraphTextureHandle importedTexture = builder.ImportTexture( "ImportedColor", desc, - reinterpret_cast(7), + &importedView, importedOptions); builder.AddRasterPass( @@ -676,15 +721,15 @@ TEST(RenderGraph_Test, ExecutesTransientDepthTransitionsWithDepthStencilView) { EXPECT_EQ(allocationState->createTextureCalls, 1); EXPECT_EQ(allocationState->createRenderTargetViewCalls, 0); EXPECT_EQ(allocationState->createDepthViewCalls, 1); - EXPECT_EQ(allocationState->createShaderViewCalls, 0); + EXPECT_EQ(allocationState->createShaderViewCalls, 1); EXPECT_EQ(allocationState->shutdownTextureCalls, 1); EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 0); EXPECT_EQ(allocationState->shutdownDepthViewCalls, 1); - EXPECT_EQ(allocationState->shutdownShaderViewCalls, 0); + EXPECT_EQ(allocationState->shutdownShaderViewCalls, 1); EXPECT_EQ(allocationState->destroyTextureCalls, 1); EXPECT_EQ(allocationState->destroyRenderTargetViewCalls, 0); EXPECT_EQ(allocationState->destroyDepthViewCalls, 1); - EXPECT_EQ(allocationState->destroyShaderViewCalls, 0); + EXPECT_EQ(allocationState->destroyShaderViewCalls, 1); } TEST(RenderGraph_Test, ExecutesGraphOwnedImportedDepthTransitionsAtGraphBoundaries) { @@ -864,10 +909,11 @@ TEST(RenderGraph_Test, ExecutesCompiledPassCallbacksInCompiledOrder) { const RenderGraphTextureDesc desc = BuildTestTextureDesc(); const RenderGraphTextureHandle sceneColor = builder.CreateTransientTexture("SceneColor", desc); + MockImportedView backBufferView; const RenderGraphTextureHandle backBuffer = builder.ImportTexture( "BackBuffer", desc, - reinterpret_cast(1)); + &backBufferView); std::vector eventLog; auto allocationState = std::make_shared();