Enhanced barrier

This commit is contained in:
2026-01-22 12:33:23 +09:00
parent 92b966fe0d
commit 139312d73b
35 changed files with 653 additions and 9544 deletions

View File

@@ -235,21 +235,39 @@ public struct Material : IResourceReleasable
return _keywordMask.IsKeywordEnabled(localIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void UploadData(ICommandBuffer cmd, bool pixelOnlyResource = true)
public readonly void UploadData(ICommandBuffer cmd, IResourceDatabase resourceDatabase)
{
if (!_isDirty)
{
return;
}
cmd.TransitionBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.CopyDest);
var cbufferResource = _cBufferCache.GpuResource.AsResource();
var r = resourceDatabase.GetResourceBarrierData(cbufferResource);
if (r.IsFailure)
{
return;
}
var barrierData = r.Value;
var desc = BarrierDesc.Buffer(
cbufferResource,
barrierData.Sync,
BarrierSync.Copy,
barrierData.Access,
BarrierAccess.CopyDest);
cmd.ResourceBarrier(desc);
cmd.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan());
var state = pixelOnlyResource
? ResourceState.PixelShaderResource
: ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource;
cmd.TransitionBarrier(_cBufferCache.GpuResource.AsResource(), state);
desc = BarrierDesc.Buffer(
cbufferResource,
BarrierSync.Copy,
BarrierSync.AllShading,
BarrierAccess.CopyDest,
BarrierAccess.ShaderResource);
cmd.ResourceBarrier(desc);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -33,12 +33,22 @@ internal class SwapChainRenderOutput : IRenderOutput
public void BeginRender(ICommandBuffer cmd)
{
cmd.TransitionBarrier(GetRenderTarget().AsResource(), ResourceState.Present, ResourceState.RenderTarget);
var barrierDesc = BarrierDesc.Texture(_swapChain.GetCurrentBackBuffer().AsResource(),
BarrierSync.None, BarrierSync.RenderTarget,
BarrierAccess.NoAccess, BarrierAccess.RenderTarget,
BarrierLayout.Present, BarrierLayout.RenderTarget);
cmd.ResourceBarrier(barrierDesc);
}
public void EndRender(ICommandBuffer cmd)
{
cmd.TransitionBarrier(GetRenderTarget().AsResource(), ResourceState.RenderTarget, ResourceState.Present);
var barrierDesc = BarrierDesc.Texture(_swapChain.GetCurrentBackBuffer().AsResource(),
BarrierSync.RenderTarget, BarrierSync.None,
BarrierAccess.RenderTarget, BarrierAccess.NoAccess,
BarrierLayout.RenderTarget, BarrierLayout.Present);
cmd.ResourceBarrier(barrierDesc);
}
public void Present()

View File

@@ -5,7 +5,6 @@ using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Core;
@@ -47,6 +46,37 @@ public readonly unsafe ref struct RenderingContext
queue.WaitIdle();
}
private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync)
{
var r = ResourceDatabase.GetResourceBarrierData(resource);
if (r.IsFailure)
{
return;
}
var data = r.Value;
if (data.Layout == newLayout && data.Access == newAccess && data.Sync == newSync)
{
return;
}
// For buffers, layout is usually Undefined/Common and doesn't change, but Access/Sync do.
// For textures, layout changes matter.
var desc = isTexture ?
BarrierDesc.Texture(
resource,
data.Sync, newSync,
data.Access, newAccess,
data.Layout, newLayout)
: BarrierDesc.Buffer(
resource,
data.Sync, newSync,
data.Access, newAccess);
_directCmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(in desc));
ResourceDatabase.SetResourceBarrierData(resource, new ResourceBarrierData(newLayout, newAccess, newSync));
}
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
{
var mesh = ResourceAllocator.CreateMesh(vertices, indices);
@@ -60,8 +90,8 @@ public readonly unsafe ref struct RenderingContext
var vertexHandle = meshData.VertexBuffer.AsResource();
var indexHandle = meshData.IndexBuffer.AsResource();
_directCmd.TransitionBarrier(vertexHandle, ResourceState.CopyDest);
_directCmd.TransitionBarrier(indexHandle, ResourceState.CopyDest);
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
@@ -69,8 +99,8 @@ public readonly unsafe ref struct RenderingContext
if (staticMesh)
{
meshData.ReleaseCpuResources();
_directCmd.TransitionBarrier(vertexHandle, ResourceState.NonPixelShaderResource);
_directCmd.TransitionBarrier(indexHandle, ResourceState.NonPixelShaderResource);
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
}
return mesh;
@@ -103,15 +133,17 @@ public readonly unsafe ref struct RenderingContext
}
ref readonly var meshRef = ref r.Value;
var vertexHandle = meshRef.VertexBuffer.AsResource();
var indexHandle = meshRef.IndexBuffer.AsResource();
_directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.CopyDest);
_directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.CopyDest);
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
_directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
_directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
_directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.NonPixelShaderResource);
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
if (markMeshStatic)
{
@@ -139,9 +171,9 @@ public readonly unsafe ref struct RenderingContext
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
_directCmd.TransitionBarrier(bufferHandle, ResourceState.CopyDest);
_directCmd.UploadBuffer(meshData.ObjectDataBuffer, [data]);
_directCmd.TransitionBarrier(bufferHandle, ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource);
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
_directCmd.UploadBuffer(meshData.ObjectDataBuffer, data);
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading);
}
public Handle<Texture> CreateTexture<T>(ref readonly TextureDesc desc, ReadOnlySpan<T> data, string name)
@@ -166,7 +198,7 @@ public readonly unsafe ref struct RenderingContext
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
_directCmd.TransitionBarrier(texture.AsResource(), ResourceState.CopyDest);
TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy);
fixed (T* pData = data)
{

View File

@@ -193,7 +193,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
}
public void ResourceBarrier(ReadOnlySpan<BarrierDesc> barrierDescs)
public void ResourceBarrier(params ReadOnlySpan<BarrierDesc> barrierDescs)
{
ThrowIfDisposed();
ThrowIfNotRecording();
@@ -210,170 +210,120 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
return;
}
var count = 0u;
var pBarriers = stackalloc D3D12_RESOURCE_BARRIER[barrierDescs.Length];
var globalCount = 0;
var bufferCount = 0;
var textureCount = 0;
for (var i = 0; i < barrierDescs.Length; i++)
{
switch (barrierDescs[i].Type)
{
case BarrierType.Global: globalCount++; break;
case BarrierType.Buffer: bufferCount++; break;
case BarrierType.Texture: textureCount++; break;
}
}
var pGlobalBarriers = stackalloc D3D12_GLOBAL_BARRIER[globalCount];
var pBufferBarriers = stackalloc D3D12_BUFFER_BARRIER[bufferCount];
var pTextureBarriers = stackalloc D3D12_TEXTURE_BARRIER[textureCount];
var globalIndex = 0;
var bufferIndex = 0;
var textureIndex = 0;
for (var i = 0; i < barrierDescs.Length; i++)
{
var desc = barrierDescs[i];
D3D12_RESOURCE_BARRIER barrier = default;
switch (desc.type)
switch (desc.Type)
{
case BarrierType.Transition:
if (desc.transition.stateBefore == desc.transition.stateAfter)
case BarrierType.Global:
pGlobalBarriers[globalIndex++] = new D3D12_GLOBAL_BARRIER
{
continue;
}
var recordResult = _resourceDatabase.GetResourceRecord(desc.transition.resource);
if (recordResult.Error != ErrorStatus.None)
{
RecordError(nameof(TransitionBarrier), recordResult.Error);
continue;
}
ref var record = ref recordResult.Value;
var stateBefore = desc.transition.stateBefore == ResourceState.Auto ? record.state : desc.transition.stateBefore;
barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr,
stateBefore.ToD3D12States(), desc.transition.stateAfter.ToD3D12States());
record.state = desc.transition.stateAfter;
SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore,
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore,
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter
};
break;
case BarrierType.Aliasing:
var recordBeforeResult = _resourceDatabase.GetResourceRecord(desc.aliasing.resourceBefore);
if (recordBeforeResult.Error != ErrorStatus.None)
case BarrierType.Buffer:
{
var resource = _resourceDatabase.GetResource(desc.Resource);
pBufferBarriers[bufferIndex++] = new D3D12_BUFFER_BARRIER
{
RecordError(nameof(TransitionBarrier), recordBeforeResult.Error);
continue;
}
var recordAfterResult = _resourceDatabase.GetResourceRecord(desc.aliasing.resourceAfter);
if (recordAfterResult.Error != ErrorStatus.None)
SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore,
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore,
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
pResource = resource,
Offset = 0,
Size = ulong.MaxValue
};
}
break;
case BarrierType.Texture:
{
var resource = _resourceDatabase.GetResource(desc.Resource);
pTextureBarriers[textureIndex++] = new D3D12_TEXTURE_BARRIER
{
RecordError(nameof(TransitionBarrier), recordAfterResult.Error);
continue;
}
barrier = D3D12_RESOURCE_BARRIER.InitAliasing(
recordBeforeResult.Value.ResourcePtr,
recordAfterResult.Value.ResourcePtr);
break;
case BarrierType.UAV:
var recordUavResult = _resourceDatabase.GetResourceRecord(desc.uav.resource);
if (recordUavResult.Error != ErrorStatus.None)
{
RecordError(nameof(TransitionBarrier), recordUavResult.Error);
continue;
}
barrier = D3D12_RESOURCE_BARRIER.InitUAV(recordUavResult.Value.ResourcePtr);
break;
SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore,
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore,
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
LayoutBefore = (D3D12_BARRIER_LAYOUT)desc.LayoutBefore,
LayoutAfter = (D3D12_BARRIER_LAYOUT)desc.LayoutAfter,
pResource = resource,
Subresources = new D3D12_BARRIER_SUBRESOURCE_RANGE
{
IndexOrFirstMipLevel = desc.Subresources.IndexOrFirstMipLevel,
NumMipLevels = desc.Subresources.NumMipLevels,
FirstArraySlice = desc.Subresources.FirstArraySlice,
NumArraySlices = desc.Subresources.NumArraySlices
},
Flags = desc.Discard ? D3D12_TEXTURE_BARRIER_FLAGS.D3D12_TEXTURE_BARRIER_FLAG_DISCARD : D3D12_TEXTURE_BARRIER_FLAGS.D3D12_TEXTURE_BARRIER_FLAG_NONE
};
}
break;
}
pBarriers[count] = barrier;
count++;
}
_commandList.Get()->ResourceBarrier(count, pBarriers);
}
var groups = stackalloc D3D12_BARRIER_GROUP[3];
var groupCount = 0u;
public void TransitionBarrier(Handle<GPUResource> resource, ResourceState stateBefore, ResourceState stateAfter)
{
ThrowIfDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
if (globalCount > 0)
{
return;
groups[groupCount] = new D3D12_BARRIER_GROUP
{
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_GLOBAL,
NumBarriers = (uint)globalCount,
};
groups[groupCount].Anonymous.pGlobalBarriers = pGlobalBarriers;
groupCount++;
}
#endif
IncrementCommandCount();
if (stateBefore == stateAfter)
if (bufferCount > 0)
{
return;
groups[groupCount] = new D3D12_BARRIER_GROUP
{
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_BUFFER,
NumBarriers = (uint)bufferCount,
};
groups[groupCount].Anonymous.pBufferBarriers = pBufferBarriers;
groupCount++;
}
var recordResult = _resourceDatabase.GetResourceRecord(resource);
if (recordResult.Error != ErrorStatus.None)
if (textureCount > 0)
{
RecordError(nameof(TransitionBarrier), recordResult.Error);
return;
groups[groupCount] = new D3D12_BARRIER_GROUP
{
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_TEXTURE,
NumBarriers = (uint)textureCount,
};
groups[groupCount].Anonymous.pTextureBarriers = pTextureBarriers;
groupCount++;
}
ref var record = ref recordResult.Value;
var barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr,
stateBefore.ToD3D12States(), stateAfter.ToD3D12States());
_commandList.Get()->ResourceBarrier(1, &barrier);
record.state = stateAfter;
}
public void TransitionBarrier(Handle<GPUResource> resource, ResourceState stateAfter)
{
ThrowIfDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount();
var recordResult = _resourceDatabase.GetResourceRecord(resource);
if (recordResult.Error != ErrorStatus.None)
{
RecordError(nameof(TransitionBarrier), recordResult.Error);
return;
}
ref var record = ref recordResult.Value;
if (record.state == stateAfter)
{
return;
}
var barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr,
record.state.ToD3D12States(), stateAfter.ToD3D12States());
_commandList.Get()->ResourceBarrier(1, &barrier);
record.state = stateAfter;
}
public void AliasBarrier(Handle<GPUResource> resourceBefore, Handle<GPUResource> resourceAfter)
{
ThrowIfDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != ErrorStatus.None)
{
return;
}
#endif
IncrementCommandCount();
var recordBeforeResult = _resourceDatabase.GetResourceRecord(resourceBefore);
if (recordBeforeResult.Error != ErrorStatus.None)
{
RecordError(nameof(AliasBarrier), recordBeforeResult.Error);
return;
}
var recordAfterResult = _resourceDatabase.GetResourceRecord(resourceAfter);
if (recordAfterResult.Error != ErrorStatus.None)
{
RecordError(nameof(AliasBarrier), recordAfterResult.Error);
return;
}
var barrier = D3D12_RESOURCE_BARRIER.InitAliasing(
recordBeforeResult.Value.ResourcePtr,
recordAfterResult.Value.ResourcePtr);
_commandList.Get()->ResourceBarrier(1, &barrier);
_commandList.Get()->Barrier(groupCount, groups);
}
public void SetRenderTargets(ReadOnlySpan<Handle<Texture>> renderTargets, Handle<Texture> depthTarget)
@@ -914,7 +864,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
}
public void UploadBuffer<T>(Handle<GraphicsBuffer> buffer, ReadOnlySpan<T> data)
public void UploadBuffer<T>(Handle<GraphicsBuffer> buffer, params ReadOnlySpan<T> data)
where T : unmanaged
{
ThrowIfDisposed();
@@ -944,7 +894,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList.Get()->CopyBufferRegion(pResource, 0, uploadResource, offset, sizeInBytes);
}
public void UploadTexture(Handle<Texture> texture, ReadOnlySpan<SubResourceData> subresources)
public void UploadTexture(Handle<Texture> texture, params ReadOnlySpan<SubResourceData> subresources)
{
ThrowIfDisposed();
ThrowIfNotRecording();

View File

@@ -179,24 +179,11 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
return Result.Success(cbufferInfo);
}
private static D3D12_COMPARISON_FUNC ToD3DCompare(ZTest z) => z switch
{
ZTest.Disabled => D3D12_COMPARISON_FUNC_NEVER,
ZTest.Less => D3D12_COMPARISON_FUNC_LESS,
ZTest.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
ZTest.Equal => D3D12_COMPARISON_FUNC_EQUAL,
ZTest.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
ZTest.Greater => D3D12_COMPARISON_FUNC_GREATER,
ZTest.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
ZTest.Always => D3D12_COMPARISON_FUNC_ALWAYS,
_ => D3D12_COMPARISON_FUNC_LESS_EQUAL
};
private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ZTest ztest, ZWrite zwrite)
{
var depthEnabled = ztest != ZTest.Disabled;
var writeEnabled = zwrite == ZWrite.On;
var cmp = ToD3DCompare(ztest);
var cmp = ztest.ToD3DCompare();
return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp);
}

View File

@@ -10,7 +10,6 @@ using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
@@ -480,84 +479,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator
_ => throw new ArgumentException($"Unsupported memory type: {memoryType}")
};
}
private static D3D12_RESOURCE_STATES DetermineInitialTextureState(TextureUsage usage)
{
if (usage.HasFlag(TextureUsage.RenderTarget))
{
return D3D12_RESOURCE_STATE_RENDER_TARGET;
}
if (usage.HasFlag(TextureUsage.DepthStencil))
{
return D3D12_RESOURCE_STATE_DEPTH_WRITE;
}
if (usage.HasFlag(TextureUsage.UnorderedAccess))
{
return D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
}
return D3D12_RESOURCE_STATE_COMMON;
}
private static D3D12_RESOURCE_STATES DetermineInitialBufferState(BufferUsage usage, ResourceMemoryType memoryType)
{
if (memoryType == ResourceMemoryType.Upload)
{
return D3D12_RESOURCE_STATE_GENERIC_READ;
}
if (memoryType == ResourceMemoryType.Readback)
{
return D3D12_RESOURCE_STATE_COPY_DEST;
}
var state = D3D12_RESOURCE_STATE_COMMON;
#if true
// D3D12 does not support state other than COMMON for buffers at creation.
return state;
#else
if (usage.HasFlag(BufferUsage.Vertex) || usage.HasFlag(BufferUsage.Constant))
{
// Vertex and Constant buffers can share this state
state |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
}
if (usage.HasFlag(BufferUsage.Index))
{
state |= D3D12_RESOURCE_STATE_INDEX_BUFFER;
}
if (usage.HasFlag(BufferUsage.UnorderedAccess))
{
state |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
}
if (usage.HasFlag(BufferUsage.IndirectArgument))
{
state |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
}
// If only one state is set (e.g., just IndexBuffer), return it directly
// This is a common optimization to avoid an initial barrier
if (math.ispow2((int)state))
{
return state;
}
// If multiple roles (e.g., Vertex and Index), start in COMMON
// or return the combined state if they are compatible
// For simplicity, we'll just check for the common "Vertex/Constant" combo
if (state == D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER)
{
return D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
}
// If it's a mix, start in common and let the user barrier
return D3D12_RESOURCE_STATE_COMMON;
#endif
}
}
internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
@@ -624,9 +545,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Handle<GPUResource> TrackAllocation(D3D12MA_Allocation* allocation, D3D12_RESOURCE_STATES state, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp)
private Handle<GPUResource> TrackAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData barrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp)
{
var handle = _resourceDatabase.AddAllocation(allocation, _fenceSynchronizer.CPUFenceValue, state.ToResourceState(), resourceDescriptor, desc, name);
var handle = _resourceDatabase.AddAllocation(allocation, _fenceSynchronizer.CPUFenceValue, barrierData, resourceDescriptor, desc, name);
if (isTemp)
{
@@ -653,8 +574,8 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
}
else
{
var nuliid = IID.IID_NULL;
hr = _d3d12MA.Get()->CreateResource(pAllocationDesc, pResourceDesc, initialState, null, (D3D12MA_Allocation**)ppv, &nuliid, null);
var iid_null = IID.IID_NULL;
hr = _d3d12MA.Get()->CreateResource(pAllocationDesc, pResourceDesc, initialState, null, (D3D12MA_Allocation**)ppv, &iid_null, null);
}
return hr;
@@ -696,7 +617,14 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
return Handle<GPUResource>.Invalid;
}
return TrackAllocation(alloc, D3D12_RESOURCE_STATE_COMMON, ResourceViewGroup.Invalid, default, name, false);
var barrierData = new ResourceBarrierData
{
Access = BarrierAccess.NoAccess,
Layout = BarrierLayout.Common,
Sync = BarrierSync.None
};
return TrackAllocation(alloc, barrierData, ResourceViewGroup.Invalid, default, name, false);
}
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, string name, CreationOptions options = default)
@@ -756,7 +684,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
Flags = D3D12MA_ALLOCATION_FLAG_NONE
};
var initialState = DetermineInitialTextureState(desc.Usage);
var isSubAllocation = options.AllocationType == ResourceAllocationType.Suballocation;
D3D12MA_Allocation* pAllocation = default;
ID3D12Resource* pResource = default;
@@ -764,18 +691,18 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
if (isSubAllocation)
{
hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, __uuidof(pResource), (void**)&pResource);
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, __uuidof(pResource), (void**)&pResource);
}
else
{
hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation);
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, null, (void**)&pAllocation);
pResource = pAllocation->GetResource();
}
if (hr.FAILED)
{
#if DEBUG
Marshal.ThrowExceptionForHR(hr);
ThrowIfFailed(hr);
#endif
return Handle<Texture>.Invalid;
}
@@ -822,14 +749,21 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
}
var barrierData = new ResourceBarrierData
{
Access = BarrierAccess.NoAccess,
Layout = BarrierLayout.Common,
Sync = BarrierSync.None
};
Handle<GPUResource> resource;
if (isSubAllocation)
{
resource = _resourceDatabase.ImportExternalResource(pResource, initialState.ToResourceState(), resourceDescriptor, name);
resource = _resourceDatabase.ImportExternalResource(pResource, barrierData, resourceDescriptor, name);
}
else
{
resource = TrackAllocation(pAllocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp);
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp);
}
return resource.AsTexture();
@@ -864,24 +798,34 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
Flags = D3D12MA_ALLOCATION_FLAG_NONE,
};
var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType);
var isSubAllocation = options.Heap.IsValid;
D3D12MA_Allocation* pAllocation = default;
ID3D12Resource* pResource = default;
HRESULT hr;
var initialState = desc.MemoryType switch
{
ResourceMemoryType.Default => D3D12_RESOURCE_STATE_COMMON,
ResourceMemoryType.Upload => D3D12_RESOURCE_STATE_GENERIC_READ,
ResourceMemoryType.Readback => D3D12_RESOURCE_STATE_COPY_DEST,
_ => D3D12_RESOURCE_STATE_COMMON
};
if (isSubAllocation)
{
hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, __uuidof(pResource), (void**)&pResource);
}
else
{
hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation);
hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation);
pResource = pAllocation->GetResource();
}
if (hr.FAILED)
{
#if DEBUG
ThrowIfFailed(hr);
#endif
return Handle<GraphicsBuffer>.Invalid;
}
@@ -922,14 +866,21 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
}
var barrierData = new ResourceBarrierData
{
Access = BarrierAccess.NoAccess,
Layout = BarrierLayout.Undefined,
Sync = BarrierSync.None
};
Handle<GPUResource> resource;
if (isSubAllocation)
{
resource = _resourceDatabase.ImportExternalResource(pResource, initialState.ToResourceState(), resourceDescriptor, name);
resource = _resourceDatabase.ImportExternalResource(pResource, barrierData, resourceDescriptor, name);
}
else
{
resource = TrackAllocation(pAllocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp);
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp);
}
return resource.AsGraphicsBuffer();

