From 6f802ac12be91bf0e21170dd635817c0af4378db Mon Sep 17 00:00:00 2001 From: Misaki Date: Thu, 26 Feb 2026 21:40:07 +0900 Subject: [PATCH] Added CopyTexture support in ICommandBuffer --- ARCHITECTURE_CN.md | 75 -- DEVELOPMENT_CN.md | 76 -- .../D3D12CommandBuffer.cs | 57 +- src/Runtime/Ghost.Graphics.RHI/Common.cs | 52 + .../Ghost.Graphics.RHI/ICommandBuffer.cs | 22 + .../Contracts/IRenderPipeline.cs | 9 + .../RenderGraphModule/RenderGraph.cs.backup | 1170 ----------------- 7 files changed, 136 insertions(+), 1325 deletions(-) delete mode 100644 ARCHITECTURE_CN.md delete mode 100644 DEVELOPMENT_CN.md create mode 100644 src/Runtime/Ghost.Graphics/Contracts/IRenderPipeline.cs delete mode 100644 src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs.backup diff --git a/ARCHITECTURE_CN.md b/ARCHITECTURE_CN.md deleted file mode 100644 index 5139742..0000000 --- a/ARCHITECTURE_CN.md +++ /dev/null @@ -1,75 +0,0 @@ -# GhostEngine 架构详解 - -本文档深入探讨 GhostEngine 核心模块的设计实现,为协同开发提供详细的技术参考。 - -## 1. 实体组件系统 (Ghost.Entities) - -Ghost.Entities 采用 Archetype (原型) 模式,旨在优化大规模实体的遍历效率和内存布局。 - -### 1.1 核心组件 -- **World**: 包含实体生命周期管理的顶层容器,包含 `EntityManager`。 -- **Archetype**: 定义了一组特定组件的组合。具有相同组件集的所有实体都存储在同一 Archetype 中。 -- **Chunk**: Archetype 内部的数据块,按列存储 (Columnar Storage) 组件数据,以确保对单个组件的线性访问具备极佳的 CPU 缓存亲和性。 -- **EntityQuery**: 通过位掩码 (Bitmask) 快速匹配 Archetype,支持 `WithAll`, `WithAny`, `WithNone` 过滤规则。 - -### 1.2 并行处理 -- 实体查询支持多线程作业系统 (Job System) 迭代。 -- **EntityCommandBuffer (ECB)**: 允许在并行作业中排队待处理的实体修改请求 (如创建、删除、添加组件),并在单线程同步点进行统一应用,以避免竞态条件。 - -## 2. 渲染架构 (Ghost.Graphics) - -渲染系统设计目标是支持高效、现代的图形渲染流水线,同时降低 D3D12 的开发复杂度。 - -### 2.1 RHI (渲染硬件接口) -RHI 位于 `Ghost.Graphics.RHI` 命名空间下,抽象了底层的渲染资源: -- **IRenderDevice**: 逻辑渲染设备。 -- **ICommandBuffer**: 用于录制 GPU 指令。 -- **IPipelineLibrary**: 缓存并管理渲染管线状态对象 (PSO)。 -- **IResourceDatabase**: 管理显存资源 (Buffer, Texture) 及其生命周期。 - -### 2.2 Render Graph (渲染图) -Render Graph 是图形模块的核心组件,负责帧内资源的依赖分析和自动调度: -- **Pass Builder**: 在帧开始阶段,每个 Pass 声明其读取 (`Read`) 和写入 (`Write`) 的资源。 -- **Resource Aliasing**: 自动识别不重叠的资源生命周期,实现显存的物理地址复用。 -- **Automatic Barrier**: 自动根据资源的读写关系插入 `ResourceBarrier` (例如从 `RenderTarget` 状态转换到 `PixelShaderResource` 状态)。 - -### 2.3 自定义着色器语言 (Ghost.DSL) -引擎支持 `.gshdr` 文件,其语法借鉴了 HLSL 但增强了元数据支持: -- **自动属性映射**: DSL 编译器会解析着色器定义的属性,并自动生成与之匹配的 C# 结构体 (`ShaderStructGenerator`),简化 CPU 到 GPU 的数据传递。 - -## 3. 编辑器架构 (Ghost.Editor) - -编辑器框架采用模块化设计,重点在于扩展性和资源工作流。 - -### 3.1 资源数据库 (Asset Database) -- **AssetRegistry**: 通过资源文件的 GUID 进行追踪,支持跨文件引用的完整性校验。 -- **AssetProcessor**: 插件化系统,针对不同文件扩展名 (如 `.png`, `.fbx`) 提供特定的导入和转换逻辑 (如调用 Nvtt 压缩纹理)。 - -### 3.2 检查器 (Inspector) -- **Service-driven**: `InspectorService` 动态检测当前选中实体的组件列表,并根据组件类型匹配对应的 `ComponentEditor` 进行 UI 渲染。 -- **Data Binding**: 利用 `ReflectionBinding` 实现编辑器 UI 与运行时组件数据的双向同步。 - -## 4. 核心设计原则 (Core Design Principles) - -项目在底层代码中遵循以下核心设计原则,以确保高性能和系统稳定性: - -### 4.1 Result over Exception (结果对象胜于异常) -在引擎的核心运行时(尤其是 `Ghost.Graphics` 和 `Ghost.Entities`)中,我们避免使用异常来处理预期的错误。 -- **Result 结构体**: 使用 `Ghost.Core.Result` 或 `Result` 结构体返回操作结果。 -- **性能**: 避免了异常产生的堆栈跟踪开销。 -- **显性处理**: 强制调用者检查 `IsSuccess` 或使用 `Deconstruct` 模式处理错误,使错误流更加清晰。 - -### 4.2 Handle over Ptr/Reference (句柄胜于指针/引用) -为了内存安全和支持序列化,引擎广泛使用句柄而非直接的内存指针。 -- **Handle**: 资源(如 `Texture`, `Buffer`, `Entity`)通过强类型句柄进行引用。 -- **安全性**: 防止野指针问题,支持资源的延迟加载和卸载,而不破坏引用。 -- **解耦**: 句柄作为后端资源的索引,使得底层的资源管理器(如 `D3D12ResourceDatabase`)可以自由地重新分配或移动物理资源。 - -## 5. 协同开发建议 - - -- **跨项目引用**: 尽量避免 `Runtime` 项目引用 `Editor` 项目。`Editor` 应依赖 `Runtime` 以提供实时预览。 -- **性能关键点**: 在 `Ghost.Entities` 和 `Ghost.Graphics` 模块中,应尽量避免堆内存分配 (GC Allocation),优先使用 `Span`, `Memory` 及非托管内存。 - ---- -*注:本架构基于当前代码实现,如有变更将及时更新文档。* diff --git a/DEVELOPMENT_CN.md b/DEVELOPMENT_CN.md deleted file mode 100644 index 29bb219..0000000 --- a/DEVELOPMENT_CN.md +++ /dev/null @@ -1,76 +0,0 @@ -# GhostEngine 开发指南 - -欢迎参与 GhostEngine 项目。本文档旨在帮助你快速了解项目的当前状态、架构设计、开发规范及环境配置。 - -## 1. 开发环境要求 (Prerequisites) - -在开始开发之前,请确保你的开发环境满足以下要求: - -- **操作系统**: Windows 10 版本 1809 (17763) 或更高版本。 -- **IDE**: Visual Studio 2022 (建议使用最新预览版以支持 .NET 10)。 -- **.NET SDK**: .NET 10.0 SDK。 -- **Windows App SDK**: 项目目前使用 Windows App SDK (WinUI 3) 版本 1.8.260101001。 -- **显卡**: 支持 DirectX 12 (Feature Level 11.0+) 的显卡。 -- **Visual Studio 工作负载**: - - .NET 桌面开发 - - 使用 C++ 的桌面开发 (部分第三方库包装需要) - - 通用 Windows 平台开发 (用于 Windows App SDK 支持) - -## 2. 项目结构 (Project Structure) - -项目代码主要位于 `src` 目录下,按功能分为三个主要部分: - -### 2.1 Runtime (引擎运行时) -- **Ghost.Graphics**: 图形渲染核心。包含 RHI (渲染硬件接口) 定义及其 D3D12 实现。集成了 Render Graph (渲染图) 模块用于管理复杂的渲染流水线。 -- **Ghost.Entities**: 基于 Archetype (原型) 的高性能 ECS (实体组件系统) 实现。包含 `World`, `EntityManager`, `EntityQuery` 等核心组件。 -- **Ghost.Engine**: 引擎基础逻辑。包含场景管理 (`Scene`)、基础组件 (如 `Hierarchy`, `LocalToWorld`) 及数学工具类。 - -### 2.2 Editor (编辑器) -- **Ghost.Editor**: 基于 WinUI 3 编写的桌面编辑器前端。采用 MVVM 架构。 -- **Ghost.Editor.Core**: 编辑器框架层。包含资源管理 (`AssetRegistry`)、检查器服务 (`InspectorService`)、场景树管理 (`SceneGraph`) 等非 UI 逻辑。 -- **Ghost.DSL**: 引擎自定义着色器语言 (GSL) 的编译器和解析器,用于处理着色器代码生成。 - -### 2.3 ThirdParty (第三方库) -- 包含对 FMOD (音频)、Nvtt (纹理压缩)、MeshOptimizer (模型优化) 等 C++ 库的 C# 包装。 - -## 3. 命名规范 (Naming Conventions) - -项目严格遵守以下 C# 编程规范: - -- **命名空间**: 以 `Ghost.` 开头,后跟模块名 (例如 `Ghost.Graphics.D3D12`)。 -- **类与方法**: 使用 `PascalCase` (大驼峰命名法)。 -- **私有字段**: 使用 `_camelCase` (下划线前缀的小驼峰命名法)。 -- **接口**: 必须以 `I` 作为前缀 (例如 `IRenderDevice`)。 -- **异步方法**: 必须以 `Async` 作为后缀 (例如 `LoadAssetAsync`)。 -- **实现类**: 针对接口的具体实现应体现技术细节 (例如 `D3D12GraphicsEngine` 实现了 `IGraphicsEngine`)。 - -## 4. 架构设计与模式 (Architecture & Patterns) - -### 4.1 ECS (Entity Component System) -运行时逻辑优先使用 ECS 模式以获得最佳的缓存命中率和并行性能: -- **Entity**: 纯 ID。 -- **Component**: 纯数据结构 (Struct)。 -- **System**: 处理特定组件组合的逻辑类。 - -### 4.2 RHI & Render Graph -- **RHI (Render Hardware Interface)**: 抽象了底层的图形 API。目前主要实现为 D3D12,通过 Vortice.Windows 进行绑定。 -- **Render Graph**: 采用有向无环图 (DAG) 管理每一帧的渲染顺序、资源屏障 (Resource Barriers) 和资源复用,简化了 D3D12 的资源管理负担。 - -### 4.3 MVVM & Service Pattern (Editor) -编辑器部分遵循: -- **MVVM**: 分离 UI (`View`) 与业务逻辑 (`ViewModel`)。 -- **Service/Contract**: 通过接口定义服务 (如 `IAssetRegistry`),并利用依赖注入 (Dependency Injection) 进行解耦。 - -### 4.4 核心设计原则 (Core Design Principles) -- **Result over Exception (结果对象胜于异常)**: 在核心运行时中,避免使用 `throw` 处理预期错误,优先返回 `Ghost.Core.Result` 结构体,以提高性能和错误流的可控性。 -- **Handle over Ptr/Reference (句柄胜于指针/引用)**: 资源引用(如实体、纹理、缓冲区)应优先使用 `Handle`,避免持有直接的内存地址,以确保资源管理的安全性并支持高效序列化。 - -## 5. 核心逻辑当前状态 - -- **渲染系统**: 已具备基础的 D3D12 渲染器、Render Graph 编译器及简单的 Mesh 渲染 pass。支持自定义着色器加载。 -- **实体系统**: 已实现 Archetype-based ECS,支持高性能的实体查询 (`EntityQuery`) 和作业系统迭代。 -- **资源管理**: 编辑器端已实现初步的资产数据库 (`AssetRegistry`),支持纹理和模型的导入流程。 -- **场景管理**: 支持基础的层级结构 (`Hierarchy`) 和变换同步 (`LocalToWorld`)。 - ---- -*注:关于后续的开发计划 (TODO) 及改进方向,我将直接通过会议或即时通讯工具进行沟通。* diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs index 04fb650..9a78944 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs @@ -5,6 +5,7 @@ using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel.Utilities; using System.Runtime.CompilerServices; using TerraFX.Interop.DirectX; +using TerraFX.Interop.Gdiplus; using TerraFX.Interop.Windows; using static TerraFX.Aliases.D3D_Alias; @@ -984,10 +985,32 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer } } - public void CopyTexture(Handle dst, Handle src) + private D3D12_TEXTURE_COPY_LOCATION GetTextureCopyLocation(SharedPtr texture, TextureSubresource subres) { - throw new NotImplementedException(); + var flatIndex = subres.MipLevel + subres.ArrayLayer * texture.Get()->GetDesc().MipLevels; + return new D3D12_TEXTURE_COPY_LOCATION + { + pResource = texture, + Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + SubresourceIndex = flatIndex + }; + } + + private bool AreTexturesIdentical(SharedPtr tex1, SharedPtr tex2) + { + var desc1 = tex1.Get()->GetDesc(); + var desc2 = tex2.Get()->GetDesc(); + return desc1.Width == desc2.Width + && desc1.Height == desc2.Height + && desc1.DepthOrArraySize == desc2.DepthOrArraySize + && desc1.MipLevels == desc2.MipLevels + && desc1.Format == desc2.Format + && desc1.SampleDesc.Count == desc2.SampleDesc.Count; + } + + public void CopyTexture(Handle dst, TextureRegion? dstRegion, Handle src, TextureRegion? srcRegion) + { ThrowIfDisposed(); ThrowIfNotRecording(); #if !DEBUG @@ -1011,8 +1034,34 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer return; } - // TODO - _commandList.Get()->CopyTextureRegion(null, 0, 0, 0, null, null); + if (dstRegion == null || srcRegion == null) + { + if (!AreTexturesIdentical(pDstResource, pSrcResource)) + { + RecordError(nameof(CopyTexture), Error.InvalidArgument); + return; + } + + _commandList.Get()->CopyResource(pDstResource, pSrcResource); + return; + } + + var dstRegionV = dstRegion.Value; + var srcRegionV = srcRegion.Value; + + var dstLocation = GetTextureCopyLocation(pDstResource, dstRegionV.Subresource); + var srcLocation = GetTextureCopyLocation(pSrcResource, srcRegionV.Subresource); + var srcBoc = new D3D12_BOX + { + left = srcRegionV.X, + top = srcRegionV.Y, + front = srcRegionV.Z, + right = srcRegionV.X + srcRegionV.Width, + bottom = srcRegionV.Y + srcRegionV.Height, + back = srcRegionV.Z + srcRegionV.Depth + }; + + _commandList.Get()->CopyTextureRegion(&dstLocation, dstRegionV.X, dstRegionV.Y, dstRegionV.Z, &srcLocation, &srcBoc); } public void Dispose() diff --git a/src/Runtime/Ghost.Graphics.RHI/Common.cs b/src/Runtime/Ghost.Graphics.RHI/Common.cs index 226fa77..b61027e 100644 --- a/src/Runtime/Ghost.Graphics.RHI/Common.cs +++ b/src/Runtime/Ghost.Graphics.RHI/Common.cs @@ -457,6 +457,58 @@ public struct PassDepthStencilDesc } +public struct TextureSubresource +{ + public uint MipLevel + { + get; set; + } + + public uint ArrayLayer + { + get; set; + } +} + +public struct TextureRegion +{ + public TextureSubresource Subresource + { + get; set; + } + + public uint X + { + get; set; + } + + public uint Y + { + get; set; + } + + public uint Z + { + get; set; + } + + public uint Width + { + get; set; + } + + public uint Height + { + get; set; + } + + public uint Depth + { + get; set; + } +} + + public struct BarrierSubresourceRange { public uint IndexOrFirstMipLevel diff --git a/src/Runtime/Ghost.Graphics.RHI/ICommandBuffer.cs b/src/Runtime/Ghost.Graphics.RHI/ICommandBuffer.cs index e824820..50f9887 100644 --- a/src/Runtime/Ghost.Graphics.RHI/ICommandBuffer.cs +++ b/src/Runtime/Ghost.Graphics.RHI/ICommandBuffer.cs @@ -65,8 +65,21 @@ public interface ICommandBuffer : IDisposable /// A handle to the texture to be used as the depth Target. Specify a invalid handle if no depth Target is required. void SetRenderTargets(ReadOnlySpan> renderTargets, Handle depthTarget); + /// + /// Clears the specified render target to a given color. + /// + /// A handle to the render target texture to be cleared. Must reference a valid render target. + /// The color value used to clear the render target. Specifies the RGBA components to fill the target. void ClearRenderTargetView(Handle renderTarget, Color128 clearColor); + /// + /// Clears the specified depth-stencil view by resetting its depth and/or stencil values. + /// + /// A handle to the depth-stencil texture to be cleared. Must reference a valid depth-stencil resource. + /// A value indicating whether the depth component should be cleared. + /// A value indicating whether the stencil component should be cleared. + /// The value to which the depth buffer will be set. Typically ranges from 0.0f (nearest) to 1.0f (farthest). + /// The value to which the stencil buffer will be set. Must be a valid stencil value supported by the format. void ClearDepthStencilView(Handle depthStencil, bool inlcludeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0); /// @@ -201,4 +214,13 @@ public interface ICommandBuffer : IDisposable /// The byte Offset in the source buffer at which to begin reading. Must be zero or greater. /// The number of bytes to copy. If zero, copies the remaining bytes from the source buffer starting at . void CopyBuffer(Handle dest, Handle src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0); + + /// + /// Copies a region of a source texture to a destination texture. The source and destination regions can be specified to copy a subset of the textures, or the entire textures if the regions are null. + /// + /// The handle to the destination texture where data will be written. + /// The region of the destination texture to copy to. If null, the entire texture will be used. + /// The handle to the source texture from which data will be read. + /// The region of the source texture to copy from. If null, the entire texture will be used. + void CopyTexture(Handle dst, TextureRegion? dstRegion, Handle src, TextureRegion? srcRegion); } diff --git a/src/Runtime/Ghost.Graphics/Contracts/IRenderPipeline.cs b/src/Runtime/Ghost.Graphics/Contracts/IRenderPipeline.cs new file mode 100644 index 0000000..6e9ab7f --- /dev/null +++ b/src/Runtime/Ghost.Graphics/Contracts/IRenderPipeline.cs @@ -0,0 +1,9 @@ +using Ghost.Graphics.Core; +using Ghost.Graphics.RHI; + +namespace Ghost.Graphics.Contracts; + +public interface IRenderPipeline +{ + void Render(RenderContext ctx, ReadOnlySpan cameras); +} diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs.backup b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs.backup deleted file mode 100644 index 84769df..0000000 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraph.cs.backup +++ /dev/null @@ -1,1170 +0,0 @@ -using Ghost.Core; -using Ghost.Graphics.Core; -using Ghost.Graphics.RHI; -using Misaki.HighPerformance.LowLevel.Buffer; -using Misaki.HighPerformance.LowLevel.Collections; -using System.Diagnostics; -using System.IO.Hashing; - -namespace Ghost.Graphics.RenderGraphModule; - -/// -/// Main render graph class that manages resource allocation and pass execution. -/// -public sealed class RenderGraph : IDisposable -{ - private readonly IGraphicsEngine _graphicsEngine; - - private readonly RenderGraphObjectPool _objectPool; - private readonly RenderGraphResourceRegistry _resources; - - private readonly List _passes; - private readonly List _compiledPasses; - private readonly List _nativePasses; - - private readonly RenderGraphBuilder _builder; - private readonly ResourceAliasingManager _aliasingManager; - - private readonly List _compiledBarriers = new(128); - - private readonly RenderGraphCompilationCache _compilationCache = new(); - private readonly RenderGraphContext _context; - - private readonly RenderGraphCompiler _compiler; - private readonly RenderGraphExecutor _executor; - private readonly RenderGraphNativePassBuilder _nativePassBuilder; - - private bool _compiled; - private ViewState _currentViewState; - - public RenderGraphBlackboard Blackboard - { - get; - } - - public RenderGraph(IGraphicsEngine graphicsEngine) - { - _graphicsEngine = graphicsEngine; - - _objectPool = new RenderGraphObjectPool(); - _resources = new RenderGraphResourceRegistry(_objectPool); - - _passes = new List(32); - _compiledPasses = new List(32); - _nativePasses = new List(32); - - _builder = new RenderGraphBuilder(); - _aliasingManager = new ResourceAliasingManager(graphicsEngine.ResourceAllocator, _objectPool); - - _compilationCache = new RenderGraphCompilationCache(); - - _context = new RenderGraphContext( - _graphicsEngine.ResourceDatabase, - _graphicsEngine.PipelineLibrary, - _graphicsEngine.ShaderCompiler, - _resources - ); - - _nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources); - _compiler = new RenderGraphCompiler(_graphicsEngine, _resources, _aliasingManager, _nativePassBuilder, _compilationCache); - _executor = new RenderGraphExecutor(_graphicsEngine, _resources, _context); - - Blackboard = new RenderGraphBlackboard(); - } - - - /// - /// Resets the render graph for a new frame. - /// Reuses existing allocations to minimize GC. - /// - public void Reset() - { - // Clear blackboard data - Blackboard.Clear(); - - // Reset resources but keep allocations - _resources.Reset(); - - // Reset aliasing manager - _aliasingManager.Reset(); - - // Clear compiled barriers - _compiledBarriers.Clear(); - - // Return passes to the pool and reset count - for (var i = 0; i < _passes.Count; i++) - { - var pass = _passes[i]; - pass.Reset(_objectPool); - } - - _passes.Clear(); - - // Clear compiled passes list - _compiledPasses.Clear(); - - // Return native passes to pool - for (var i = 0; i < _nativePasses.Count; i++) - { - _objectPool.Return(_nativePasses[i]); - } - _nativePasses.Clear(); - - _compiled = false; - } - - /// - /// Imports an external texture into the render graph. - /// - /// The external texture handle. - /// The identifier of the imported render graph texture. Invalid if import fails. - public Identifier ImportTexture(Handle texture, string name, - Color128 clearColor = default, float clearDepth = 1.0f, byte clearStencil = 0, - bool clearAtFirstUse = true, bool discardAtLastUse = true) - { - var r = _graphicsEngine.ResourceDatabase.GetResourceDescription(texture.AsResource()); - if (r.IsFailure) - { - return Identifier.Invalid; - } - - var desc = r.Value; - return _resources.ImportTexture(in desc._desc.textureDescription, texture, name, clearColor, clearDepth, clearStencil, clearAtFirstUse, discardAtLastUse); - } - - /// - /// Imports an external buffer into the render graph. - /// - /// The external buffer handle. - /// The identifier of the imported render graph buffer. Invalid if import fails. - public Identifier ImportBuffer(Handle buffer, string name) - { - var r = _graphicsEngine.ResourceDatabase.GetResourceDescription(buffer.AsResource()); - if (r.IsFailure) - { - return Identifier.Invalid; - } - - var desc = r.Value; - return _resources.ImportBuffer(in desc._desc.bufferDescription, buffer, name); - } - - public IRasterRenderGraphBuilder AddRasterRenderPass(string name, out TPassData passData) - where TPassData : class, new() - { - var renderPass = _objectPool.Rent>(); - renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Raster); - passData = renderPass.passData; - - _passes.Add(renderPass); - - _builder.Init(this, renderPass, _resources); - return _builder; - } - - public IComputeRenderGraphBuilder AddComputeRenderPass(string name, out TPassData passData) - where TPassData : class, new() - { - var renderPass = _objectPool.Rent>(); - renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Compute); - passData = renderPass.passData; - - _passes.Add(renderPass); - - _builder.Init(this, renderPass, _resources); - return _builder; - } - - public IUnsafeRenderGraphBuilder AddUnsafeRenderPass(string name, out TPassData passData) - where TPassData : class, new() - { - var renderPass = _objectPool.Rent>(); - renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Unsafe); - passData = renderPass.passData; - - _passes.Add(renderPass); - - _builder.Init(this, renderPass, _resources); - return _builder; - } - - /// - /// Compiles the render graph by culling unused passes and determining resource lifetimes. - /// - public void Compile(in ViewState viewState) - { - if (_compiled) - { - return; - } - - _currentViewState = viewState; - - // Resolve texture sizes before computing hash - _resources.ResolveTextureSizes(in viewState); - - var graphHash = RenderGraphHasher.ComputeGraphHash(_passes, _resources); - - _compiler.Compile(in viewState, graphHash, _passes, _compiledPasses, _nativePasses, _compiledBarriers); - - _compiled = true; - } - - /// - /// Executes all compiled passes using native render passes where possible. - /// - public void Execute(ICommandBuffer cmd) - { - if (!_compiled) - { - throw new InvalidOperationException("Render graph must be compiled before execution. Call Compile(viewState) first."); - } - - _executor.Execute(cmd, _compiledPasses, _nativePasses, _compiledBarriers); - } - - public void Dispose() - { - _compiler.Dispose(); - - // We need to reset the whole graph to return resources to the pool - Reset(); - } -} - - { - Size = _aliasingManager.Heap.size + 64 * 1024, // Add 64KB padding to avoid potential overflows - Alignment = ResourceHeap.DEFAULT_ALIGNMENT, - HeapFlags = HeapFlags.AlowBufferAndTexture, - HeapType = HeapType.Default - }; - - _resourceHeap = _graphicsEngine.ResourceAllocator.Allocate(in allocationDesc, "RenderGraphResourceHeap"); - - for (var i = 0; i < _resources.Resources.Count; i++) - { - var placedIndex = _aliasingManager.GetPlacedResourceIndex(i); - var placed = _aliasingManager.GetPlacedResource(placedIndex); - if (placed == null) - { - continue; - } - - var res = _resources.Resources[i]; - var ops = new CreationOptions - { - AllocationType = ResourceAllocationType.Suballocation, - Heap = _resourceHeap, - Offset = placed.heapOffset, - }; - - if (res.type == RenderGraphResourceType.Texture) - { - var textureDesc = res.rgTextureDesc.ToTextureDesc(res.resolvedWidth, res.resolvedHeight); - res.backingResource = _graphicsEngine.ResourceAllocator.CreateTexture(in textureDesc, res.name, ops).AsResource(); - } - else if (res.type == RenderGraphResourceType.Buffer) - { - res.backingResource = _graphicsEngine.ResourceAllocator.CreateBuffer(in res.bufferDesc, res.name, ops).AsResource(); - } - else - { - throw new NotSupportedException(); - } - - _compilationCache.UpdateBackingResource(i, res.backingResource); - } - } - - /// - /// Restores the render graph state from cached compilation results. - /// - private void RestoreFromCache(CachedCompilation cached) - { - // Restore compiled pass list - _compiledPasses.Clear(); - for (var i = 0; i < cached.compiledPassIndices.Count; i++) - { - var passIndex = cached.compiledPassIndices[i]; - _compiledPasses.Add(_passes[passIndex]); - } - - // Restore culling flags - for (var i = 0; i < _passes.Count && i < cached.passCulledFlags.Count; i++) - { - _passes[i].culled = cached.passCulledFlags[i]; - } - - // Restore aliasing mappings (need to update ResourceAliasingManager) - _aliasingManager.RestoreFromCache(cached.logicalToPhysical, cached.placedResources); - - // Restore compiled barriers (deep copy to avoid shared references) - _compiledBarriers.Clear(); - for (var i = 0; i < cached.compiledBarriers.Count; i++) - { - _compiledBarriers.Add(cached.compiledBarriers[i]); - } - - for (var i = 0; i < _resources.ResourceCount; i++) - { - var res = _resources.Resources[i]; - - if (!res.isImported) - { - res.backingResource = cached.backingResources[i]; - } - } - - BuildNativeRenderPasses(); - } - - /// - /// Stores current compilation results in the cache. - /// - private void StoreInCache(ulong graphHash) - { - var cacheData = new CachedCompilation(); - - // Store view state - cacheData.viewState = _currentViewState; - - // Store compiled pass indices - for (var i = 0; i < _compiledPasses.Count; i++) - { - cacheData.compiledPassIndices.Add(_compiledPasses[i].index); - } - - // Store culling flags for all passes - for (var i = 0; i < _passes.Count; i++) - { - cacheData.passCulledFlags.Add(_passes[i].culled); - } - - // Store aliasing mappings - _aliasingManager.StoreToCache(cacheData.logicalToPhysical, cacheData.placedResources); - - // Store compiled barriers - for (var i = 0; i < _compiledBarriers.Count; i++) - { - cacheData.compiledBarriers.Add(_compiledBarriers[i]); - } - - for (var i = 0; i < _resources.ResourceCount; i++) - { - var res = _resources.Resources[i]; - cacheData.backingResources.Add(res.backingResource); - } - - _compilationCache.Store(graphHash, cacheData); - } - - private void UnculProducer(Identifier resource) - { - var res = _resources.GetResource(resource); - if (res.producerPass >= 0) - { - var producer = _passes[res.producerPass]; - if (producer.culled) - { - producer.culled = false; - UnculDependencies(producer); - } - } - } - - private void UnculDependencies(RenderGraphPassBase pass) - { - // Un-cull producers of read resources - for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) - { - var readList = pass.resourceReads[i]; - for (var j = 0; j < readList.Count; j++) - { - UnculProducer(readList[j]); - } - } - - // Un-cull producers of color attachments - for (var i = 0; i < pass.maxColorIndex; i++) - { - if (pass.colorAccess[i].id.IsValid) - { - UnculProducer(pass.colorAccess[i].id.AsResource()); - } - } - - // Un-cull producer of depth attachment - if (pass.depthAccess.id.IsValid) - { - UnculProducer(pass.depthAccess.id.AsResource()); - } - - // Un-cull producers of UAV resources (if not already in reads/writes) - for (var i = 0; i < pass.randomAccess.Count; i++) - { - UnculProducer(pass.randomAccess[i]); - } - } - - /// - /// Compiles all barriers needed for execution, storing only target states. - /// Barriers include aliasing barriers and implicit state transitions. - /// - private void CompileBarriers() - { - _compiledBarriers.Clear(); - - // Process each compiled pass in order - for (var passIdx = 0; passIdx < _compiledPasses.Count; passIdx++) - { - var pass = _compiledPasses[passIdx]; - - // 1. Insert aliasing barriers for resources that reuse physical memory - InsertAliasingBarriers(pass, passIdx); - - // 2. Compile implicit transitions for all resources accessed by this pass - CompileImplicitTransitions(pass, passIdx); - } - } - - /// - /// Inserts aliasing barriers when a placed resource is reused. - /// - private void InsertAliasingBarriers(RenderGraphPassBase pass, int passIdx) - { - // Check all resources written by this pass (both textures and buffers) - for (var resType = 0; resType < (int)RenderGraphResourceType.Count; resType++) - { - var writeList = pass.resourceWrites[resType]; - for (var i = 0; i < writeList.Count; i++) - { - var id = writeList[i]; - var resource = _resources.GetResource(id); - - // Skip imported resources - if (resource.isImported) - { - continue; - } - - // Check if this is the first use of this logical resource - if (resource.firstUsePass == pass.index) - { - // Get the placed resource - var placedIndex = _aliasingManager.GetPlacedResourceIndex(id.Value); - if (placedIndex >= 0) - { - var placed = _aliasingManager.GetPlacedResource(placedIndex); - - // If this placed resource has multiple aliased resources, - // we need an aliasing barrier when switching between them - if (placed != null && placed.aliasedLogicalResources.Count > 1) - { - // Find the resource that used this placed memory most recently before this pass - Identifier resourceBefore = default; - var mostRecentLastUse = -1; - - foreach (var otherLogicalIndex in placed.aliasedLogicalResources) - { - if (otherLogicalIndex != id.Value) - { - // Get resource by global index - var otherResource = _resources.GetResourceByIndex(otherLogicalIndex); - - // Check if this resource finished before our resource starts - if (otherResource.lastUsePass < pass.index && - otherResource.lastUsePass > mostRecentLastUse) - { - mostRecentLastUse = otherResource.lastUsePass; - resourceBefore = new Identifier(otherLogicalIndex); - } - } - } - - // If we found a previous resource, insert aliasing barrier - if (mostRecentLastUse >= 0) - { - // Aliasing Requirement: Transition to Undefined, Sync with Predecessor - var targetState = new ResourceBarrierData(BarrierLayout.Undefined, BarrierAccess.NoAccess, BarrierSync.None); - var barrier = new CompiledBarrier - { - PassIndex = passIdx, - Resource = id, - TargetState = targetState, - AliasingPredecessor = resourceBefore, - Flags = BarrierFlags.FirstUsage | BarrierFlags.Discard, - ResourceType = resource.type - }; - _compiledBarriers.Add(barrier); - } - } - } - } - } - } - } - - /// - /// Compiles implicit state transitions for all resources accessed by a pass. - /// Stores only the target state - the before state will be queried from ResourceDatabase at execution time. - /// - private void CompileImplicitTransitions(RenderGraphPassBase pass, int passIdx) - { - // Helper to add a compiled barrier for a resource transition - void AddTransition(Identifier id, ResourceBarrierData targetState) - { - var resource = _resources.GetResource(id); - var barrier = new CompiledBarrier - { - PassIndex = passIdx, - Resource = id, - TargetState = targetState, - AliasingPredecessor = Identifier.Invalid, - Flags = BarrierFlags.None, - ResourceType = resource.type - }; - _compiledBarriers.Add(barrier); - } - - // Compile transitions for read resources - for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) - { - var readList = pass.resourceReads[i]; - for (var j = 0; j < readList.Count; j++) - { - var handle = readList[j]; - var targetState = GetBufferReadBarrierData(handle, pass, (RenderGraphResourceType)i); - AddTransition(handle, targetState); - } - } - - // Compile transitions based on pass type - switch (pass.type) - { - case RenderPassType.Raster: - // Color attachments - for (var i = 0; i <= pass.maxColorIndex; i++) - { - if (pass.colorAccess[i].id.IsValid) - { - var usage = pass.colorAccess[i].usage; - var targetState = new ResourceBarrierData(usage.Layout, usage.Access, usage.Sync); - AddTransition(pass.colorAccess[i].id.AsResource(), targetState); - } - } - - // Depth attachment - if (pass.depthAccess.id.IsValid) - { - var usage = pass.depthAccess.usage; - var targetState = new ResourceBarrierData(usage.Layout, usage.Access, usage.Sync); - AddTransition(pass.depthAccess.id.AsResource(), targetState); - } - - // UAV resources - var uavState = new ResourceBarrierData(BarrierLayout.UnorderedAccess, BarrierAccess.UnorderedAccess, BarrierSync.AllShading); - for (var i = 0; i < pass.randomAccess.Count; i++) - { - AddTransition(pass.randomAccess[i], uavState); - } - break; - - case RenderPassType.Compute: - var computeUavState = new ResourceBarrierData(BarrierLayout.UnorderedAccess, BarrierAccess.UnorderedAccess, BarrierSync.ComputeShading); - for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) - { - var writeList = pass.resourceWrites[i]; - for (var j = 0; j < writeList.Count; j++) - { - AddTransition(writeList[j], computeUavState); - } - } - break; - - case RenderPassType.Unsafe: - var rtState = new ResourceBarrierData(BarrierLayout.RenderTarget, BarrierAccess.RenderTarget, BarrierSync.RenderTarget); - for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) - { - var writeList = pass.resourceWrites[i]; - for (var j = 0; j < writeList.Count; j++) - { - AddTransition(writeList[j], rtState); - } - } - - var unsafeUavState = new ResourceBarrierData(BarrierLayout.UnorderedAccess, BarrierAccess.UnorderedAccess, BarrierSync.AllShading); - for (var i = 0; i < pass.randomAccess.Count; i++) - { - AddTransition(pass.randomAccess[i], unsafeUavState); - } - break; - } - } - - private ResourceBarrierData GetBufferReadBarrierData(Identifier handle, RenderGraphPassBase pass, RenderGraphResourceType resourceType) - { - if (resourceType == RenderGraphResourceType.Texture) - { - return new ResourceBarrierData(BarrierLayout.ShaderResource, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading); - } - - var sync = BarrierSync.PixelShading | BarrierSync.NonPixelShading; - var access = BarrierAccess.ShaderResource; - - var resource = _resources.GetResource(handle); - if (resource.bufferDesc.Usage.HasFlag(BufferUsage.IndirectArgument)) - { - sync = BarrierSync.ExecuteIndirect; - access = BarrierAccess.IndirectArgument; - } - - return new ResourceBarrierData(BarrierLayout.Undefined, access, sync); - } - - /// - /// Builds native render passes by merging compatible consecutive raster passes. - /// Uses conservative merging: only merge passes with identical attachments and no barriers between them. - /// - private void BuildNativeRenderPasses() - { - // Clear previous native passes - for (var i = 0; i < _nativePasses.Count; i++) - { - _objectPool.Return(_nativePasses[i]); - } - _nativePasses.Clear(); - - NativeRenderPass? currentNativePass = null; - - for (var i = 0; i < _compiledPasses.Count; i++) - { - var pass = _compiledPasses[i]; - - // Only raster passes can be merged into native render passes - // Compute passes break the current native render pass - if (pass.type != RenderPassType.Raster) - { - // Close current native pass if open - if (currentNativePass != null) - { - _nativePasses.Add(currentNativePass); - currentNativePass = null; - } - continue; // Compute/Unsafe passes execute outside native render passes - } - - - // Check if we can merge with current native pass - if (currentNativePass != null && CanMergePasses(currentNativePass, pass, i)) - { - // Merge into existing native pass - currentNativePass.mergedPassIndices.Add(i); - currentNativePass.lastLogicalPass = i; - } - else - { - // Start new native pass - if (currentNativePass != null) - { - _nativePasses.Add(currentNativePass); - } - - currentNativePass = CreateNativePass(pass, i); - } - } - - // Add final native pass - if (currentNativePass != null) - { - _nativePasses.Add(currentNativePass); - } - - // Infer load/store operations for all native passes - for (var i = 0; i < _nativePasses.Count; i++) - { - InferLoadStoreOps(_nativePasses[i]); - } - } - - /// - /// Creates a new native render pass from a logical pass. - /// - private NativeRenderPass CreateNativePass(RenderGraphPassBase pass, int passIndex) - { - var nativePass = _objectPool.Rent(); - nativePass.Reset(); - - nativePass.index = _nativePasses.Count; - nativePass.mergedPassIndices.Add(passIndex); - nativePass.firstLogicalPass = passIndex; - nativePass.lastLogicalPass = passIndex; - nativePass.allowUAVWrites = pass.randomAccess.Count > 0; - - // Copy color attachments - nativePass.colorAttachmentCount = pass.maxColorIndex + 1; - for (var i = 0; i <= pass.maxColorIndex; i++) - { - var access = pass.colorAccess[i]; - nativePass.colorAttachments[i] = new RenderTargetInfo - { - texture = access.id, - access = access.accessFlags - }; - } - - // Copy depth attachment - if (!pass.depthAccess.id.IsInvalid) - { - nativePass.hasDepthAttachment = true; - nativePass.depthAttachment = new DepthStencilInfo - { - texture = pass.depthAccess.id, - access = pass.depthAccess.accessFlags - }; - } - - return nativePass; - } - - /// - /// Checks if a logical pass can be merged into an existing native render pass. - /// Conservative merging: only merge if attachments match and no barriers needed. - /// - private bool CanMergePasses(NativeRenderPass nativePass, RenderGraphPassBase pass, int passIndex) - { - // Don't merge if UAVs are involved (conservative) - if (pass.randomAccess.Count > 0 || nativePass.allowUAVWrites) - { - return false; - } - - // Check if attachment configuration matches - if (!AttachmentsMatch(nativePass, pass)) - { - return false; - } - - // Check if barriers are needed between last merged pass and this pass - if (RequiresBarrierBetweenPasses(nativePass.lastLogicalPass, passIndex)) - { - return false; - } - - return true; - } - - /// - /// Checks if the attachment configuration of a pass matches the native pass. - /// - private static bool AttachmentsMatch(NativeRenderPass nativePass, RenderGraphPassBase pass) - { - // Check color attachment count - if (nativePass.colorAttachmentCount != pass.maxColorIndex + 1) - { - return false; - } - - // Check each color attachment - for (var i = 0; i < nativePass.colorAttachmentCount; i++) - { - if (nativePass.colorAttachments[i].texture != pass.colorAccess[i].id) - { - return false; - } - } - - // Check depth attachment - if (nativePass.hasDepthAttachment != !pass.depthAccess.id.IsInvalid) - { - return false; - } - - if (nativePass.hasDepthAttachment && nativePass.depthAttachment.texture != pass.depthAccess.id) - { - return false; - } - - return true; - } - - /// - /// Checks if any barriers are required between two passes that would prevent merging. - /// Only barriers affecting render targets prevent merging; SRV barriers are fine. - /// - private bool RequiresBarrierBetweenPasses(int passA, int passB) - { - var laterPass = _compiledPasses[passB]; - - // Build a set of render target resource IDs (color + depth) - var renderTargets = new HashSet>(); - for (var i = 0; i <= laterPass.maxColorIndex; i++) - { - if (!laterPass.colorAccess[i].id.IsInvalid) - { - renderTargets.Add(laterPass.colorAccess[i].id.AsResource()); - } - } - if (!laterPass.depthAccess.id.IsInvalid) - { - renderTargets.Add(laterPass.depthAccess.id.AsResource()); - } - - // Check if any compiled barriers for passB affect render targets - for (var i = 0; i < _compiledBarriers.Count; i++) - { - if (_compiledBarriers[i].PassIndex == passB) - { - // Only prevent merge if barrier affects a render target - if (renderTargets.Contains(_compiledBarriers[i].Resource)) - { - return true; // Barrier affects render target, cannot merge - } - } - - if (_compiledBarriers[i].PassIndex > passB) - { - break; // No more barriers for this pass - } - } - - return false; - } - - /// - /// Infers optimal load/store operations for all attachments in a native render pass. - /// Uses resource lifetime information to minimize memory bandwidth (critical for TBDR GPUs). - /// - private void InferLoadStoreOps(NativeRenderPass nativePass) - { - // Infer load/store ops for color attachments - for (var i = 0; i < nativePass.colorAttachmentCount; i++) - { - ref var attachment = ref nativePass.colorAttachments[i]; - var resource = _resources.GetResource(attachment.texture); - var flags = attachment.access; - - // ===== LOAD OP INFERENCE ===== - - // 1. First use - if (resource.firstUsePass == nativePass.firstLogicalPass) - { - // Clear at first use - if (resource.rgTextureDesc.clearAtFirstUse) - { - attachment.loadOp = AttachmentLoadOp.Clear; - attachment.clearColor = resource.rgTextureDesc.clearColor; - } - else - { - attachment.loadOp = AttachmentLoadOp.DontCare; - } - } - // 2. Discard flag: DontCare for performance - else if (flags.HasFlag(AccessFlags.Discard)) - { - attachment.loadOp = AttachmentLoadOp.DontCare; - } - // 3. Read flag: Must preserve existing contents - else if (flags.HasFlag(AccessFlags.Read)) - { - attachment.loadOp = AttachmentLoadOp.Load; - } - // 4. Continuation from previous pass - else - { - attachment.loadOp = AttachmentLoadOp.Load; - } - - // ===== STORE OP INFERENCE ===== - - // Last use: No one needs it after this native pass - if (resource.lastUsePass == nativePass.lastLogicalPass) - { - if (resource.rgTextureDesc.discardAtLastUse) - { - attachment.storeOp = AttachmentStoreOp.DontCare; - } - else - { - attachment.storeOp = AttachmentStoreOp.Store; - } - } - // Intermediate: Store for future passes - else - { - attachment.storeOp = AttachmentStoreOp.Store; - } - - } - - // Infer load/store ops for depth attachment - if (nativePass.hasDepthAttachment) - { - ref var attachment = ref nativePass.depthAttachment; - var resource = _resources.GetResource(attachment.texture); - var flags = attachment.access; - - // ===== LOAD OP INFERENCE ===== - - // 1. First Use - if (resource.firstUsePass == nativePass.firstLogicalPass) - { - // Clear at first use - if (resource.rgTextureDesc.clearAtFirstUse) - { - attachment.loadOp = AttachmentLoadOp.Clear; - attachment.clearDepth = resource.rgTextureDesc.clearDepth; - attachment.clearStencil = resource.rgTextureDesc.clearStencil; - } - else - { - attachment.loadOp = AttachmentLoadOp.DontCare; - } - } - // 2. Discard flag: DontCare for performance - else if (flags.HasFlag(AccessFlags.Discard)) - { - attachment.loadOp = AttachmentLoadOp.DontCare; - } - // 3. Read flag: Must preserve existing contents - else if (flags.HasFlag(AccessFlags.Read)) - { - attachment.loadOp = AttachmentLoadOp.Load; - } - // 4. Continuation from previous pass - else - { - attachment.loadOp = AttachmentLoadOp.Load; - } - - // ===== STORE OP INFERENCE ===== - - // Depth is commonly discarded (depth-only passes, intermediate depth) - if (resource.lastUsePass == nativePass.lastLogicalPass) - { - if (resource.rgTextureDesc.discardAtLastUse) - { - attachment.storeOp = AttachmentStoreOp.DontCare; - } - else - { - attachment.storeOp = AttachmentStoreOp.Store; - } - } - else - { - attachment.storeOp = AttachmentStoreOp.Store; - } - - } - } - - /// - /// Executes all compiled passes using native render passes where possible. - /// - public unsafe void Execute(ICommandBuffer cmd) - { - if (!_compiled) - { - throw new InvalidOperationException("Render graph must be compiled before execution. Call Compile(viewState) first."); - } - - var barrierIndex = 0; - var nativePassIndex = 0; - var logicalPassIndex = 0; - - _context.SetCommandBuffer(cmd); - - var pPassRTDescs = stackalloc PassRenderTargetDesc[8]; - var pRtFormats = stackalloc TextureFormat[8]; - - while (logicalPassIndex < _compiledPasses.Count) - { - var pass = _compiledPasses[logicalPassIndex]; - - // Check if this pass is part of a native render pass - if (pass.type == RenderPassType.Raster && nativePassIndex < _nativePasses.Count) - { - var nativePass = _nativePasses[nativePassIndex]; - - // Build barriers for ALL merged passes before beginning the native render pass - for (var i = 0; i < nativePass.mergedPassIndices.Count; i++) - { - var mergedPassIdx = nativePass.mergedPassIndices[i]; - ExecuteBarriersForPass(cmd, mergedPassIdx, ref barrierIndex); - } - - // Begin native render pass - for (var i = 0; i < nativePass.colorAttachmentCount; i++) - { - var attachment = nativePass.colorAttachments[i]; - pPassRTDescs[i] = new PassRenderTargetDesc - { - Texture = _resources.GetResource(attachment.texture).backingResource.AsTexture(), - ClearColor = attachment.clearColor, - LoadOp = attachment.loadOp, - StoreOp = attachment.storeOp - }; - } - - var depthDesc = new PassDepthStencilDesc - { - Texture = nativePass.hasDepthAttachment - ? _resources.GetResource(nativePass.depthAttachment.texture).backingResource.AsTexture() - : Handle.Invalid, - ClearDepth = nativePass.depthAttachment.clearDepth, - ClearStencil = nativePass.depthAttachment.clearStencil, - DepthLoadOp = nativePass.hasDepthAttachment - ? nativePass.depthAttachment.loadOp - : AttachmentLoadOp.DontCare, - DepthStoreOp = nativePass.hasDepthAttachment - ? nativePass.depthAttachment.storeOp - : AttachmentStoreOp.DontCare, - StencilLoadOp = nativePass.hasDepthAttachment - ? nativePass.depthAttachment.loadOp - : AttachmentLoadOp.DontCare, - StencilStoreOp = nativePass.hasDepthAttachment - ? nativePass.depthAttachment.storeOp - : AttachmentStoreOp.DontCare - }; - - cmd.BeginRenderPass(new Span(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc); - - for (var i = 0; i < nativePass.colorAttachmentCount; i++) - { - var attachment = nativePass.colorAttachments[i]; - var resource = _resources.GetResource(attachment.texture); - pRtFormats[i] = resource.rgTextureDesc.format; - } - - var depthFormat = nativePass.hasDepthAttachment - ? _resources.GetResource(nativePass.depthAttachment.texture).rgTextureDesc.format - : TextureFormat.Unknown; - _context.SetRenderTargetFormats(new ReadOnlySpan(pRtFormats, nativePass.colorAttachmentCount), depthFormat); - - // Build all merged logical passes within this native render pass - for (var i = 0; i < nativePass.mergedPassIndices.Count; i++) - { - var mergedPassIdx = nativePass.mergedPassIndices[i]; - var mergedPass = _compiledPasses[mergedPassIdx]; - mergedPass.Execute(_context); - logicalPassIndex++; - } - - cmd.EndRenderPass(); - nativePassIndex++; - } - else - { - // Compute pass or standalone raster pass (not merged) or Unsafe pass - ExecuteBarriersForPass(cmd, logicalPassIndex, ref barrierIndex); - pass.Execute(_context); - logicalPassIndex++; - } - - } - } - - /// - /// Executes all barriers for a specific pass. - /// Uses pre-compiled barriers and queries before state from ResourceDatabase. - /// - private unsafe void ExecuteBarriersForPass(ICommandBuffer cmd, int passIndex, ref int barrierIndex) - { - const int MaxBatch = 64; - var barriers = stackalloc BarrierDesc[MaxBatch]; - var barrierCount = 0; - - void Flush() - { - if (barrierCount > 0) - { - cmd.ResourceBarrier(new ReadOnlySpan(barriers, barrierCount)); - barrierCount = 0; - } - } - - // Process all pre-compiled barriers for this pass - while (barrierIndex < _compiledBarriers.Count && _compiledBarriers[barrierIndex].PassIndex == passIndex) - { - var compiledBarrier = _compiledBarriers[barrierIndex++]; - var resource = _resources.GetResource(compiledBarrier.Resource); - var resourceHandle = resource.backingResource; - - // Always query the before state from ResourceDatabase (single source of truth) - var currentState = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(resourceHandle).GetValueOrThrow(); - - BarrierLayout layoutBefore; - BarrierAccess accessBefore; - BarrierSync syncBefore; - - // Handle aliasing barriers specially - if (compiledBarrier.AliasingPredecessor.IsValid) - { - var predHandle = _resources.GetResource(compiledBarrier.AliasingPredecessor).backingResource; - var predState = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(predHandle).GetValueOrThrow(); - - layoutBefore = BarrierLayout.Undefined; - accessBefore = BarrierAccess.NoAccess; - syncBefore = predState.Sync; - } - else - { - layoutBefore = currentState.Layout; - accessBefore = currentState.Access; - syncBefore = currentState.Sync; - } - - var target = compiledBarrier.TargetState; - - // Skip if already in target state (optimization) - if (!compiledBarrier.AliasingPredecessor.IsValid && - layoutBefore == target.Layout && - accessBefore == target.Access && - syncBefore == target.Sync) - { - continue; - } - - // Create barrier descriptor - BarrierDesc desc; - if (compiledBarrier.ResourceType == RenderGraphResourceType.Texture) - { - desc = BarrierDesc.Texture(resourceHandle, - syncBefore, target.Sync, - accessBefore, target.Access, - layoutBefore, target.Layout, - discard: compiledBarrier.Flags.HasFlag(BarrierFlags.Discard)); - } - else - { - desc = BarrierDesc.Buffer(resourceHandle, - syncBefore, target.Sync, - accessBefore, target.Access); - } - - if (barrierCount >= MaxBatch) - { - Flush(); - } - - barriers[barrierCount++] = desc; - } - - Flush(); - } - - public void Dispose() - { - foreach (var resource in _resources.Resources) - { - _graphicsEngine.ResourceDatabase.ReleaseResource(resource.backingResource); - } - - _graphicsEngine.ResourceDatabase.ReleaseResource(_resourceHeap); - - // We need to reset the whole graph to return resources to the pool - // FIX: Ideally we should call dispose here for each subsystem - Reset(); - } -}