forked from Misaki/GhostEngine
Enhanced barrier
This commit is contained in:
@@ -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)]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
{
|
||||
RecordError(nameof(TransitionBarrier), recordBeforeResult.Error);
|
||||
continue;
|
||||
}
|
||||
|
||||
var recordAfterResult = _resourceDatabase.GetResourceRecord(desc.aliasing.resourceAfter);
|
||||
if (recordAfterResult.Error != ErrorStatus.None)
|
||||
var resource = _resourceDatabase.GetResource(desc.Resource);
|
||||
pBufferBarriers[bufferIndex++] = new D3D12_BUFFER_BARRIER
|
||||
{
|
||||
RecordError(nameof(TransitionBarrier), recordAfterResult.Error);
|
||||
continue;
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
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)
|
||||
case BarrierType.Texture:
|
||||
{
|
||||
RecordError(nameof(TransitionBarrier), recordUavResult.Error);
|
||||
continue;
|
||||
var resource = _resourceDatabase.GetResource(desc.Resource);
|
||||
pTextureBarriers[textureIndex++] = new D3D12_TEXTURE_BARRIER
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
barrier = D3D12_RESOURCE_BARRIER.InitUAV(recordUavResult.Value.ResourcePtr);
|
||||
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)
|
||||
if (globalCount > 0)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotRecording();
|
||||
#if !DEBUG
|
||||
if (_lastError.Status != ErrorStatus.None)
|
||||
groups[groupCount] = new D3D12_BARRIER_GROUP
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
IncrementCommandCount();
|
||||
|
||||
if (stateBefore == stateAfter)
|
||||
{
|
||||
return;
|
||||
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_GLOBAL,
|
||||
NumBarriers = (uint)globalCount,
|
||||
};
|
||||
groups[groupCount].Anonymous.pGlobalBarriers = pGlobalBarriers;
|
||||
groupCount++;
|
||||
}
|
||||
|
||||
var recordResult = _resourceDatabase.GetResourceRecord(resource);
|
||||
if (recordResult.Error != ErrorStatus.None)
|
||||
if (bufferCount > 0)
|
||||
{
|
||||
RecordError(nameof(TransitionBarrier), recordResult.Error);
|
||||
return;
|
||||
groups[groupCount] = new D3D12_BARRIER_GROUP
|
||||
{
|
||||
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_BUFFER,
|
||||
NumBarriers = (uint)bufferCount,
|
||||
};
|
||||
groups[groupCount].Anonymous.pBufferBarriers = pBufferBarriers;
|
||||
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;
|
||||
if (textureCount > 0)
|
||||
{
|
||||
groups[groupCount] = new D3D12_BARRIER_GROUP
|
||||
{
|
||||
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_TEXTURE,
|
||||
NumBarriers = (uint)textureCount,
|
||||
};
|
||||
groups[groupCount].Anonymous.pTextureBarriers = pTextureBarriers;
|
||||
groupCount++;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,12 +798,19 @@ 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);
|
||||
@@ -882,6 +823,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
|
||||
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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
const int BatchSize = 64;
|
||||
var descs = stackalloc BarrierDesc[BatchSize];
|
||||
int processed = 0;
|
||||
while (processed < count)
|
||||
{
|
||||
count = 0;
|
||||
hasRemain = false;
|
||||
goto Start;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ shader "Hidden/Blit"
|
||||
{
|
||||
pipeline
|
||||
{
|
||||
ztest = always;
|
||||
ztest = disabled;
|
||||
zwrite = off;
|
||||
cull = off;
|
||||
blend = opaque;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user