View File

@@ -37,32 +37,37 @@ internal class D3D12ResourceDatabase : IResourceDatabase
public ResourceDesc desc;
public ResourceViewGroup viewGroup;
public ResourceUnion resource;
public ResourceState state;
//public BarrierLayout layout;
//public BarrierAccess access;
//public BarrierSync sync;
public ResourceBarrierData barrierData;
public uint cpuFenceValue;
public readonly bool isExternal;
public readonly bool Allocated => isExternal ? resource.resource.Get() != null : resource.allocation.Get() != null;
public readonly SharedPtr<ID3D12Resource> ResourcePtr => isExternal ? resource.resource.Get() : resource.allocation.Get()->GetResource();
public ResourceRecord(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState state, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
public ResourceRecord(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceBarrierData barrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
{
this.resource = new ResourceUnion(allocation);
this.isExternal = false;
this.viewGroup = resourceDescriptor;
this.cpuFenceValue = cpuFenceValue;
this.state = state;
this.barrierData = barrierData;
this.desc = desc;
}
public ResourceRecord(ID3D12Resource* resource, ResourceState state, ResourceViewGroup viewGroup)
public ResourceRecord(ID3D12Resource* resource, ResourceBarrierData barrierData, ResourceViewGroup viewGroup)
{
this.resource = new ResourceUnion(resource);
this.isExternal = true;
this.viewGroup = viewGroup;
this.cpuFenceValue = ~0u;
this.state = state;
this.barrierData = barrierData;
this.desc = resource->GetDesc().ToResourceDesc();
}
@@ -130,7 +135,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
resource = default!;
}
public unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceState initialState, ResourceViewGroup viewGroup, string? name = null)
public unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceBarrierData initialBarrierData, ResourceViewGroup viewGroup, string? name = null)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (pResource == null)
@@ -141,7 +146,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return Handle<GPUResource>.Invalid;
}
var id = _resources.Add(new ResourceRecord(pResource, initialState, viewGroup), out var generation);
var id = _resources.Add(new ResourceRecord(pResource, initialBarrierData, viewGroup), out var generation);
var handle = new Handle<GPUResource>(id, generation);
#if DEBUG || GHOST_EDITOR
@@ -155,7 +160,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return handle;
}
public unsafe Handle<GPUResource> AddAllocation(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
public unsafe Handle<GPUResource> AddAllocation(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (allocation == null)
@@ -166,7 +171,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return Handle<GPUResource>.Invalid;
}
var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation);
var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialBarrierData, resourceDescriptor, desc), out var generation);
var handle = new Handle<GPUResource>(id, generation);
#if DEBUG || GHOST_EDITOR
@@ -215,7 +220,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return r.Value.ResourcePtr;
}
public Result<ResourceState, ErrorStatus> GetResourceState(Handle<GPUResource> handle)
public Result<ResourceBarrierData, ErrorStatus> GetResourceBarrierData(Handle<GPUResource> handle)
{
var r = GetResourceRecord(handle);
if (r.IsFailure)
@@ -223,10 +228,10 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return r.Error;
}
return r.Value.state;
return r.Value.barrierData;
}
public ErrorStatus SetResourceState(Handle<GPUResource> handle, ResourceState state)
public ErrorStatus SetResourceBarrierData(Handle<GPUResource> handle, ResourceBarrierData data)
{
var r = GetResourceRecord(handle);
if (r.IsFailure)
@@ -234,7 +239,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return r.Error;
}
r.Value.state = state;
r.Value.barrierData = data;
return ErrorStatus.None;
}

View File

@@ -156,7 +156,14 @@ internal unsafe class D3D12SwapChain : ISwapChain
rtv = rtv
};
var handle = _resourceDatabase.ImportExternalResource(pBackBuffer, ResourceState.Present, view);
var barrierData = new ResourceBarrierData
{
Access = BarrierAccess.NoAccess,
Layout = BarrierLayout.Present,
Sync = BarrierSync.None,
};
var handle = _resourceDatabase.ImportExternalResource(pBackBuffer, barrierData, view);
_backBuffers[i] = handle.AsTexture();
}
}

View File

@@ -1,3 +1,4 @@
using Ghost.Core.Graphics;
using Ghost.Graphics.RHI;
using TerraFX.Interop.DirectX;
@@ -79,62 +80,62 @@ internal unsafe static class D3D12Utility
{
var d3dStates = D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COMMON;
if ((state & ResourceState.VertexAndConstantBuffer) == ResourceState.VertexAndConstantBuffer)
if (state.HasFlag(ResourceState.VertexAndConstantBuffer))
{
d3dStates |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
}
if ((state & ResourceState.IndexBuffer) == ResourceState.IndexBuffer)
if (state.HasFlag(ResourceState.IndexBuffer))
{
d3dStates |= D3D12_RESOURCE_STATE_INDEX_BUFFER;
}
if ((state & ResourceState.RenderTarget) == ResourceState.RenderTarget)
if (state.HasFlag(ResourceState.RenderTarget))
{
d3dStates |= D3D12_RESOURCE_STATE_RENDER_TARGET;
}
if ((state & ResourceState.UnorderedAccess) == ResourceState.UnorderedAccess)
if (state.HasFlag(ResourceState.UnorderedAccess))
{
d3dStates |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
}
if ((state & ResourceState.DepthWrite) == ResourceState.DepthWrite)
if (state.HasFlag(ResourceState.DepthWrite))
{
d3dStates |= D3D12_RESOURCE_STATE_DEPTH_WRITE;
}
if ((state & ResourceState.DepthRead) == ResourceState.DepthRead)
if (state.HasFlag(ResourceState.DepthRead))
{
d3dStates |= D3D12_RESOURCE_STATE_DEPTH_READ;
}
if ((state & ResourceState.PixelShaderResource) == ResourceState.PixelShaderResource)
if (state.HasFlag(ResourceState.PixelShaderResource))
{
d3dStates |= D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
}
if ((state & ResourceState.CopyDest) == ResourceState.CopyDest)
if (state.HasFlag(ResourceState.CopyDest))
{
d3dStates |= D3D12_RESOURCE_STATE_COPY_DEST;
}
if ((state & ResourceState.CopySource) == ResourceState.CopySource)
if (state.HasFlag(ResourceState.CopySource))
{
d3dStates |= D3D12_RESOURCE_STATE_COPY_SOURCE;
}
if ((state & ResourceState.GenericRead) == ResourceState.GenericRead)
if (state.HasFlag(ResourceState.GenericRead))
{
d3dStates |= D3D12_RESOURCE_STATE_GENERIC_READ;
}
if ((state & ResourceState.IndirectArgument) == ResourceState.IndirectArgument)
if (state.HasFlag(ResourceState.IndirectArgument))
{
d3dStates |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
}
if ((state & ResourceState.NonPixelShaderResource) == ResourceState.NonPixelShaderResource)
if (state.HasFlag(ResourceState.NonPixelShaderResource))
{
d3dStates |= D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
}
@@ -250,6 +251,22 @@ internal unsafe static class D3D12Utility
};
}
public static D3D12_COMPARISON_FUNC ToD3DCompare(this ZTest z)
{
return z switch
{
ZTest.Disabled => D3D12_COMPARISON_FUNC_NEVER,
ZTest.Less => D3D12_COMPARISON_FUNC_LESS,
ZTest.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
ZTest.Equal => D3D12_COMPARISON_FUNC_EQUAL,
ZTest.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
ZTest.Greater => D3D12_COMPARISON_FUNC_GREATER,
ZTest.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
ZTest.Always => D3D12_COMPARISON_FUNC_ALWAYS,
_ => D3D12_COMPARISON_FUNC_LESS_EQUAL
};
}
public static D3D12_COMMAND_LIST_TYPE ToCommandListType(CommandBufferType type)
{
return type switch

View File

@@ -294,35 +294,122 @@ public struct PassDepthStencilDesc
}
[StructLayout(LayoutKind.Explicit)]
public struct BarrierSubresourceRange
{
public uint IndexOrFirstMipLevel
{
get; set;
}
public uint NumMipLevels
{
get; set;
}
public uint FirstArraySlice
{
get; set;
}
public uint NumArraySlices
{
get; set;
}
}
public struct BarrierDesc
{
public struct barrierdesc_transition
public BarrierType Type
{
public Handle<GPUResource> resource;
public ResourceState stateBefore;
public ResourceState stateAfter;
get; set;
}
public struct barrierdesc_aliasing
public BarrierSync SyncBefore
{
public Handle<GPUResource> resourceBefore;
public Handle<GPUResource> resourceAfter;
get; set;
}
public struct barrierdesc_uav
public BarrierSync SyncAfter
{
public Handle<GPUResource> resource;
get; set;
}
[FieldOffset(0)]
public BarrierType type;
[FieldOffset(4)]
public barrierdesc_transition transition;
[FieldOffset(4)]
public barrierdesc_aliasing aliasing;
[FieldOffset(4)]
public barrierdesc_uav uav;
public BarrierAccess AccessBefore
{
get; set;
}
public BarrierAccess AccessAfter
{
get; set;
}
public BarrierLayout LayoutBefore
{
get; set;
}
public BarrierLayout LayoutAfter
{
get; set;
}
public Handle<GPUResource> Resource
{
get; set;
}
public BarrierSubresourceRange Subresources
{
get; set;
}
public bool Discard
{
get; set;
}
public static BarrierDesc Global(BarrierSync syncBefore, BarrierSync syncAfter, BarrierAccess accessBefore, BarrierAccess accessAfter)
{
return new BarrierDesc
{
Type = BarrierType.Global,
SyncBefore = syncBefore,
SyncAfter = syncAfter,
AccessBefore = accessBefore,
AccessAfter = accessAfter
};
}
public static BarrierDesc Buffer(Handle<GPUResource> resource, BarrierSync syncBefore, BarrierSync syncAfter, BarrierAccess accessBefore, BarrierAccess accessAfter)
{
return new BarrierDesc
{
Type = BarrierType.Buffer,
Resource = resource,
SyncBefore = syncBefore,
SyncAfter = syncAfter,
AccessBefore = accessBefore,
AccessAfter = accessAfter
};
}
public static BarrierDesc Texture(Handle<GPUResource> resource, BarrierSync syncBefore, BarrierSync syncAfter, BarrierAccess accessBefore, BarrierAccess accessAfter, BarrierLayout layoutBefore, BarrierLayout layoutAfter, BarrierSubresourceRange subresources = default, bool discard = false)
{
return new BarrierDesc
{
Type = BarrierType.Texture,
Resource = resource,
SyncBefore = syncBefore,
SyncAfter = syncAfter,
AccessBefore = accessBefore,
AccessAfter = accessAfter,
LayoutBefore = layoutBefore,
LayoutAfter = layoutAfter,
Subresources = subresources,
Discard = discard
};
}
}
public struct ResourceDesc
@@ -574,9 +661,6 @@ public struct TextureDesc
}
}
/// <summary>
/// Describes the parameters used to configure a texture sampler for graphics rendering operations.
/// </summary>
public record struct SamplerDesc
{
public TextureFilterMode FilterMode
@@ -625,14 +709,8 @@ public record struct SamplerDesc
}
}
/// <summary>
/// Buffer description
/// </summary>
public struct BufferDesc
{
/// <summary>
/// Size of the buffer in bytes
/// </summary>
public ulong Size
{
get; set;
@@ -643,17 +721,11 @@ public struct BufferDesc
get; set;
}
/// <summary>
/// Buffer usage flags
/// </summary>
public BufferUsage Usage
{
get; set;
}
/// <summary>
/// Memory space for the buffer
/// </summary>
public ResourceMemoryType MemoryType
{
get; set;
@@ -678,22 +750,13 @@ public struct CommandError
}
}
/// <summary>
/// Swap chain description
/// </summary>
public struct SwapChainDesc
{
/// <summary>
/// Width of the swap chain
/// </summary>
public uint Width
{
get; set;
}
/// <summary>
/// Height of the swap chain
/// </summary>
public uint Height
{
get; set;
@@ -709,50 +772,29 @@ public struct SwapChainDesc
get; set;
}
/// <summary>
/// Back buffer Format
/// </summary>
public TextureFormat Format
{
get; set;
}
/// <summary>
/// Target for presentation (window handle or composition Target)
/// </summary>
public SwapChainTarget Target
{
get; set;
}
}
/// <summary>
/// Swap chain Target (window handle or composition surface)
/// </summary>
public struct SwapChainTarget
{
/// <summary>
/// Target space
/// </summary>
public SwapChainTargetType Type
{
get; set;
}
/// <summary>
/// Window handle for HWND targets
/// </summary>
public nint WindowHandle
{
get; set;
}
/// <summary>
/// Composition surface for UWP/WinUI targets
/// </summary>
public object? CompositionSurface
{
get; set;
@@ -787,11 +829,97 @@ public enum SwapChainTargetType
}
public enum BarrierType : int
public enum BarrierType
{
Transition,
Aliasing,
UAV
Global,
Texture,
Buffer
}
[Flags]
public enum BarrierSync : uint
{
None = 0x0,
All = 0x0,
Draw = 0x1,
IndexInput = 0x2,
VertexShading = 0x4,
PixelShading = 0x8,
DepthStencil = 0x10,
RenderTarget = 0x20,
ComputeShading = 0x40,
Raytracing = 0x80,
Copy = 0x100,
Resolve = 0x200,
ExecuteIndirect = 0x400,
Predication = 0x800,
AllShading = VertexShading | PixelShading | ComputeShading | Raytracing,
NonPixelShading = VertexShading | ComputeShading | Raytracing,
EmitRaytracingAccelerationStructurePostbuildInfo = 0x1000,
ClearUnorderedAccessView = 0x2000,
VideoDecode = 0x40000,
VideoProcess = 0x80000,
VideoEncode = 0x100000,
BuildRaytracingAccelerationStructure = 0x200000,
CopyRaytracingAccelerationStructure = 0x400000,
Split = 0x800000
}
[Flags]
public enum BarrierAccess : uint
{
Common = 0,
VertexBuffer = 0x1,
ConstantBuffer = 0x2,
IndexBuffer = 0x4,
RenderTarget = 0x8,
UnorderedAccess = 0x10,
DepthStencilWrite = 0x20,
DepthStencilRead = 0x40,
ShaderResource = 0x80,
StreamOutput = 0x100,
IndirectArgument = 0x200,
CopyDest = 0x400,
CopySource = 0x800,
ResolveDest = 0x1000,
ResolveSource = 0x2000,
RaytracingAccelerationStructureRead = 0x4000,
RaytracingAccelerationStructureWrite = 0x8000,
ShadingRateSource = 0x10000,
VideoDecodeRead = 0x20000,
VideoDecodeWrite = 0x40000,
VideoProcessRead = 0x80000,
VideoProcessWrite = 0x100000,
VideoEncodeRead = 0x200000,
VideoEncodeWrite = 0x400000,
NoAccess = 0x80000000
}
public enum BarrierLayout
{
Undefined = -1,
Common = 0,
Present = 0,
GenericRead = 1,
RenderTarget = 2,
UnorderedAccess = 3,
DepthStencilWrite = 4,
DepthStencilRead = 5,
ShaderResource = 6,
CopyDest = 7,
CopySource = 8,
ResolveDest = 9,
ResolveSource = 10,
ShadingRateSource = 11,
VideoDecodeRead = 12,
VideoDecodeWrite = 13,
VideoProcessRead = 14,
VideoProcessWrite = 15,
VideoEncodeRead = 16,
VideoEncodeWrite = 17,
DirectQueueCommon = 18,
ComputeQueueCommon = 19,
VideoQueueCommon = 20
}
[Flags]
@@ -948,41 +1076,15 @@ public enum ComparisonFunction
Always
}
/// <summary>
/// Specifies how to load attachment contents at the start of a render pass.
/// </summary>
public enum AttachmentLoadOp
{
/// <summary>
/// Load existing contents from memory. Use when you need to preserve previous data.
/// </summary>
Load,
/// <summary>
/// Clear the attachment to a specified value. Use when you want to start with a clean slate.
/// </summary>
Clear,
/// <summary>
/// Don't care about previous contents. Use when you'll overwrite all pixels (fullscreen pass).
/// On tile-based deferred renderers (TBDR), this can save significant memory bandwidth.
/// </summary>
DontCare
}
/// <summary>
/// Specifies how to store attachment contents at the end of a render pass.
/// </summary>
public enum AttachmentStoreOp
{
/// <summary>
/// Store the contents to memory for later use.
/// </summary>
Store,
/// <summary>
/// Discard the contents (not needed after this pass).
/// On tile-based deferred renderers (TBDR), this can save significant memory bandwidth.
/// </summary>
DontCare
}

View File

@@ -68,7 +68,7 @@ public interface ICommandBuffer : IDisposable
void ClearRenderTargetView(Handle<Texture> renderTarget, Color128 clearColor);
void ClearDepthStencilView(Handle<Texture> depthStencil, bool inlcudeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0);
void ClearDepthStencilView(Handle<Texture> depthStencil, bool inlcludeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0);
/// <summary>
/// Begins a render pass with the specified render Target
@@ -89,29 +89,7 @@ public interface ICommandBuffer : IDisposable
/// Inserts multiple resource barriers.
/// </summary>
/// <param name="barrierDescs">Resource barrier descriptions</param>
void ResourceBarrier(ReadOnlySpan<BarrierDesc> barrierDescs);
/// <summary>
/// Inserts a resource barrier for state transitions.
/// </summary>
/// <param name="resource">A handle to the GPU resource to transition.</param>
/// <param name="stateBefore">The current state of the resource before the transition.</param>
/// <param name="stateAfter">The desired state of the resource after the transition.</param>
void TransitionBarrier(Handle<GPUResource> resource, ResourceState stateBefore, ResourceState stateAfter);
/// <summary>
/// Inserts a resource barrier for state transitions. The current state is tracked internally.
/// </summary>
/// <param name="resource">A handle to the GPU resource to transition.</param>
/// <param name="stateAfter">The desired state of the resource after the transition.</param>
void TransitionBarrier(Handle<GPUResource> resource, ResourceState stateAfter);
/// <summary>
/// Inserts a barrier to ensure correct aliasing transitions between two GPU resources.
/// </summary>
/// <param name="resourceBefore">A handle to the GPU resource representing the state before the aliasing transition</param>
/// <param name="resourceAfter">A handle to the GPU resource representing the state after the aliasing transition</param>
void AliasBarrier(Handle<GPUResource> resourceBefore, Handle<GPUResource> resourceAfter);
void ResourceBarrier(params ReadOnlySpan<BarrierDesc> barrierDescs);
/// <summary>
/// Sets the pipeline state object
@@ -204,18 +182,16 @@ public interface ICommandBuffer : IDisposable
/// <param name="buffer">A handle to the buffer that will receive the uploaded data.</param>
/// <param name="data">A read-only span containing the data to upload to the buffer. The span must contain elements of space
/// <typeparamref name="T"/>.</param>
void UploadBuffer<T>(Handle<GraphicsBuffer> buffer, ReadOnlySpan<T> data)
void UploadBuffer<T>(Handle<GraphicsBuffer> buffer, params ReadOnlySpan<T> data)
where T : unmanaged;
/// <summary>
/// Uploads texture data to the specified texture resource starting at the given subresource index.
/// </summary>
/// <param name="texture">The texture resource to which the subresource data will be uploaded. Must be a valid, initialized texture handle.</param>
/// <param name="firstSubresource">The index of the first subresource in the texture to receive data. Must be less than the total number of subresources in the texture.</param>
/// <param name="subresources">A reference to the structure containing the subresource data to upload. The data must match the Format and layout expected by the texture.</param>
/// <param name="numSubresources">The number of subresources to upload, starting from <paramref name="firstSubresource"/>.
/// Must be greater than zero and not exceed the remaining subresources in the texture.</param>
void UploadTexture(Handle<Texture> texture, ReadOnlySpan<SubResourceData> subresources);
void UploadTexture(Handle<Texture> texture, params ReadOnlySpan<SubResourceData> subresources);
/// <summary>
/// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer.

View File

