Refactor render graph: modular compilation & execution
Major overhaul of render graph system for modularity and performance: - Split compilation and execution logic into dedicated classes (Compiler, Executor, NativePassBuilder, Barriers) - Overhauled barrier system: now uses CompiledBarrier with target state only, querying before state at execution - Resource size/alignment now queried from D3D12 device for accurate heap allocation - ResourceDesc now includes Type field and asserts correct union access - Centralized D3D12 interop logic in D3D12Utility extensions - Added RenderGraphHasher for structural graph hashing and cache invalidation - RenderGraph class simplified to orchestrate specialized components - ResourceAliasingManager now uses allocator for size queries - Compilation cache now stores compiled barriers, reducing memory usage - Improved comments, debug assertions, and removed redundant code Result: more maintainable, efficient, and robust render graph pipeline.
This commit is contained in:
@@ -435,40 +435,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator
|
|||||||
return uavDesc;
|
return uavDesc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static D3D12_RESOURCE_FLAGS ConvertTextureUsage(TextureUsage usage)
|
|
||||||
{
|
|
||||||
var flags = D3D12_RESOURCE_FLAG_NONE;
|
|
||||||
|
|
||||||
if (usage.HasFlag(TextureUsage.RenderTarget))
|
|
||||||
{
|
|
||||||
flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usage.HasFlag(TextureUsage.DepthStencil))
|
|
||||||
{
|
|
||||||
flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usage.HasFlag(TextureUsage.UnorderedAccess))
|
|
||||||
{
|
|
||||||
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static D3D12_RESOURCE_FLAGS ConvertBufferUsage(BufferUsage usage)
|
|
||||||
{
|
|
||||||
var flags = D3D12_RESOURCE_FLAG_NONE;
|
|
||||||
|
|
||||||
if (usage.HasFlag(BufferUsage.Raw) || usage.HasFlag(BufferUsage.UnorderedAccess))
|
|
||||||
{
|
|
||||||
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static D3D12_HEAP_TYPE ConvertMemoryType(ResourceMemoryType memoryType)
|
private static D3D12_HEAP_TYPE ConvertMemoryType(ResourceMemoryType memoryType)
|
||||||
{
|
{
|
||||||
return memoryType switch
|
return memoryType switch
|
||||||
@@ -559,7 +525,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
|
|
||||||
private HRESULT CreateResource(D3D12MA_ALLOCATION_DESC* pAllocationDesc, D3D12_RESOURCE_DESC* pResourceDesc, D3D12_RESOURCE_STATES initialState, CreationOptions options, Guid* riid, void** ppv)
|
private HRESULT CreateResource(D3D12MA_ALLOCATION_DESC* pAllocationDesc, D3D12_RESOURCE_DESC* pResourceDesc, D3D12_RESOURCE_STATES initialState, CreationOptions options, Guid* riid, void** ppv)
|
||||||
{
|
{
|
||||||
var hr = S.S_OK;
|
HRESULT hr;
|
||||||
|
|
||||||
if (options.AllocationType == ResourceAllocationType.Suballocation)
|
if (options.AllocationType == ResourceAllocationType.Suballocation)
|
||||||
{
|
{
|
||||||
@@ -581,6 +547,26 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
return hr;
|
return hr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResourceSizeInfo GetSizeInfo(ResourceDesc desc)
|
||||||
|
{
|
||||||
|
D3D12_RESOURCE_DESC d3d12Desc;
|
||||||
|
if (desc.Type == ResourceType.Texture)
|
||||||
|
{
|
||||||
|
d3d12Desc = desc.TextureDescription.ToD3D12ResourceDesc();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d3d12Desc = desc.BufferDescription.ToD3D12ResourceDesc();
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = _device.NativeDevice.Get()->GetResourceAllocationInfo(0, 1, &d3d12Desc);
|
||||||
|
return new ResourceSizeInfo
|
||||||
|
{
|
||||||
|
Size = info.SizeInBytes,
|
||||||
|
Alignment = info.Alignment
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string name)
|
public Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string name)
|
||||||
{
|
{
|
||||||
var allocDesc = new D3D12MA_ALLOCATION_DESC
|
var allocDesc = new D3D12MA_ALLOCATION_DESC
|
||||||
@@ -633,51 +619,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
|
|
||||||
CheckTexture2DSize(desc.Width, desc.Height);
|
CheckTexture2DSize(desc.Width, desc.Height);
|
||||||
|
|
||||||
var dxgiFormat = D3D12Utility.ToDXGIFormat(desc.Format);
|
var resourceDesc = desc.ToD3D12ResourceDesc();
|
||||||
var maxDimension = Math.Max(desc.Width, Math.Max(desc.Height, desc.Slice));
|
|
||||||
var mipLevels = desc.MipLevels == 0
|
|
||||||
? (ushort)(1 + Math.Floor(Math.Log2(maxDimension)))
|
|
||||||
: (ushort)desc.MipLevels;
|
|
||||||
|
|
||||||
var resourceFlags = ConvertTextureUsage(desc.Usage);
|
|
||||||
var resourceDesc = desc.Dimension switch
|
|
||||||
{
|
|
||||||
TextureDimension.Texture2D => D3D12_RESOURCE_DESC.Tex2D(
|
|
||||||
dxgiFormat,
|
|
||||||
desc.Width,
|
|
||||||
desc.Height,
|
|
||||||
mipLevels: mipLevels,
|
|
||||||
flags: resourceFlags),
|
|
||||||
TextureDimension.Texture3D => D3D12_RESOURCE_DESC.Tex3D(
|
|
||||||
dxgiFormat,
|
|
||||||
desc.Width,
|
|
||||||
desc.Height,
|
|
||||||
(ushort)desc.Slice,
|
|
||||||
flags: resourceFlags),
|
|
||||||
TextureDimension.TextureCube => D3D12_RESOURCE_DESC.Tex2D(
|
|
||||||
dxgiFormat,
|
|
||||||
desc.Width,
|
|
||||||
desc.Height,
|
|
||||||
mipLevels: mipLevels,
|
|
||||||
arraySize: 6,
|
|
||||||
flags: resourceFlags),
|
|
||||||
TextureDimension.Texture2DArray => D3D12_RESOURCE_DESC.Tex2D(
|
|
||||||
dxgiFormat,
|
|
||||||
desc.Width,
|
|
||||||
desc.Height,
|
|
||||||
mipLevels: mipLevels,
|
|
||||||
arraySize: (ushort)desc.Slice,
|
|
||||||
flags: resourceFlags),
|
|
||||||
TextureDimension.TextureCubeArray => D3D12_RESOURCE_DESC.Tex2D(
|
|
||||||
dxgiFormat,
|
|
||||||
desc.Width,
|
|
||||||
desc.Height,
|
|
||||||
mipLevels: mipLevels,
|
|
||||||
arraySize: (ushort)(desc.Slice * 6),
|
|
||||||
flags: resourceFlags),
|
|
||||||
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
var allocationDesc = new D3D12MA_ALLOCATION_DESC
|
var allocationDesc = new D3D12MA_ALLOCATION_DESC
|
||||||
{
|
{
|
||||||
HeapType = D3D12_HEAP_TYPE_DEFAULT,
|
HeapType = D3D12_HEAP_TYPE_DEFAULT,
|
||||||
@@ -715,7 +657,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
||||||
|
|
||||||
var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray;
|
var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray;
|
||||||
var srvDesc = CreateTextureSrvDesc(pResource, mipLevels, desc.Slice, isCubeMap);
|
var srvDesc = CreateTextureSrvDesc(pResource, resourceDesc.MipLevels, resourceDesc.DepthOrArraySize, isCubeMap);
|
||||||
|
|
||||||
_device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
_device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
||||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
|
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
|
||||||
@@ -782,14 +724,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
CheckBufferSize(desc.Size);
|
CheckBufferSize(desc.Size);
|
||||||
|
|
||||||
var alignedSize = desc.Size;
|
var resourceDesc = desc.ToD3D12ResourceDesc();
|
||||||
if (desc.Usage.HasFlag(BufferUsage.Constant))
|
|
||||||
{
|
|
||||||
// D3D12 CBV size must be 256-byte aligned
|
|
||||||
alignedSize = (uint)(desc.Size + 255) & ~255u;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resourceDesc = D3D12_RESOURCE_DESC.Buffer(alignedSize, ConvertBufferUsage(desc.Usage));
|
|
||||||
var isRaw = desc.Usage.HasFlag(BufferUsage.Raw);
|
var isRaw = desc.Usage.HasFlag(BufferUsage.Raw);
|
||||||
|
|
||||||
var allocationDesc = new D3D12MA_ALLOCATION_DESC
|
var allocationDesc = new D3D12MA_ALLOCATION_DESC
|
||||||
@@ -839,7 +774,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
|||||||
var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC
|
var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC
|
||||||
{
|
{
|
||||||
BufferLocation = pResource->GetGPUVirtualAddress(),
|
BufferLocation = pResource->GetGPUVirtualAddress(),
|
||||||
SizeInBytes = (uint)alignedSize
|
SizeInBytes = (uint)resourceDesc.Width,
|
||||||
};
|
};
|
||||||
|
|
||||||
_device.NativeDevice.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle);
|
_device.NativeDevice.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle);
|
||||||
|
|||||||
@@ -294,7 +294,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
}
|
}
|
||||||
|
|
||||||
info.Release(_descriptorAllocator);
|
info.Release(_descriptorAllocator);
|
||||||
//System.Diagnostics.Debug.Assert(info.Release(_descriptorAllocator) == 0);
|
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
_resourceName.Remove(handle, out var name);
|
_resourceName.Remove(handle, out var name);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using static TerraFX.Aliases.DXGI_Alias;
|
|||||||
|
|
||||||
namespace Ghost.Graphics.D3D12.Utilities;
|
namespace Ghost.Graphics.D3D12.Utilities;
|
||||||
|
|
||||||
internal unsafe static class D3D12Utility
|
internal static unsafe class D3D12Utility
|
||||||
{
|
{
|
||||||
public static void SetName<T>(ref this T obj, ReadOnlySpan<char> name)
|
public static void SetName<T>(ref this T obj, ReadOnlySpan<char> name)
|
||||||
where T : unmanaged, ID3D12Object.Interface
|
where T : unmanaged, ID3D12Object.Interface
|
||||||
@@ -278,6 +278,102 @@ internal unsafe static class D3D12Utility
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static D3D12_RESOURCE_FLAGS ToD3D12ResourceFlag(this TextureUsage usage)
|
||||||
|
{
|
||||||
|
var flags = D3D12_RESOURCE_FLAG_NONE;
|
||||||
|
|
||||||
|
if (usage.HasFlag(TextureUsage.RenderTarget))
|
||||||
|
{
|
||||||
|
flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usage.HasFlag(TextureUsage.DepthStencil))
|
||||||
|
{
|
||||||
|
flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usage.HasFlag(TextureUsage.UnorderedAccess))
|
||||||
|
{
|
||||||
|
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in TextureDesc desc)
|
||||||
|
{
|
||||||
|
var dxgiFormat = desc.Format.ToDXGIFormat();
|
||||||
|
var maxDimension = Math.Max(desc.Width, Math.Max(desc.Height, desc.Slice));
|
||||||
|
var mipLevels = desc.MipLevels == 0
|
||||||
|
? (ushort)(1 + Math.Floor(Math.Log2(maxDimension)))
|
||||||
|
: (ushort)desc.MipLevels;
|
||||||
|
|
||||||
|
var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
|
||||||
|
return desc.Dimension switch
|
||||||
|
{
|
||||||
|
TextureDimension.Texture2D => D3D12_RESOURCE_DESC.Tex2D(
|
||||||
|
dxgiFormat,
|
||||||
|
desc.Width,
|
||||||
|
desc.Height,
|
||||||
|
mipLevels: mipLevels,
|
||||||
|
flags: resourceFlags),
|
||||||
|
TextureDimension.Texture3D => D3D12_RESOURCE_DESC.Tex3D(
|
||||||
|
dxgiFormat,
|
||||||
|
desc.Width,
|
||||||
|
desc.Height,
|
||||||
|
(ushort)desc.Slice,
|
||||||
|
flags: resourceFlags),
|
||||||
|
TextureDimension.TextureCube => D3D12_RESOURCE_DESC.Tex2D(
|
||||||
|
dxgiFormat,
|
||||||
|
desc.Width,
|
||||||
|
desc.Height,
|
||||||
|
mipLevels: mipLevels,
|
||||||
|
arraySize: 6,
|
||||||
|
flags: resourceFlags),
|
||||||
|
TextureDimension.Texture2DArray => D3D12_RESOURCE_DESC.Tex2D(
|
||||||
|
dxgiFormat,
|
||||||
|
desc.Width,
|
||||||
|
desc.Height,
|
||||||
|
mipLevels: mipLevels,
|
||||||
|
arraySize: (ushort)desc.Slice,
|
||||||
|
flags: resourceFlags),
|
||||||
|
TextureDimension.TextureCubeArray => D3D12_RESOURCE_DESC.Tex2D(
|
||||||
|
dxgiFormat,
|
||||||
|
desc.Width,
|
||||||
|
desc.Height,
|
||||||
|
mipLevels: mipLevels,
|
||||||
|
arraySize: (ushort)(desc.Slice * 6),
|
||||||
|
flags: resourceFlags),
|
||||||
|
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static D3D12_RESOURCE_FLAGS ToD3D12ResourceFlag(this BufferUsage usage)
|
||||||
|
{
|
||||||
|
var flags = D3D12_RESOURCE_FLAG_NONE;
|
||||||
|
|
||||||
|
if (usage.HasFlag(BufferUsage.Raw) || usage.HasFlag(BufferUsage.UnorderedAccess))
|
||||||
|
{
|
||||||
|
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in BufferDesc desc)
|
||||||
|
{
|
||||||
|
var alignedSize = desc.Size;
|
||||||
|
if (desc.Usage.HasFlag(BufferUsage.Constant))
|
||||||
|
{
|
||||||
|
// D3D12 CBV size must be 256-byte aligned
|
||||||
|
alignedSize = (uint)(desc.Size + 255) & ~255u;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
|
||||||
|
return D3D12_RESOURCE_DESC.Buffer(alignedSize, resourceFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static ResourceDesc ToResourceDesc(this D3D12_RESOURCE_DESC desc)
|
public static ResourceDesc ToResourceDesc(this D3D12_RESOURCE_DESC desc)
|
||||||
{
|
{
|
||||||
if (desc.Dimension == D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_BUFFER)
|
if (desc.Dimension == D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_BUFFER)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Ghost.Core;
|
|||||||
using Ghost.Core.Graphics;
|
using Ghost.Core.Graphics;
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
@@ -425,22 +426,44 @@ public struct ResourceDesc
|
|||||||
|
|
||||||
internal resource_union _desc;
|
internal resource_union _desc;
|
||||||
|
|
||||||
|
public ResourceType Type
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
|
}
|
||||||
|
|
||||||
public TextureDesc TextureDescription
|
public TextureDesc TextureDescription
|
||||||
{
|
{
|
||||||
readonly get => _desc.textureDescription;
|
readonly get
|
||||||
set => _desc.textureDescription = value;
|
{
|
||||||
|
Debug.Assert(Type == ResourceType.Texture);
|
||||||
|
return _desc.textureDescription;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Debug.Assert(Type == ResourceType.Texture);
|
||||||
|
_desc.textureDescription = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferDesc BufferDescription
|
public BufferDesc BufferDescription
|
||||||
{
|
{
|
||||||
readonly get => _desc.bufferDescription;
|
readonly get
|
||||||
set => _desc.bufferDescription = value;
|
{
|
||||||
|
Debug.Assert(Type == ResourceType.Buffer);
|
||||||
|
return _desc.bufferDescription;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Debug.Assert(Type == ResourceType.Buffer);
|
||||||
|
_desc.bufferDescription = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceDesc Buffer(BufferDesc desc)
|
public static ResourceDesc Buffer(BufferDesc desc)
|
||||||
{
|
{
|
||||||
return new ResourceDesc
|
return new ResourceDesc
|
||||||
{
|
{
|
||||||
|
Type = ResourceType.Buffer,
|
||||||
BufferDescription = desc
|
BufferDescription = desc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -449,6 +472,7 @@ public struct ResourceDesc
|
|||||||
{
|
{
|
||||||
return new ResourceDesc
|
return new ResourceDesc
|
||||||
{
|
{
|
||||||
|
Type = ResourceType.Texture,
|
||||||
TextureDescription = desc
|
TextureDescription = desc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -990,6 +1014,12 @@ public enum ResourceMemoryType
|
|||||||
Readback // GPU-to-CPU memory
|
Readback // GPU-to-CPU memory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ResourceType
|
||||||
|
{
|
||||||
|
Texture,
|
||||||
|
Buffer
|
||||||
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum TextureUsage
|
public enum TextureUsage
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -69,8 +69,23 @@ public struct AllocationDesc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly struct ResourceSizeInfo
|
||||||
|
{
|
||||||
|
public ulong Size
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Alignment
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public interface IResourceAllocator : IDisposable
|
public interface IResourceAllocator : IDisposable
|
||||||
{
|
{
|
||||||
|
ResourceSizeInfo GetSizeInfo(ResourceDesc desc);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocates a block of memory on the GPU
|
/// Allocates a block of memory on the GPU
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1170
Ghost.Graphics/RenderGraphModule/RenderGraph.cs.backup
Normal file
1170
Ghost.Graphics/RenderGraphModule/RenderGraph.cs.backup
Normal file
File diff suppressed because it is too large
Load Diff
@@ -285,6 +285,7 @@ internal sealed class PlacedResource
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class ResourceAliasingManager
|
internal sealed class ResourceAliasingManager
|
||||||
{
|
{
|
||||||
|
private readonly IResourceAllocator _allocator;
|
||||||
private readonly RenderGraphObjectPool _pool;
|
private readonly RenderGraphObjectPool _pool;
|
||||||
|
|
||||||
private readonly ResourceHeap _heap;
|
private readonly ResourceHeap _heap;
|
||||||
@@ -306,17 +307,20 @@ internal sealed class ResourceAliasingManager
|
|||||||
if (resource.type == RenderGraphResourceType.Texture)
|
if (resource.type == RenderGraphResourceType.Texture)
|
||||||
{
|
{
|
||||||
var textureDesc = resource.rgTextureDesc.ToTextureDesc(resource.resolvedWidth, resource.resolvedHeight);
|
var textureDesc = resource.rgTextureDesc.ToTextureDesc(resource.resolvedWidth, resource.resolvedHeight);
|
||||||
return AlignUp(textureDesc.GetTotalBytes(), _DEFAULT_TEXTURE_ALIGNMENT);
|
return _allocator.GetSizeInfo(ResourceDesc.Texture(textureDesc)).Size;
|
||||||
}
|
}
|
||||||
else // Buffer
|
else // Buffer
|
||||||
{
|
{
|
||||||
return resource.bufferDesc.Size;
|
//return resource.bufferDesc.Size;
|
||||||
|
return _allocator.GetSizeInfo(ResourceDesc.Buffer(resource.bufferDesc)).Size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceAliasingManager(RenderGraphObjectPool pool)
|
public ResourceAliasingManager(IResourceAllocator allocator, RenderGraphObjectPool pool)
|
||||||
{
|
{
|
||||||
|
_allocator = allocator;
|
||||||
_pool = pool;
|
_pool = pool;
|
||||||
|
|
||||||
_heap = new ResourceHeap(0);
|
_heap = new ResourceHeap(0);
|
||||||
_placedResources = new List<PlacedResource>(32);
|
_placedResources = new List<PlacedResource>(32);
|
||||||
_logicalToPlaced = new Dictionary<int, int>(64);
|
_logicalToPlaced = new Dictionary<int, int>(64);
|
||||||
|
|||||||
@@ -71,3 +71,263 @@ internal sealed class ResourceStateTracker
|
|||||||
lastAccessPass = -1;
|
lastAccessPass = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a compiled barrier with only the target state.
|
||||||
|
/// The before state is always queried from ResourceDatabase at execution time.
|
||||||
|
/// </summary>
|
||||||
|
internal struct CompiledBarrier
|
||||||
|
{
|
||||||
|
public int PassIndex;
|
||||||
|
public Identifier<RGResource> Resource;
|
||||||
|
public ResourceBarrierData TargetState;
|
||||||
|
public Identifier<RGResource> AliasingPredecessor; // Invalid if not aliasing
|
||||||
|
public BarrierFlags Flags;
|
||||||
|
public RenderGraphResourceType ResourceType;
|
||||||
|
|
||||||
|
public override readonly string ToString()
|
||||||
|
{
|
||||||
|
return AliasingPredecessor.IsValid
|
||||||
|
? $"[Pass {PassIndex}] Aliasing: {AliasingPredecessor.Value}->{Resource.Value} -> {TargetState.Layout}"
|
||||||
|
: $"[Pass {PassIndex}] Transition: {Resource.Value} -> {TargetState.Layout}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static class containing barrier compilation logic.
|
||||||
|
/// Compiles barriers at graph compilation time, storing only target states.
|
||||||
|
/// </summary>
|
||||||
|
internal static class RenderGraphBarriers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Compiles all barriers needed for execution, storing only target states.
|
||||||
|
/// Barriers include aliasing barriers and implicit state transitions.
|
||||||
|
/// </summary>
|
||||||
|
public static void CompileBarriers(
|
||||||
|
List<RenderGraphPassBase> compiledPasses,
|
||||||
|
List<CompiledBarrier> compiledBarriers,
|
||||||
|
RenderGraphResourceRegistry resources,
|
||||||
|
ResourceAliasingManager aliasingManager)
|
||||||
|
{
|
||||||
|
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, compiledBarriers, resources, aliasingManager);
|
||||||
|
|
||||||
|
// 2. Compile implicit transitions for all resources accessed by this pass
|
||||||
|
CompileImplicitTransitions(pass, passIdx, compiledBarriers, resources);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts aliasing barriers when a placed resource is reused.
|
||||||
|
/// </summary>
|
||||||
|
private static void InsertAliasingBarriers(
|
||||||
|
RenderGraphPassBase pass,
|
||||||
|
int passIdx,
|
||||||
|
List<CompiledBarrier> compiledBarriers,
|
||||||
|
RenderGraphResourceRegistry resources,
|
||||||
|
ResourceAliasingManager aliasingManager)
|
||||||
|
{
|
||||||
|
// 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<RGResource> 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<RGResource>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private static void CompileImplicitTransitions(
|
||||||
|
RenderGraphPassBase pass,
|
||||||
|
int passIdx,
|
||||||
|
List<CompiledBarrier> compiledBarriers,
|
||||||
|
RenderGraphResourceRegistry resources)
|
||||||
|
{
|
||||||
|
// Helper to add a compiled barrier for a resource transition
|
||||||
|
void AddTransition(Identifier<RGResource> id, ResourceBarrierData targetState)
|
||||||
|
{
|
||||||
|
var resource = resources.GetResource(id);
|
||||||
|
var barrier = new CompiledBarrier
|
||||||
|
{
|
||||||
|
PassIndex = passIdx,
|
||||||
|
Resource = id,
|
||||||
|
TargetState = targetState,
|
||||||
|
AliasingPredecessor = Identifier<RGResource>.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, resources);
|
||||||
|
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 static ResourceBarrierData GetBufferReadBarrierData(
|
||||||
|
Identifier<RGResource> handle,
|
||||||
|
RenderGraphPassBase pass,
|
||||||
|
RenderGraphResourceType resourceType,
|
||||||
|
RenderGraphResourceRegistry resources)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,11 +23,8 @@ internal sealed class CachedCompilation
|
|||||||
// Placed resource metadata
|
// Placed resource metadata
|
||||||
public readonly List<PlacedResourceData> placedResources = new(32);
|
public readonly List<PlacedResourceData> placedResources = new(32);
|
||||||
|
|
||||||
// Resource barriers
|
// Compiled barriers (stores only target states, queries before state from ResourceDatabase)
|
||||||
public readonly List<ResourceBarrier> barriers = new(128);
|
public readonly List<CompiledBarrier> compiledBarriers = new(128);
|
||||||
|
|
||||||
// Resource state mappings (for barrier generation)
|
|
||||||
public readonly Dictionary<int, ResourceBarrierData> resourceStates = new(128);
|
|
||||||
|
|
||||||
// Real gpu resource
|
// Real gpu resource
|
||||||
public readonly List<Handle<GPUResource>> backingResources = new(32);
|
public readonly List<Handle<GPUResource>> backingResources = new(32);
|
||||||
@@ -41,8 +38,7 @@ internal sealed class CachedCompilation
|
|||||||
passCulledFlags.Clear();
|
passCulledFlags.Clear();
|
||||||
logicalToPhysical.Clear();
|
logicalToPhysical.Clear();
|
||||||
placedResources.Clear();
|
placedResources.Clear();
|
||||||
barriers.Clear();
|
compiledBarriers.Clear();
|
||||||
resourceStates.Clear();
|
|
||||||
backingResources.Clear();
|
backingResources.Clear();
|
||||||
viewState = default;
|
viewState = default;
|
||||||
}
|
}
|
||||||
@@ -112,12 +108,7 @@ internal sealed class RenderGraphCompilationCache
|
|||||||
}
|
}
|
||||||
|
|
||||||
_cached.placedResources.AddRange(data.placedResources);
|
_cached.placedResources.AddRange(data.placedResources);
|
||||||
_cached.barriers.AddRange(data.barriers);
|
_cached.compiledBarriers.AddRange(data.compiledBarriers);
|
||||||
|
|
||||||
foreach (var kvp in data.resourceStates)
|
|
||||||
{
|
|
||||||
_cached.resourceStates[kvp.Key] = kvp.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_cached.backingResources.AddRange(data.backingResources);
|
_cached.backingResources.AddRange(data.backingResources);
|
||||||
}
|
}
|
||||||
|
|||||||
391
Ghost.Graphics/RenderGraphModule/RenderGraphCompiler.cs
Normal file
391
Ghost.Graphics/RenderGraphModule/RenderGraphCompiler.cs
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles compilation of the render graph including pass culling, resource allocation,
|
||||||
|
/// barrier compilation, and cache management.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class RenderGraphCompiler
|
||||||
|
{
|
||||||
|
private readonly IGraphicsEngine _graphicsEngine;
|
||||||
|
private readonly RenderGraphResourceRegistry _resources;
|
||||||
|
private readonly ResourceAliasingManager _aliasingManager;
|
||||||
|
private readonly RenderGraphNativePassBuilder _nativePassBuilder;
|
||||||
|
private readonly RenderGraphCompilationCache _compilationCache;
|
||||||
|
|
||||||
|
private Handle<GPUResource> _resourceHeap;
|
||||||
|
|
||||||
|
public RenderGraphCompiler(
|
||||||
|
IGraphicsEngine graphicsEngine,
|
||||||
|
RenderGraphResourceRegistry resources,
|
||||||
|
ResourceAliasingManager aliasingManager,
|
||||||
|
RenderGraphNativePassBuilder nativePassBuilder,
|
||||||
|
RenderGraphCompilationCache compilationCache)
|
||||||
|
{
|
||||||
|
_graphicsEngine = graphicsEngine;
|
||||||
|
_resources = resources;
|
||||||
|
_aliasingManager = aliasingManager;
|
||||||
|
_nativePassBuilder = nativePassBuilder;
|
||||||
|
_compilationCache = compilationCache;
|
||||||
|
_resourceHeap = Handle<GPUResource>.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compiles the render graph by culling passes, allocating resources, and preparing barriers.
|
||||||
|
/// </summary>
|
||||||
|
public void Compile(
|
||||||
|
in ViewState viewState,
|
||||||
|
ulong graphHash,
|
||||||
|
List<RenderGraphPassBase> passes,
|
||||||
|
List<RenderGraphPassBase> compiledPasses,
|
||||||
|
List<NativeRenderPass> nativePasses,
|
||||||
|
List<CompiledBarrier> compiledBarriers)
|
||||||
|
{
|
||||||
|
// Try to restore from cache
|
||||||
|
if (_compilationCache.TryGetCached(graphHash, out var cached))
|
||||||
|
{
|
||||||
|
// Check if view state changed
|
||||||
|
if (!cached.viewState.Equals(viewState))
|
||||||
|
{
|
||||||
|
// View state changed - re-resolve sizes and recreate GPU resources
|
||||||
|
_resources.ResolveTextureSizes(in viewState);
|
||||||
|
RestoreFromCache(cached, compiledPasses, passes, nativePasses, compiledBarriers);
|
||||||
|
|
||||||
|
_aliasingManager.AssignPhysicalResources(_resources, passes.Count);
|
||||||
|
AllocateResources();
|
||||||
|
|
||||||
|
cached.viewState = viewState;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Perfect cache hit - restore everything
|
||||||
|
RestoreFromCache(cached, compiledPasses, passes, nativePasses, compiledBarriers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresh compilation needed
|
||||||
|
compiledPasses.Clear();
|
||||||
|
|
||||||
|
// Mark passes with side effects (writes to imported resources)
|
||||||
|
MarkPassesWithSideEffects(passes);
|
||||||
|
|
||||||
|
// Cull passes based on dependency analysis
|
||||||
|
CullPasses(passes);
|
||||||
|
|
||||||
|
// Build final pass list (only non-culled passes)
|
||||||
|
for (var i = 0; i < passes.Count; i++)
|
||||||
|
{
|
||||||
|
var pass = passes[i];
|
||||||
|
if (!pass.culled)
|
||||||
|
{
|
||||||
|
compiledPasses.Add(pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_aliasingManager.AssignPhysicalResources(_resources, passes.Count);
|
||||||
|
AllocateResources();
|
||||||
|
|
||||||
|
CompileBarriers(compiledPasses, compiledBarriers);
|
||||||
|
_nativePassBuilder.BuildNativeRenderPasses(compiledPasses, nativePasses, compiledBarriers);
|
||||||
|
StoreInCache(graphHash, viewState, compiledPasses, passes, compiledBarriers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks passes that write to imported resources as having side effects.
|
||||||
|
/// </summary>
|
||||||
|
private void MarkPassesWithSideEffects(List<RenderGraphPassBase> passes)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < passes.Count; i++)
|
||||||
|
{
|
||||||
|
var pass = passes[i];
|
||||||
|
|
||||||
|
// Check if this pass writes to any imported resources
|
||||||
|
for (var j = 0; j < (int)RenderGraphResourceType.Count; j++)
|
||||||
|
{
|
||||||
|
var writeList = pass.resourceWrites[j];
|
||||||
|
for (var k = 0; k < writeList.Count; k++)
|
||||||
|
{
|
||||||
|
var writeHandle = writeList[k];
|
||||||
|
var resource = _resources.GetResource(writeHandle);
|
||||||
|
if (resource.isImported)
|
||||||
|
{
|
||||||
|
pass.hasSideEffects = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Culls unused passes based on dependency analysis.
|
||||||
|
/// </summary>
|
||||||
|
private void CullPasses(List<RenderGraphPassBase> passes)
|
||||||
|
{
|
||||||
|
// Mark all passes as culled initially
|
||||||
|
for (var i = 0; i < passes.Count; i++)
|
||||||
|
{
|
||||||
|
passes[i].culled = passes[i].allowCulling && !passes[i].hasSideEffects;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse backwards from passes with side effects
|
||||||
|
for (var i = passes.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var pass = passes[i];
|
||||||
|
if (!pass.culled)
|
||||||
|
{
|
||||||
|
UnculDependencies(pass, passes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recursively un-culls dependencies of a pass.
|
||||||
|
/// </summary>
|
||||||
|
private void UnculDependencies(RenderGraphPassBase pass, List<RenderGraphPassBase> passes)
|
||||||
|
{
|
||||||
|
// 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], passes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(), passes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Un-cull producer of depth attachment
|
||||||
|
if (pass.depthAccess.id.IsValid)
|
||||||
|
{
|
||||||
|
UnculProducer(pass.depthAccess.id.AsResource(), passes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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], passes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Un-culls the producer of a resource.
|
||||||
|
/// </summary>
|
||||||
|
private void UnculProducer(Identifier<RGResource> resource, List<RenderGraphPassBase> passes)
|
||||||
|
{
|
||||||
|
var res = _resources.GetResource(resource);
|
||||||
|
if (res.producerPass >= 0)
|
||||||
|
{
|
||||||
|
var producer = passes[res.producerPass];
|
||||||
|
if (producer.culled)
|
||||||
|
{
|
||||||
|
producer.culled = false;
|
||||||
|
UnculDependencies(producer, passes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allocates GPU resources for the render graph.
|
||||||
|
/// </summary>
|
||||||
|
private void AllocateResources()
|
||||||
|
{
|
||||||
|
if (_resourceHeap.IsValid)
|
||||||
|
{
|
||||||
|
foreach (var res in _resources.Resources)
|
||||||
|
{
|
||||||
|
if (res.isImported)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_graphicsEngine.ResourceDatabase.ReleaseResource(res.backingResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
_graphicsEngine.ResourceDatabase.ReleaseResource(_resourceHeap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_aliasingManager.Heap.size == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allocationDesc = new AllocationDesc
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compiles all barriers needed for execution.
|
||||||
|
/// Delegates to RenderGraphBarriers for the actual compilation logic.
|
||||||
|
/// </summary>
|
||||||
|
private void CompileBarriers(List<RenderGraphPassBase> compiledPasses, List<CompiledBarrier> compiledBarriers)
|
||||||
|
{
|
||||||
|
RenderGraphBarriers.CompileBarriers(compiledPasses, compiledBarriers, _resources, _aliasingManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restores the render graph state from cached compilation results.
|
||||||
|
/// </summary>
|
||||||
|
private void RestoreFromCache(
|
||||||
|
CachedCompilation cached,
|
||||||
|
List<RenderGraphPassBase> compiledPasses,
|
||||||
|
List<RenderGraphPassBase> passes,
|
||||||
|
List<NativeRenderPass> nativePasses,
|
||||||
|
List<CompiledBarrier> compiledBarriers)
|
||||||
|
{
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_nativePassBuilder.BuildNativeRenderPasses(compiledPasses, nativePasses, compiledBarriers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores current compilation results in the cache.
|
||||||
|
/// </summary>
|
||||||
|
private void StoreInCache(
|
||||||
|
ulong graphHash,
|
||||||
|
in ViewState viewState,
|
||||||
|
List<RenderGraphPassBase> compiledPasses,
|
||||||
|
List<RenderGraphPassBase> passes,
|
||||||
|
List<CompiledBarrier> compiledBarriers)
|
||||||
|
{
|
||||||
|
var cacheData = new CachedCompilation();
|
||||||
|
|
||||||
|
// Store view state
|
||||||
|
cacheData.viewState = viewState;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases allocated GPU resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_resourceHeap.IsValid)
|
||||||
|
{
|
||||||
|
foreach (var res in _resources.Resources)
|
||||||
|
{
|
||||||
|
if (!res.isImported)
|
||||||
|
{
|
||||||
|
_graphicsEngine.ResourceDatabase.ReleaseResource(res.backingResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_graphicsEngine.ResourceDatabase.ReleaseResource(_resourceHeap);
|
||||||
|
_resourceHeap = Handle<GPUResource>.Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
223
Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs
Normal file
223
Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles execution of compiled render graphs, including barrier execution and native render passes.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class RenderGraphExecutor
|
||||||
|
{
|
||||||
|
private readonly IGraphicsEngine _graphicsEngine;
|
||||||
|
private readonly RenderGraphResourceRegistry _resources;
|
||||||
|
private readonly RenderGraphContext _context;
|
||||||
|
|
||||||
|
public RenderGraphExecutor(
|
||||||
|
IGraphicsEngine graphicsEngine,
|
||||||
|
RenderGraphResourceRegistry resources,
|
||||||
|
RenderGraphContext context)
|
||||||
|
{
|
||||||
|
_graphicsEngine = graphicsEngine;
|
||||||
|
_resources = resources;
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes all compiled passes using native render passes where possible.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe void Execute(
|
||||||
|
ICommandBuffer cmd,
|
||||||
|
List<RenderGraphPassBase> compiledPasses,
|
||||||
|
List<NativeRenderPass> nativePasses,
|
||||||
|
List<CompiledBarrier> compiledBarriers)
|
||||||
|
{
|
||||||
|
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, compiledBarriers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<Texture>.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<PassRenderTargetDesc>(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<TextureFormat>(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, compiledBarriers);
|
||||||
|
pass.Execute(_context);
|
||||||
|
logicalPassIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes all barriers for a specific pass.
|
||||||
|
/// Uses pre-compiled barriers and queries before state from ResourceDatabase.
|
||||||
|
/// </summary>
|
||||||
|
private unsafe void ExecuteBarriersForPass(
|
||||||
|
ICommandBuffer cmd,
|
||||||
|
int passIndex,
|
||||||
|
ref int barrierIndex,
|
||||||
|
List<CompiledBarrier> compiledBarriers)
|
||||||
|
{
|
||||||
|
const int MaxBatch = 64;
|
||||||
|
var barriers = stackalloc BarrierDesc[MaxBatch];
|
||||||
|
var barrierCount = 0;
|
||||||
|
|
||||||
|
void Flush()
|
||||||
|
{
|
||||||
|
if (barrierCount > 0)
|
||||||
|
{
|
||||||
|
cmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
177
Ghost.Graphics/RenderGraphModule/RenderGraphHasher.cs
Normal file
177
Ghost.Graphics/RenderGraphModule/RenderGraphHasher.cs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using System.IO.Hashing;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes structural hashes of render graphs for compilation caching.
|
||||||
|
/// Hashes are based on graph topology and resource configurations, not runtime values.
|
||||||
|
/// </summary>
|
||||||
|
internal static class RenderGraphHasher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a hash of the entire render graph structure.
|
||||||
|
/// Used for cache invalidation - same hash means same compilation result.
|
||||||
|
/// </summary>
|
||||||
|
public static unsafe ulong ComputeGraphHash(List<RenderGraphPassBase> passes, RenderGraphResourceRegistry resources)
|
||||||
|
{
|
||||||
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
|
var bufferPool = new UnsafeList<byte>(2048, scope.AllocationHandle);
|
||||||
|
var pData = (byte*)bufferPool.GetUnsafePtr();
|
||||||
|
var offset = 0;
|
||||||
|
|
||||||
|
// Hash pass count
|
||||||
|
*(int*)(pData + offset) = passes.Count;
|
||||||
|
offset += sizeof(int);
|
||||||
|
|
||||||
|
// Hash each pass structure (excluding names)
|
||||||
|
for (var i = 0; i < passes.Count; i++)
|
||||||
|
{
|
||||||
|
var pass = passes[i];
|
||||||
|
|
||||||
|
*(RenderPassType*)(pData + offset) = pass.type;
|
||||||
|
offset += sizeof(RenderPassType);
|
||||||
|
|
||||||
|
*(bool*)(pData + offset) = pass.allowCulling;
|
||||||
|
offset += sizeof(bool);
|
||||||
|
|
||||||
|
*(bool*)(pData + offset) = pass.asyncCompute;
|
||||||
|
offset += sizeof(bool);
|
||||||
|
|
||||||
|
// Hash depth attachment
|
||||||
|
offset = ComputeTextureHash(pData, offset, pass.depthAccess.id, resources);
|
||||||
|
|
||||||
|
pData[offset] = (byte)pass.depthAccess.accessFlags;
|
||||||
|
offset += sizeof(AccessFlags);
|
||||||
|
|
||||||
|
*(int*)(pData + offset) = pass.maxColorIndex;
|
||||||
|
offset += sizeof(int);
|
||||||
|
for (var j = 0; j <= pass.maxColorIndex; j++)
|
||||||
|
{
|
||||||
|
offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id, resources);
|
||||||
|
|
||||||
|
pData[offset] = (byte)pass.colorAccess[j].accessFlags;
|
||||||
|
offset += sizeof(AccessFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < (int)RenderGraphResourceType.Count; j++)
|
||||||
|
{
|
||||||
|
var readList = pass.resourceReads[j];
|
||||||
|
var writeList = pass.resourceWrites[j];
|
||||||
|
var createList = pass.resourceCreates[j];
|
||||||
|
|
||||||
|
*(int*)(pData + offset) = readList.Count;
|
||||||
|
offset += sizeof(int);
|
||||||
|
for (var k = 0; k < readList.Count; k++)
|
||||||
|
{
|
||||||
|
*(int*)(pData + offset) = readList[k].Value;
|
||||||
|
offset += sizeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
*(int*)(pData + offset) = writeList.Count;
|
||||||
|
offset += sizeof(int);
|
||||||
|
for (var k = 0; k < writeList.Count; k++)
|
||||||
|
{
|
||||||
|
*(int*)(pData + offset) = writeList[k].Value;
|
||||||
|
offset += sizeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
*(int*)(pData + offset) = createList.Count;
|
||||||
|
offset += sizeof(int);
|
||||||
|
for (var k = 0; k < createList.Count; k++)
|
||||||
|
{
|
||||||
|
*(int*)(pData + offset) = createList[k].Value;
|
||||||
|
offset += sizeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
*(int*)(pData + offset) = pass.randomAccess.Count;
|
||||||
|
offset += sizeof(int);
|
||||||
|
for (var k = 0; k < pass.randomAccess.Count; k++)
|
||||||
|
{
|
||||||
|
*(int*)(pData + offset) = pass.randomAccess[k].Value;
|
||||||
|
offset += sizeof(int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*(int*)(pData + offset) = pass.GetRenderFuncHashCode();
|
||||||
|
offset += sizeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
var span = new Span<byte>(pData, offset);
|
||||||
|
return XxHash64.HashToUInt64(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a hash of a texture resource's structural properties.
|
||||||
|
/// For imported textures, hashes the backing handle.
|
||||||
|
/// For transient textures, hashes the descriptor (respecting size mode).
|
||||||
|
/// </summary>
|
||||||
|
private static unsafe int ComputeTextureHash(byte* pData, int offset, Identifier<RGTexture> texture, RenderGraphResourceRegistry resources)
|
||||||
|
{
|
||||||
|
if (texture.IsInvalid)
|
||||||
|
{
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resource = resources.GetResource(texture.AsResource());
|
||||||
|
|
||||||
|
// Hash imported flag
|
||||||
|
*(pData + offset) = resource.isImported ? (byte)1 : (byte)0;
|
||||||
|
offset += sizeof(byte);
|
||||||
|
|
||||||
|
// For imported textures, hash the backing resource handle
|
||||||
|
if (resource.isImported)
|
||||||
|
{
|
||||||
|
*(int*)(pData + offset) = resource.backingResource.GetHashCode();
|
||||||
|
offset += sizeof(int);
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
var desc = resource.rgTextureDesc;
|
||||||
|
|
||||||
|
// Hash format (structural)
|
||||||
|
*(TextureFormat*)(pData + offset) = desc.format;
|
||||||
|
offset += sizeof(TextureFormat);
|
||||||
|
|
||||||
|
// Hash size mode (structural)
|
||||||
|
*(RGTextureSizeMode*)(pData + offset) = desc.sizeMode;
|
||||||
|
offset += sizeof(RGTextureSizeMode);
|
||||||
|
|
||||||
|
// Hash size specification based on mode
|
||||||
|
if (desc.sizeMode == RGTextureSizeMode.Absolute)
|
||||||
|
{
|
||||||
|
// Absolute mode: hash actual dimensions
|
||||||
|
*(uint*)(pData + offset) = desc.width;
|
||||||
|
offset += sizeof(uint);
|
||||||
|
*(uint*)(pData + offset) = desc.height;
|
||||||
|
offset += sizeof(uint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Relative mode: hash scale factors (NOT resolved dimensions)
|
||||||
|
*(float*)(pData + offset) = desc.scaleX;
|
||||||
|
offset += sizeof(float);
|
||||||
|
*(float*)(pData + offset) = desc.scaleY;
|
||||||
|
offset += sizeof(float);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash other structural properties
|
||||||
|
*(TextureDimension*)(pData + offset) = desc.dimension;
|
||||||
|
offset += sizeof(TextureDimension);
|
||||||
|
*(uint*)(pData + offset) = desc.mipLevels;
|
||||||
|
offset += sizeof(uint);
|
||||||
|
*(TextureUsage*)(pData + offset) = desc.usage;
|
||||||
|
offset += sizeof(TextureUsage);
|
||||||
|
|
||||||
|
*(bool*)(pData + offset) = desc.clearAtFirstUse;
|
||||||
|
offset += sizeof(bool);
|
||||||
|
*(bool*)(pData + offset) = desc.discardAtLastUse;
|
||||||
|
offset += sizeof(bool);
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
370
Ghost.Graphics/RenderGraphModule/RenderGraphNativePassBuilder.cs
Normal file
370
Ghost.Graphics/RenderGraphModule/RenderGraphNativePassBuilder.cs
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds native render passes by merging compatible consecutive raster passes.
|
||||||
|
/// Optimizes for tile-based deferred rendering (TBDR) GPUs by minimizing load/store operations.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class RenderGraphNativePassBuilder
|
||||||
|
{
|
||||||
|
private readonly RenderGraphObjectPool _objectPool;
|
||||||
|
private readonly RenderGraphResourceRegistry _resources;
|
||||||
|
|
||||||
|
public RenderGraphNativePassBuilder(RenderGraphObjectPool objectPool, RenderGraphResourceRegistry resources)
|
||||||
|
{
|
||||||
|
_objectPool = objectPool;
|
||||||
|
_resources = resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds native render passes by merging compatible consecutive raster passes.
|
||||||
|
/// Uses conservative merging: only merge passes with identical attachments and no barriers between them.
|
||||||
|
/// </summary>
|
||||||
|
public void BuildNativeRenderPasses(
|
||||||
|
List<RenderGraphPassBase> compiledPasses,
|
||||||
|
List<NativeRenderPass> nativePasses,
|
||||||
|
List<CompiledBarrier> compiledBarriers)
|
||||||
|
{
|
||||||
|
// 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, compiledPasses, compiledBarriers))
|
||||||
|
{
|
||||||
|
// 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new native render pass from a logical pass.
|
||||||
|
/// </summary>
|
||||||
|
private NativeRenderPass CreateNativePass(RenderGraphPassBase pass, int passIndex)
|
||||||
|
{
|
||||||
|
var nativePass = _objectPool.Rent<NativeRenderPass>();
|
||||||
|
nativePass.Reset();
|
||||||
|
|
||||||
|
nativePass.index = 0; // Will be set by caller
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private bool CanMergePasses(
|
||||||
|
NativeRenderPass nativePass,
|
||||||
|
RenderGraphPassBase pass,
|
||||||
|
int passIndex,
|
||||||
|
List<RenderGraphPassBase> compiledPasses,
|
||||||
|
List<CompiledBarrier> compiledBarriers)
|
||||||
|
{
|
||||||
|
// 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, compiledPasses, compiledBarriers))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the attachment configuration of a pass matches the native pass.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any barriers are required between two passes that would prevent merging.
|
||||||
|
/// Only barriers affecting render targets prevent merging; SRV barriers are fine.
|
||||||
|
/// </summary>
|
||||||
|
private bool RequiresBarrierBetweenPasses(
|
||||||
|
int passA,
|
||||||
|
int passB,
|
||||||
|
List<RenderGraphPassBase> compiledPasses,
|
||||||
|
List<CompiledBarrier> compiledBarriers)
|
||||||
|
{
|
||||||
|
var laterPass = compiledPasses[passB];
|
||||||
|
|
||||||
|
// Build a set of render target resource IDs (color + depth)
|
||||||
|
var renderTargets = new HashSet<Identifier<RGResource>>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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).
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user