#include #include #include #include #include #include #include #include #include using namespace XCEngine::Rendering; using namespace XCEngine::RHI; namespace { struct MockTransientAllocationState { int createTextureCalls = 0; int shutdownTextureCalls = 0; int destroyTextureCalls = 0; 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; int createUnorderedAccessViewCalls = 0; int shutdownUnorderedAccessViewCalls = 0; int destroyUnorderedAccessViewCalls = 0; }; class MockTransientTexture final : public RHITexture { public: MockTransientTexture( std::shared_ptr state, uint32_t width, uint32_t height, Format format) : m_state(std::move(state)) , m_width(width) , m_height(height) , m_format(format) { } ~MockTransientTexture() override { ++m_state->destroyTextureCalls; } 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 { ++m_state->shutdownTextureCalls; } private: std::shared_ptr m_state; uint32_t m_width = 0u; uint32_t m_height = 0u; Format m_format = Format::Unknown; ResourceStates m_stateValue = ResourceStates::Common; std::string m_name; }; class MockTransientView final : public RHIResourceView { public: MockTransientView( std::shared_ptr state, ResourceViewType viewType, Format format) : m_state(std::move(state)) , m_viewType(viewType) , m_format(format) { } ~MockTransientView() override { if (m_viewType == ResourceViewType::RenderTarget) { ++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; } } void Shutdown() override { if (m_viewType == ResourceViewType::RenderTarget) { ++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; } } void* GetNativeHandle() override { return nullptr; } bool IsValid() const override { return true; } ResourceViewType GetViewType() const override { return m_viewType; } ResourceViewDimension GetDimension() const override { return ResourceViewDimension::Texture2D; } Format GetFormat() const override { return m_format; } private: std::shared_ptr m_state; ResourceViewType m_viewType = ResourceViewType::ShaderResource; Format m_format = Format::Unknown; }; 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 m_viewType; } ResourceViewDimension GetDimension() const override { return ResourceViewDimension::Texture2D; } Format GetFormat() const override { return m_format; } private: ResourceViewType m_viewType = ResourceViewType::RenderTarget; Format m_format = Format::R8G8B8A8_UNorm; }; class MockTransientDevice final : public RHIDevice { public: explicit MockTransientDevice(std::shared_ptr state) : m_state(std::move(state)) { } bool Initialize(const RHIDeviceDesc&) override { return true; } void Shutdown() override {} RHIBuffer* CreateBuffer(const BufferDesc&) override { return nullptr; } RHITexture* CreateTexture(const TextureDesc& desc) override { ++m_state->createTextureCalls; return new MockTransientTexture( m_state, desc.width, desc.height, static_cast(desc.format)); } RHITexture* CreateTexture( const TextureDesc& desc, const void*, size_t, uint32_t) override { return CreateTexture(desc); } RHISwapChain* CreateSwapChain(const SwapChainDesc&, RHICommandQueue*) override { return nullptr; } RHICommandList* CreateCommandList(const CommandListDesc&) override { return nullptr; } RHICommandQueue* CreateCommandQueue(const CommandQueueDesc&) override { return nullptr; } RHIShader* CreateShader(const ShaderCompileDesc&) override { return nullptr; } RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc&) override { return nullptr; } RHIPipelineLayout* CreatePipelineLayout(const RHIPipelineLayoutDesc&) override { return nullptr; } RHIFence* CreateFence(const FenceDesc&) override { return nullptr; } RHISampler* CreateSampler(const SamplerDesc&) override { return nullptr; } RHIRenderPass* CreateRenderPass(uint32_t, const AttachmentDesc*, const AttachmentDesc*) override { return nullptr; } RHIFramebuffer* CreateFramebuffer( RHIRenderPass*, uint32_t, uint32_t, uint32_t, RHIResourceView**, RHIResourceView*) override { return nullptr; } RHIDescriptorPool* CreateDescriptorPool(const DescriptorPoolDesc&) override { return nullptr; } RHIDescriptorSet* CreateDescriptorSet(RHIDescriptorPool*, const DescriptorSetLayoutDesc&) override { return nullptr; } RHIResourceView* CreateVertexBufferView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; } RHIResourceView* CreateIndexBufferView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; } RHIResourceView* CreateRenderTargetView( RHITexture*, const ResourceViewDesc& desc) override { ++m_state->createRenderTargetViewCalls; return new MockTransientView( m_state, ResourceViewType::RenderTarget, static_cast(desc.format)); } 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( RHITexture*, const ResourceViewDesc& desc) override { ++m_state->createShaderViewCalls; return new MockTransientView( m_state, ResourceViewType::ShaderResource, static_cast(desc.format)); } RHIResourceView* CreateUnorderedAccessView(RHIBuffer*, 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; } const RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; } void* GetNativeDevice() override { return nullptr; } private: std::shared_ptr m_state; RHICapabilities m_capabilities = {}; RHIDeviceInfo m_deviceInfo = {}; }; struct MockTransitionBarrierCall { RHIResourceView* resource = nullptr; ResourceStates before = ResourceStates::Common; ResourceStates after = ResourceStates::Common; }; class MockTransientCommandList final : public RHICommandList { public: void Shutdown() override {} void Reset() override {} void Close() override {} void TransitionBarrier( RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) override { MockTransitionBarrierCall call = {}; call.resource = resource; call.before = stateBefore; call.after = stateAfter; transitionCalls.push_back(call); } void BeginRenderPass( RHIRenderPass*, RHIFramebuffer*, const Rect&, uint32_t, const ClearValue*) override { } void EndRenderPass() override {} void SetShader(RHIShader*) override {} void SetPipelineState(RHIPipelineState*) override {} void SetGraphicsDescriptorSets(uint32_t, uint32_t, RHIDescriptorSet**, RHIPipelineLayout*) override {} void SetComputeDescriptorSets(uint32_t, uint32_t, RHIDescriptorSet**, RHIPipelineLayout*) override {} void SetPrimitiveTopology(PrimitiveTopology) override {} void SetViewport(const Viewport&) override {} void SetViewports(uint32_t, const Viewport*) override {} void SetScissorRect(const Rect&) override {} void SetScissorRects(uint32_t, const Rect*) override {} void SetRenderTargets(uint32_t, RHIResourceView**, RHIResourceView*) override {} void SetStencilRef(uint8_t) override {} void SetBlendFactor(const float[4]) override {} void SetVertexBuffers(uint32_t, uint32_t, RHIResourceView**, const uint64_t*, const uint32_t*) override {} void SetIndexBuffer(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(RHIResourceView*, const float[4], uint32_t, const Rect*) override {} void ClearDepthStencil(RHIResourceView*, float, uint8_t, uint32_t, const Rect*) override {} void CopyResource(RHIResourceView*, RHIResourceView*) override {} void Dispatch(uint32_t, uint32_t, uint32_t) override {} std::vector transitionCalls = {}; }; RenderGraphTextureDesc BuildTestTextureDesc() { RenderGraphTextureDesc desc = {}; desc.width = 1280u; desc.height = 720u; desc.format = static_cast(Format::R8G8B8A8_UNorm); desc.textureType = static_cast(TextureType::Texture2D); desc.sampleCount = 1u; desc.sampleQuality = 0u; return desc; } RenderGraphTextureDesc BuildTestDepthTextureDesc() { RenderGraphTextureDesc desc = {}; desc.width = 1024u; desc.height = 1024u; desc.format = static_cast(Format::D24_UNorm_S8_UInt); desc.textureType = static_cast(TextureType::Texture2D); desc.sampleCount = 1u; desc.sampleQuality = 0u; return desc; } } // namespace TEST(RenderGraph_Test, CompilesDeclaredPassOrderAndTracksTextureLifetimes) { RenderGraph graph; RenderGraphBuilder builder(graph); const RenderGraphTextureDesc desc = BuildTestTextureDesc(); const RenderGraphTextureHandle sceneColor = builder.CreateTransientTexture("SceneColor", desc); const RenderGraphTextureHandle bloomColor = builder.CreateTransientTexture("BloomColor", desc); const RenderGraphTextureHandle backBuffer = builder.ImportTexture("BackBuffer", desc); builder.AddRasterPass( "Opaque", [&](RenderGraphPassBuilder& pass) { pass.WriteTexture(sceneColor); }); builder.AddComputePass( "Bloom", [&](RenderGraphPassBuilder& pass) { pass.ReadTexture(sceneColor); pass.WriteTexture(bloomColor); }); builder.AddRasterPass( "Final", [&](RenderGraphPassBuilder& pass) { pass.ReadTexture(bloomColor); pass.WriteTexture(backBuffer); }); CompiledRenderGraph compiledGraph; XCEngine::Containers::String errorMessage; ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) << errorMessage.CStr(); ASSERT_EQ(compiledGraph.GetPassCount(), 3u); EXPECT_EQ(compiledGraph.GetPassName(0u), "Opaque"); EXPECT_EQ(compiledGraph.GetPassName(1u), "Bloom"); EXPECT_EQ(compiledGraph.GetPassName(2u), "Final"); EXPECT_EQ(compiledGraph.GetPassType(0u), RenderGraphPassType::Raster); EXPECT_EQ(compiledGraph.GetPassType(1u), RenderGraphPassType::Compute); RenderGraphTextureLifetime lifetime = {}; ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(sceneColor, lifetime)); EXPECT_TRUE(lifetime.used); EXPECT_EQ(lifetime.kind, RenderGraphTextureKind::Transient); EXPECT_EQ(lifetime.firstPassIndex, 0u); EXPECT_EQ(lifetime.lastPassIndex, 1u); ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(bloomColor, lifetime)); EXPECT_TRUE(lifetime.used); EXPECT_EQ(lifetime.firstPassIndex, 1u); EXPECT_EQ(lifetime.lastPassIndex, 2u); ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(backBuffer, lifetime)); EXPECT_TRUE(lifetime.used); EXPECT_EQ(lifetime.kind, RenderGraphTextureKind::Imported); EXPECT_EQ(lifetime.firstPassIndex, 2u); EXPECT_EQ(lifetime.lastPassIndex, 2u); } TEST(RenderGraph_Test, OrdersImportedTextureHazardsAcrossFullscreenStyleChain) { RenderGraph graph; RenderGraphBuilder builder(graph); const RenderGraphTextureDesc desc = BuildTestTextureDesc(); const RenderGraphTextureHandle sceneColor = builder.ImportTexture( "SceneColor", desc, reinterpret_cast(1)); const RenderGraphTextureHandle postColor = builder.ImportTexture( "PostColor", desc, reinterpret_cast(2)); builder.AddRasterPass( "MainScene", [&](RenderGraphPassBuilder& pass) { pass.WriteTexture(sceneColor); }); builder.AddRasterPass( "PostProcess", [&](RenderGraphPassBuilder& pass) { pass.ReadTexture(sceneColor); pass.WriteTexture(postColor); }); builder.AddRasterPass( "FinalOutput", [&](RenderGraphPassBuilder& pass) { pass.ReadTexture(postColor); pass.WriteTexture(sceneColor); }); CompiledRenderGraph compiledGraph; XCEngine::Containers::String errorMessage; ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) << errorMessage.CStr(); ASSERT_EQ(compiledGraph.GetPassCount(), 3u); EXPECT_EQ(compiledGraph.GetPassName(0u), "MainScene"); EXPECT_EQ(compiledGraph.GetPassName(1u), "PostProcess"); EXPECT_EQ(compiledGraph.GetPassName(2u), "FinalOutput"); RenderGraphTextureLifetime lifetime = {}; ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(sceneColor, lifetime)); EXPECT_EQ(lifetime.kind, RenderGraphTextureKind::Imported); EXPECT_EQ(lifetime.firstPassIndex, 0u); EXPECT_EQ(lifetime.lastPassIndex, 2u); ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(postColor, lifetime)); EXPECT_EQ(lifetime.kind, RenderGraphTextureKind::Imported); EXPECT_EQ(lifetime.firstPassIndex, 1u); EXPECT_EQ(lifetime.lastPassIndex, 2u); } TEST(RenderGraph_Test, PreservesImportedTextureStateContractAcrossCompile) { RenderGraph graph; RenderGraphBuilder builder(graph); const RenderGraphTextureDesc desc = BuildTestTextureDesc(); RenderGraphImportedTextureOptions importedOptions = {}; importedOptions.initialState = ResourceStates::Present; importedOptions.finalState = ResourceStates::PixelShaderResource; importedOptions.graphOwnsTransitions = true; const RenderGraphTextureHandle importedTexture = builder.ImportTexture( "ImportedColor", desc, reinterpret_cast(7), importedOptions); builder.AddRasterPass( "SampleImported", [&](RenderGraphPassBuilder& pass) { pass.ReadTexture(importedTexture); }); CompiledRenderGraph compiledGraph; XCEngine::Containers::String errorMessage; ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) << errorMessage.CStr(); RenderGraphImportedTextureOptions resolvedOptions = {}; ASSERT_TRUE(compiledGraph.TryGetImportedTextureOptions(importedTexture, resolvedOptions)); 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) { RenderGraph graph; RenderGraphBuilder builder(graph); const RenderGraphTextureDesc desc = BuildTestTextureDesc(); RenderGraphImportedTextureOptions importedOptions = {}; importedOptions.initialState = ResourceStates::Present; importedOptions.finalState = ResourceStates::Present; importedOptions.graphOwnsTransitions = true; MockImportedView importedView; const RenderGraphTextureHandle backBuffer = builder.ImportTexture( "BackBuffer", desc, &importedView, importedOptions); builder.AddRasterPass( "FinalBlit", [&](RenderGraphPassBuilder& pass) { pass.WriteTexture(backBuffer); }); 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::Present); EXPECT_EQ(commandList.transitionCalls[0].after, ResourceStates::RenderTarget); EXPECT_EQ(commandList.transitionCalls[1].resource, &importedView); EXPECT_EQ(commandList.transitionCalls[1].before, ResourceStates::RenderTarget); EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::Present); } TEST(RenderGraph_Test, RejectsTransientTextureReadBeforeWrite) { RenderGraph graph; RenderGraphBuilder builder(graph); const RenderGraphTextureDesc desc = BuildTestTextureDesc(); const RenderGraphTextureHandle sceneColor = builder.CreateTransientTexture("SceneColor", desc); builder.AddRasterPass( "InvalidRead", [&](RenderGraphPassBuilder& pass) { pass.ReadTexture(sceneColor); }); CompiledRenderGraph compiledGraph; XCEngine::Containers::String errorMessage; EXPECT_FALSE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)); 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, TracksDepthAttachmentTransitionPlan) { RenderGraph graph; RenderGraphBuilder builder(graph); const RenderGraphTextureDesc depthDesc = BuildTestDepthTextureDesc(); const RenderGraphTextureHandle depthTexture = builder.CreateTransientTexture("SceneDepth", depthDesc); builder.AddRasterPass( "DepthPrepass", [&](RenderGraphPassBuilder& pass) { pass.WriteDepthTexture(depthTexture); }); CompiledRenderGraph compiledGraph; XCEngine::Containers::String errorMessage; ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) << errorMessage.CStr(); RenderGraphTextureTransitionPlan transitionPlan = {}; ASSERT_TRUE(compiledGraph.TryGetTextureTransitionPlan(depthTexture, transitionPlan)); EXPECT_TRUE(transitionPlan.graphOwnsTransitions); EXPECT_TRUE(transitionPlan.hasFirstAccessState); EXPECT_TRUE(transitionPlan.hasLastAccessState); EXPECT_EQ(transitionPlan.initialState, ResourceStates::Common); EXPECT_EQ(transitionPlan.firstAccessState, ResourceStates::DepthWrite); EXPECT_EQ(transitionPlan.lastAccessState, ResourceStates::DepthWrite); 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, 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); const RenderGraphTextureDesc desc = BuildTestTextureDesc(); const RenderGraphTextureHandle sceneColor = builder.CreateTransientTexture("SceneColor", desc); const RenderGraphTextureHandle backBuffer = builder.ImportTexture( "BackBuffer", desc, reinterpret_cast(1)); std::vector eventLog; auto allocationState = std::make_shared(); MockTransientDevice device(allocationState); MockTransientCommandList commandList; RHIResourceView* opaqueTargetView = nullptr; RHIResourceView* finalSourceView = nullptr; builder.AddRasterPass( "Opaque", [&](RenderGraphPassBuilder& pass) { pass.WriteTexture(sceneColor); pass.SetExecuteCallback( [&](const RenderGraphExecutionContext& executionContext) { EXPECT_TRUE(executionContext.IsTransientTexture(sceneColor)); EXPECT_NE( executionContext.ResolveTextureView( sceneColor, RenderGraphTextureViewType::RenderTarget), nullptr); opaqueTargetView = executionContext.ResolveTextureView( sceneColor, RenderGraphTextureViewType::RenderTarget); eventLog.push_back("Opaque"); }); }); builder.AddRasterPass( "Final", [&](RenderGraphPassBuilder& pass) { pass.ReadTexture(sceneColor); pass.WriteTexture(backBuffer); pass.SetExecuteCallback( [&](const RenderGraphExecutionContext& executionContext) { RenderGraphTextureDesc resolvedDesc = {}; EXPECT_TRUE(executionContext.TryGetTextureDesc(sceneColor, resolvedDesc)); EXPECT_EQ(resolvedDesc.width, desc.width); EXPECT_NE( executionContext.ResolveTextureView( sceneColor, RenderGraphTextureViewType::ShaderResource), nullptr); finalSourceView = executionContext.ResolveTextureView( sceneColor, RenderGraphTextureViewType::ShaderResource); eventLog.push_back("Final"); }); }); CompiledRenderGraph compiledGraph; XCEngine::Containers::String errorMessage; ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage)) << errorMessage.CStr(); RenderContext renderContext = {}; renderContext.device = &device; renderContext.commandList = &commandList; renderContext.commandQueue = reinterpret_cast(1); ASSERT_TRUE(RenderGraphExecutor::Execute(compiledGraph, renderContext, &errorMessage)) << errorMessage.CStr(); ASSERT_EQ(eventLog.size(), 2u); EXPECT_EQ(eventLog[0], "Opaque"); EXPECT_EQ(eventLog[1], "Final"); ASSERT_EQ(commandList.transitionCalls.size(), 2u); EXPECT_EQ(commandList.transitionCalls[0].resource, opaqueTargetView); EXPECT_EQ(commandList.transitionCalls[0].before, ResourceStates::Common); EXPECT_EQ(commandList.transitionCalls[0].after, ResourceStates::RenderTarget); EXPECT_EQ(commandList.transitionCalls[1].resource, finalSourceView); EXPECT_EQ(commandList.transitionCalls[1].before, ResourceStates::RenderTarget); EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::PixelShaderResource); EXPECT_EQ(allocationState->createTextureCalls, 1); EXPECT_EQ(allocationState->createRenderTargetViewCalls, 1); EXPECT_EQ(allocationState->createShaderViewCalls, 1); EXPECT_EQ(allocationState->shutdownTextureCalls, 1); EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 1); EXPECT_EQ(allocationState->shutdownShaderViewCalls, 1); EXPECT_EQ(allocationState->destroyTextureCalls, 1); EXPECT_EQ(allocationState->destroyRenderTargetViewCalls, 1); EXPECT_EQ(allocationState->destroyShaderViewCalls, 1); }