@@ -11,6 +11,20 @@ public interface IResourceReleasable
void ReleaseResource(IResourceDatabase database);
}
public struct ResourceBarrierData
{
public BarrierLayout Layout;
public BarrierAccess Access;
public BarrierSync Sync;
public ResourceBarrierData(BarrierLayout layout, BarrierAccess access, BarrierSync sync)
{
Layout = layout;
Access = access;
Sync = sync;
}
}
// TODO: Consider adding methods for resource enumeration, statistics, and bulk operations.
// TODO: Consider adding async resource loading and streaming support.
// TODO: Mesh, Material, Shader management could be separated into their own interfaces for better modularity because they are not bound to specific graphics API.
@@ -35,19 +49,19 @@ public interface IResourceDatabase : IDisposable
bool HasResource(Handle<GPUResource> handle);
/// <summary>
/// Retrieves the current state of the specified resource.
/// Retrieves the current barrier data of the specified resource.
/// </summary>
/// <param name="handle">The handle that uniquely identifies the resource whose state is to be retrieved.</param>
/// <returns>A ResourceState Value representing the current state of the resource associated with the specified handle.</returns>
Result<ResourceState, ErrorStatus> GetResourceState(Handle<GPUResource> handle);
/// <param name="handle">The handle that uniquely identifies the resource.</param>
/// <returns>A ResourceBarrierData value representing the current barrier state.</returns>
Result<ResourceBarrierData, ErrorStatus> GetResourceBarrierData(Handle<GPUResource> handle);
/// <summary>
/// Sets the state of the specified resource handle to the given Value.
/// Sets the barrier data of the specified resource handle.
/// </summary>
/// <param name="handle">The handle that identifies the resource whose state will be updated.</param>
/// <param name="state">The new state to assign to the resource represented by <paramref name="handle"/>.</param>
/// <param name="handle">The handle that identifies the resource.</param>
/// <param name="data">The new barrier data.</param>
/// <returns>An ErrorStatus indicating the success or failure of the operation.</returns>
ErrorStatus SetResourceState(Handle<GPUResource> handle, ResourceState state);
ErrorStatus SetResourceBarrierData(Handle<GPUResource> handle, ResourceBarrierData data);
/// <summary>
/// Retrieves the description of a GPU resource associated with the specified handle.

View File

@@ -744,11 +744,23 @@ public sealed class RenderGraph : IDisposable
// If we found a previous resource, insert aliasing barrier
if (mostRecentLastUse >= 0)
{
var barrier = ResourceBarrier.CreateAliasingBarrier(
resourceBefore,
id,
passIdx
);
BarrierDesc desc;
if (resource.type == RenderGraphResourceType.Texture)
{
desc = BarrierDesc.Texture(resource.backingResource,
BarrierSync.All, BarrierSync.None,
BarrierAccess.NoAccess, BarrierAccess.NoAccess,
BarrierLayout.Undefined, BarrierLayout.Common,
discard: true);
}
else
{
desc = BarrierDesc.Buffer(resource.backingResource,
BarrierSync.All, BarrierSync.None,
BarrierAccess.NoAccess, BarrierAccess.NoAccess);
}
var barrier = ResourceBarrier.Create(passIdx, desc, id);
_barriers.Add(barrier);
}
}
@@ -843,17 +855,120 @@ public sealed class RenderGraph : IDisposable
if (currentState != newState)
{
var barrier = ResourceBarrier.CreateTransitionBarrier(
resource,
currentState,
newState,
passIdx
);
var res = _resources.GetResource(resource);
GetBarrierInfo(currentState, out var syncBefore, out var accessBefore, out var layoutBefore);
GetBarrierInfo(newState, out var syncAfter, out var accessAfter, out var layoutAfter);
BarrierDesc desc;
if (res.type == RenderGraphResourceType.Texture)
{
desc = BarrierDesc.Texture(res.backingResource,
syncBefore, syncAfter,
accessBefore, accessAfter,
layoutBefore, layoutAfter);
}
else
{
desc = BarrierDesc.Buffer(res.backingResource,
syncBefore, syncAfter,
accessBefore, accessAfter);
}
var barrier = ResourceBarrier.Create(passIdx, desc, resource);
_barriers.Add(barrier);
_resourceStates[resource.Value] = newState;
}
}
private static void GetBarrierInfo(ResourceState state, out BarrierSync sync, out BarrierAccess access, out BarrierLayout layout)
{
sync = BarrierSync.None;
access = BarrierAccess.Common;
layout = BarrierLayout.Common;
if (state == ResourceState.Common)
{
return;
}
if (state.HasFlag(ResourceState.RenderTarget))
{
sync |= BarrierSync.RenderTarget;
access |= BarrierAccess.RenderTarget;
layout = BarrierLayout.RenderTarget;
}
if (state.HasFlag(ResourceState.DepthWrite))
{
sync |= BarrierSync.DepthStencil;
access |= BarrierAccess.DepthStencilWrite;
layout = BarrierLayout.DepthStencilWrite;
}
if (state.HasFlag(ResourceState.DepthRead))
{
sync |= BarrierSync.DepthStencil;
access |= BarrierAccess.DepthStencilRead;
layout = BarrierLayout.DepthStencilRead;
}
if (state.HasFlag(ResourceState.UnorderedAccess))
{
sync |= BarrierSync.AllShading;
access |= BarrierAccess.UnorderedAccess;
layout = BarrierLayout.UnorderedAccess;
}
if (state.HasFlag(ResourceState.PixelShaderResource))
{
sync |= BarrierSync.PixelShading;
access |= BarrierAccess.ShaderResource;
layout = BarrierLayout.ShaderResource;
}
if (state.HasFlag(ResourceState.NonPixelShaderResource))
{
sync |= BarrierSync.NonPixelShading;
access |= BarrierAccess.ShaderResource;
layout = BarrierLayout.ShaderResource;
}
if (state.HasFlag(ResourceState.CopyDest))
{
sync |= BarrierSync.Copy;
access |= BarrierAccess.CopyDest;
layout = BarrierLayout.CopyDest;
}
if (state.HasFlag(ResourceState.CopySource))
{
sync |= BarrierSync.Copy;
access |= BarrierAccess.CopySource;
layout = BarrierLayout.CopySource;
}
if (state.HasFlag(ResourceState.VertexAndConstantBuffer))
{
sync |= BarrierSync.VertexShading;
access |= BarrierAccess.VertexBuffer | BarrierAccess.ConstantBuffer;
layout = BarrierLayout.Common;
}
if (state.HasFlag(ResourceState.IndexBuffer))
{
sync |= BarrierSync.IndexInput;
access |= BarrierAccess.IndexBuffer;
layout = BarrierLayout.Common;
}
if (state.HasFlag(ResourceState.IndirectArgument))
{
sync |= BarrierSync.ExecuteIndirect;
access |= BarrierAccess.IndirectArgument;
layout = BarrierLayout.GenericRead;
}
if (state.HasFlag(ResourceState.GenericRead))
{
layout = BarrierLayout.GenericRead;
}
if (state.HasFlag(ResourceState.Present))
{
sync = BarrierSync.All;
access = BarrierAccess.Common;
layout = BarrierLayout.Present;
}
}
/// <summary>
/// Determines the appropriate resource state for a buffer read operation based on usage hints.
/// </summary>
@@ -1327,55 +1442,30 @@ public sealed class RenderGraph : IDisposable
/// </summary>
private unsafe void ExecuteBarriersForPass(ICommandBuffer cmd, int passIndex, ref int barrierIndex)
{
var pBarrierDescs = stackalloc BarrierDesc[16]; // batch by 16
var count = 0;
var hasRemain = false;
int start = barrierIndex;
int count = 0;
Start:
while (barrierIndex < _barriers.Count && _barriers[barrierIndex].PassIndex == passIndex)
{
var barrier = _barriers[barrierIndex];
var desc = new BarrierDesc();
desc.type = barrier.Type;
switch (desc.type)
{
case BarrierType.Transition:
desc.transition.resource = _resources.GetResource(barrier.Resource).backingResource;
desc.transition.stateBefore = barrier.StateBefore;
desc.transition.stateAfter = barrier.StateAfter;
break;
case BarrierType.Aliasing:
desc.aliasing.resourceBefore = _resources.GetResource(barrier.ResourceBefore).backingResource;
desc.aliasing.resourceAfter = _resources.GetResource(barrier.ResourceAfter).backingResource;
break;
case BarrierType.UAV:
desc.uav.resource = _resources.GetResource(barrier.Resource).backingResource;
break;
}
pBarrierDescs[count] = desc;
count++;
barrierIndex++;
if (count == 16)
{
hasRemain = _barriers.Count > barrierIndex && _barriers[barrierIndex].PassIndex == passIndex;
break;
}
}
if (count > 0)
{
cmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(pBarrierDescs, count));
}
if (hasRemain)
{
count = 0;
hasRemain = false;
goto Start;
const int BatchSize = 64;
var descs = stackalloc BarrierDesc[BatchSize];
int processed = 0;
while (processed < count)
{
int batch = Math.Min(count - processed, BatchSize);
for (int i = 0; i < batch; i++)
{
descs[i] = _barriers[start + processed + i].Desc;
}
cmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(descs, batch));
processed += batch;
}
}
}

View File

@@ -6,95 +6,22 @@ namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Represents a resource barrier that needs to be inserted.
/// For D3D12 aliasing barriers: ResourceBefore is the old resource, ResourceAfter is the new resource.
/// </summary>
internal struct ResourceBarrier
{
[StructLayout(LayoutKind.Explicit)]
private struct barrier_union
{
internal struct barrier_union_transition
{
public Identifier<RGResource> resource;
public ResourceState stateBefore;
public ResourceState stateAfter;
}
public int PassIndex;
public BarrierDesc Desc;
public Identifier<RGResource> LogicalResource;
internal struct barrier_union_aliasing
{
public Identifier<RGResource> resourceBefore;
public Identifier<RGResource> resourceAfter;
}
public readonly Identifier<RGResource> Resource => LogicalResource;
// TODO: union can not have non-blittable types
[FieldOffset(0)]
public barrier_union_transition transition;
[FieldOffset(0)]
public barrier_union_aliasing aliasing;
}
private barrier_union _union;
public BarrierType Type
{
get; init;
}
public int PassIndex
{
get; init;
}
// For Transition and UAV barriers
public readonly Identifier<RGResource> Resource => _union.transition.resource;
public readonly ResourceState StateBefore => _union.transition.stateBefore;
public readonly ResourceState StateAfter => _union.transition.stateAfter;
// For Aliasing barriers (D3D12_RESOURCE_BARRIER::Aliasing)
public readonly Identifier<RGResource> ResourceBefore => _union.aliasing.resourceBefore;
public readonly Identifier<RGResource> ResourceAfter => _union.aliasing.resourceAfter;
// Constructor for Aliasing barriers
public static ResourceBarrier CreateAliasingBarrier(
Identifier<RGResource> resourceBefore,
Identifier<RGResource> resourceAfter,
int passIndex)
public static ResourceBarrier Create(int passIndex, BarrierDesc desc, Identifier<RGResource> logicalResource)
{
return new ResourceBarrier
{
Type = BarrierType.Aliasing,
PassIndex = passIndex,
_union = new barrier_union
{
aliasing = new barrier_union.barrier_union_aliasing
{
resourceBefore = resourceBefore,
resourceAfter = resourceAfter
}
}
};
}
public static ResourceBarrier CreateTransitionBarrier(
Identifier<RGResource> resource,
ResourceState before,
ResourceState after,
int passIndex)
{
return new ResourceBarrier
{
Type = BarrierType.Transition,
PassIndex = passIndex,
_union = new barrier_union
{
transition = new barrier_union.barrier_union_transition
{
resource = resource,
stateBefore = before,
stateAfter = after
}
}
Desc = desc,
LogicalResource = logicalResource
};
}
}

View File

@@ -245,7 +245,7 @@ internal class MeshRenderPass : IRenderPass
};
matRef.SetPropertyCache(in matProps).ThrowIfFailed();
matRef.UploadData(ctx.DirectCommandBuffer);
matRef.UploadData(ctx.DirectCommandBuffer, ctx.ResourceDatabase);
}
public void Build(RenderGraph graph, Identifier<RGTexture> backbuffer)
@@ -298,7 +298,7 @@ internal class MeshRenderPass : IRenderPass
};
matRef.SetPropertyCache(in blitProps).ThrowIfFailed();
matRef.UploadData(ctx.CommandBuffer);
matRef.UploadData(ctx.CommandBuffer, ctx.ResourceDatabase);
ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle<Texture>.Invalid);

View File

@@ -10,7 +10,7 @@ shader "Hidden/Blit"
{
pipeline
{
ztest = always;
ztest = disabled;
zwrite = off;
cull = off;
blend = opaque;

View File

@@ -1,218 +0,0 @@
using BenchmarkDotNet.Attributes;
using Ghost.Core;
namespace Ghost.RenderGraph.Concept.Benchmark;
[MemoryDiagnoser]
public class RenderGraphBenchmark
{
private RenderGraph _renderGraph = null!;
[GlobalSetup]
public void Setup()
{
_renderGraph = new RenderGraph();
// Warm up
ExecuteGraph(_renderGraph);
}
[Benchmark]
public void Execute()
{
_renderGraph.Reset();
ExecuteGraph(_renderGraph);
}
public static void ExecuteGraph(RenderGraph renderGraph, int idx = 0)
{
// Import external resources
var backbuffer = renderGraph.ImportTexture(
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "Backbuffer"));
// ===== GBuffer Pass =====
GBufferData gbufferData;
using (var builder = renderGraph.AddRasterRenderPass<GBufferData>("GBuffer Pass", out gbufferData))
{
// Create GBuffer textures
gbufferData.Albedo = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "GBuffer.Albedo"));
gbufferData.Normal = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "GBuffer.Normal"));
gbufferData.Depth = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.Depth32F, "GBuffer.Depth"));
// Store in pass data and mark as written
builder.SetColorAttachment(gbufferData.Albedo, 0);
builder.SetColorAttachment(gbufferData.Normal, 1);
builder.SetDepthAttachment(gbufferData.Depth);
builder.SetRenderFunc<GBufferData>(static (data, cmd) =>
{
// New api will handle render target setup and clearing automatically
//cmd.SetRenderTarget(data.Albedo.Name);
//cmd.SetRenderTarget(data.Normal.Name);
//cmd.SetDepthStencil(data.Depth.Name);
//cmd.ClearRenderTarget(data.Albedo.Name, 0, 0, 0, 1);
//cmd.ClearRenderTarget(data.Normal.Name, 0.5f, 0.5f, 1.0f, 1);
//cmd.ClearDepth(data.Depth.Name, 1.0f);
cmd.Draw(36000);
});
}
// Store GBuffer data in blackboard for other passes
renderGraph.Blackboard.Add(gbufferData);
// ===== Lighting Pass =====
Identifier<RGTexture> lightingOutput;
using (var builder = renderGraph.AddRasterRenderPass<LightingPassData>("Lighting Pass", out var lightingData))
{
// Read GBuffer from blackboard
var gbuffer = renderGraph.Blackboard.Get<GBufferData>();
lightingData.GBufferAlbedo = builder.UseTexture(gbuffer.Albedo, AccessFlags.Read);
lightingData.GBufferNormal = builder.UseTexture(gbuffer.Normal, AccessFlags.Read);
lightingData.GBufferDepth = builder.UseTexture(gbuffer.Depth, AccessFlags.Read);
// Create output texture
lightingOutput = builder.CreateTexture(
new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "LightingResult"));
builder.SetColorAttachment(lightingOutput, 0);
lightingData.OutputLighting = lightingOutput;
builder.SetRenderFunc<LightingPassData>(static (data, cmd) =>
{
cmd.BindShaderResource(data.GBufferAlbedo.AsResource(), 0);
cmd.BindShaderResource(data.GBufferNormal.AsResource(), 1);
cmd.BindShaderResource(data.GBufferDepth.AsResource(), 2);
cmd.Draw(3);
});
}
// ===== SSAO Pass (Async Compute) =====
Identifier<RGTexture> ssaoOutput;
Identifier<RGBuffer> ssaoBufferOutput;
using (var builder = renderGraph.AddComputeRenderPass<SSAOPassData>("SSAO Pass (Async)", out var ssaoData))
{
var gbuffer = renderGraph.Blackboard.Get<GBufferData>();
ssaoData.GBufferDepth = builder.UseTexture(gbuffer.Depth, AccessFlags.Read);
ssaoData.GBufferNormal = builder.UseTexture(gbuffer.Normal, AccessFlags.Read);
// SSAO Output
ssaoOutput = builder.CreateTexture(
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "SSAO"));
ssaoData.OutputSSAO = builder.UseTexture(ssaoOutput, AccessFlags.Write);
ssaoBufferOutput = builder.CreateBuffer(
new BufferDescriptor(1920 * 1080 * 4, sizeof(byte), BufferUsage.UnorderedAccess, "SSAO.Buffer"));
ssaoData.OutputSSAOBuffer = builder.UseBuffer(ssaoBufferOutput, AccessFlags.WriteAll);
builder.EnableAsyncCompute(true);
// Use SetComputeFunc with asyncCompute: true
builder.SetRenderFunc<SSAOPassData>(static (data, cmd) =>
{
cmd.BindShaderResource(data.GBufferDepth.AsResource(), 0);
cmd.BindShaderResource(data.GBufferNormal.AsResource(), 1);
cmd.BindUnorderedAccess(data.OutputSSAO.AsResource(), 0);
cmd.Dispatch(1920 / 8, 1080 / 8, 1);
});
}
// ===== Bloom Downsample Pass (will alias with albedo) =====
Identifier<RGTexture> bloomOutput;
using (var builder = renderGraph.AddRasterRenderPass<BloomDownsampleData>("Bloom Downsample", out var bloomData))
{
bloomData.Input = builder.UseTexture(lightingOutput, AccessFlags.Read);
// Create a texture that will alias with SSAO (same size, same format)
bloomOutput = builder.CreateTexture(
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "BloomDownsample"));
builder.SetColorAttachment(bloomOutput, 0);
builder.UseBuffer(ssaoBufferOutput, AccessFlags.Read);
bloomData.Output = bloomOutput;
builder.SetRenderFunc<BloomDownsampleData>(static (data, cmd) =>
{
cmd.BindShaderResource(data.Input.AsResource(), 0);
cmd.Draw(3);
});
}
// ===== Temporal AA Pass =====
Identifier<RGTexture> taaOutput;
using (var builder = renderGraph.AddRasterRenderPass<TAAPassData>("Temporal AA", out var taaData))
{
taaData.InputLighting = builder.UseTexture(lightingOutput, AccessFlags.Read);
taaOutput = builder.CreateTexture(
new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "TAA.Result"));
builder.SetColorAttachment(taaOutput, 0);
taaData.OutputTAA = taaOutput;
builder.SetRenderFunc<TAAPassData>(static (data, cmd) =>
{
cmd.BindShaderResource(data.InputLighting.AsResource(), 0);
cmd.Draw(3);
});
}
// ===== Post Processing Pass =====
using (var builder = renderGraph.AddRasterRenderPass<PostProcessingPassDataV1>("Post Processing 1", out var postData))
{
postData.InputLighting = lightingOutput;
builder.SetColorAttachment(backbuffer, 0);
builder.SetRenderFunc<PostProcessingPassDataV1>(static (data, cmd) =>
{
cmd.BindShaderResource(data.InputLighting.AsResource(), 0);
cmd.Draw(3);
});
}
using (var builder = renderGraph.AddRasterRenderPass<PostProcessingPassDataV2>("Post Processing 2", out var postData))
{
postData.InputTAA = builder.UseTexture(taaOutput, AccessFlags.Read);
postData.InputSSAO = builder.UseTexture(ssaoOutput, AccessFlags.Read);
postData.InputBloom = builder.UseTexture(bloomOutput, AccessFlags.Read);
builder.SetColorAttachment(backbuffer, 0);
builder.SetRenderFunc<PostProcessingPassDataV2>(static (data, cmd) =>
{
cmd.BindShaderResource(data.InputTAA.AsResource(), 0);
cmd.BindShaderResource(data.InputSSAO.AsResource(), 1);
cmd.BindShaderResource(data.InputBloom.AsResource(), 2);
cmd.Draw(3);
});
}
// ===== GPU Profiler Marker Pass (non-cullable, textureless) =====
using (var builder = renderGraph.AddRasterRenderPass<ProfilerMarkerData>("GPU Profiler Begin Frame", out var profilerData))
{
builder.AllowPassCulling(false); // Never cull this - it's for debugging/profiling
builder.SetRenderFunc<ProfilerMarkerData>(static (data, cmd) =>
{
// Note: In a real implementation we would have specific profiler commands
// For now, since RasterRenderContext doesn't expose generic console write, we skip the print
// or we would add a specific Profiler method to the context
});
}
// ===== Unused Debug Pass (will be culled) =====
using (var builder = renderGraph.AddRasterRenderPass<DebugPassData>("Unused Debug Pass", out var debugData))
{
builder.SetColorAttachment(
builder.CreateTexture(new TextureDescriptor(512, 512, TextureFormat.RGBA8, "DebugTexture")), 0);
builder.SetRenderFunc<DebugPassData>(static (data, cmd) =>
{
cmd.Draw(100);
});
}
// Compile and execute the render graph
renderGraph.Compile();
renderGraph.Execute();
}
}

View File

@@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>$(DefineConstants);PROFILING</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>$(DefineConstants);PROFILING</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,64 +0,0 @@
using Ghost.Core;
namespace Ghost.RenderGraph.Concept;
// ===== Pass Data Structures =====
// These are user-defined data structures that get passed to render functions
public sealed class GBufferData : IPassData
{
public Identifier<RGTexture> Albedo;
public Identifier<RGTexture> Normal;
public Identifier<RGTexture> Depth;
}
public sealed class LightingPassData : IPassData
{
public Identifier<RGTexture> GBufferAlbedo;
public Identifier<RGTexture> GBufferNormal;
public Identifier<RGTexture> GBufferDepth;
public Identifier<RGTexture> OutputLighting;
}
public sealed class SSAOPassData : IPassData
{
public Identifier<RGTexture> GBufferDepth;
public Identifier<RGTexture> GBufferNormal;
public Identifier<RGTexture> OutputSSAO;
public Identifier<RGBuffer> OutputSSAOBuffer;
}
public sealed class BloomDownsampleData : IPassData
{
public Identifier<RGTexture> Input;
public Identifier<RGTexture> Output;
}
public sealed class TAAPassData : IPassData
{
public Identifier<RGTexture> InputLighting;
public Identifier<RGTexture> OutputTAA;
}
public sealed class PostProcessingPassDataV1 : IPassData
{
public Identifier<RGTexture> InputLighting;
public Identifier<RGTexture> OutputBackbuffer;
}
public sealed class PostProcessingPassDataV2 : IPassData
{
public Identifier<RGTexture> InputTAA;
public Identifier<RGTexture> InputSSAO;
public Identifier<RGTexture> InputBloom;
public Identifier<RGTexture> OutputBackbuffer;
}
public sealed class ProfilerMarkerData : IPassData
{
}
public sealed class DebugPassData : IPassData
{
public Identifier<RGTexture> DebugTexture;
}

View File

@@ -1,47 +0,0 @@
using Ghost.RenderGraph.Concept;
using Ghost.RenderGraph.Concept.Benchmark;
#if !DEBUG
BenchmarkDotNet.Running.BenchmarkRunner.Run<RenderGraphBenchmark>();
return;
var renderGraph = new RenderGraph();
const int _ITERATION = 500000;
for (var i = 0; i < _ITERATION; i++)
{
RenderGraphBenchmark.ExecuteGraph(renderGraph);
renderGraph.Reset();
}
GC.Collect();
GC.WaitForPendingFinalizers();
Thread.Sleep(1000); // Leave a gap in visual studio allocations timeline
var sw = new System.Diagnostics.Stopwatch();
var gcBefore = GC.GetAllocatedBytesForCurrentThread();
sw.Start();
for (var i = 0; i < _ITERATION; i++)
{
RenderGraphBenchmark.ExecuteGraph(renderGraph);
renderGraph.Reset();
}
sw.Stop();
var gcAfter = GC.GetAllocatedBytesForCurrentThread();
Console.WriteLine($"{sw.Elapsed.TotalNanoseconds / _ITERATION} ns (per iteration)");
Console.WriteLine($"GC Allocated Bytes: {(gcAfter - gcBefore) / _ITERATION} bytes (per iteration)");
#else
var renderGraph = new RenderGraph();
// Run twice to demonstrate cache hit
Console.WriteLine("=== FRAME 1 (Cache Miss Expected) ===");
RenderGraphBenchmark.ExecuteGraph(renderGraph);
//Thread.Sleep(5000);
//renderGraph.Reset();
//Console.WriteLine("\n\n=== FRAME 2 (Cache Hit Expected) ===");
//RenderGraphBenchmark.ExecuteGraph(renderGraph);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,686 +0,0 @@
using Ghost.Core.Utilities;
using System.Runtime.InteropServices;
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// Represents a memory block within a heap.
/// </summary>
internal struct MemoryBlock
{
public ulong offset;
public ulong size;
public bool isFree;
public int firstUsePass;
public int lastUsePass;
public int logicalResourceIndex; // Which logical resource is currently using this block
public MemoryBlock(ulong offset, ulong size)
{
this.offset = offset;
this.size = size;
isFree = true;
firstUsePass = int.MaxValue;
lastUsePass = -1;
logicalResourceIndex = -1;
}
public void Reset()
{
isFree = true;
firstUsePass = int.MaxValue;
lastUsePass = -1;
logicalResourceIndex = -1;
}
}
/// <summary>
/// Represents a GPU memory heap for placed resources.
/// Supports D3D12-style heap tier 2 (buffers and textures can alias).
/// </summary>
internal sealed class ResourceHeap
{
public int index;
public ulong size;
private readonly List<MemoryBlock> _blocks = new(32);
// D3D12 heap alignment requirement (64KB for MSAA textures, 4KB for others)
private const ulong DefaultAlignment = 65536; // 64KB
public ResourceHeap(int index, ulong initialSize = 16 * 1024 * 1024) // 16MB default
{
this.index = index;
this.size = initialSize;
// Initially one large free block
_blocks.Add(new MemoryBlock(0, initialSize));
}
public void Reset()
{
_blocks.Clear();
_blocks.Add(new MemoryBlock(0, size));
}
/// <summary>
/// Attempts to allocate a block of the requested size with proper alignment.
/// Uses best-fit algorithm with lifetime-aware allocation.
/// </summary>
public (bool success, ulong offset, MemoryBlock block) TryAllocate(
ulong requestedSize,
int firstUsePass,
int lastUsePass,
int logicalResourceIndex,
ulong alignment = DefaultAlignment)
{
var alignedSize = AlignUp(requestedSize, alignment);
var bestFitIndex = -1;
ulong bestFitOffset = 0;
var smallestWaste = ulong.MaxValue;
// Find the best fit block that doesn't overlap with lifetime
var blockSpan = CollectionsMarshal.AsSpan(_blocks);
for (var i = 0; i < blockSpan.Length; i++)
{
ref var block = ref blockSpan[i];
// Try to find space within this block
var alignedOffset = AlignUp(block.offset, alignment);
var endOffset = alignedOffset + alignedSize;
if (endOffset <= block.offset + block.size)
{
// Check if this offset range conflicts with ANY existing allocations
var canUseOffset = CanPlaceAtOffset(alignedOffset, alignedSize, firstUsePass, lastUsePass);
if (canUseOffset)
{
var waste = block.size - alignedSize;
if (waste < smallestWaste)
{
smallestWaste = waste;
bestFitIndex = i;
bestFitOffset = alignedOffset;
}
}
}
}
if (bestFitIndex == -1)
{
return (false, 0, default);
}
ref var bestFit = ref CollectionsMarshal.AsSpan(_blocks)[bestFitIndex];
// If the block is free, we need to split it
if (bestFit.isFree)
{
var remainingSize = (bestFit.offset + bestFit.size) - (bestFitOffset + alignedSize);
// Update the current block to be allocated
bestFit.offset = bestFitOffset;
bestFit.size = alignedSize;
bestFit.isFree = false;
bestFit.firstUsePass = firstUsePass;
bestFit.lastUsePass = lastUsePass;
bestFit.logicalResourceIndex = logicalResourceIndex;
// Create a new free block for the remaining space if there is any
if (remainingSize > 0)
{
var newBlock = new MemoryBlock(bestFitOffset + alignedSize, remainingSize);
_blocks.Insert(bestFitIndex + 1, newBlock);
}
}
else
{
// Block is already allocated but lifetime doesn't overlap, we can alias it
// Create a new aliased block at the same location
var aliasedBlock = new MemoryBlock(bestFitOffset, alignedSize)
{
isFree = false,
firstUsePass = firstUsePass,
lastUsePass = lastUsePass,
logicalResourceIndex = logicalResourceIndex
};
// Insert in sorted order by offset
var insertIndex = 0;
for (var i = 0; i < _blocks.Count; i++)
{
if (_blocks[i].offset > bestFitOffset)
{
break;
}
insertIndex = i + 1;
}
_blocks.Insert(insertIndex, aliasedBlock);
// Update bestFit to point to the newly inserted block
bestFit = ref CollectionsMarshal.AsSpan(_blocks)[insertIndex];
}
return (true, bestFitOffset, bestFit);
}
/// <summary>
/// Checks if a resource can be placed at the given offset without lifetime conflicts.
/// Must check ALL blocks that overlap with this offset range.
/// </summary>
private bool CanPlaceAtOffset(ulong offset, ulong size, int firstUsePass, int lastUsePass)
{
var endOffset = offset + size;
foreach (var block in _blocks)
{
// Skip free blocks - they don't have lifetime constraints
if (block.isFree)
continue;
// Check if this block's memory range overlaps with our target range
var blockEnd = block.offset + block.size;
var memoryOverlap = !(offset >= blockEnd || endOffset <= block.offset);
if (memoryOverlap)
{
// Memory ranges overlap, check if lifetimes also overlap
var lifetimeOverlap = !(firstUsePass > block.lastUsePass || lastUsePass < block.firstUsePass);
if (lifetimeOverlap)
{
// Both memory AND lifetime overlap - cannot place here!
return false;
}
}
}
return true;
}
/// <summary>
/// Gets the total memory that would be used if no aliasing occurred.
/// </summary>
public ulong GetTotalAllocatedWithoutAliasing()
{
ulong total = 0;
foreach (var block in _blocks)
{
if (!block.isFree)
{
total += block.size;
}
}
return total;
}
/// <summary>
/// Gets the peak memory usage considering aliasing (max offset + size).
/// </summary>
public ulong GetPeakUsage()
{
ulong peak = 0;
foreach (var block in _blocks)
{
if (!block.isFree)
{
peak = Math.Max(peak, block.offset + block.size);
}
}
return peak;
}
private static ulong AlignUp(ulong value, ulong alignment)
{
return (value + alignment - 1) & ~(alignment - 1);
}
}
/// <summary>
/// Represents a placed resource within a heap.
/// </summary>
internal sealed class PlacedResource
{
public int index;
public RenderGraphResourceType type;
public int heapIndex;
public ulong heapOffset;
public ulong sizeInBytes;
// Original descriptor
public TextureDescriptor textureDesc;
public BufferDescriptor bufferDesc;
// Lifetime tracking
public int firstUsePass = int.MaxValue;
public int lastUsePass = -1;
// Aliasing tracking
public readonly List<int> aliasedLogicalResources = new(4);
public MemoryBlock memoryBlock;
public void Reset()
{
index = -1;
type = RenderGraphResourceType.Texture;
heapIndex = -1;
heapOffset = 0;
sizeInBytes = 0;
textureDesc = default;
bufferDesc = default;
firstUsePass = int.MaxValue;
lastUsePass = -1;
aliasedLogicalResources.Clear();
memoryBlock = default;
}
public void UpdateLifetime(int passIndex)
{
firstUsePass = Math.Min(firstUsePass, passIndex);
lastUsePass = Math.Max(lastUsePass, passIndex);
}
}
/// <summary>
/// Manages physical resource allocation and aliasing using heap-based allocation.
/// Supports D3D12 heap tier 2: buffers and textures can alias as long as lifetimes don't overlap.
/// </summary>
internal sealed class ResourceAliasingManager
{
private readonly List<ResourceHeap> _heaps = new(4);
private readonly List<PlacedResource> _placedResources = new(32);
private readonly RenderGraphObjectPool _pool = new();
// Mapping from logical resource index to placed resource index
private readonly Dictionary<int, int> _logicalToPlaced = new(64);
// D3D12 alignment constants
private const ulong DefaultTextureAlignment = 65536; // 64KB
private const ulong DefaultBufferAlignment = 65536; // 64KB for D3D12
public void BeginFrame()
{
for (var i = 0; i < _placedResources.Count; i++)
{
_pool.Return(_placedResources[i]);
}
_placedResources.Clear();
_logicalToPlaced.Clear();
// Reset heaps
for (var i = 0; i < _heaps.Count; i++)
{
_heaps[i].Reset();
}
}
/// <summary>
/// Assigns physical resources (placed resources) to logical resources using heap-based allocation.
/// This is the modern D3D12 approach: check if resource fits in a hole, not if it matches size/format.
/// Uses a two-pass algorithm:
/// 1. First pass: Simulate allocation to determine peak memory usage
/// 2. Second pass: Create a single heap of the peak size and do the real allocation
/// </summary>
public void AssignPhysicalResources(RenderGraphResourceRegistry registry, int passCount)
{
#if DEBUG
Console.WriteLine("\n=== Heap-Based Resource Aliasing Analysis ===");
ulong totalLogicalSize = 0;
#endif
// Build list of all logical resources (both textures and buffers) with their lifetimes
var logicalResources = ListPool<(int index, RenderGraphResource resource)>.Rent();
// Iterate through all resources in unified list
for (var i = 0; i < registry.ResourceCount; i++)
{
var resource = registry.GetResourceByIndex(i);
if (!resource.isImported) // Don't alias imported resources
{
logicalResources.Add((resource.index, resource));
#if DEBUG
var size = resource.type == RenderGraphResourceType.Texture
? CalculateTextureSize(resource.textureDescriptor)
: resource.bufferDescriptor.sizeInBytes;
totalLogicalSize += size;
var typeName = resource.type == RenderGraphResourceType.Texture ? "Texture" : "Buffer";
var name = resource.type == RenderGraphResourceType.Texture
? resource.textureDescriptor.name
: resource.bufferDescriptor.name;
Console.WriteLine($"Logical {typeName} {i}: {name}");
Console.WriteLine($" Lifetime: Pass {resource.firstUsePass} -> {resource.lastUsePass}");
Console.WriteLine($" Size: {size / 1024.0:F2} KB");
#endif
}
}
// Sort by size descending (larger resources first for better packing)
logicalResources.Sort(static (a, b) =>
{
var sizeA = a.resource.type == RenderGraphResourceType.Texture
? CalculateTextureSize(a.resource.textureDescriptor)
: a.resource.bufferDescriptor.sizeInBytes;
var sizeB = b.resource.type == RenderGraphResourceType.Texture
? CalculateTextureSize(b.resource.textureDescriptor)
: b.resource.bufferDescriptor.sizeInBytes;
return sizeB.CompareTo(sizeA); // Descending
});
// ===== PASS 1: Simulate allocation to determine peak memory usage =====
var simulationHeap = new ResourceHeap(0, ulong.MaxValue); // Unlimited size for simulation
foreach (var (logicalIndex, logicalResource) in logicalResources)
{
ulong size;
ulong alignment;
if (logicalResource.type == RenderGraphResourceType.Texture)
{
size = CalculateTextureSize(logicalResource.textureDescriptor);
alignment = DefaultTextureAlignment;
}
else // Buffer
{
size = logicalResource.bufferDescriptor.sizeInBytes;
alignment = DefaultBufferAlignment;
}
var (success, offset, block) = simulationHeap.TryAllocate(
size,
logicalResource.firstUsePass,
logicalResource.lastUsePass,
logicalIndex,
alignment);
if (!success)
{
throw new InvalidOperationException("Simulation allocation failed - this should never happen with unlimited heap");
}
}
// Get peak usage from simulation
var peakMemoryUsage = simulationHeap.GetPeakUsage();
// Align peak usage to 64KB (D3D12 requirement)
peakMemoryUsage = AlignUp(peakMemoryUsage, DefaultTextureAlignment);
#if DEBUG
Console.WriteLine($"\nPeak Memory Usage: {peakMemoryUsage / (1024.0 * 1024.0):F2} MB");
#endif
// ===== PASS 2: Create a single heap of the peak size and do the real allocation =====
var mainHeap = new ResourceHeap(0, peakMemoryUsage);
_heaps.Add(mainHeap);
#if DEBUG
Console.WriteLine($"Created Single Heap:");
Console.WriteLine($" Size: {peakMemoryUsage / (1024.0 * 1024.0):F2} MB\n");
#endif
// Allocate each logical resource in the heap
foreach (var (logicalIndex, logicalResource) in logicalResources)
{
ulong size;
ulong alignment;
if (logicalResource.type == RenderGraphResourceType.Texture)
{
size = CalculateTextureSize(logicalResource.textureDescriptor);
alignment = DefaultTextureAlignment;
}
else // Buffer
{
size = logicalResource.bufferDescriptor.sizeInBytes;
alignment = DefaultBufferAlignment;
}
var (success, offset, block) = mainHeap.TryAllocate(
size,
logicalResource.firstUsePass,
logicalResource.lastUsePass,
logicalIndex,
alignment);
if (!success)
{
throw new InvalidOperationException("Real allocation failed - this should match simulation");
}
var assignedHeapIndex = 0;
var assignedOffset = offset;
var assignedBlock = block;
var assignedPlaced = _pool.Rent<PlacedResource>();
assignedPlaced.index = _placedResources.Count;
assignedPlaced.type = logicalResource.type;
assignedPlaced.heapIndex = assignedHeapIndex;
assignedPlaced.heapOffset = assignedOffset;
assignedPlaced.sizeInBytes = size;
assignedPlaced.firstUsePass = logicalResource.firstUsePass;
assignedPlaced.lastUsePass = logicalResource.lastUsePass;
assignedPlaced.memoryBlock = assignedBlock;
if (logicalResource.type == RenderGraphResourceType.Texture)
{
assignedPlaced.textureDesc = logicalResource.textureDescriptor;
}
else
{
assignedPlaced.bufferDesc = logicalResource.bufferDescriptor;
}
assignedPlaced.aliasedLogicalResources.Clear();
assignedPlaced.aliasedLogicalResources.Add(logicalIndex);
_placedResources.Add(assignedPlaced);
#if DEBUG
var isAliased = assignedBlock.logicalResourceIndex != logicalIndex && assignedBlock.logicalResourceIndex != -1;
var name = logicalResource.type == RenderGraphResourceType.Texture
? logicalResource.textureDescriptor.name
: logicalResource.bufferDescriptor.name;
var typeName = logicalResource.type == RenderGraphResourceType.Texture ? "Texture" : "Buffer";
if (isAliased)
{
Console.WriteLine($"\nALIASING {typeName}: {name}");
Console.WriteLine($" Placed in Heap {assignedHeapIndex} at offset {assignedOffset} ({assignedOffset / 1024.0:F2} KB)");
Console.WriteLine($" Size: {size / 1024.0:F2} KB");
}
else
{
Console.WriteLine($"\nAllocated {typeName}: {name}");
Console.WriteLine($" Heap {assignedHeapIndex}, Offset {assignedOffset} ({assignedOffset / 1024.0:F2} KB)");
Console.WriteLine($" Size: {size / 1024.0:F2} KB");
}
#endif
// Record the mapping
_logicalToPlaced[logicalIndex] = assignedPlaced.index;
}
// Second pass: Populate aliasedLogicalResources lists
// For each placed resource, find all OTHER placed resources at the same heap+offset
for (var i = 0; i < _placedResources.Count; i++)
{
var placed = _placedResources[i];
// Find all logical resources that share the same heap location
for (var j = 0; j < _placedResources.Count; j++)
{
if (i == j) continue; // Skip self
var other = _placedResources[j];
// Check if they're at the same heap+offset
if (other.heapIndex == placed.heapIndex && other.heapOffset == placed.heapOffset)
{
// Add the other's logical resource to this one's aliased list
var otherLogicalIndex = other.aliasedLogicalResources[0]; // Each has exactly one at this point
if (!placed.aliasedLogicalResources.Contains(otherLogicalIndex))
{
placed.aliasedLogicalResources.Add(otherLogicalIndex);
}
}
}
}
#if DEBUG
// Debug output: Show which resources alias with each other
Console.WriteLine("\n=== Aliasing Groups ===");
var processedOffsets = new HashSet<(int heapIndex, ulong offset)>();
for (var i = 0; i < _placedResources.Count; i++)
{
var placed = _placedResources[i];
var key = (placed.heapIndex, placed.heapOffset);
if (!processedOffsets.Contains(key) && placed.aliasedLogicalResources.Count > 1)
{
processedOffsets.Add(key);
Console.WriteLine($"Heap {placed.heapIndex} @ Offset {placed.heapOffset / 1024.0:F2} KB ({placed.aliasedLogicalResources.Count} resources):");
foreach (var logicalIdx in placed.aliasedLogicalResources)
{
var res = registry.GetResourceByIndex(logicalIdx);
var name = res.type == RenderGraphResourceType.Texture
? res.textureDescriptor.name
: res.bufferDescriptor.name;
Console.WriteLine($" - {name} (Pass {res.firstUsePass}-{res.lastUsePass})");
}
}
}
Console.WriteLine("=======================\n");
#endif
#if DEBUG
ulong totalPhysicalSize = 0;
for (var i = 0; i < _heaps.Count; i++)
{
totalPhysicalSize += _heaps[i].GetPeakUsage();
}
Console.WriteLine($"\n=== Heap-Based Aliasing Summary ===");
Console.WriteLine($"Logical Resources: {logicalResources.Count}");
Console.WriteLine($"Placed Resources: {_placedResources.Count}");
Console.WriteLine($"Heaps: {_heaps.Count}");
Console.WriteLine($"Total Logical Memory: {totalLogicalSize / 1024.0:F2} KB");
Console.WriteLine($"Total Physical Memory: {totalPhysicalSize / 1024.0:F2} KB");
Console.WriteLine($"Memory Saved: {(totalLogicalSize - totalPhysicalSize) / 1024.0:F2} KB ({(1.0 - (double)totalPhysicalSize / totalLogicalSize) * 100.0:F1}%)");
Console.WriteLine("================================\n");
#endif
ListPool<(int index, RenderGraphResource resource)>.Return(logicalResources);
}
public int GetPlacedResourceIndex(int logicalIndex)
{
return _logicalToPlaced.TryGetValue(logicalIndex, out var placedIndex) ? placedIndex : -1;
}
public PlacedResource? GetPlacedResource(int placedIndex)
{
return placedIndex >= 0 && placedIndex < _placedResources.Count
? _placedResources[placedIndex]
: null;
}
private static ulong CalculateTextureSize(TextureDescriptor descriptor)
{
var bytesPerPixel = descriptor.format switch
{
TextureFormat.RGBA8 => 4,
TextureFormat.RGBA16F => 8,
TextureFormat.RGBA32F => 16,
TextureFormat.Depth32F => 4,
TextureFormat.Depth24Stencil8 => 4,
_ => 4
};
// Add alignment padding (D3D12 requires 64KB alignment)
var size = (ulong)(descriptor.width * descriptor.height * bytesPerPixel);
return AlignUp(size, DefaultTextureAlignment);
}
private static ulong AlignUp(ulong value, ulong alignment)
{
return (value + alignment - 1) & ~(alignment - 1);
}
public void Clear()
{
for (var i = 0; i < _placedResources.Count; i++)
{
_pool.Return(_placedResources[i]);
}
_placedResources.Clear();
_logicalToPlaced.Clear();
_heaps.Clear();
}
/// <summary>
/// Restores aliasing state from cache.
/// </summary>
public void RestoreFromCache(Dictionary<int, int> logicalToPlaced, List<PlacedResourceData> placedData)
{
_logicalToPlaced.Clear();
foreach (var kvp in logicalToPlaced)
{
_logicalToPlaced[kvp.Key] = kvp.Value;
}
// Restore placed resources
for (var i = 0; i < placedData.Count; i++)
{
var placed = _pool.Rent<PlacedResource>();
var data = placedData[i];
placed.index = data.index;
placed.type = data.type;
placed.heapIndex = data.heapIndex;
placed.heapOffset = data.heapOffset;
placed.sizeInBytes = data.sizeInBytes;
placed.textureDesc = data.textureDesc;
placed.bufferDesc = data.bufferDesc;
placed.firstUsePass = data.firstUsePass;
placed.lastUsePass = data.lastUsePass;
placed.aliasedLogicalResources.Clear();
_placedResources.Add(placed);
}
}
/// <summary>
/// Stores current aliasing state to cache.
/// </summary>
public void StoreToCache(Dictionary<int, int> outLogicalToPlaced, List<PlacedResourceData> outPlacedData)
{
outLogicalToPlaced.Clear();
foreach (var kvp in _logicalToPlaced)
{
outLogicalToPlaced[kvp.Key] = kvp.Value;
}
outPlacedData.Clear();
for (var i = 0; i < _placedResources.Count; i++)
{
var placed = _placedResources[i];
outPlacedData.Add(new PlacedResourceData
{
index = placed.index,
type = placed.type,
heapIndex = placed.heapIndex,
heapOffset = placed.heapOffset,
sizeInBytes = placed.sizeInBytes,
textureDesc = placed.textureDesc,
bufferDesc = placed.bufferDesc,
firstUsePass = placed.firstUsePass,
lastUsePass = placed.lastUsePass
});
}
}
}

View File

@@ -1,154 +0,0 @@
using Ghost.Core;
using System.Runtime.InteropServices;
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// GPU resource states for barrier tracking.
/// Based on D3D12 resource states.
/// </summary>
[Flags]
public enum ResourceState
{
Common = 0,
RenderTarget = 1 << 0,
DepthWrite = 1 << 1,
DepthRead = 1 << 2,
ShaderResource = 1 << 3,
UnorderedAccess = 1 << 4,
CopySource = 1 << 5,
CopyDest = 1 << 6,
Present = 1 << 7,
IndirectArgument = 1 << 8,
}
/// <summary>
/// Types of barriers that can be inserted.
/// </summary>
public enum BarrierType
{
Transition, // State transition (e.g., RenderTarget -> ShaderResource)
Aliasing, // Aliasing barrier (new resource reusing memory)
}
/// <summary>
/// Represents a resource barrier that needs to be inserted.
/// For D3D12 aliasing barriers: ResourceBefore is the old resource, ResourceAfter is the new resource.
/// </summary>
internal struct ResourceBarrier
{
[StructLayout(LayoutKind.Explicit)]
private struct barrier_union
{
internal struct barrier_union_transition
{
public Identifier<RGResource> resource;
public ResourceState stateBefore;
public ResourceState stateAfter;
}
internal struct barrier_union_aliasing
{
public Identifier<RGResource> resourceBefore;
public Identifier<RGResource> resourceAfter;
}
// TODO: union can not have non-blittable types
[FieldOffset(0)]
public barrier_union_transition transition;
[FieldOffset(0)]
public barrier_union_aliasing aliasing;
}
private barrier_union _union;
public BarrierType Type
{
get; init;
}
public int PassIndex
{
get; init;
}
// For Transition and UAV barriers
public readonly Identifier<RGResource> Resource => _union.transition.resource;
public readonly ResourceState StateBefore => _union.transition.stateBefore;
public readonly ResourceState StateAfter => _union.transition.stateAfter;
// For Aliasing barriers (D3D12_RESOURCE_BARRIER::Aliasing)
public readonly Identifier<RGResource> ResourceBefore => _union.aliasing.resourceBefore;
public readonly Identifier<RGResource> ResourceAfter => _union.aliasing.resourceAfter;
// Constructor for Aliasing barriers
public static ResourceBarrier CreateAliasingBarrier(
Identifier<RGResource> resourceBefore,
Identifier<RGResource> resourceAfter,
int passIndex)
{
return new ResourceBarrier
{
Type = BarrierType.Aliasing,
PassIndex = passIndex,
_union = new barrier_union
{
aliasing = new barrier_union.barrier_union_aliasing
{
resourceBefore = resourceBefore,
resourceAfter = resourceAfter
}
}
};
}
public static ResourceBarrier CreateTransitionBarrier(
Identifier<RGResource> resource,
ResourceState before,
ResourceState after,
int passIndex)
{
return new ResourceBarrier
{
Type = BarrierType.Transition,
PassIndex = passIndex,
_union = new barrier_union
{
transition = new barrier_union.barrier_union_transition
{
resource = resource,
stateBefore = before,
stateAfter = after
}
}
};
}
public override readonly string ToString()
{
return Type switch
{
BarrierType.Transition => $"[Pass {PassIndex}] Transition Barrier: Resource {Resource.Value} from {StateBefore} to {StateAfter}",
BarrierType.Aliasing => $"[Pass {PassIndex}] Aliasing Barrier: ResourceBefore {ResourceBefore.Value} to ResourceAfter {ResourceAfter.Value}",
_ => "Unknown Barrier Type"
};
}
}
/// <summary>
/// Tracks the current state of a resource across passes.
/// </summary>
internal sealed class ResourceStateTracker
{
public int resourceIndex;
public ResourceState currentState = ResourceState.Common;
public int lastAccessPass = -1;
public void Reset()
{
resourceIndex = -1;
currentState = ResourceState.Common;
lastAccessPass = -1;
}
}

View File

@@ -1,62 +0,0 @@
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// Blackboard for sharing data between render passes.
/// Uses a dictionary with type keys to store different pass data types.
/// Avoids allocations by reusing the same dictionary across frames.
/// </summary>
public sealed class RenderGraphBlackboard
{
private readonly Dictionary<Type, IPassData> _data = new(16);
/// <summary>
/// Adds or updates pass data in the blackboard.
/// </summary>
public void Add<T>(T data)
where T : class, IPassData
{
var type = typeof(T);
_data[type] = data;
}
/// <summary>
/// Retrieves pass data from the blackboard.
/// </summary>
public T Get<T>()
where T : class, IPassData
{
var type = typeof(T);
if (_data.TryGetValue(type, out var obj))
{
return (T)obj;
}
throw new KeyNotFoundException($"Pass data of type {type.Name} not found in blackboard");
}
/// <summary>
/// Tries to get pass data from the blackboard.
/// </summary>
public bool TryGet<T>(out T? data)
where T : class, IPassData
{
var type = typeof(T);
if (_data.TryGetValue(type, out var obj))
{
data = (T)obj;
return true;
}
data = null;
return false;
}
/// <summary>
/// Clears all data from the blackboard.
/// Does not deallocate the backing dictionary to avoid allocations.
/// </summary>
public void Clear()
{
_data.Clear();
}
}

View File

@@ -1,283 +0,0 @@
using Ghost.Core;
using System.Diagnostics;
namespace Ghost.RenderGraph.Concept;
[Flags]
public enum AccessFlags : byte
{
None = 0,
Read = 1 << 0,
Write = 1 << 1,
Discard = 1 << 2,
WriteAll = Write | Discard,
ReadWrite = Read | Write,
}
public interface IRenderGraphBuilder : IDisposable
{
/// <summary>
/// Enables or disables pass culling for the current context.
/// </summary>
/// <param name="value">A value indicating whether pass culling is allowed.</param>
void AllowPassCulling(bool value);
/// <summary>
/// Creates a new texture resource based on the specified descriptor.
/// </summary>
/// <param name="descriptor">A structure that defines the properties and configuration of the texture to create.</param>
/// <returns>An identifier for the newly created texture resource.</returns>
Identifier<RGTexture> CreateTexture(in TextureDescriptor descriptor);
/// <summary>
/// Creates a new buffer resource based on the specified descriptor.
/// </summary>
/// <param name="descriptor">A structure that defines the properties and configuration of the buffer to create.</param>
/// <returns>An identifier for the newly created buffer resource.</returns>
Identifier<RGBuffer> CreateBuffer(in BufferDescriptor descriptor);
/// <summary>
/// Registers the specified texture for use in the current render graph pass with the given access mode.
/// </summary>
/// <param name="texture">The identifier of the texture to be used in the render graph pass.</param>
/// <param name="accessMode">The access mode specifying how the texture will be read or written during the pass.</param>
/// <returns>An identifier for the texture.</returns>
Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags accessMode);
/// <summary>
/// Registers the specified buffer for use in the current render graph pass with the given access mode.
/// </summary>
/// <param name="buffer">The identifier of the buffer to be used in the render graph pass.</param>
/// <param name="accessMode">The access mode specifying how the buffer will be read or written during the pass.</param>
/// <param name="hint">Optional hint about how the buffer will be used (e.g., IndirectArgument). Default is None (ByteAddressBuffer SRV).</param>
/// <returns>An identifier for the buffer.</returns>
Identifier<RGBuffer> UseBuffer(Identifier<RGBuffer> buffer, AccessFlags accessMode, BufferHint hint = BufferHint.None);
}
public interface IRasterRenderGraphBuilder : IRenderGraphBuilder
{
/// <summary>
/// Binds a texture for random access operations within the current rendering pass.
/// </summary>
/// <param name="texture">The identifier of the texture to be used for random access.</param>
/// <returns>An identifier for the texture.</returns>
Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture);
/// <summary>
/// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
/// </summary>
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer resource.</param>
/// <returns>An identifier for the buffer.</returns>
Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer);
/// <summary>
/// Sets the color attachment at the specified index to the given texture.
/// </summary>
/// <param name="texture">The identifier of the texture to use as the color attachment.</param>
/// <param name="index">The zero-based index of the color attachment to set.</param>
/// <param name="flags">Access flags. Default is Write (assumes partial update). Use WriteAll for fullscreen passes.</param>
void SetColorAttachment(Identifier<RGTexture> texture, int index, AccessFlags flags = AccessFlags.Write);
/// <summary>
/// Sets the depth attachment for the current render pass using the specified texture.
/// </summary>
/// <param name="texture">The identifier of the texture to use as the depth attachment. Cannot be null.</param>
/// <param name="flags">Access flags. Default is ReadWrite (assumes partial update). Use WriteAll for fullscreen passes.</param>
void SetDepthAttachment(Identifier<RGTexture> texture, AccessFlags flags = AccessFlags.ReadWrite);
/// <summary>
/// Sets the function used to render a pass with the specified pass data and render context.
/// </summary>
/// <typeparam name="TPassData">The type of data associated with the render pass.</typeparam>
/// <param name="renderFunc">The delegate that defines the rendering logic for the pass.</param>
void SetRenderFunc<TPassData>(Action<TPassData, RasterRenderContext> renderFunc)
where TPassData : class, new();
}
public interface IComputeRenderGraphBuilder : IRenderGraphBuilder
{
/// <summary>
/// Enables or disables asynchronous compute operations.
/// </summary>
/// <param name="value">true to enable asynchronous compute; otherwise, false.</param>
void EnableAsyncCompute(bool value);
/// <summary>
/// Sets the render function to be invoked during the compute rendering process.
/// </summary>
/// <typeparam name="TPassData">The type of the data object passed to the render function.</typeparam>
/// <param name="renderFunc">The delegate that defines the rendering logic to execute.</param>
void SetRenderFunc<TPassData>(Action<TPassData, ComputeRenderContext> renderFunc)
where TPassData : class, new();
}
internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGraphBuilder
{
private RenderGraph _graph = null!;
private RenderGraphPassBase _pass = null!;
private RenderGraphResourceRegistry _resources = null!;
private bool _disposed;
internal void Init(RenderGraph graph, RenderGraphPassBase pass, RenderGraphResourceRegistry resources)
{
_graph = graph;
_pass = pass;
_resources = resources;
_disposed = false;
}
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
private Identifier<RGResource> UseResource(Identifier<RGResource> resource, AccessFlags accessFlags, RenderGraphResourceType type)
{
if (accessFlags.HasFlag(AccessFlags.Read))
{
_pass.resourceReads[(int)type].Add(resource);
_resources.AddConsumer(resource, _pass.index);
}
if (accessFlags.HasFlag(AccessFlags.Write))
{
_pass.resourceWrites[(int)type].Add(resource);
_resources.SetProducer(resource, _pass.index);
}
return resource;
}
public void AllowPassCulling(bool value)
{
_pass.allowCulling = value;
}
public void EnableAsyncCompute(bool value)
{
_pass.asyncCompute = value;
}
public Identifier<RGTexture> CreateTexture(in TextureDescriptor descriptor)
{
ThrowIfDisposed();
var handle = _resources.CreateTexture(descriptor);
_pass.resourceCreates[(int)RenderGraphResourceType.Texture].Add(handle.AsResource());
_resources.SetProducer(handle.AsResource(), _pass.index);
return handle;
}
public Identifier<RGBuffer> CreateBuffer(in BufferDescriptor descriptor)
{
ThrowIfDisposed();
var handle = _resources.CreateBuffer(descriptor);
_pass.resourceCreates[(int)RenderGraphResourceType.Buffer].Add(handle.AsResource());
_resources.SetProducer(handle.AsResource(), _pass.index);
return handle;
}
public Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags flags)
{
ThrowIfDisposed();
return UseResource(texture.AsResource(), flags, RenderGraphResourceType.Texture).AsTexture();
}
public Identifier<RGBuffer> UseBuffer(Identifier<RGBuffer> buffer, AccessFlags flags, BufferHint hint = BufferHint.None)
{
ThrowIfDisposed();
// Store buffer hint if not None
if (hint != BufferHint.None)
{
_pass.bufferHints[buffer.AsResource().Value] = hint;
}
return UseResource(buffer.AsResource(), flags, RenderGraphResourceType.Buffer).AsBuffer();
}
public Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture)
{
ThrowIfDisposed();
var resource = texture.AsResource();
UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Texture);
_pass.randomAccess.Add(resource);
return texture;
}
public Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer)
{
ThrowIfDisposed();
var resource = buffer.AsResource();
UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Buffer);
_pass.randomAccess.Add(resource);
return buffer;
}
public void SetColorAttachment(Identifier<RGTexture> texture, int index, AccessFlags flags = AccessFlags.Write)
{
ThrowIfDisposed();
Debug.Assert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range.");
var id = UseTexture(texture, flags);
if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid)
{
_pass.maxColorIndex = Math.Max(_pass.maxColorIndex, index);
_pass.colorAccess[index] = new TextureAccess(id, flags);
}
else
{
throw new InvalidOperationException($"Color attachment at index {index} is already set to a different texture.");
}
}
public void SetDepthAttachment(Identifier<RGTexture> texture, AccessFlags flags = AccessFlags.Write)
{
ThrowIfDisposed();
var id = UseTexture(texture, flags);
if (_pass.depthAccess.id == id || _pass.depthAccess.id.IsInvalid)
{
_pass.depthAccess = new TextureAccess(id, flags);
}
else
{
throw new InvalidOperationException("Depth attachment is already set to a different texture.");
}
}
public void SetRenderFunc<TPassData>(Action<TPassData, RasterRenderContext> renderFunc)
where TPassData : class, new()
{
((RasterRenderGraphPass<TPassData>)_pass).renderFunc = renderFunc;
}
public void SetRenderFunc<TPassData>(Action<TPassData, ComputeRenderContext> renderFunc)
where TPassData : class, new()
{
((ComputeRenderGraphPass<TPassData>)_pass).renderFunc = renderFunc;
}
public void Dispose()
{
if (_disposed)
{
return;
}
if (!_pass.HasRenderFunc())
{
throw new InvalidOperationException("RenderGraphBuilder must be disposed after setting up the render function.");
}
_graph = null!;
_pass = null!;
_resources = null!;
_disposed = true;
}
}

View File

@@ -1,134 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// Represents cached compilation results for a render graph.
/// This avoids recompiling the graph when the structure hasn't changed.
/// </summary>
internal sealed class CachedCompilation
{
// Compiled pass indices (indices into the _passes list)
public readonly List<int> compiledPassIndices = new(64);
// Culling decisions for each pass
public readonly List<bool> passCulledFlags = new(64);
// Physical resource aliasing mappings (logical index -> physical index)
public readonly Dictionary<int, int> logicalToPhysical = new(128);
// Placed resource metadata
public readonly List<PlacedResourceData> placedResources = new(32);
// Resource barriers
public readonly List<ResourceBarrier> barriers = new(128);
// Resource state mappings (for barrier generation)
public readonly Dictionary<int, ResourceState> resourceStates = new(128);
public void Clear()
{
compiledPassIndices.Clear();
passCulledFlags.Clear();
logicalToPhysical.Clear();
placedResources.Clear();
barriers.Clear();
resourceStates.Clear();
}
}
/// <summary>
/// Placed resource data for caching.
/// </summary>
internal struct PlacedResourceData
{
public int index;
public RenderGraphResourceType type;
public int heapIndex;
public ulong heapOffset;
public ulong sizeInBytes;
public TextureDescriptor textureDesc;
public BufferDescriptor bufferDesc;
public int firstUsePass;
public int lastUsePass;
}
/// <summary>
/// Manages compilation caching for render graphs.
/// Stores compiled results and allows cache hits when graph structure is unchanged.
/// </summary>
internal sealed class RenderGraphCompilationCache
{
private ulong _cachedHash;
private readonly CachedCompilation _cached = new();
private bool _hasCachedData;
// Statistics
public int CacheHits { get; private set; }
public int CacheMisses { get; private set; }
/// <summary>
/// Attempts to retrieve cached compilation results.
/// </summary>
public bool TryGetCached(ulong hash, [MaybeNullWhen(false)] out CachedCompilation result)
{
if (_hasCachedData && _cachedHash == hash)
{
result = _cached;
CacheHits++;
return true;
}
result = null;
CacheMisses++;
return false;
}
/// <summary>
/// Stores compilation results in the cache.
/// </summary>
public void Store(ulong hash, CachedCompilation data)
{
_cachedHash = hash;
_hasCachedData = true;
// Deep copy the data
_cached.Clear();
_cached.compiledPassIndices.AddRange(data.compiledPassIndices);
_cached.passCulledFlags.AddRange(data.passCulledFlags);
foreach (var kvp in data.logicalToPhysical)
{
_cached.logicalToPhysical[kvp.Key] = kvp.Value;
}
_cached.placedResources.AddRange(data.placedResources);
_cached.barriers.AddRange(data.barriers);
foreach (var kvp in data.resourceStates)
{
_cached.resourceStates[kvp.Key] = kvp.Value;
}
}
/// <summary>
/// Invalidates the cache, forcing recompilation on next Compile().
/// </summary>
public void Invalidate()
{
_hasCachedData = false;
_cachedHash = 0;
_cached.Clear();
}
/// <summary>
/// Gets cache statistics for debugging.
/// </summary>
public (int hits, int misses, double hitRate) GetStatistics()
{
int total = CacheHits + CacheMisses;
double hitRate = total > 0 ? (double)CacheHits / total : 0.0;
return (CacheHits, CacheMisses, hitRate);
}
}

View File

@@ -1,151 +0,0 @@
using Ghost.Core;
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// Mock command buffer for recording GPU commands.
/// In a real implementation, this would wrap D3D12 command lists.
/// </summary>
internal sealed class MockCommandBuffer
{
public void SetRenderTarget(Identifier<RGTexture> texture)
{
#if DEBUG
Console.WriteLine(nameof(SetRenderTarget) + ": " + texture);
#endif
}
public void SetDepthStencil(Identifier<RGTexture> texture)
{
#if DEBUG
Console.WriteLine(nameof(SetDepthStencil) + ": " + texture);
#endif
}
public void ClearRenderTarget(Identifier<RGTexture> texture, float r, float g, float b, float a)
{
#if DEBUG
Console.WriteLine(nameof(ClearRenderTarget) + ": " + texture);
#endif
}
public void ClearDepth(Identifier<RGTexture> texture, float depth)
{
#if DEBUG
Console.WriteLine(nameof(ClearDepth) + ": " + texture);
#endif
}
public void Draw(int vertexCount)
{
#if DEBUG
Console.WriteLine(nameof(Draw) + ": " + vertexCount);
#endif
}
public void BindShaderResource(Identifier<RGResource> resource, int slot)
{
#if DEBUG
Console.WriteLine(nameof(BindShaderResource) + ": " + resource + ", slot " + slot);
#endif
}
public void BindUnorderedAccess(Identifier<RGResource> resource, int slot)
{
#if DEBUG
Console.WriteLine(nameof(BindUnorderedAccess) + ": " + resource + ", slot " + slot);
#endif
}
public void Dispatch(int x, int y, int z)
{
#if DEBUG
Console.WriteLine(nameof(Dispatch) + ": " + x + ", " + y + ", " + z);
#endif
}
public void ResourceBarrier(Identifier<RGResource> resource, ResourceState stateBefore, ResourceState stateAfter)
{
#if DEBUG
Console.WriteLine(nameof(ResourceBarrier) + ": " + resource + " from " + stateBefore + " to " + stateAfter);
#endif
}
public void AliasBarrier(Identifier<RGResource> resourceBefore, Identifier<RGResource> resourceAfter)
{
#if DEBUG
Console.WriteLine(nameof(AliasBarrier) + ": " + resourceBefore + " to " + resourceAfter);
#endif
}
public void BeginRenderPass(int nativePassIndex, int colorAttachmentCount, bool hasDepth)
{
#if DEBUG
Console.WriteLine($"\n=== BeginRenderPass (Native Pass {nativePassIndex}) ===");
Console.WriteLine($" Color attachments: {colorAttachmentCount}, Depth: {hasDepth}");
#endif
}
public void EndRenderPass()
{
#if DEBUG
Console.WriteLine("=== EndRenderPass ===\n");
#endif
}
}
/// <summary>
/// Context for raster rendering passes.
/// Directly exposes command buffer methods.
/// </summary>
public readonly struct RasterRenderContext
{
private readonly MockCommandBuffer _cmd;
internal RasterRenderContext(MockCommandBuffer cmd)
{
_cmd = cmd;
}
// Expose command buffer methods directly
public void SetRenderTarget(Identifier<RGTexture> texture) => _cmd.SetRenderTarget(texture);
public void SetDepthStencil(Identifier<RGTexture> texture) => _cmd.SetDepthStencil(texture);
public void ClearRenderTarget(Identifier<RGTexture> texture, float r, float g, float b, float a) => _cmd.ClearRenderTarget(texture, r, g, b, a);
public void ClearDepth(Identifier<RGTexture> texture, float depth) => _cmd.ClearDepth(texture, depth);
public void Draw(int vertexCount) => _cmd.Draw(vertexCount);
public void BindShaderResource(Identifier<RGResource> resource, int slot) => _cmd.BindShaderResource(resource, slot);
}
/// <summary>
/// Context for compute rendering passes.
/// Directly exposes command buffer methods.
/// </summary>
public readonly struct ComputeRenderContext
{
private readonly MockCommandBuffer _cmd;
internal ComputeRenderContext(MockCommandBuffer cmd)
{
_cmd = cmd;
}
// Expose command buffer methods directly
public void BindShaderResource(Identifier<RGResource> resource, int slot) => _cmd.BindShaderResource(resource, slot);
public void BindUnorderedAccess(Identifier<RGResource> resource, int slot) => _cmd.BindUnorderedAccess(resource, slot);
public void Dispatch(int x, int y, int z) => _cmd.Dispatch(x, y, z);
}
/// <summary>
/// Unified render context containing both raster and compute contexts.
/// </summary>
internal readonly struct RenderContext
{
public readonly RasterRenderContext RasterContext;
public readonly ComputeRenderContext ComputeContext;
public RenderContext(MockCommandBuffer cmd)
{
RasterContext = new RasterRenderContext(cmd);
ComputeContext = new ComputeRenderContext(cmd);
}
}

View File

@@ -1,52 +0,0 @@
using Ghost.Core;
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// Represents a native render pass that can contain multiple merged logical passes.
/// Maps to D3D12 BeginRenderPass/EndRenderPass or Vulkan vkCmdBeginRenderPass/vkCmdEndRenderPass.
/// </summary>
internal sealed class NativeRenderPass
{
public int index;
/// <summary>
/// Indices of logical passes merged into this native render pass.
/// </summary>
public readonly List<int> mergedPassIndices = new(4);
/// <summary>
/// Color attachments shared across all merged passes.
/// </summary>
public RenderTargetInfo[] colorAttachments = new RenderTargetInfo[8];
public int colorAttachmentCount;
/// <summary>
/// Depth-stencil attachment (optional).
/// </summary>
public DepthStencilInfo depthAttachment;
public bool hasDepthAttachment;
/// <summary>
/// Range of logical passes included in this native pass.
/// </summary>
public int firstLogicalPass;
public int lastLogicalPass;
/// <summary>
/// Whether UAV writes are allowed during this render pass.
/// </summary>
public bool allowUAVWrites;
public void Reset()
{
index = -1;
mergedPassIndices.Clear();
colorAttachmentCount = 0;
hasDepthAttachment = false;
depthAttachment = default;
firstLogicalPass = int.MaxValue;
lastLogicalPass = -1;
allowUAVWrites = false;
}
}

View File

@@ -1,155 +0,0 @@
using Ghost.Core;
using System.Runtime.CompilerServices;
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// Represents different types of render passes.
/// </summary>
public enum RenderPassType : byte
{
Raster,
Compute
}
/// <summary>
/// Base class for render passes.
/// Uses pooling to avoid allocations after the first frame.
/// </summary>
internal abstract class RenderGraphPassBase
{
public string name = string.Empty;
public int index;
public RenderPassType type;
public bool allowCulling = true;
public bool asyncCompute;
public TextureAccess depthAccess;
public TextureAccess[] colorAccess = new TextureAccess[8];
public int maxColorIndex = -1;
public List<Identifier<RGResource>> randomAccess = new(8);
// Resource dependencies
public readonly List<Identifier<RGResource>>[] resourceReads = new List<Identifier<RGResource>>[(int)RenderGraphResourceType.Count];
public readonly List<Identifier<RGResource>>[] resourceWrites = new List<Identifier<RGResource>>[(int)RenderGraphResourceType.Count];
public readonly List<Identifier<RGResource>>[] resourceCreates = new List<Identifier<RGResource>>[(int)RenderGraphResourceType.Count];
// Buffer usage hints (maps buffer resource ID to hint)
public readonly Dictionary<int, BufferHint> bufferHints = new(8);
// Execution state
public bool culled;
public bool hasSideEffects;
public RenderGraphPassBase()
{
for (int i = 0; i < (int)RenderGraphResourceType.Count; i++)
{
resourceReads[i] = new List<Identifier<RGResource>>(8);
resourceWrites[i] = new List<Identifier<RGResource>>(4);
resourceCreates[i] = new List<Identifier<RGResource>>(4);
}
}
public abstract void Execute(RenderContext context);
public abstract bool HasRenderFunc();
public abstract int GetRenderFuncHashCode();
public virtual void Reset(RenderGraphObjectPool pool)
{
name = string.Empty;
index = -1;
type = RenderPassType.Raster;
allowCulling = true;
asyncCompute = false;
depthAccess = default;
colorAccess.AsSpan().Clear();
maxColorIndex = -1;
randomAccess.Clear();
for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
{
resourceReads[i].Clear();
resourceWrites[i].Clear();
resourceCreates[i].Clear();
}
bufferHints.Clear();
culled = false;
hasSideEffects = false;
}
}
internal abstract class RenderGraphPassT<TPassData, TRenderContext> : RenderGraphPassBase
where TPassData : class, new()
{
public TPassData passData = null!;
public Action<TPassData, TRenderContext>? renderFunc;
public void Init(int index, TPassData passData, string name, RenderPassType type)
{
this.index = index;
this.passData = passData;
this.name = name;
this.type = type;
}
public sealed override bool HasRenderFunc()
{
return renderFunc != null;
}
public override int GetRenderFuncHashCode()
{
if (renderFunc == null)
{
return 0;
}
var methodHashCode = RuntimeHelpers.GetHashCode(renderFunc.Method);
return renderFunc.Target == null ? methodHashCode : methodHashCode ^ RuntimeHelpers.GetHashCode(renderFunc.Target); // static deleget does not have target
}
public override void Reset(RenderGraphObjectPool pool)
{
base.Reset(pool);
pool.Return(passData);
passData = null!;
renderFunc = null;
}
}
internal sealed class RasterRenderGraphPass<TPassData> : RenderGraphPassT<TPassData, RasterRenderContext>
where TPassData : class, new()
{
public override void Execute(RenderContext context)
{
renderFunc!(passData, context.RasterContext);
}
public override void Reset(RenderGraphObjectPool pool)
{
base.Reset(pool);
pool.Return(this);
}
}
internal sealed class ComputeRenderGraphPass<TPassData> : RenderGraphPassT<TPassData, ComputeRenderContext>
where TPassData : class, new()
{
public override void Execute(RenderContext context)
{
renderFunc!(passData, context.ComputeContext);
}
public override void Reset(RenderGraphObjectPool pool)
{
base.Reset(pool);
pool.Return(this);
}
}

View File

@@ -1,251 +0,0 @@
using Ghost.Core;
using Misaki.HighPerformance.Buffer;
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// Object pool for reusing allocated objects across frames.
/// This is key to minimizing GC allocations after the first frame.
/// </summary>
internal sealed class RenderGraphObjectPool
{
private static readonly List<SharedObjectPoolBase> s_allocatedPools = new();
private class SharedObjectPoolBase
{
public SharedObjectPoolBase() { }
public virtual void Clear() { }
}
private class SharedObjectPool<T> : SharedObjectPoolBase where T : class, new()
{
private static readonly ObjectPool<T> s_pool = AllocatePool();
private static ObjectPool<T> AllocatePool()
{
var newPool = new ObjectPool<T>(() => new T(), null);
// Storing instance to clear the static pool of the same type if needed
s_allocatedPools.Add(new SharedObjectPool<T>());
return newPool;
}
/// <summary>
/// Clear the pool using SharedObjectPool instance.
/// </summary>
/// <returns></returns>
public override void Clear()
{
s_pool.Reset();
}
/// <summary>
/// Rent a new instance from the pool.
/// </summary>
/// <returns></returns>
// FIX: ObjectPool<T>.Rent() has a critical bug that it will put the newly created object into the pool directly and give out the same instance again.
// This will cause multiple renters to get the same instance.
public static T Rent() => s_pool.Rent();
/// <summary>
/// Return an object to the pool.
/// </summary>
/// <param name="toRelease">instance to release.</param>
public static void Return(T toRelease) => s_pool.Return(toRelease);
}
public T Rent<T>()
where T : class, new()
{
return SharedObjectPool<T>.Rent();
}
public void Return<T>(T obj)
where T : class, new()
{
SharedObjectPool<T>.Return(obj);
}
public void Clear()
{
for (var i = 0; i < s_allocatedPools.Count; i++)
{
s_allocatedPools[i].Clear();
}
}
}
/// <summary>
/// Represents a resource in the render graph (texture or buffer).
/// </summary>
internal sealed class RenderGraphResource
{
public RenderGraphResourceType type;
public int index;
public TextureDescriptor textureDescriptor;
public BufferDescriptor bufferDescriptor;
public bool isImported;
public int firstUsePass = -1;
public int lastUsePass = -1;
public int producerPass = -1;
public List<int> consumerPasses = new(4);
public int refCount;
public void Reset()
{
type = RenderGraphResourceType.Texture;
index = -1;
textureDescriptor = default;
bufferDescriptor = default;
isImported = false;
firstUsePass = -1;
lastUsePass = -1;
producerPass = -1;
consumerPasses.Clear();
refCount = 0;
}
}
/// <summary>
/// Registry for managing all resources in the render graph.
/// Uses pooling to minimize allocations after the first frame.
/// Uses a single unified list for both textures and buffers with global indexing.
/// </summary>
internal sealed class RenderGraphResourceRegistry
{
private readonly List<RenderGraphResource> _resources = new(64);
private readonly RenderGraphObjectPool _pool = new();
public int ResourceCount => _resources.Count;
public int TextureResourceCount
{
get
{
int count = 0;
for (int i = 0; i < _resources.Count; i++)
{
if (_resources[i].type == RenderGraphResourceType.Texture)
count++;
}
return count;
}
}
public int BufferResourceCount
{
get
{
int count = 0;
for (int i = 0; i < _resources.Count; i++)
{
if (_resources[i].type == RenderGraphResourceType.Buffer)
count++;
}
return count;
}
}
public void BeginFrame()
{
// Return all resources to pool
for (var i = 0; i < _resources.Count; i++)
{
_pool.Return(_resources[i]);
}
_resources.Clear();
}
public Identifier<RGTexture> ImportTexture(TextureDescriptor descriptor)
{
var resource = _pool.Rent<RenderGraphResource>();
resource.type = RenderGraphResourceType.Texture;
resource.index = _resources.Count;
resource.textureDescriptor = descriptor;
resource.isImported = true;
_resources.Add(resource);
return new Identifier<RGTexture>(resource.index);
}
public Identifier<RGTexture> CreateTexture(TextureDescriptor descriptor)
{
var resource = _pool.Rent<RenderGraphResource>();
resource.type = RenderGraphResourceType.Texture;
resource.index = _resources.Count;
resource.textureDescriptor = descriptor;
resource.isImported = false;
_resources.Add(resource);
return new Identifier<RGTexture>(resource.index);
}
public Identifier<RGBuffer> ImportBuffer(BufferDescriptor descriptor)
{
var resource = _pool.Rent<RenderGraphResource>();
resource.type = RenderGraphResourceType.Buffer;
resource.index = _resources.Count;
resource.bufferDescriptor = descriptor;
resource.isImported = true;
_resources.Add(resource);
return new Identifier<RGBuffer>(resource.index);
}
public Identifier<RGBuffer> CreateBuffer(BufferDescriptor descriptor)
{
var resource = _pool.Rent<RenderGraphResource>();
resource.type = RenderGraphResourceType.Buffer;
resource.index = _resources.Count;
resource.bufferDescriptor = descriptor;
resource.isImported = false;
_resources.Add(resource);
return new Identifier<RGBuffer>(resource.index);
}
public RenderGraphResource GetResource(Identifier<RGResource> resource)
{
return _resources[resource.Value];
}
public RenderGraphResource GetResource(Identifier<RGTexture> texture)
{
return _resources[texture.Value];
}
public RenderGraphResource GetResource(Identifier<RGBuffer> buffer)
{
return _resources[buffer.Value];
}
/// <summary>
/// Gets resource by global index. Use this when iterating over all resources.
/// </summary>
public RenderGraphResource GetResourceByIndex(int index)
{
return _resources[index];
}
public void SetProducer(Identifier<RGResource> resourceID, int passIndex)
{
var resource = GetResource(resourceID);
resource.producerPass = passIndex;
if (resource.firstUsePass < 0)
{
resource.firstUsePass = passIndex;
}
}
public void AddConsumer(Identifier<RGResource> resourceID, int passIndex)
{
var resource = GetResource(resourceID);
resource.consumerPasses.Add(passIndex);
resource.lastUsePass = passIndex;
if (resource.firstUsePass < 0)
{
resource.firstUsePass = passIndex;
}
}
}

View File

@@ -1,270 +0,0 @@
using Ghost.Core;
using System.Runtime.CompilerServices;
namespace Ghost.RenderGraph.Concept;
internal enum RenderGraphResourceType : int
{
Texture,
Buffer,
// AccelerationStructure,
Count
}
public struct RGResource;
public struct RGTexture;
public struct RGBuffer;
public static class RGResourceExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Identifier<RGResource> AsResource(this Identifier<RGTexture> texture)
{
return new Identifier<RGResource>(texture.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Identifier<RGResource> AsResource(this Identifier<RGBuffer> buffer)
{
return new Identifier<RGResource>(buffer.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Identifier<RGTexture> AsTexture(this Identifier<RGResource> resource)
{
return new Identifier<RGTexture>(resource.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Identifier<RGBuffer> AsBuffer(this Identifier<RGResource> resource)
{
return new Identifier<RGBuffer>(resource.Value);
}
}
/// <summary>
/// Hints for how a buffer will be used in a pass.
/// Used to determine correct resource state transitions.
/// </summary>
[Flags]
public enum BufferHint
{
/// <summary>
/// No special usage - buffer will be used as shader resource (SRV) or UAV based on AccessFlags.
/// </summary>
None = 0,
/// <summary>
/// Buffer will be used as indirect argument buffer (ExecuteIndirect).
/// Requires ResourceState.IndirectArgument.
/// </summary>
IndirectArgument = 1 << 0,
}
internal readonly struct TextureAccess
{
public readonly Identifier<RGTexture> id;
public readonly AccessFlags accessFlags;
public TextureAccess(Identifier<RGTexture> id, AccessFlags accessFlags)
{
this.id = id;
this.accessFlags = accessFlags;
}
}
/// <summary>
/// Tracks buffer access information including usage hints.
/// </summary>
internal readonly struct BufferAccess
{
public readonly Identifier<RGBuffer> id;
public readonly AccessFlags accessFlags;
public readonly BufferHint hint;
public BufferAccess(Identifier<RGBuffer> id, AccessFlags accessFlags, BufferHint hint = BufferHint.None)
{
this.id = id;
this.accessFlags = accessFlags;
this.hint = hint;
}
}
/// <summary>
/// Texture formats supported by the render graph.
/// </summary>
public enum TextureFormat : int
{
RGBA8,
RGBA16F,
RGBA32F,
Depth32F,
Depth24Stencil8
}
/// <summary>
/// Descriptor for creating a texture resource.
/// </summary>
public readonly struct TextureDescriptor : IEquatable<TextureDescriptor>
{
public readonly int width;
public readonly int height;
public readonly TextureFormat format;
public readonly string name;
public TextureDescriptor(int width, int height, TextureFormat format, string name)
{
this.width = width;
this.height = height;
this.format = format;
this.name = name;
}
public readonly bool Equals(TextureDescriptor other)
{
return width == other.width &&
height == other.height &&
format == other.format &&
name == other.name;
}
public override readonly bool Equals(object? obj) => obj is TextureDescriptor other && Equals(other);
public override readonly int GetHashCode() => HashCode.Combine(width, height, format, name);
public static bool operator ==(TextureDescriptor left, TextureDescriptor right)
{
return left.Equals(right);
}
public static bool operator !=(TextureDescriptor left, TextureDescriptor right)
{
return !(left == right);
}
}
[Flags]
public enum BufferUsage
{
None = 0,
Vertex = 1 << 0,
Index = 1 << 1,
IndirectArgument = 1 << 7,
Constant = 1 << 2,
ShaderResource = 1 << 3,
UnorderedAccess = 1 << 4,
Structured = 1 << 5,
Raw = 1 << 6,
Upload = 1 << 8,
Readback = 1 << 9,
}
public readonly struct BufferDescriptor : IEquatable<BufferDescriptor>
{
public readonly uint sizeInBytes;
public readonly uint stride;
public readonly BufferUsage usage;
public readonly string name;
public BufferDescriptor(uint sizeInBytes, uint stride, BufferUsage usage, string name)
{
this.sizeInBytes = sizeInBytes;
this.stride = stride;
this.usage = usage;
this.name = name;
}
public readonly bool Equals(BufferDescriptor other)
{
return sizeInBytes == other.sizeInBytes &&
stride == other.stride &&
usage == other.usage &&
name == other.name;
}
public override readonly bool Equals(object? obj) => obj is BufferDescriptor other && Equals(other);
public override readonly int GetHashCode() => HashCode.Combine(sizeInBytes, name);
public static bool operator ==(BufferDescriptor left, BufferDescriptor right)
{
return left.Equals(right);
}
public static bool operator !=(BufferDescriptor left, BufferDescriptor right)
{
return !(left == right);
}
}
/// <summary>
/// Base interface for pass data that can be stored in the blackboard.
/// </summary>
public interface IPassData
{
}
/// <summary>
/// Specifies how to load attachment contents at the beginning of a render pass.
/// </summary>
public enum AttachmentLoadOp
{
/// <summary>
/// Load existing contents from memory. Required when reading previous data.
/// </summary>
Load,
/// <summary>
/// Clear attachment to a specified value.
/// </summary>
Clear,
/// <summary>
/// Don't care about previous contents (best performance on TBDR GPUs).
/// Use when you guarantee to overwrite all pixels.
/// </summary>
DontCare
}
/// <summary>
/// Specifies how to store attachment contents at the end of a render pass.
/// </summary>
public enum AttachmentStoreOp
{
/// <summary>
/// Store contents to memory. Required if resource is used after this pass.
/// </summary>
Store,
/// <summary>
/// Don't care about storing contents (best performance on TBDR GPUs).
/// Use when resource is not needed after this pass.
/// </summary>
DontCare
}
/// <summary>
/// Information about a render target attachment in a native render pass.
/// </summary>
internal struct RenderTargetInfo
{
public Identifier<RGTexture> texture;
public AccessFlags access;
public AttachmentLoadOp loadOp;
public AttachmentStoreOp storeOp;
public float clearR;
public float clearG;
public float clearB;
public float clearA;
}
/// <summary>
/// Information about a depth-stencil attachment in a native render pass.
/// </summary>
internal struct DepthStencilInfo
{
public Identifier<RGTexture> texture;
public AccessFlags access;
public AttachmentLoadOp loadOp;
public AttachmentStoreOp storeOp;
public float clearDepth;
public byte clearStencil;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,598 +0,0 @@
#if false
using System;
using System.Diagnostics;
using UnityEngine.Experimental.Rendering;
using static UnityEngine.Rendering.RenderGraphModule.RenderGraph;
namespace UnityEngine.Rendering.RenderGraphModule
{
// This is a class making it a struct wouldn't help as we pas it around as an interface which means it would be boxed/unboxed anyway
// Publicly this class has different faces to help the users with different pass types through type safety but internally
// we just have a single implementation for all builders
internal class RenderGraphBuilders : IBaseRenderGraphBuilder, IComputeRenderGraphBuilder, IRasterRenderGraphBuilder, IUnsafeRenderGraphBuilder
{
RenderGraphPass m_RenderPass;
RenderGraphResourceRegistry m_Resources;
RenderGraph m_RenderGraph;
bool m_Disposed;
public RenderGraphBuilders()
{
m_RenderPass = null;
m_Resources = null;
m_RenderGraph = null;
m_Disposed = true;
}
public void Setup(RenderGraphPass renderPass, RenderGraphResourceRegistry resources, RenderGraph renderGraph)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
// If the object is not disposed yet this is an error as the pass is not finished (only in the dispose we register it with the rendergraph)
// This is likely cause by a user not doing a clean using and then forgetting to manually dispose the object.
if (m_Disposed != true)
{
throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_UndisposedBuilderPreviousPass);
}
#endif
m_RenderPass = renderPass;
m_Resources = resources;
m_RenderGraph = renderGraph;
m_Disposed = false;
renderPass.useAllGlobalTextures = false;
if (renderPass.type == RenderGraphPassType.Raster)
{
CommandBuffer.ThrowOnSetRenderTarget = true;
}
}
public void EnableAsyncCompute(bool value)
{
m_RenderPass.EnableAsyncCompute(value);
}
public void AllowPassCulling(bool value)
{
// This pass cannot be culled if it allows global state modifications
if (value && m_RenderPass.allowGlobalState)
return;
m_RenderPass.AllowPassCulling(value);
}
public void AllowGlobalStateModification(bool value)
{
m_RenderPass.AllowGlobalState(value);
// This pass cannot be culled if it allows global state modifications
if (value)
{
AllowPassCulling(false);
}
}
/// <summary>
/// Enable foveated rendering for this pass.
/// </summary>
/// <param name="value">True to enable foveated rendering.</param>
public void EnableFoveatedRasterization(bool value)
{
m_RenderPass.EnableFoveatedRasterization(value);
}
public BufferHandle CreateTransientBuffer(in BufferDesc desc)
{
var result = m_Resources.CreateBuffer(desc, m_RenderPass.index);
UseTransientResource(result.handle);
return result;
}
public BufferHandle CreateTransientBuffer(in BufferHandle computebuffer)
{
ref readonly var desc = ref m_Resources.GetBufferResourceDesc(computebuffer.handle);
return CreateTransientBuffer(desc);
}
public TextureHandle CreateTransientTexture(in TextureDesc desc)
{
var result = m_Resources.CreateTexture(desc, m_RenderPass.index);
UseTransientResource(result.handle);
return result;
}
public TextureHandle CreateTransientTexture(in TextureHandle texture)
{
ref readonly var desc = ref m_Resources.GetTextureResourceDesc(texture.handle);
return CreateTransientTexture(desc);
}
public void GenerateDebugData(bool value)
{
m_RenderPass.GenerateDebugData(value);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (m_Disposed)
return;
try
{
if (disposing)
{
m_RenderGraph.RenderGraphState = RenderGraphState.RecordingGraph;
// Use all globals simply means this... we do a UseTexture on all globals so the pass has the correct dependencies.
// This of course goes to show how bad an idea shader-system wide globals really are dependency/lifetime tracking wise :-)
if (m_RenderPass.useAllGlobalTextures)
{
foreach (var texture in m_RenderGraph.AllGlobals())
{
if (texture.IsValid())
this.UseTexture(texture, AccessFlags.Read);
}
}
// Set globals on the graph fronted side so subsequent passes can have pass dependencies on these global texture handles
foreach (var t in m_RenderPass.setGlobalsList)
{
m_RenderGraph.SetGlobal(t.Item1, t.Item2);
}
}
}
finally
{
if (m_RenderPass.type == RenderGraphPassType.Raster)
{
CommandBuffer.ThrowOnSetRenderTarget = false;
}
m_RenderPass = null;
m_Resources = null;
m_RenderGraph = null;
m_Disposed = true;
}
}
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
private void CheckWriteTo(in ResourceHandle handle)
{
if (RenderGraph.enableValidityChecks)
{
// Write by design generates a new version of the resource. However
// you could in theory write to v2 of the resource while there is already
// a v3 so this write would then introduce a new v4 of the resource.
// This would mean a divergence in the versioning history subsequent versions based on v2 and other subsequent versions based on v3
// this would be very confusing as they are all still refered to by the same texture just different versions
// so we decide to disallow this. It can always be (at zero cost) handled by using a "Move" pass to move the divergent version
// so it's own texture resource which again has it's own single history of versions.
if (handle.IsVersioned)
{
var name = m_Resources.GetRenderGraphResourceName(handle);
throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {handle.type} at index {handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_WriteToVersionedResource);
}
if (m_RenderPass.IsWritten(handle))
{
// We bump the version and write count if you call USeResource with writing flags. So calling this several times
// would lead to the side effect of new versions for every call to UseResource leading to incorrect versions.
// In theory we could detect and ignore the second UseResource but we decided to just disallow is as it might also
// Stem from user confusion.
// It seems the most likely cause of such a situation would be something like:
// TextureHandle b = a;
// ...
// much much code in between, user lost track that a=b
// ...
// builder.WriteTexture(a)
// builder.WriteTexture(b)
// > Get this error they were probably thinking they were writing two separate outputs... but they are just two versions of resource 'a'
// where they can only differ between by careful management of versioned resources.
var name = m_Resources.GetRenderGraphResourceName(handle);
throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {handle.type} at index {handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_WriteToResourceTwice);
}
}
}
// Lifetime of a transient resource is one render graph pass
private ResourceHandle UseTransientResource(in ResourceHandle inputHandle)
{
CheckResource(inputHandle);
ResourceHandle versionedHandle = inputHandle.IsVersioned ? inputHandle : m_Resources.GetLatestVersionHandle(inputHandle);
// Transient resources are always considered written and read in the render graph pass where they are used
// Compiler will take it into account later, no need to add them to read and write lists
m_RenderPass.AddTransientResource(versionedHandle);
return versionedHandle;
}
private ResourceHandle UseResource(in ResourceHandle inputHandle, AccessFlags flags)
{
CheckResource(inputHandle);
bool discard = (flags & AccessFlags.Discard) != 0;
bool read = (flags & AccessFlags.Read) != 0;
bool write = (flags & AccessFlags.Write) != 0;
ResourceHandle versionedHandle = inputHandle.IsVersioned ? inputHandle : m_Resources.GetLatestVersionHandle(inputHandle);
// If we are not discarding the current version and its data, add a "read" dependency on it
// this is a bit of a misnomer it really means more like "Preserve existing content or read"
if (!discard)
{
m_Resources.IncrementReadCount(versionedHandle);
m_RenderPass.AddResourceRead(versionedHandle);
// Implicit read - not user-specified
if (!read)
{
m_RenderPass.implicitReadsList.Add(versionedHandle);
}
}
else
{
// We are discarding it but we still read it, so we add a dependency on version "0" of this resource
if (read)
{
var zeroVersionHandle = m_Resources.GetZeroVersionHandle(versionedHandle);
m_Resources.IncrementReadCount(zeroVersionHandle);
m_RenderPass.AddResourceRead(zeroVersionHandle);
}
}
if (write)
{
CheckWriteTo(inputHandle);
// New versioned written by this render graph pass
versionedHandle = m_Resources.IncrementWriteCount(inputHandle);
m_RenderPass.AddResourceWrite(versionedHandle);
}
return versionedHandle;
}
public BufferHandle UseBuffer(in BufferHandle input, AccessFlags flags)
{
UseResource(input.handle, flags);
return input;
}
// UseTexture and SetRenderAttachment are currently forced to be mutually exclusive in the same pass
// check this.
// We currently ignore the version. In theory there might be some cases that are actually allowed with versioning
// for ample UseTexture(myTexV1, read) UseFragment(myTexV2, ReadWrite) as they are different versions
// but for now we don't allow any of that.
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
private void CheckNotUseFragment(in TextureHandle tex)
{
if (RenderGraph.enableValidityChecks)
{
bool usedAsFragment = (m_RenderPass.depthAccess.textureHandle.IsValid() && m_RenderPass.depthAccess.textureHandle.handle.index == tex.handle.index);
if (!usedAsFragment)
{
for (int i = 0; i <= m_RenderPass.colorBufferMaxIndex; i++)
{
if (m_RenderPass.colorBufferAccess[i].textureHandle.IsValid() && m_RenderPass.colorBufferAccess[i].textureHandle.handle.index == tex.handle.index)
{
usedAsFragment = true;
break;
}
}
}
if (usedAsFragment)
{
var name = m_Resources.GetRenderGraphResourceName(tex.handle);
throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_TextureAlreadyBeingUsedThroughSetAttachment);
}
}
}
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
private void CheckTextureUVOriginIsValid(in ResourceHandle handle, TextureResource texRes)
{
if (texRes.textureUVOrigin == TextureUVOriginSelection.TopLeft)
{
var name = m_Resources.GetRenderGraphResourceName(handle);
throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type `{handle.type}` at index `{handle.index}` - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOriginUseTexture(texRes.textureUVOrigin));
}
}
public void UseTexture(in TextureHandle input, AccessFlags flags)
{
CheckNotUseFragment(input);
UseResource(input.handle, flags);
if ((flags & AccessFlags.Read) == AccessFlags.Read)
{
if (m_RenderGraph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation)
{
TextureResource texRes = m_Resources.GetTextureResource(input.handle);
CheckTextureUVOriginIsValid(input.handle, texRes);
texRes.textureUVOrigin = TextureUVOriginSelection.BottomLeft;
}
}
}
public void UseGlobalTexture(int propertyId, AccessFlags flags)
{
var h = m_RenderGraph.GetGlobal(propertyId);
if (h.IsValid())
{
UseTexture(h, flags);
}
else
{
// rose test this path
var name = m_Resources.GetRenderGraphResourceName(h.handle);
throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {h.handle.type} at index {h.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.NoGlobalTextureAtPropertyID(propertyId));
}
}
public void UseAllGlobalTextures(bool enable)
{
m_RenderPass.useAllGlobalTextures = enable;
}
public void SetGlobalTextureAfterPass(in TextureHandle input, int propertyId)
{
m_RenderPass.setGlobalsList.Add(ValueTuple.Create(input, propertyId));
}
// Shared validation between SetRenderAttachment/SetRenderAttachmentDepth
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
private void CheckUseFragment(in TextureHandle tex, bool isDepth)
{
if (RenderGraph.enableValidityChecks)
{
// We ignore the version as we don't allow mixing UseTexture/UseFragment between different versions
// even though it should theoretically work (and we might do so in the future) for now we're overly strict.
bool alreadyUsed = false;
//TODO: Check grab textures here and allow if it's grabbed. For now
// SetRenderAttachment()
// UseTexture(grab)
// will work but not the other way around
for (int i = 0; i < m_RenderPass.resourceReadLists[tex.handle.iType].Count; i++)
{
if (m_RenderPass.resourceReadLists[tex.handle.iType][i].index == tex.handle.index)
{
alreadyUsed = true;
break;
}
}
for (int i = 0; i < m_RenderPass.resourceWriteLists[tex.handle.iType].Count; i++)
{
if (m_RenderPass.resourceWriteLists[tex.handle.iType][i].index == tex.handle.index)
{
alreadyUsed = true;
break;
}
}
if (alreadyUsed)
{
var name = m_Resources.GetRenderGraphResourceName(tex.handle);
throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentTextureAlreadyUsed);
}
m_Resources.GetRenderTargetInfo(tex.handle, out var info);
bool isDepthFormat = GraphicsFormatUtility.IsDepthFormat(info.format);
bool formatMismatch = isDepth != isDepthFormat;
if (formatMismatch)
{
var name = m_Resources.GetRenderGraphResourceName(tex.handle);
var errorMsg = isDepth
? RenderGraph.RenderGraphExceptionMessages.UseDepthWithColorFormat(info.format)
: RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentOnDepthTexture;
throw new InvalidOperationException(
$"In pass '{m_RenderPass.name}' when trying to use resource '{name}' " +
$"of type {tex.handle.type} at index {tex.handle.index} - {errorMsg}");
}
if (m_RenderGraph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation)
{
var texResource = m_Resources.GetTextureResource(tex.handle);
TextureResource checkTexResource = null;
for (int i = 0; i < m_RenderPass.fragmentInputMaxIndex + 1; ++i)
{
if (m_RenderPass.fragmentInputAccess[i].textureHandle.IsValid())
{
ref readonly TextureHandle texCheck = ref m_RenderPass.fragmentInputAccess[i].textureHandle;
checkTexResource = m_Resources.GetTextureResource(texCheck.handle);
if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin)
{
var name = m_Resources.GetRenderGraphResourceName(tex.handle);
var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle);
throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "input", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin));
}
}
}
for (int i = 0; i < m_RenderPass.colorBufferMaxIndex + 1; ++i)
{
if (m_RenderPass.colorBufferAccess[i].textureHandle.IsValid())
{
ref readonly TextureHandle texCheck = ref m_RenderPass.colorBufferAccess[i].textureHandle;
checkTexResource = m_Resources.GetTextureResource(texCheck.handle);
if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin)
{
var name = m_Resources.GetRenderGraphResourceName(tex.handle);
var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle);
throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "render", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin));
}
}
}
if (!isDepth && m_RenderPass.depthAccess.textureHandle.IsValid())
{
TextureHandle texCheck = m_RenderPass.depthAccess.textureHandle;
checkTexResource = m_Resources.GetTextureResource(texCheck.handle);
if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin)
{
var name = m_Resources.GetRenderGraphResourceName(tex.handle);
var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle);
throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "depth", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin));
}
}
}
foreach (var globalTex in m_RenderPass.setGlobalsList)
{
if (globalTex.Item1.handle.index == tex.handle.index)
{
var name = m_Resources.GetRenderGraphResourceName(tex.handle);
throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentOnGlobalTexture);
}
}
}
}
public void SetRenderAttachment(TextureHandle tex, int index, AccessFlags flags, int mipLevel, int depthSlice)
{
CheckUseFragment(tex, false);
var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, flags));
m_RenderPass.SetColorBufferRaw(versionedTextureHandle, index, flags, mipLevel, depthSlice);
}
public void SetInputAttachment(TextureHandle tex, int index, AccessFlags flags, int mipLevel, int depthSlice)
{
CheckFrameBufferFetchEmulationIsSupported(tex);
CheckUseFragment(tex, false);
var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, flags));
m_RenderPass.SetFragmentInputRaw(versionedTextureHandle, index, flags, mipLevel, depthSlice);
}
public void SetRenderAttachmentDepth(TextureHandle tex, AccessFlags flags, int mipLevel, int depthSlice)
{
CheckUseFragment(tex, true);
var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, flags));
m_RenderPass.SetDepthBufferRaw(versionedTextureHandle, flags, mipLevel, depthSlice);
}
public TextureHandle SetRandomAccessAttachment(TextureHandle input, int index, AccessFlags flags = AccessFlags.Read)
{
CheckNotUseFragment(input);
ResourceHandle result = UseResource(input.handle, flags);
m_RenderPass.SetRandomWriteResourceRaw(result, index, false, flags);
return input;
}
public void SetShadingRateImageAttachment(in TextureHandle tex)
{
CheckNotUseFragment(tex);
var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, AccessFlags.Read));
m_RenderPass.SetShadingRateImageRaw(versionedTextureHandle);
}
public BufferHandle UseBufferRandomAccess(BufferHandle input, int index, AccessFlags flags = AccessFlags.Read)
{
var h = UseBuffer(input, flags);
m_RenderPass.SetRandomWriteResourceRaw(h.handle, index, true, flags);
return input;
}
public BufferHandle UseBufferRandomAccess(BufferHandle input, int index, bool preserveCounterValue, AccessFlags flags = AccessFlags.Read)
{
var h = UseBuffer(input, flags);
m_RenderPass.SetRandomWriteResourceRaw(h.handle, index, preserveCounterValue, flags);
return input;
}
public void SetRenderFunc<PassData>(BaseRenderFunc<PassData, ComputeGraphContext> renderFunc) where PassData : class, new()
{
((ComputeRenderGraphPass<PassData>)m_RenderPass).renderFunc = renderFunc;
}
public void SetRenderFunc<PassData>(BaseRenderFunc<PassData, RasterGraphContext> renderFunc) where PassData : class, new()
{
((RasterRenderGraphPass<PassData>)m_RenderPass).renderFunc = renderFunc;
}
public void SetRenderFunc<PassData>(BaseRenderFunc<PassData, UnsafeGraphContext> renderFunc) where PassData : class, new()
{
((UnsafeRenderGraphPass<PassData>)m_RenderPass).renderFunc = renderFunc;
}
public void UseRendererList(in RendererListHandle input)
{
m_RenderPass.UseRendererList(input);
}
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
void CheckResource(in ResourceHandle res, bool checkTransientReadWrite = false)
{
if (RenderGraph.enableValidityChecks)
{
if (res.IsValid())
{
int transientIndex = m_Resources.GetRenderGraphResourceTransientIndex(res);
// We have dontCheckTransientReadWrite here because users may want to use UseColorBuffer/UseDepthBuffer API to benefit from render target auto binding. In this case we don't want to raise the error.
if (transientIndex == m_RenderPass.index && checkTransientReadWrite)
{
var name = m_Resources.GetRenderGraphResourceName(res);
Debug.LogError($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {res.type} at index {res.index} - " + RenderGraph.RenderGraphExceptionMessages.k_ReadWriteTransient);
}
if (transientIndex != -1 && transientIndex != m_RenderPass.index)
{
var name = m_Resources.GetRenderGraphResourceName(res);
throw new ArgumentException(
$"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {res.type} at index {res.index} - " +
RenderGraph.RenderGraphExceptionMessages.UseTransientTextureInWrongPass(transientIndex));
}
}
else
{
var name = m_Resources.GetRenderGraphResourceName(res);
throw new Exception($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {res.type} at index {res.index} - " + RenderGraph.RenderGraphExceptionMessages.k_InvalidResource);
}
}
}
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
void CheckFrameBufferFetchEmulationIsSupported(in TextureHandle tex)
{
if (enableValidityChecks)
{
if (!Util.RenderGraphUtils.IsFramebufferFetchEmulationMSAASupportedOnCurrentPlatform())
{
var sourceInfo = m_RenderGraph.GetRenderTargetInfo(tex);
if (sourceInfo.bindMS)
throw new InvalidOperationException($"This API is not supported with MSAA attachments on the current platform: {SystemInfo.graphicsDeviceType}");
}
}
}
public void SetShadingRateFragmentSize(ShadingRateFragmentSize shadingRateFragmentSize)
{
m_RenderPass.SetShadingRateFragmentSize(shadingRateFragmentSize);
}
public void SetShadingRateCombiner(ShadingRateCombinerStage stage, ShadingRateCombiner combiner)
{
m_RenderPass.SetShadingRateCombiner(stage, combiner);
}
public void SetExtendedFeatureFlags(ExtendedFeatureFlags extendedFeatureFlags)
{
m_RenderPass.SetExtendedFeatureFlags(extendedFeatureFlags);
}
}
}
#endif

View File

@@ -1,752 +0,0 @@
#if false
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace UnityEngine.Rendering.RenderGraphModule
{
[DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
abstract class RenderGraphPass
{
public abstract void Execute(InternalRenderGraphContext renderGraphContext);
public abstract void Release(RenderGraphObjectPool pool);
public abstract bool HasRenderFunc();
public abstract int GetRenderFuncHash();
public string name { get; protected set; }
public int index { get; protected set; }
public RenderGraphPassType type { get; internal set; }
public ProfilingSampler customSampler { get; protected set; }
public bool enableAsyncCompute { get; protected set; }
public bool allowPassCulling { get; protected set; }
public bool allowGlobalState { get; protected set; }
public bool enableFoveatedRasterization { get; protected set; }
public ExtendedFeatureFlags extendedFeatureFlags { get; protected set; }
// Before using the AccessFlags use resourceHandle.isValid()
// to make sure that the data in the colorBuffer/fragmentInput/randomAccessResource buffers are up to date
public TextureAccess depthAccess { get; protected set; }
public TextureAccess[] colorBufferAccess { get; protected set; } = new TextureAccess[RenderGraph.kMaxMRTCount];
public int colorBufferMaxIndex { get; protected set; } = -1;
public bool hasShadingRateImage { get; protected set; }
public TextureAccess shadingRateAccess { get; protected set; }
public bool hasShadingRateStates { get; protected set; }
public ShadingRateFragmentSize shadingRateFragmentSize { get; protected set; }
public ShadingRateCombiner primitiveShadingRateCombiner { get; protected set; }
public ShadingRateCombiner fragmentShadingRateCombiner { get; protected set; }
// Used by native pass compiler only
public TextureAccess[] fragmentInputAccess { get; protected set; } = new TextureAccess[RenderGraph.kMaxMRTCount];
public int fragmentInputMaxIndex { get; protected set; } = -1;
public struct RandomWriteResourceInfo
{
public ResourceHandle h;
public bool preserveCounterValue;
}
// This list can contain both texture and buffer resources based on their binding index.
public RandomWriteResourceInfo[] randomAccessResource { get; protected set; } = new RandomWriteResourceInfo[RenderGraph.kMaxMRTCount];
public int randomAccessResourceMaxIndex { get; protected set; } = -1;
public bool generateDebugData { get; protected set; }
public bool allowRendererListCulling { get; protected set; }
public List<ResourceHandle>[] resourceReadLists = new List<ResourceHandle>[(int)RenderGraphResourceType.Count];
public List<ResourceHandle>[] resourceWriteLists = new List<ResourceHandle>[(int)RenderGraphResourceType.Count];
public List<ResourceHandle>[] transientResourceList = new List<ResourceHandle>[(int)RenderGraphResourceType.Count];
public List<RendererListHandle> usedRendererListList = new List<RendererListHandle>();
public List<ValueTuple<TextureHandle, int>> setGlobalsList = new List<ValueTuple<TextureHandle, int>>();
public bool useAllGlobalTextures;
public List<ResourceHandle> implicitReadsList = new List<ResourceHandle>();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
public RenderGraph.DebugData.PassScriptInfo debugScriptInfo { get; set; }
#endif
public RenderGraphPass()
{
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
resourceReadLists[i] = new List<ResourceHandle>();
resourceWriteLists[i] = new List<ResourceHandle>();
transientResourceList[i] = new List<ResourceHandle>();
}
}
public void Clear()
{
name = "";
index = -1;
customSampler = null;
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
resourceReadLists[i].Clear();
resourceWriteLists[i].Clear();
transientResourceList[i].Clear();
}
usedRendererListList.Clear();
setGlobalsList.Clear();
useAllGlobalTextures = false;
implicitReadsList.Clear();
enableAsyncCompute = false;
allowPassCulling = true;
allowRendererListCulling = true;
allowGlobalState = false;
enableFoveatedRasterization = false;
generateDebugData = true;
// Invalidate the buffers without clearing them, as it is too costly
// Use IsValid() to make sure that the data in the colorBuffer/fragmentInput/randomAccessResource buffers are up to date
colorBufferMaxIndex = -1;
fragmentInputMaxIndex = -1;
randomAccessResourceMaxIndex = -1;
// We do not need to clear colorBufferAccess and fragmentInputAccess as we have the colorBufferMaxIndex and fragmentInputMaxIndex
// which are reset above so we only clear depthAccess here.
depthAccess = default(TextureAccess);
hasShadingRateImage = false;
hasShadingRateStates = false;
shadingRateFragmentSize = ShadingRateFragmentSize.FragmentSize1x1;
primitiveShadingRateCombiner = ShadingRateCombiner.Keep;
fragmentShadingRateCombiner = ShadingRateCombiner.Keep;
// Invalidate ExtendedFeatureFlags
extendedFeatureFlags = ExtendedFeatureFlags.None;
}
// Check if the pass has any render targets set-up
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasRenderAttachments()
{
return depthAccess.textureHandle.IsValid() || colorBufferAccess[0].textureHandle.IsValid() || colorBufferMaxIndex > 0;
}
// Checks if the resource is involved in this pass
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsTransient(in ResourceHandle res)
{
// Versioning doesn't matter much for transient resources as they are only used within a single pass
for (int i = 0; i < transientResourceList[res.iType].Count; i++)
{
if (transientResourceList[res.iType][i].index == res.index)
{
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsWritten(in ResourceHandle res)
{
// You can only ever write to the latest version so we ignore it when looking in the list
for (int i = 0; i < resourceWriteLists[res.iType].Count; i++)
{
if (resourceWriteLists[res.iType][i].index == res.index)
{
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsRead(in ResourceHandle res)
{
if (res.IsVersioned)
{
return resourceReadLists[res.iType].Contains(res);
}
else
{
// Just look if we are reading any version of this texture.
// Note that in theory this pass could read from several versions of the same texture
// e.g. ColorBuffer,v3 and ColorBuffer,v5 so this check is always conservative
for (int i = 0; i < resourceReadLists[res.iType].Count; i++)
{
if (resourceReadLists[res.iType][i].index == res.index)
{
return true;
}
}
return false;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsAttachment(in TextureHandle res)
{
// We ignore the version when checking, if any version is used it is considered a match
if (depthAccess.textureHandle.IsValid() && depthAccess.textureHandle.handle.index == res.handle.index) return true;
for (int i = 0; i < colorBufferAccess.Length; i++)
{
if (colorBufferAccess[i].textureHandle.IsValid() && colorBufferAccess[i].textureHandle.handle.index == res.handle.index) return true;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddResourceWrite(in ResourceHandle res)
{
resourceWriteLists[res.iType].Add(res);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddResourceRead(in ResourceHandle res)
{
resourceReadLists[res.iType].Add(res);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddTransientResource(in ResourceHandle res)
{
transientResourceList[res.iType].Add(res);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UseRendererList(in RendererListHandle rendererList)
{
usedRendererListList.Add(rendererList);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnableAsyncCompute(bool value)
{
enableAsyncCompute = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AllowPassCulling(bool value)
{
allowPassCulling = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnableFoveatedRasterization(bool value)
{
enableFoveatedRasterization = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AllowRendererListCulling(bool value)
{
allowRendererListCulling = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AllowGlobalState(bool value)
{
allowGlobalState = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void GenerateDebugData(bool value)
{
generateDebugData = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetColorBuffer(in TextureHandle resource, int index)
{
Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
colorBufferMaxIndex = Math.Max(colorBufferMaxIndex, index);
colorBufferAccess[index] = new TextureAccess(colorBufferAccess[index], resource);
AddResourceWrite(resource.handle);
}
// Sets up the color buffer for this pass but not any resource Read/Writes for it
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetColorBufferRaw(in TextureHandle resource, int index, AccessFlags accessFlags, int mipLevel, int depthSlice)
{
Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
if (colorBufferAccess[index].textureHandle.handle.Equals(resource.handle) || !colorBufferAccess[index].textureHandle.IsValid())
{
colorBufferMaxIndex = Math.Max(colorBufferMaxIndex, index);
colorBufferAccess[index] = new TextureAccess(resource, accessFlags, mipLevel, depthSlice);
}
else
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
// You tried to do SetRenderAttachment(tex1, 1, ..); SetRenderAttachment(tex2, 1, ..); that is not valid for different textures on the same index
throw new InvalidOperationException(
$"In pass '{name}' when trying to call SetRenderAttachment with resource of type {resource.handle.type} at index {index} - " +
RenderGraph.RenderGraphExceptionMessages.k_MoreThanOneResourceForMRTIndex);
#endif
}
}
// Sets up the color buffer for this pass but not any resource Read/Writes for it
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetFragmentInputRaw(in TextureHandle resource, int index, AccessFlags accessFlags, int mipLevel, int depthSlice)
{
Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
if (fragmentInputAccess[index].textureHandle.handle.Equals(resource.handle) || !fragmentInputAccess[index].textureHandle.IsValid())
{
fragmentInputMaxIndex = Math.Max(fragmentInputMaxIndex, index);
fragmentInputAccess[index] = new TextureAccess(resource, accessFlags, mipLevel, depthSlice);
}
else
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
// You tried to do SetRenderAttachment(tex1, 1, ..); SetRenderAttachment(tex2, 1, ..); that is not valid for different textures on the same index
throw new InvalidOperationException(
$"In pass '{name}' when trying to call SetInputAttachment with resource of type {resource.handle.type} at index {index} - " +
RenderGraph.RenderGraphExceptionMessages.k_MoreThanOneTextureForFragInputIndex);
#endif
}
}
// Sets up the color buffer for this pass but not any resource Read/Writes for it
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetRandomWriteResourceRaw(in ResourceHandle resource, int index, bool preserveCounterValue, AccessFlags accessFlags)
{
Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
if (randomAccessResource[index].h.Equals(resource) || !randomAccessResource[index].h.IsValid())
{
randomAccessResourceMaxIndex = Math.Max(randomAccessResourceMaxIndex, index);
ref var info = ref randomAccessResource[index];
info.h = resource;
info.preserveCounterValue = preserveCounterValue;
}
else
{
// You tried to do SetRenderAttachment(tex1, 1, ..); SetRenderAttachment(tex2, 1, ..); that is not valid for different textures on the same index
throw new InvalidOperationException(
$"In pass '{name}' when trying to call SetRandomAccessAttachment/UseBufferRandomAccess with resource of type {resource.type} at index {index} - " +
RenderGraph.RenderGraphExceptionMessages.k_MoreThanOneTextureRandomWriteInputIndex);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetDepthBuffer(in TextureHandle resource, DepthAccess flags)
{
depthAccess = new TextureAccess(resource, (AccessFlags)flags, 0, 0);
if ((flags & DepthAccess.Read) != 0)
AddResourceRead(resource.handle);
if ((flags & DepthAccess.Write) != 0)
AddResourceWrite(resource.handle);
}
// Sets up the depth buffer for this pass but not any resource Read/Writes for it
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetDepthBufferRaw(in TextureHandle resource, AccessFlags accessFlags, int mipLevel, int depthSlice)
{
// If no depth buffer yet or if it is the same one as the previous one, allow the call otherwise log an error.
if (depthAccess.textureHandle.handle.Equals(resource.handle) || !depthAccess.textureHandle.IsValid())
{
depthAccess = new TextureAccess(resource, accessFlags, mipLevel, depthSlice);
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
else
{
throw new InvalidOperationException(
$"In pass '{name}' when trying to call SetRenderAttachmentDepth with resource of type {resource.handle.type} at index {index} - " +
RenderGraph.RenderGraphExceptionMessages.k_MultipleDepthTextures);
}
#endif
}
// Here we want to keep computation to a minimum and only hash what will influence NRP compiler: Pass merging, load/store actions etc.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void ComputeTextureHash(ref HashFNV1A32 generator, in ResourceHandle handle, RenderGraphResourceRegistry resources)
{
if (handle.index == 0)
return;
if (resources.IsRenderGraphResourceImported(handle))
{
var res = resources.GetTextureResource(handle);
var graphicsResource = res.graphicsResource;
ref readonly var desc = ref res.desc;
var externalTexture = graphicsResource.externalTexture;
if (externalTexture != null) // External texture
{
generator.Append((int) externalTexture.graphicsFormat);
generator.Append((int) externalTexture.dimension);
generator.Append(externalTexture.width);
generator.Append(externalTexture.height);
if (externalTexture is RenderTexture externalRT)
generator.Append(externalRT.antiAliasing);
}
else if (graphicsResource.rt != null) // Regular RTHandle
{
var rt = graphicsResource.rt;
generator.Append((int) rt.graphicsFormat);
generator.Append((int) rt.dimension);
generator.Append(rt.antiAliasing);
if (graphicsResource.useScaling)
if (graphicsResource.scaleFunc != null)
generator.Append(DelegateHashCodeUtils.GetFuncHashCode(graphicsResource.scaleFunc));
else
generator.Append(graphicsResource.scaleFactor);
else
{
generator.Append(rt.width);
generator.Append(rt.height);
}
}
else if (graphicsResource.nameID != default) // External RTI
{
// The only info we have is from the provided desc upon importing.
generator.Append((int) desc.format);
generator.Append((int) desc.dimension);
generator.Append((int) desc.msaaSamples);
generator.Append(desc.width);
generator.Append(desc.height);
}
// Add the clear/discard buffer flags to the hash (used in all the cases above)
generator.Append(desc.clearBuffer);
generator.Append(desc.discardBuffer);
}
else
{
ref readonly var desc = ref resources.GetTextureResourceDesc(handle);
generator.Append((int) desc.format);
generator.Append((int) desc.dimension);
generator.Append((int) desc.msaaSamples);
generator.Append(desc.clearBuffer);
generator.Append(desc.discardBuffer);
switch (desc.sizeMode)
{
case TextureSizeMode.Explicit:
generator.Append(desc.width);
generator.Append(desc.height);
break;
case TextureSizeMode.Scale:
generator.Append(desc.scale);
break;
case TextureSizeMode.Functor:
generator.Append(DelegateHashCodeUtils.GetFuncHashCode(desc.func));
break;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void ComputeHashForTextureAccess(ref HashFNV1A32 generator, in ResourceHandle handle, in TextureAccess textureAccess)
{
generator.Append(handle.index);
generator.Append((int) textureAccess.flags);
generator.Append(textureAccess.mipLevel);
generator.Append(textureAccess.depthSlice);
}
// This function is performance sensitive.
// Avoid mass function calls to get the hashCode and compute locally instead.
public void ComputeHash(ref HashFNV1A32 generator, RenderGraphResourceRegistry resources)
{
generator.Append((int) type);
generator.Append(enableAsyncCompute);
generator.Append(allowPassCulling);
generator.Append(allowGlobalState);
generator.Append(enableFoveatedRasterization);
generator.Append(extendedFeatureFlags);
var depthHandle = depthAccess.textureHandle.handle;
if (depthHandle.IsValid())
{
ComputeTextureHash(ref generator, depthHandle, resources);
ComputeHashForTextureAccess(ref generator, depthHandle, depthAccess);
}
for (int i = 0; i < colorBufferMaxIndex + 1; ++i)
{
var colorBufferAccessElement = colorBufferAccess[i];
var handle = colorBufferAccessElement.textureHandle.handle;
if (!handle.IsValid())
continue;
ComputeTextureHash(ref generator, handle, resources);
ComputeHashForTextureAccess(ref generator, handle, colorBufferAccessElement);
}
generator.Append(colorBufferMaxIndex);
generator.Append(hasShadingRateImage);
if (hasShadingRateImage)
{
var handle = shadingRateAccess.textureHandle.handle;
if (handle.IsValid())
{
ComputeTextureHash(ref generator, handle, resources);
ComputeHashForTextureAccess(ref generator, handle, shadingRateAccess);
}
}
generator.Append(hasShadingRateStates);
generator.Append((int)shadingRateFragmentSize);
generator.Append((int)primitiveShadingRateCombiner);
generator.Append((int)fragmentShadingRateCombiner);
for (int i = 0; i < fragmentInputMaxIndex + 1; ++i)
{
var fragmentInputAccessElement = fragmentInputAccess[i];
var handle = fragmentInputAccessElement.textureHandle.handle;
if (!handle.IsValid())
continue;
ComputeTextureHash(ref generator, handle, resources);
ComputeHashForTextureAccess(ref generator, handle, fragmentInputAccessElement);
}
for (int i = 0; i < randomAccessResourceMaxIndex + 1; ++i)
{
var rar = randomAccessResource[i];
if (!rar.h.IsValid())
continue;
generator.Append(rar.h.index);
generator.Append(rar.preserveCounterValue);
}
generator.Append(randomAccessResourceMaxIndex);
generator.Append(fragmentInputMaxIndex);
generator.Append(generateDebugData);
generator.Append(allowRendererListCulling);
for (int resType = 0; resType < (int)RenderGraphResourceType.Count; resType++)
{
var resourceReads = resourceReadLists[resType];
var resourceReadsCount = resourceReads.Count;
for (int i = 0; i < resourceReadsCount; ++i)
generator.Append(resourceReads[i].index);
var resourceWrites = resourceWriteLists[resType];
var resourceWritesCount = resourceWrites.Count;
for (int i = 0; i < resourceWritesCount; ++i)
generator.Append(resourceWrites[i].index);
var resourceTransient = transientResourceList[resType];
var resourceTransientCount = resourceTransient.Count;
for (int i = 0; i < resourceTransientCount; ++i)
generator.Append(resourceTransient[i].index);
}
var usedRendererListListCount = usedRendererListList.Count;
for (int i = 0; i < usedRendererListListCount; ++i)
generator.Append(usedRendererListList[i].handle);
var setGlobalsListCount = setGlobalsList.Count;
for (int i = 0; i < setGlobalsListCount; ++i)
{
var global = setGlobalsList[i];
generator.Append(global.Item1.handle.index);
generator.Append(global.Item2);
}
generator.Append(useAllGlobalTextures);
var implicitReadsListCount = implicitReadsList.Count;
for (int i = 0; i < implicitReadsListCount; ++i)
generator.Append(implicitReadsList[i].index);
generator.Append(GetRenderFuncHash());
}
public void SetShadingRateImageRaw(in TextureHandle shadingRateImage)
{
if (ShadingRateInfo.supportsPerImageTile)
{
hasShadingRateImage = true;
// shading rate image access flag is always read, only 1 mip and 1 slice
shadingRateAccess = new TextureAccess(shadingRateImage, AccessFlags.Read, 0, 0);
}
}
public void SetShadingRateImage(in TextureHandle shadingRateImage, AccessFlags accessFlags, int mipLevel, int depthSlice)
{
if (ShadingRateInfo.supportsPerImageTile)
{
hasShadingRateImage = true;
shadingRateAccess = new TextureAccess(shadingRateImage, accessFlags, mipLevel, depthSlice);
AddResourceRead(shadingRateAccess.textureHandle.handle);
}
}
public void SetShadingRateFragmentSize(ShadingRateFragmentSize shadingRateFragmentSize)
{
if (ShadingRateInfo.supportsPerDrawCall)
{
hasShadingRateStates = true;
this.shadingRateFragmentSize = shadingRateFragmentSize;
}
}
public void SetShadingRateCombiner(ShadingRateCombinerStage stage, ShadingRateCombiner combiner)
{
if (ShadingRateInfo.supportsPerImageTile)
{
switch (stage)
{
case ShadingRateCombinerStage.Primitive:
hasShadingRateStates = true;
primitiveShadingRateCombiner = combiner;
break;
case ShadingRateCombinerStage.Fragment:
hasShadingRateStates = true;
fragmentShadingRateCombiner = combiner;
break;
}
}
}
public void SetExtendedFeatureFlags(ExtendedFeatureFlags value)
{
extendedFeatureFlags |= value;
}
}
// This used to have an extra generic argument 'RenderGraphContext' abstracting the context and avoiding
// the RenderGraphPass/ComputeRenderGraphPass/RasterRenderGraphPass/UnsafeRenderGraphPass classes below
// but this confuses IL2CPP and causes garbage when boxing the context created (even though they are structs)
[DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
internal abstract class BaseRenderGraphPass<PassData, TRenderGraphContext> : RenderGraphPass
where PassData : class, new()
{
internal PassData data;
internal BaseRenderFunc<PassData, TRenderGraphContext> renderFunc;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initialize(int passIndex, PassData passData, string passName, RenderGraphPassType passType, ProfilingSampler sampler)
{
Clear();
index = passIndex;
data = passData;
name = passName;
type = passType;
customSampler = sampler;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Release(RenderGraphObjectPool pool)
{
pool.Release(data);
data = null;
renderFunc = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool HasRenderFunc()
{
return renderFunc != null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetRenderFuncHash()
{
return renderFunc != null ? DelegateHashCodeUtils.GetFuncHashCode(renderFunc) : 0;
}
}
[DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
[Obsolete("RenderGraphPass is deprecated, use RasterRenderGraphPass/ComputeRenderGraphPass/UnsafeRenderGraphPass instead.")]
internal sealed class RenderGraphPass<PassData> : BaseRenderGraphPass<PassData, RenderGraphContext>
where PassData : class, new()
{
internal static RenderGraphContext c = new RenderGraphContext();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Execute(InternalRenderGraphContext renderGraphContext)
{
c.FromInternalContext(renderGraphContext);
renderFunc(data, c);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Release(RenderGraphObjectPool pool)
{
base.Release(pool);
// We need to do the release from here because we need the final type.
pool.Release(this);
}
}
[DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
internal sealed class ComputeRenderGraphPass<PassData> : BaseRenderGraphPass<PassData, ComputeGraphContext>
where PassData : class, new()
{
internal static ComputeGraphContext c = new ComputeGraphContext();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Execute(InternalRenderGraphContext renderGraphContext)
{
c.FromInternalContext(renderGraphContext);
renderFunc(data, c);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Release(RenderGraphObjectPool pool)
{
base.Release(pool);
// We need to do the release from here because we need the final type.
pool.Release(this);
}
}
[DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
internal sealed class RasterRenderGraphPass<PassData> : BaseRenderGraphPass<PassData, RasterGraphContext>
where PassData : class, new()
{
internal static RasterGraphContext c = new RasterGraphContext();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Execute(InternalRenderGraphContext renderGraphContext)
{
c.FromInternalContext(renderGraphContext);
renderFunc(data, c);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Release(RenderGraphObjectPool pool)
{
base.Release(pool);
// We need to do the release from here because we need the final type.
pool.Release(this);
}
}
[DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
internal sealed class UnsafeRenderGraphPass<PassData> : BaseRenderGraphPass<PassData, UnsafeGraphContext>
where PassData : class, new()
{
internal static UnsafeGraphContext c = new UnsafeGraphContext();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Execute(InternalRenderGraphContext renderGraphContext)
{
c.FromInternalContext(renderGraphContext);
renderFunc(data, c);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void Release(RenderGraphObjectPool pool)
{
base.Release(pool);
// We need to do the release from here because we need the final type.
pool.Release(this);
}
}
}
#endif