feat(rendergraph): async queue, pool refactor, barrier cleanup
Refactor resource pool to use UnsafeQueue/UnsafeList for transient resources, improving memory management and performance. Add async GPU wait support to ICommandQueue and D3D12. Refactor render graph barrier system, streamline CompiledBarrier, and remove ResourceBarrier. RenderGraphCompiler now returns Result<float2, Error> for dynamic resolution scaling. Replace custom memory pools with Allocator.FreeList for temp allocations. Add ResourceUploadBatch for async/sync resource uploads. Fix D3D12 disposal and fence tracking bugs. Update NuGet dependencies. Numerous minor cleanups and code improvements.
This commit is contained in:
@@ -8,21 +8,20 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS</DefineConstants>
|
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS</DefineConstants>
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
<!--<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS</DefineConstants>-->
|
|
||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.6" />
|
<PackageReference Include="Misaki.HighPerformance" Version="1.0.7" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.8" />
|
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.8" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.9">
|
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.10">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -18,31 +18,37 @@ public readonly struct Result
|
|||||||
_message = message;
|
_message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result Success()
|
public static Result Success()
|
||||||
{
|
{
|
||||||
return new Result(true);
|
return new Result(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result Failure(string? message = null)
|
public static Result Failure(string? message = null)
|
||||||
{
|
{
|
||||||
return new Result(false, message);
|
return new Result(false, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result Failure(Error status)
|
public static Result Failure(Error status)
|
||||||
{
|
{
|
||||||
return new Result(false, status.ToString());
|
return new Result(false, status.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result<T> Success<T>(T value)
|
public static Result<T> Success<T>(T value)
|
||||||
{
|
{
|
||||||
return Result<T>.Success(value);
|
return Result<T>.Success(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result<T> Failure<T>(string? message = null)
|
public static Result<T> Failure<T>(string? message = null)
|
||||||
{
|
{
|
||||||
return Result<T>.Failure(message);
|
return Result<T>.Failure(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result<T> Failure<T>(Error status)
|
public static Result<T> Failure<T>(Error status)
|
||||||
{
|
{
|
||||||
return Result<T>.Failure(status.ToString());
|
return Result<T>.Failure(status.ToString());
|
||||||
@@ -69,6 +75,7 @@ public readonly struct Result<T>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public T Value
|
public T Value
|
||||||
{
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
@@ -92,11 +99,13 @@ public readonly struct Result<T>
|
|||||||
_message = message;
|
_message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result<T> Success(T value)
|
public static Result<T> Success(T value)
|
||||||
{
|
{
|
||||||
return new Result<T>(true, value);
|
return new Result<T>(true, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result<T> Failure(string? message = null)
|
public static Result<T> Failure(string? message = null)
|
||||||
{
|
{
|
||||||
return new Result<T>(false, default!, message);
|
return new Result<T>(false, default!, message);
|
||||||
@@ -144,6 +153,7 @@ public readonly struct Result<T, E>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public T Value
|
public T Value
|
||||||
{
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
@@ -166,11 +176,13 @@ public readonly struct Result<T, E>
|
|||||||
_error = status;
|
_error = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result<T, E> Success(T value)
|
public static Result<T, E> Success(T value)
|
||||||
{
|
{
|
||||||
return new Result<T, E>(value, default);
|
return new Result<T, E>(value, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Result<T, E> Failure(E status)
|
public static Result<T, E> Failure(E status)
|
||||||
{
|
{
|
||||||
return new Result<T, E>(default!, status);
|
return new Result<T, E>(default!, status);
|
||||||
@@ -200,6 +212,7 @@ public readonly ref struct RefResult<T, E>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ref T Value
|
public ref T Value
|
||||||
{
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
@@ -222,11 +235,13 @@ public readonly ref struct RefResult<T, E>
|
|||||||
_error = error;
|
_error = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static RefResult<T, E> Success(ref T value)
|
public static RefResult<T, E> Success(ref T value)
|
||||||
{
|
{
|
||||||
return new RefResult<T, E>(ref value, default);
|
return new RefResult<T, E>(ref value, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static RefResult<T, E> Failure(E error)
|
public static RefResult<T, E> Failure(E error)
|
||||||
{
|
{
|
||||||
return new RefResult<T, E>(ref Unsafe.NullRef<T>(), error);
|
return new RefResult<T, E>(ref Unsafe.NullRef<T>(), error);
|
||||||
|
|||||||
@@ -682,6 +682,14 @@ public ref partial struct QueryBuilder : IDisposable
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_all.Dispose();
|
||||||
|
_any.Dispose();
|
||||||
|
_absent.Dispose();
|
||||||
|
_none.Dispose();
|
||||||
|
_disabled.Dispose();
|
||||||
|
_present.Dispose();
|
||||||
|
_rw.Dispose();
|
||||||
|
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,6 +355,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -566,6 +568,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -788,6 +791,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1020,6 +1024,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1262,6 +1267,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1514,6 +1520,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1776,6 +1783,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -384,6 +385,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -603,6 +605,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -832,6 +835,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1071,6 +1075,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1320,6 +1325,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1579,6 +1585,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1848,6 +1855,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
{
|
{
|
||||||
|
_changedComponentIDs.Dispose();
|
||||||
_scope.Dispose();
|
_scope.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
@@ -215,6 +216,19 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
throw new InvalidOperationException($"Resource of type {typeof(T).FullName} has not been registered in the World.");
|
throw new InvalidOperationException($"Resource of type {typeof(T).FullName} has not been registered in the World.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetService<T>([MaybeNullWhen(false)]out T resource)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
if (_services.TryGetValue(typeof(T), out var obj))
|
||||||
|
{
|
||||||
|
resource = (T)obj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
resource = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a global resource exists.
|
/// Checks if a global resource exists.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ using Ghost.Core;
|
|||||||
using Ghost.Graphics.D3D12.Utilities;
|
using Ghost.Graphics.D3D12.Utilities;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
using TerraFX.Interop.Gdiplus;
|
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
using static TerraFX.Aliases.D3D_Alias;
|
using static TerraFX.Aliases.D3D_Alias;
|
||||||
@@ -44,7 +42,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
|||||||
D3D12ResourceAllocator resourceAllocator,
|
D3D12ResourceAllocator resourceAllocator,
|
||||||
D3D12DescriptorAllocator descriptorAllocator,
|
D3D12DescriptorAllocator descriptorAllocator,
|
||||||
CommandBufferType type)
|
CommandBufferType type)
|
||||||
:base (CreateCommandList(device.NativeObject, D3D12Utility.ToCommandListType(type)))
|
: base(CreateCommandList(device.NativeObject, D3D12Utility.ToCommandListType(type)))
|
||||||
{
|
{
|
||||||
_type = type;
|
_type = type;
|
||||||
|
|
||||||
@@ -216,78 +214,78 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
|||||||
_resourceDatabase.globalBarrier = new ResourceBarrierData(BarrierLayout.Undefined, desc.AccessAfter, desc.SyncAfter);
|
_resourceDatabase.globalBarrier = new ResourceBarrierData(BarrierLayout.Undefined, desc.AccessAfter, desc.SyncAfter);
|
||||||
break;
|
break;
|
||||||
case BarrierType.Buffer:
|
case BarrierType.Buffer:
|
||||||
{
|
|
||||||
var r = _resourceDatabase.GetResourceRecord(desc.Resource);
|
|
||||||
if (r.IsFailure)
|
|
||||||
{
|
{
|
||||||
RecordError(nameof(Barrier), r.Error);
|
var r = _resourceDatabase.GetResourceRecord(desc.Resource);
|
||||||
continue;
|
if (r.IsFailure)
|
||||||
}
|
|
||||||
|
|
||||||
ref var record = ref r.Value;
|
|
||||||
var accessBefore = desc.IsAliasing ? BarrierAccess.NoAccess : record.barrierData.access;
|
|
||||||
|
|
||||||
if (record.barrierData.sync == desc.SyncAfter && accessBefore == desc.AccessAfter)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resource = record.ResourcePtr;
|
|
||||||
pBufferBarriers[bufferIndex++] = new D3D12_BUFFER_BARRIER
|
|
||||||
{
|
|
||||||
SyncBefore = (D3D12_BARRIER_SYNC)record.barrierData.sync,
|
|
||||||
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
|
||||||
AccessBefore = (D3D12_BARRIER_ACCESS)accessBefore,
|
|
||||||
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
|
|
||||||
pResource = resource,
|
|
||||||
Offset = 0,
|
|
||||||
Size = ulong.MaxValue
|
|
||||||
};
|
|
||||||
|
|
||||||
record.barrierData = new ResourceBarrierData(BarrierLayout.Undefined, desc.AccessAfter, desc.SyncAfter);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BarrierType.Texture:
|
|
||||||
{
|
|
||||||
var r = _resourceDatabase.GetResourceRecord(desc.Resource);
|
|
||||||
if (r.IsFailure)
|
|
||||||
{
|
|
||||||
RecordError(nameof(Barrier), r.Error);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref var record = ref r.Value;
|
|
||||||
var accessBefore = desc.IsAliasing ? BarrierAccess.NoAccess : record.barrierData.access;
|
|
||||||
var layoutBefore = desc.IsAliasing ? BarrierLayout.Undefined : record.barrierData.layout;
|
|
||||||
|
|
||||||
if (record.barrierData.sync == desc.SyncAfter && accessBefore == desc.AccessAfter && layoutBefore == desc.LayoutAfter)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resource = record.ResourcePtr;
|
|
||||||
pTextureBarriers[textureIndex++] = new D3D12_TEXTURE_BARRIER
|
|
||||||
{
|
|
||||||
SyncBefore = (D3D12_BARRIER_SYNC)record.barrierData.sync,
|
|
||||||
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
|
||||||
AccessBefore = (D3D12_BARRIER_ACCESS)accessBefore,
|
|
||||||
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
|
|
||||||
LayoutBefore = (D3D12_BARRIER_LAYOUT)layoutBefore,
|
|
||||||
LayoutAfter = (D3D12_BARRIER_LAYOUT)desc.LayoutAfter,
|
|
||||||
pResource = resource,
|
|
||||||
Subresources = new D3D12_BARRIER_SUBRESOURCE_RANGE
|
|
||||||
{
|
{
|
||||||
IndexOrFirstMipLevel = desc.Subresources.IndexOrFirstMipLevel,
|
RecordError(nameof(Barrier), r.Error);
|
||||||
NumMipLevels = desc.Subresources.NumMipLevels,
|
continue;
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
record.barrierData = new ResourceBarrierData(desc.LayoutAfter, desc.AccessAfter, desc.SyncAfter);
|
ref var record = ref r.Value;
|
||||||
}
|
var accessBefore = desc.IsAliasing ? BarrierAccess.NoAccess : record.barrierData.access;
|
||||||
break;
|
|
||||||
|
if (record.barrierData.sync == desc.SyncAfter && accessBefore == desc.AccessAfter)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resource = record.ResourcePtr;
|
||||||
|
pBufferBarriers[bufferIndex++] = new D3D12_BUFFER_BARRIER
|
||||||
|
{
|
||||||
|
SyncBefore = (D3D12_BARRIER_SYNC)record.barrierData.sync,
|
||||||
|
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
||||||
|
AccessBefore = (D3D12_BARRIER_ACCESS)accessBefore,
|
||||||
|
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
|
||||||
|
pResource = resource,
|
||||||
|
Offset = 0,
|
||||||
|
Size = ulong.MaxValue
|
||||||
|
};
|
||||||
|
|
||||||
|
record.barrierData = new ResourceBarrierData(BarrierLayout.Undefined, desc.AccessAfter, desc.SyncAfter);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BarrierType.Texture:
|
||||||
|
{
|
||||||
|
var r = _resourceDatabase.GetResourceRecord(desc.Resource);
|
||||||
|
if (r.IsFailure)
|
||||||
|
{
|
||||||
|
RecordError(nameof(Barrier), r.Error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var record = ref r.Value;
|
||||||
|
var accessBefore = desc.IsAliasing ? BarrierAccess.NoAccess : record.barrierData.access;
|
||||||
|
var layoutBefore = desc.IsAliasing ? BarrierLayout.Undefined : record.barrierData.layout;
|
||||||
|
|
||||||
|
if (record.barrierData.sync == desc.SyncAfter && accessBefore == desc.AccessAfter && layoutBefore == desc.LayoutAfter)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resource = record.ResourcePtr;
|
||||||
|
pTextureBarriers[textureIndex++] = new D3D12_TEXTURE_BARRIER
|
||||||
|
{
|
||||||
|
SyncBefore = (D3D12_BARRIER_SYNC)record.barrierData.sync,
|
||||||
|
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
||||||
|
AccessBefore = (D3D12_BARRIER_ACCESS)accessBefore,
|
||||||
|
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
|
||||||
|
LayoutBefore = (D3D12_BARRIER_LAYOUT)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
|
||||||
|
};
|
||||||
|
|
||||||
|
record.barrierData = new ResourceBarrierData(desc.LayoutAfter, desc.AccessAfter, desc.SyncAfter);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,8 +849,6 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
|||||||
|
|
||||||
public void ExecuteIndirect(Handle<GPUBuffer> argumentBuffer, ulong argumentOffset, Handle<GPUBuffer> countBuffer, ulong countBufferOffset)
|
public void ExecuteIndirect(Handle<GPUBuffer> argumentBuffer, ulong argumentOffset, Handle<GPUBuffer> countBuffer, ulong countBufferOffset)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
|
||||||
|
|
||||||
AssertNotDisposed();
|
AssertNotDisposed();
|
||||||
ThrowIfNotRecording();
|
ThrowIfNotRecording();
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@@ -870,7 +866,6 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
|||||||
// TODO
|
// TODO
|
||||||
pNativeObject->ExecuteIndirect(null, 0,
|
pNativeObject->ExecuteIndirect(null, 0,
|
||||||
resource, argumentOffset, countResource, countBufferOffset);
|
resource, argumentOffset, countResource, countBufferOffset);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyBuffer(Handle<GPUBuffer> dst, Handle<GPUBuffer> src, ulong dstOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
|
public void CopyBuffer(Handle<GPUBuffer> dst, Handle<GPUBuffer> src, ulong dstOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Ghost.Graphics.D3D12.Utilities;
|
using Ghost.Graphics.D3D12.Utilities;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
using System.Diagnostics;
|
|
||||||
using TerraFX.Interop.DirectX;
|
using TerraFX.Interop.DirectX;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ internal unsafe class D3D12CommandQueue : D3D12Object<ID3D12CommandQueue>, IComm
|
|||||||
}
|
}
|
||||||
|
|
||||||
public D3D12CommandQueue(D3D12RenderDevice device, CommandQueueType type)
|
public D3D12CommandQueue(D3D12RenderDevice device, CommandQueueType type)
|
||||||
:base(CreateCommandQueue(device.NativeObject, type))
|
: base(CreateCommandQueue(device.NativeObject, type))
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
_fenceEvent = new AutoResetEvent(false);
|
_fenceEvent = new AutoResetEvent(false);
|
||||||
@@ -129,7 +128,7 @@ internal unsafe class D3D12CommandQueue : D3D12Object<ID3D12CommandQueue>, IComm
|
|||||||
AssertNotDisposed();
|
AssertNotDisposed();
|
||||||
|
|
||||||
_fenceValue = value;
|
_fenceValue = value;
|
||||||
ThrowIfFailed(pNativeObject->Signal((ID3D12Fence*)_fence.Get(), _fenceValue));
|
ThrowIfFailed(pNativeObject->Signal(_fence.Get(), _fenceValue));
|
||||||
return _fenceValue;
|
return _fenceValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,9 +160,46 @@ internal unsafe class D3D12CommandQueue : D3D12Object<ID3D12CommandQueue>, IComm
|
|||||||
WaitForValue(fenceValue);
|
WaitForValue(fenceValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task WaitAsync()
|
||||||
|
{
|
||||||
|
AssertNotDisposed();
|
||||||
|
|
||||||
|
var fenceValue = Signal(Interlocked.Increment(ref _fenceValue));
|
||||||
|
|
||||||
|
if (_fence.Get()->GetCompletedValue() >= fenceValue)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource();
|
||||||
|
var handle = new HANDLE((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
|
||||||
|
|
||||||
|
if (_fence.Get()->SetEventOnCompletion(fenceValue, handle).FAILED)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to set event on completion.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var registeredWait = ThreadPool.RegisterWaitForSingleObject(
|
||||||
|
_fenceEvent,
|
||||||
|
(state, timedOut) =>
|
||||||
|
{
|
||||||
|
var capturedTcs = (TaskCompletionSource)state!;
|
||||||
|
capturedTcs.SetResult();
|
||||||
|
_fenceEvent.Dispose();
|
||||||
|
},
|
||||||
|
tcs,
|
||||||
|
Timeout.Infinite,
|
||||||
|
executeOnlyOnce: true
|
||||||
|
);
|
||||||
|
|
||||||
|
tcs.Task.ContinueWith(_ => registeredWait.Unregister(null));
|
||||||
|
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
_fence.Dispose();
|
_fence.Dispose();
|
||||||
_fenceEvent?.Dispose();
|
_fenceEvent.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
|||||||
|
|
||||||
_resourceDatabase.EndFrame(gpuFrame);
|
_resourceDatabase.EndFrame(gpuFrame);
|
||||||
|
|
||||||
while (_commandBufferReturnQueue.TryPeek(out var entry) && entry.returnFrame <= gpuFrame)
|
while (_commandBufferReturnQueue.TryPeek(out var entry) && entry.returnFrame < gpuFrame)
|
||||||
{
|
{
|
||||||
_commandBufferPool.Enqueue(entry.commandBuffer);
|
_commandBufferPool.Enqueue(entry.commandBuffer);
|
||||||
_commandBufferReturnQueue.Dequeue();
|
_commandBufferReturnQueue.Dequeue();
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public unsafe abstract class D3D12Object<T>: IRHIObject
|
|||||||
|
|
||||||
~D3D12Object()
|
~D3D12Object()
|
||||||
{
|
{
|
||||||
Dispose(disposing: false);
|
Dispose(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -65,7 +65,7 @@ public unsafe abstract class D3D12Object<T>: IRHIObject
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispose(disposing: true);
|
Dispose(true);
|
||||||
|
|
||||||
_nativeObject.Dispose();
|
_nativeObject.Dispose();
|
||||||
|
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
BindlessAccess.ShaderResource => (uint)r.Value.viewGroup.srv.Value,
|
BindlessAccess.ShaderResource => (uint)r.Value.viewGroup.srv.Value,
|
||||||
BindlessAccess.ConstantBuffer => (uint)r.Value.viewGroup.cbv.Value,
|
BindlessAccess.ConstantBuffer => (uint)r.Value.viewGroup.cbv.Value,
|
||||||
BindlessAccess.UnorderedAccess => (uint)r.Value.viewGroup.uav.Value,
|
BindlessAccess.UnorderedAccess => (uint)r.Value.viewGroup.uav.Value,
|
||||||
_ => ~0u,
|
_ => uint.MaxValue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
var rRange = readRange.HasValue ? new D3D12_RANGE { Begin = readRange.Value.Start, End = readRange.Value.End } : default;
|
var rRange = readRange.HasValue ? new D3D12_RANGE { Begin = readRange.Value.Start, End = readRange.Value.End } : default;
|
||||||
var wRange = writeRange.HasValue ? new D3D12_RANGE { Begin = writeRange.Value.Start, End = writeRange.Value.End } : default;
|
var wRange = writeRange.HasValue ? new D3D12_RANGE { Begin = writeRange.Value.Start, End = writeRange.Value.End } : default;
|
||||||
|
|
||||||
void * mappedData = null;
|
void* mappedData = null;
|
||||||
resource.Get()->Map(subResource, readRange.HasValue ? &rRange : null, &mappedData);
|
resource.Get()->Map(subResource, readRange.HasValue ? &rRange : null, &mappedData);
|
||||||
MemoryUtility.MemCpy(mappedData, pData, size);
|
MemoryUtility.MemCpy(mappedData, pData, size);
|
||||||
resource.Get()->Unmap(subResource, writeRange.HasValue ? &wRange : null);
|
resource.Get()->Unmap(subResource, writeRange.HasValue ? &wRange : null);
|
||||||
@@ -433,26 +433,19 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
return GetRequiredIntermediateSize(r.Value.ResourcePtr.Get(), firstSubResource, numSubResources);
|
return GetRequiredIntermediateSize(r.Value.ResourcePtr.Get(), firstSubResource, numSubResources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BeginFrame(ulong cpuFrame)
|
internal void BeginFrame(ulong cpuFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Debug.Assert(!_disposed);
|
||||||
_cpuFrame = cpuFrame;
|
_cpuFrame = cpuFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndFrame(ulong gpuFrame)
|
internal void EndFrame(ulong gpuFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Debug.Assert(!_disposed);
|
||||||
|
|
||||||
while (_releaseQueue.Count > 0)
|
while (_releaseQueue.TryPeek(out var toRelease) && toRelease.fenceValue < gpuFrame)
|
||||||
{
|
{
|
||||||
var toRelease = _releaseQueue.Peek();
|
|
||||||
if (toRelease.fenceValue > gpuFrame)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_releaseQueue.Dequeue();
|
_releaseQueue.Dequeue();
|
||||||
|
|
||||||
toRelease.record.Release(_descriptorAllocator);
|
toRelease.record.Release(_descriptorAllocator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -461,6 +454,11 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Debug.Assert(!_disposed);
|
||||||
|
|
||||||
|
foreach (ref var entry in _releaseQueue)
|
||||||
|
{
|
||||||
|
entry.record.Release(_descriptorAllocator);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (ref var record in _resources)
|
foreach (ref var record in _resources)
|
||||||
{
|
{
|
||||||
record.Release(_descriptorAllocator);
|
record.Release(_descriptorAllocator);
|
||||||
|
|||||||
@@ -48,4 +48,10 @@ public interface ICommandQueue : IDisposable
|
|||||||
/// Waits until all submitted commands have finished executing
|
/// Waits until all submitted commands have finished executing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void WaitIdle();
|
void WaitIdle();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits asynchronously until all submitted commands have finished executing
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Task that completes when the queue is idle</returns>
|
||||||
|
Task WaitAsync();
|
||||||
}
|
}
|
||||||
@@ -79,19 +79,12 @@ public readonly unsafe ref struct RenderContext
|
|||||||
throw new OutOfMemoryException("Failed to create upload buffer for buffer data.");
|
throw new OutOfMemoryException("Failed to create upload buffer for buffer data.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
fixed (T* pData = data)
|
||||||
{
|
{
|
||||||
fixed (T* pData = data)
|
ResourceDatabase.MapResource(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
|
||||||
{
|
}
|
||||||
ResourceDatabase.MapResource(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ResourceDatabase.ReleaseResource(uploadHandle.AsResource());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,25 +273,18 @@ public readonly unsafe ref struct RenderContext
|
|||||||
throw new OutOfMemoryException("Failed to create upload buffer for texture data.");
|
throw new OutOfMemoryException("Failed to create upload buffer for texture data.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||||
{
|
|
||||||
TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy);
|
|
||||||
|
|
||||||
fixed (T* pData = data)
|
fixed (T* pData = data)
|
||||||
|
{
|
||||||
|
var subresourceData = new SubResourceData
|
||||||
{
|
{
|
||||||
var subresourceData = new SubResourceData
|
pData = pData,
|
||||||
{
|
rowPitch = rowPitch,
|
||||||
pData = pData,
|
slicePitch = slicePitch
|
||||||
rowPitch = rowPitch,
|
};
|
||||||
slicePitch = slicePitch
|
|
||||||
};
|
|
||||||
|
|
||||||
_cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
_cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ResourceDatabase.ReleaseResource(uploadHandle.AsResource());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public sealed class RenderGraph : IDisposable
|
|||||||
);
|
);
|
||||||
|
|
||||||
_nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources);
|
_nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources);
|
||||||
_compiler = new RenderGraphCompiler(_resourceManager, _resourceDatabase, _resourceAllocator, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
|
_compiler = new RenderGraphCompiler(_resourceDatabase, _resourceAllocator, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
|
||||||
_executor = new RenderGraphExecutor(_resourceManager, _resourceDatabase, _resources, _context);
|
_executor = new RenderGraphExecutor(_resourceManager, _resourceDatabase, _resources, _context);
|
||||||
|
|
||||||
_blackboard = new RenderGraphBlackboard();
|
_blackboard = new RenderGraphBlackboard();
|
||||||
@@ -190,12 +190,14 @@ public sealed class RenderGraph : IDisposable
|
|||||||
_resources.ResolveTextureSizes(in viewState);
|
_resources.ResolveTextureSizes(in viewState);
|
||||||
|
|
||||||
var graphHash = RenderGraphHasher.ComputeGraphHash(_passes, _resources);
|
var graphHash = RenderGraphHasher.ComputeGraphHash(_passes, _resources);
|
||||||
var error = _compiler.Compile(in viewState, graphHash, _passes, _compiledPasses, _nativePasses, _compiledBarriers);
|
var result = _compiler.Compile(in viewState, graphHash, _passes, _compiledPasses, _nativePasses, _compiledBarriers);
|
||||||
if (error != Error.None)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return error;
|
return result.Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_context.RelativeScale = result.Value;
|
||||||
|
|
||||||
_compiled = true;
|
_compiled = true;
|
||||||
return Error.None;
|
return Error.None;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,97 +11,39 @@ internal enum BarrierFlags
|
|||||||
Discard = 1 << 1
|
Discard = 1 << 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a heap barrier requirement that needs to be resolved at runtime.
|
|
||||||
/// </summary>
|
|
||||||
internal struct ResourceBarrier
|
|
||||||
{
|
|
||||||
public int PassIndex;
|
|
||||||
public Identifier<RGResource> Resource;
|
|
||||||
public ResourceBarrierData TargetState;
|
|
||||||
public Identifier<RGResource> AliasingPredecessor; // Invalid if not aliasing
|
|
||||||
public BarrierFlags Flags;
|
|
||||||
|
|
||||||
public static ResourceBarrier CreateTransition(int passIndex, Identifier<RGResource> resource, ResourceBarrierData targetState, BarrierFlags flags = BarrierFlags.None)
|
|
||||||
{
|
|
||||||
return new ResourceBarrier
|
|
||||||
{
|
|
||||||
PassIndex = passIndex,
|
|
||||||
Resource = resource,
|
|
||||||
TargetState = targetState,
|
|
||||||
AliasingPredecessor = Identifier<RGResource>.Invalid,
|
|
||||||
Flags = flags
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ResourceBarrier CreateAliasing(int passIndex, Identifier<RGResource> resource, Identifier<RGResource> predecessor, ResourceBarrierData targetState)
|
|
||||||
{
|
|
||||||
return new ResourceBarrier
|
|
||||||
{
|
|
||||||
PassIndex = passIndex,
|
|
||||||
Resource = resource,
|
|
||||||
TargetState = targetState,
|
|
||||||
AliasingPredecessor = predecessor,
|
|
||||||
Flags = BarrierFlags.FirstUsage | BarrierFlags.Discard // Aliasing implies starting fresh
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override readonly string ToString()
|
|
||||||
{
|
|
||||||
return AliasingPredecessor.IsValid
|
|
||||||
? $"[Pass {PassIndex}] Aliasing Barrier: {AliasingPredecessor.Value}->{Resource.Value} Target: {TargetState.layout}"
|
|
||||||
: $"[Pass {PassIndex}] Barrier: {Resource.Value} Target: {TargetState.layout}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tracks the current state of a heap across passes during compilation.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class ResourceStateTracker
|
internal sealed class ResourceStateTracker
|
||||||
{
|
{
|
||||||
public int resourceIndex;
|
public int resourceIndex;
|
||||||
public ResourceBarrierData currentState;
|
|
||||||
public int lastAccessPass = -1;
|
public int lastAccessPass = -1;
|
||||||
|
public ResourceBarrierData currentState;
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
resourceIndex = -1;
|
resourceIndex = -1;
|
||||||
currentState = default;
|
|
||||||
lastAccessPass = -1;
|
lastAccessPass = -1;
|
||||||
|
currentState = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a compiled barrier with only the target state.
|
|
||||||
/// The before state is always queried from ResourceManager at execution time.
|
|
||||||
/// </summary>
|
|
||||||
internal struct CompiledBarrier
|
internal struct CompiledBarrier
|
||||||
{
|
{
|
||||||
public int PassIndex;
|
public int passIndex;
|
||||||
public Identifier<RGResource> Resource;
|
public Identifier<RGResource> resource;
|
||||||
public ResourceBarrierData TargetState;
|
public ResourceBarrierData targetState;
|
||||||
public Identifier<RGResource> AliasingPredecessor; // Invalid if not aliasing
|
public Identifier<RGResource> aliasingPredecessor; // Invalid if not aliasing
|
||||||
public BarrierFlags Flags;
|
public BarrierFlags flags;
|
||||||
public RenderGraphResourceType ResourceType;
|
public RenderGraphResourceType resourceType;
|
||||||
|
|
||||||
public override readonly string ToString()
|
public override readonly string ToString()
|
||||||
{
|
{
|
||||||
return AliasingPredecessor.IsValid
|
return aliasingPredecessor.IsValid
|
||||||
? $"[Pass {PassIndex}] Aliasing: {AliasingPredecessor.Value}->{Resource.Value} -> {TargetState.layout}"
|
? $"[Pass {passIndex}] Aliasing: {aliasingPredecessor.Value}->{resource.Value} -> {targetState.layout}"
|
||||||
: $"[Pass {PassIndex}] Transition: {Resource.Value} -> {TargetState.layout}";
|
: $"[Pass {passIndex}] Transition: {resource.Value} -> {targetState.layout}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Static class containing barrier compilation logic.
|
|
||||||
/// Compiles barriers at graph compilation time, storing only target states.
|
|
||||||
/// </summary>
|
|
||||||
internal static class RenderGraphBarriers
|
internal static class RenderGraphBarriers
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Compiles all barriers needed for execution, storing only target states.
|
|
||||||
/// Barriers include aliasing barriers and implicit state transitions.
|
|
||||||
/// </summary>
|
|
||||||
public static void CompileBarriers(
|
public static void CompileBarriers(
|
||||||
List<RenderGraphPassBase> compiledPasses,
|
List<RenderGraphPassBase> compiledPasses,
|
||||||
List<CompiledBarrier> compiledBarriers,
|
List<CompiledBarrier> compiledBarriers,
|
||||||
@@ -123,9 +65,6 @@ internal static class RenderGraphBarriers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Inserts aliasing barriers when a placed heap is reused.
|
|
||||||
/// </summary>
|
|
||||||
private static void InsertAliasingBarriers(
|
private static void InsertAliasingBarriers(
|
||||||
RenderGraphPassBase pass,
|
RenderGraphPassBase pass,
|
||||||
int passIdx,
|
int passIdx,
|
||||||
@@ -189,12 +128,12 @@ internal static class RenderGraphBarriers
|
|||||||
var targetState = new ResourceBarrierData(BarrierLayout.Undefined, BarrierAccess.NoAccess, BarrierSync.None);
|
var targetState = new ResourceBarrierData(BarrierLayout.Undefined, BarrierAccess.NoAccess, BarrierSync.None);
|
||||||
var barrier = new CompiledBarrier
|
var barrier = new CompiledBarrier
|
||||||
{
|
{
|
||||||
PassIndex = passIdx,
|
passIndex = passIdx,
|
||||||
Resource = id,
|
resource = id,
|
||||||
TargetState = targetState,
|
targetState = targetState,
|
||||||
AliasingPredecessor = resourceBefore,
|
aliasingPredecessor = resourceBefore,
|
||||||
Flags = BarrierFlags.FirstUsage | BarrierFlags.Discard,
|
flags = BarrierFlags.FirstUsage | BarrierFlags.Discard,
|
||||||
ResourceType = resource.type
|
resourceType = resource.type
|
||||||
};
|
};
|
||||||
compiledBarriers.Add(barrier);
|
compiledBarriers.Add(barrier);
|
||||||
}
|
}
|
||||||
@@ -205,10 +144,6 @@ internal static class RenderGraphBarriers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compiles implicit state transitions for all resources accessed by a pass.
|
|
||||||
/// Stores only the target state - the before state will be queried from ResourceManager at execution time.
|
|
||||||
/// </summary>
|
|
||||||
private static void CompileImplicitTransitions(
|
private static void CompileImplicitTransitions(
|
||||||
RenderGraphPassBase pass,
|
RenderGraphPassBase pass,
|
||||||
int passIdx,
|
int passIdx,
|
||||||
@@ -221,12 +156,12 @@ internal static class RenderGraphBarriers
|
|||||||
var resource = resources.GetResource(id);
|
var resource = resources.GetResource(id);
|
||||||
var barrier = new CompiledBarrier
|
var barrier = new CompiledBarrier
|
||||||
{
|
{
|
||||||
PassIndex = passIdx,
|
passIndex = passIdx,
|
||||||
Resource = id,
|
resource = id,
|
||||||
TargetState = targetState,
|
targetState = targetState,
|
||||||
AliasingPredecessor = Identifier<RGResource>.Invalid,
|
aliasingPredecessor = Identifier<RGResource>.Invalid,
|
||||||
Flags = BarrierFlags.None,
|
flags = BarrierFlags.None,
|
||||||
ResourceType = resource.type
|
resourceType = resource.type
|
||||||
};
|
};
|
||||||
compiledBarriers.Add(barrier);
|
compiledBarriers.Add(barrier);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
|
|
||||||
namespace Ghost.Graphics.RenderGraphModule;
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
|
|
||||||
/// <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
|
internal sealed class CachedCompilation
|
||||||
{
|
{
|
||||||
// Compiled pass indices (indices into the _passes list)
|
// Compiled pass indices (indices into the _passes list)
|
||||||
@@ -43,9 +39,6 @@ internal sealed class CachedCompilation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Placed heap data for caching.
|
|
||||||
/// </summary>
|
|
||||||
internal struct PlacedResourceData
|
internal struct PlacedResourceData
|
||||||
{
|
{
|
||||||
public int index;
|
public int index;
|
||||||
@@ -56,40 +49,24 @@ internal struct PlacedResourceData
|
|||||||
public int lastUsePass;
|
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
|
internal sealed class RenderGraphCompilationCache
|
||||||
{
|
{
|
||||||
private readonly CachedCompilation _cached = new();
|
private readonly CachedCompilation _cached = new();
|
||||||
private ulong _cachedHash;
|
private ulong _cachedHash;
|
||||||
private bool _hasCachedData;
|
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)
|
public bool TryGetCached(ulong hash, [MaybeNullWhen(false)] out CachedCompilation result)
|
||||||
{
|
{
|
||||||
if (_hasCachedData && _cachedHash == hash)
|
if (_hasCachedData && _cachedHash == hash)
|
||||||
{
|
{
|
||||||
result = _cached;
|
result = _cached;
|
||||||
CacheHits++;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = null;
|
result = null;
|
||||||
CacheMisses++;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stores compilation results in the cache.
|
|
||||||
/// </summary>
|
|
||||||
public void Store(ulong hash, CachedCompilation data)
|
public void Store(ulong hash, CachedCompilation data)
|
||||||
{
|
{
|
||||||
_cachedHash = hash;
|
_cachedHash = hash;
|
||||||
@@ -112,9 +89,6 @@ internal sealed class RenderGraphCompilationCache
|
|||||||
_cached.backingResources.AddRange(data.backingResources);
|
_cached.backingResources.AddRange(data.backingResources);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invalidates the cache, forcing recompilation on next Compile().
|
|
||||||
/// </summary>
|
|
||||||
public void Invalidate()
|
public void Invalidate()
|
||||||
{
|
{
|
||||||
_hasCachedData = false;
|
_hasCachedData = false;
|
||||||
@@ -131,14 +105,4 @@ internal sealed class RenderGraphCompilationCache
|
|||||||
|
|
||||||
_cached.backingResources[logicalIndex] = resource;
|
_cached.backingResources[logicalIndex] = resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets cache statistics for debugging.
|
|
||||||
/// </summary>
|
|
||||||
public (int hits, int misses, double hitRate) GetStatistics()
|
|
||||||
{
|
|
||||||
var total = CacheHits + CacheMisses;
|
|
||||||
var hitRate = total > 0 ? (double)CacheHits / total : 0.0;
|
|
||||||
return (CacheHits, CacheMisses, hitRate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|
||||||
namespace Ghost.Graphics.RenderGraphModule;
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
|
|
||||||
@@ -9,7 +10,6 @@ namespace Ghost.Graphics.RenderGraphModule;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class RenderGraphCompiler
|
internal sealed class RenderGraphCompiler
|
||||||
{
|
{
|
||||||
private readonly ResourceManager _resourceManager;
|
|
||||||
private readonly IResourceDatabase _resourceDatabase;
|
private readonly IResourceDatabase _resourceDatabase;
|
||||||
private readonly IResourceAllocator _resourceAllocator;
|
private readonly IResourceAllocator _resourceAllocator;
|
||||||
private readonly RenderGraphResourceRegistry _resources;
|
private readonly RenderGraphResourceRegistry _resources;
|
||||||
@@ -20,7 +20,6 @@ internal sealed class RenderGraphCompiler
|
|||||||
private Handle<GPUResource> _resourceHeap;
|
private Handle<GPUResource> _resourceHeap;
|
||||||
|
|
||||||
public RenderGraphCompiler(
|
public RenderGraphCompiler(
|
||||||
ResourceManager resourceManager,
|
|
||||||
IResourceDatabase resourceDatabase,
|
IResourceDatabase resourceDatabase,
|
||||||
IResourceAllocator resourceAllocator,
|
IResourceAllocator resourceAllocator,
|
||||||
RenderGraphResourceRegistry resources,
|
RenderGraphResourceRegistry resources,
|
||||||
@@ -28,7 +27,6 @@ internal sealed class RenderGraphCompiler
|
|||||||
RenderGraphNativePassBuilder nativePassBuilder,
|
RenderGraphNativePassBuilder nativePassBuilder,
|
||||||
RenderGraphCompilationCache compilationCache)
|
RenderGraphCompilationCache compilationCache)
|
||||||
{
|
{
|
||||||
_resourceManager = resourceManager;
|
|
||||||
_resourceDatabase = resourceDatabase;
|
_resourceDatabase = resourceDatabase;
|
||||||
_resourceAllocator = resourceAllocator;
|
_resourceAllocator = resourceAllocator;
|
||||||
_resources = resources;
|
_resources = resources;
|
||||||
@@ -41,7 +39,7 @@ internal sealed class RenderGraphCompiler
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compiles the render graph by culling passes, allocating resources, and preparing barriers.
|
/// Compiles the render graph by culling passes, allocating resources, and preparing barriers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Error Compile(
|
public Result<float2, Error> Compile(
|
||||||
in ViewState viewState,
|
in ViewState viewState,
|
||||||
ulong graphHash,
|
ulong graphHash,
|
||||||
List<RenderGraphPassBase> passes,
|
List<RenderGraphPassBase> passes,
|
||||||
@@ -55,7 +53,8 @@ internal sealed class RenderGraphCompiler
|
|||||||
if (_compilationCache.TryGetCached(graphHash, out var cached))
|
if (_compilationCache.TryGetCached(graphHash, out var cached))
|
||||||
{
|
{
|
||||||
// Check if view state changed
|
// Check if view state changed
|
||||||
if (!cached.viewState.Equals(viewState))
|
var scale = viewState.CalculateScale(cached.viewState);
|
||||||
|
if (math.any(scale > float2.one))
|
||||||
{
|
{
|
||||||
// View state changed - re-resolve sizes and recreate GPU resources
|
// View state changed - re-resolve sizes and recreate GPU resources
|
||||||
_resources.ResolveTextureSizes(in viewState);
|
_resources.ResolveTextureSizes(in viewState);
|
||||||
@@ -69,14 +68,15 @@ internal sealed class RenderGraphCompiler
|
|||||||
}
|
}
|
||||||
|
|
||||||
cached.viewState = viewState;
|
cached.viewState = viewState;
|
||||||
|
|
||||||
|
return float2.one;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Perfect cache hit - restore everything
|
// Perfect cache hit - restore everything
|
||||||
RestoreFromCache(cached, compiledPasses, passes, nativePasses, compiledBarriers);
|
RestoreFromCache(cached, compiledPasses, passes, nativePasses, compiledBarriers);
|
||||||
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Error.None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fresh compilation needed
|
// Fresh compilation needed
|
||||||
@@ -109,7 +109,7 @@ internal sealed class RenderGraphCompiler
|
|||||||
_nativePassBuilder.BuildNativeRenderPasses(compiledPasses, nativePasses, compiledBarriers);
|
_nativePassBuilder.BuildNativeRenderPasses(compiledPasses, nativePasses, compiledBarriers);
|
||||||
StoreInCache(graphHash, viewState, compiledPasses, passes, compiledBarriers);
|
StoreInCache(graphHash, viewState, compiledPasses, passes, compiledBarriers);
|
||||||
|
|
||||||
return Error.None;
|
return float2.one;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MarkPassesWithSideEffects(List<RenderGraphPassBase> passes)
|
private void MarkPassesWithSideEffects(List<RenderGraphPassBase> passes)
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ public interface IRenderGraphContext
|
|||||||
ResourceManager ResourceManager { get; }
|
ResourceManager ResourceManager { get; }
|
||||||
IResourceDatabase ResourceDatabase { get; }
|
IResourceDatabase ResourceDatabase { get; }
|
||||||
|
|
||||||
|
float2 RelativeScale { get; }
|
||||||
|
|
||||||
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
|
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
|
||||||
Handle<GPUTexture> GetActualTexture(Identifier<RGTexture> texture);
|
Handle<GPUTexture> GetActualTexture(Identifier<RGTexture> texture);
|
||||||
Handle<GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer);
|
Handle<GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer);
|
||||||
|
|
||||||
Handle<GPUTexture> GetHistoryTexture(ReadOnlySpan<Identifier<RGTexture>> texture, int historyOffset);
|
|
||||||
Handle<GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffer, int historyOffset);
|
|
||||||
|
|
||||||
ICommandBuffer GetCommandBufferUnsafe();
|
ICommandBuffer GetCommandBufferUnsafe();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +52,6 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
private readonly IShaderCompiler _shaderCompiler;
|
private readonly IShaderCompiler _shaderCompiler;
|
||||||
private readonly RenderGraphResourceRegistry _resources;
|
private readonly RenderGraphResourceRegistry _resources;
|
||||||
|
|
||||||
private uint _frameIndex;
|
|
||||||
private ICommandBuffer _commandBuffer;
|
private ICommandBuffer _commandBuffer;
|
||||||
|
|
||||||
private readonly TextureFormat[] _rtvFormats;
|
private readonly TextureFormat[] _rtvFormats;
|
||||||
@@ -74,6 +72,11 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
|
|
||||||
public int ActiveMeshIndexCount => _activeMeshIndexCount;
|
public int ActiveMeshIndexCount => _activeMeshIndexCount;
|
||||||
|
|
||||||
|
public float2 RelativeScale
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
internal RenderGraphContext(ResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
|
internal RenderGraphContext(ResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
|
||||||
{
|
{
|
||||||
_resourceManager = resourceManager;
|
_resourceManager = resourceManager;
|
||||||
@@ -88,9 +91,8 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
_dsvFormat = TextureFormat.Unknown;
|
_dsvFormat = TextureFormat.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void BeginNewFrame(uint frameIndex, ICommandBuffer commandBuffer)
|
internal void BeginNewFrame(ICommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
_frameIndex = frameIndex;
|
|
||||||
_commandBuffer = commandBuffer;
|
_commandBuffer = commandBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,38 +127,6 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
return _resources.GetResource(buffer.AsResource()).backingResource.AsBuffer();
|
return _resources.GetResource(buffer.AsResource()).backingResource.AsBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Handle<GPUTexture> GetHistoryTexture(ReadOnlySpan<Identifier<RGTexture>> textures, int historyOffset)
|
|
||||||
{
|
|
||||||
if (historyOffset < 0 || historyOffset >= textures.Length)
|
|
||||||
{
|
|
||||||
return Handle<GPUTexture>.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = (int)(_frameIndex % textures.Length) - historyOffset;
|
|
||||||
if (index < 0)
|
|
||||||
{
|
|
||||||
index += textures.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetActualTexture(textures[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Handle<GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffers, int historyOffset)
|
|
||||||
{
|
|
||||||
if (historyOffset < 0 || historyOffset >= buffers.Length)
|
|
||||||
{
|
|
||||||
return Handle<GPUBuffer>.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = (int)(_frameIndex % buffers.Length) - historyOffset;
|
|
||||||
if (index < 0)
|
|
||||||
{
|
|
||||||
index += buffers.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetActualBuffer(buffers[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetViewport(ViewportDesc desc)
|
public void SetViewport(ViewportDesc desc)
|
||||||
{
|
{
|
||||||
_commandBuffer.SetViewport(desc);
|
_commandBuffer.SetViewport(desc);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
@@ -12,8 +13,6 @@ internal sealed class RenderGraphExecutor
|
|||||||
private readonly RenderGraphResourceRegistry _resources;
|
private readonly RenderGraphResourceRegistry _resources;
|
||||||
private readonly RenderGraphContext _context;
|
private readonly RenderGraphContext _context;
|
||||||
|
|
||||||
private uint _frameIndex;
|
|
||||||
|
|
||||||
public RenderGraphExecutor(
|
public RenderGraphExecutor(
|
||||||
ResourceManager resourceManager,
|
ResourceManager resourceManager,
|
||||||
IResourceDatabase resourceDatabase,
|
IResourceDatabase resourceDatabase,
|
||||||
@@ -89,7 +88,7 @@ internal sealed class RenderGraphExecutor
|
|||||||
var nativePassIndex = 0;
|
var nativePassIndex = 0;
|
||||||
var logicalPassIndex = 0;
|
var logicalPassIndex = 0;
|
||||||
|
|
||||||
_context.BeginNewFrame(_frameIndex++, commandBuffer);
|
_context.BeginNewFrame(commandBuffer);
|
||||||
|
|
||||||
var pPassRTDescs = stackalloc PassRenderTargetDesc[8];
|
var pPassRTDescs = stackalloc PassRenderTargetDesc[8];
|
||||||
var pRtFormats = stackalloc TextureFormat[8];
|
var pRtFormats = stackalloc TextureFormat[8];
|
||||||
@@ -218,10 +217,10 @@ internal sealed class RenderGraphExecutor
|
|||||||
|
|
||||||
// Process all pre-compiled barriers for this pass
|
// Process all pre-compiled barriers for this pass
|
||||||
// TODO: We can insert BarrierAccess.NoAccess to the resource that aliased with others after their last usage to reduce cache burden.
|
// TODO: We can insert BarrierAccess.NoAccess to the resource that aliased with others after their last usage to reduce cache burden.
|
||||||
while (barrierIndex < compiledBarriers.Count && compiledBarriers[barrierIndex].PassIndex == passIndex)
|
while (barrierIndex < compiledBarriers.Count && compiledBarriers[barrierIndex].passIndex == passIndex)
|
||||||
{
|
{
|
||||||
var compiledBarrier = compiledBarriers[barrierIndex++];
|
var compiledBarrier = compiledBarriers[barrierIndex++];
|
||||||
var resource = _resources.GetResource(compiledBarrier.Resource);
|
var resource = _resources.GetResource(compiledBarrier.resource);
|
||||||
var resourceHandle = resource.backingResource;
|
var resourceHandle = resource.backingResource;
|
||||||
|
|
||||||
// Always query the before state from ResourceManager (single source of truth)
|
// Always query the before state from ResourceManager (single source of truth)
|
||||||
@@ -232,21 +231,21 @@ internal sealed class RenderGraphExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
var currentState = currentStateResult.Value;
|
var currentState = currentStateResult.Value;
|
||||||
var target = compiledBarrier.TargetState;
|
var target = compiledBarrier.targetState;
|
||||||
|
|
||||||
// Create barrier descriptor
|
// Create barrier descriptor
|
||||||
BarrierDesc desc;
|
BarrierDesc desc;
|
||||||
if (compiledBarrier.ResourceType == RenderGraphResourceType.Texture)
|
if (compiledBarrier.resourceType == RenderGraphResourceType.Texture)
|
||||||
{
|
{
|
||||||
desc = BarrierDesc.Texture(resourceHandle, target.sync, target.access, target.layout,
|
desc = BarrierDesc.Texture(resourceHandle, target.sync, target.access, target.layout,
|
||||||
discard: compiledBarrier.Flags.HasFlag(BarrierFlags.Discard));
|
discard: compiledBarrier.flags.HasFlag(BarrierFlags.Discard));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
desc = BarrierDesc.Buffer(resourceHandle, target.sync, target.access);
|
desc = BarrierDesc.Buffer(resourceHandle, target.sync, target.access);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compiledBarrier.AliasingPredecessor.IsValid)
|
if (compiledBarrier.aliasingPredecessor.IsValid)
|
||||||
{
|
{
|
||||||
desc.IsAliasing = true;
|
desc.IsAliasing = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using System.IO.Hashing;
|
|||||||
|
|
||||||
namespace Ghost.Graphics.RenderGraphModule;
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
|
|
||||||
internal static class RenderGraphHasher
|
internal static unsafe class RenderGraphHasher
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes a hash of the entire render graph structure.
|
/// Computes a hash of the entire render graph structure.
|
||||||
@@ -14,7 +14,7 @@ internal static class RenderGraphHasher
|
|||||||
public static ulong ComputeGraphHash(List<RenderGraphPassBase> passes, RenderGraphResourceRegistry resources)
|
public static ulong ComputeGraphHash(List<RenderGraphPassBase> passes, RenderGraphResourceRegistry resources)
|
||||||
{
|
{
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
var writer = new BufferWriter(2048, scope.AllocationHandle);
|
using var writer = new BufferWriter(2048, scope.AllocationHandle);
|
||||||
|
|
||||||
// Hash pass count
|
// Hash pass count
|
||||||
writer.Write(passes.Count);
|
writer.Write(passes.Count);
|
||||||
@@ -29,14 +29,14 @@ internal static class RenderGraphHasher
|
|||||||
writer.Write(pass.asyncCompute);
|
writer.Write(pass.asyncCompute);
|
||||||
|
|
||||||
// Hash depth attachment
|
// Hash depth attachment
|
||||||
ComputeTextureHash(ref writer, pass.depthAccess.id, resources);
|
ComputeTextureHash(&writer, pass.depthAccess.id, resources);
|
||||||
|
|
||||||
writer.Write(pass.depthAccess.accessFlags);
|
writer.Write(pass.depthAccess.accessFlags);
|
||||||
writer.Write(pass.maxColorIndex);
|
writer.Write(pass.maxColorIndex);
|
||||||
|
|
||||||
for (var j = 0; j <= pass.maxColorIndex; j++)
|
for (var j = 0; j <= pass.maxColorIndex; j++)
|
||||||
{
|
{
|
||||||
ComputeTextureHash(ref writer, pass.colorAccess[j].id, resources);
|
ComputeTextureHash(&writer, pass.colorAccess[j].id, resources);
|
||||||
writer.Write(pass.colorAccess[j].accessFlags);
|
writer.Write(pass.colorAccess[j].accessFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ internal static class RenderGraphHasher
|
|||||||
/// For imported textures, hashes the backing handle.
|
/// For imported textures, hashes the backing handle.
|
||||||
/// For transient textures, hashes the descriptor (respecting size mode).
|
/// For transient textures, hashes the descriptor (respecting size mode).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ComputeTextureHash(ref BufferWriter writer, Identifier<RGTexture> texture, RenderGraphResourceRegistry resources)
|
private static void ComputeTextureHash(BufferWriter* writer, Identifier<RGTexture> texture, RenderGraphResourceRegistry resources)
|
||||||
{
|
{
|
||||||
if (texture.IsInvalid)
|
if (texture.IsInvalid)
|
||||||
{
|
{
|
||||||
@@ -92,39 +92,39 @@ internal static class RenderGraphHasher
|
|||||||
var resource = resources.GetResource(texture.AsResource());
|
var resource = resources.GetResource(texture.AsResource());
|
||||||
|
|
||||||
// Hash imported flag
|
// Hash imported flag
|
||||||
writer.Write(resource.isImported);
|
writer->Write(resource.isImported);
|
||||||
|
|
||||||
// For imported textures, hash the backing heap handle
|
// For imported textures, hash the backing heap handle
|
||||||
if (resource.isImported)
|
if (resource.isImported)
|
||||||
{
|
{
|
||||||
writer.Write(resource.backingResource.GetHashCode());
|
writer->Write(resource.backingResource.GetHashCode());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var desc = resource.rgTextureDesc;
|
var desc = resource.rgTextureDesc;
|
||||||
|
|
||||||
writer.Write(desc.format);
|
writer->Write(desc.format);
|
||||||
writer.Write(desc.sizeMode);
|
writer->Write(desc.sizeMode);
|
||||||
|
|
||||||
// Hash size specification based on mode
|
// Hash size specification based on mode
|
||||||
if (desc.sizeMode == RGTextureSizeMode.Absolute)
|
if (desc.sizeMode == RGTextureSizeMode.Absolute)
|
||||||
{
|
{
|
||||||
// Absolute mode: hash actual dimensions
|
// Absolute mode: hash actual dimensions
|
||||||
writer.Write(desc.width);
|
writer->Write(desc.width);
|
||||||
writer.Write(desc.height);
|
writer->Write(desc.height);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Relative mode: hash scale factors (NOT resolved dimensions)
|
// Relative mode: hash scale factors (NOT resolved dimensions)
|
||||||
writer.Write(desc.scaleX);
|
writer->Write(desc.scaleX);
|
||||||
writer.Write(desc.scaleY);
|
writer->Write(desc.scaleY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash other structural properties
|
// Hash other structural properties
|
||||||
writer.Write(desc.dimension);
|
writer->Write(desc.dimension);
|
||||||
writer.Write(desc.mipLevels);
|
writer->Write(desc.mipLevels);
|
||||||
writer.Write(desc.usage);
|
writer->Write(desc.usage);
|
||||||
writer.Write(desc.clearAtFirstUse);
|
writer->Write(desc.clearAtFirstUse);
|
||||||
writer.Write(desc.discardAtLastUse);
|
writer->Write(desc.discardAtLastUse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,16 +221,16 @@ internal sealed class RenderGraphNativePassBuilder
|
|||||||
// Check if any compiled barriers for passB affect render targets
|
// Check if any compiled barriers for passB affect render targets
|
||||||
for (var i = 0; i < compiledBarriers.Count; i++)
|
for (var i = 0; i < compiledBarriers.Count; i++)
|
||||||
{
|
{
|
||||||
if (compiledBarriers[i].PassIndex == passB)
|
if (compiledBarriers[i].passIndex == passB)
|
||||||
{
|
{
|
||||||
// Only prevent merge if barrier affects a render target
|
// Only prevent merge if barrier affects a render target
|
||||||
if (renderTargets.Contains(compiledBarriers[i].Resource))
|
if (renderTargets.Contains(compiledBarriers[i].resource))
|
||||||
{
|
{
|
||||||
return true; // Barrier affects render target, cannot merge
|
return true; // Barrier affects render target, cannot merge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compiledBarriers[i].PassIndex > passB)
|
if (compiledBarriers[i].passIndex > passB)
|
||||||
{
|
{
|
||||||
break; // No more barriers for this pass
|
break; // No more barriers for this pass
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Graphics.RenderGraphModule;
|
namespace Ghost.Graphics.RenderGraphModule;
|
||||||
@@ -48,6 +49,14 @@ public struct ViewState : IEquatable<ViewState>
|
|||||||
this.actualHeight = actualHeight;
|
this.actualHeight = actualHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly float2 CalculateScale(ViewState other)
|
||||||
|
{
|
||||||
|
return new float2(
|
||||||
|
(float)viewportWidth / other.viewportWidth,
|
||||||
|
(float)viewportHeight / other.viewportHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public readonly bool Equals(ViewState other)
|
public readonly bool Equals(ViewState other)
|
||||||
{
|
{
|
||||||
return viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight
|
return viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ public class RenderSystem : IDisposable
|
|||||||
private uint _frameIndex;
|
private uint _frameIndex;
|
||||||
private ulong _cpuFenceValue;
|
private ulong _cpuFenceValue;
|
||||||
private ulong _submittedFenceValue;
|
private ulong _submittedFenceValue;
|
||||||
private ulong _completedFenceValue;
|
|
||||||
|
|
||||||
private bool _isRunning;
|
private bool _isRunning;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
@@ -119,7 +118,6 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
public ulong CPUFenceValue => _cpuFenceValue;
|
public ulong CPUFenceValue => _cpuFenceValue;
|
||||||
public ulong SubmittedFenceValue => _submittedFenceValue;
|
public ulong SubmittedFenceValue => _submittedFenceValue;
|
||||||
public ulong CompletedFenceValue => _completedFenceValue;
|
|
||||||
public uint FrameIndex => _frameIndex;
|
public uint FrameIndex => _frameIndex;
|
||||||
public uint MaxFrameLatency => _config.FrameBufferCount;
|
public uint MaxFrameLatency => _config.FrameBufferCount;
|
||||||
|
|
||||||
@@ -261,19 +259,17 @@ public class RenderSystem : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_completedFenceValue = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
|
var completedFenceValue = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
|
||||||
if (_submittedFenceValue < _completedFenceValue)
|
if (_submittedFenceValue < completedFenceValue)
|
||||||
{
|
{
|
||||||
_submittedFenceValue = _completedFenceValue;
|
_submittedFenceValue = completedFenceValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextFenceValue = _submittedFenceValue + 1;
|
|
||||||
|
|
||||||
// Begin rendering for this frame
|
// Begin rendering for this frame
|
||||||
frameResource.CommandAllocator.Reset();
|
frameResource.CommandAllocator.Reset();
|
||||||
|
|
||||||
_resourceManager.BeginFrame(nextFenceValue);
|
_resourceManager.BeginFrame(_submittedFenceValue);
|
||||||
var r = _graphicsEngine.BeginFrame(nextFenceValue);
|
var r = _graphicsEngine.BeginFrame(_submittedFenceValue);
|
||||||
|
|
||||||
if (r.IsFailure)
|
if (r.IsFailure)
|
||||||
{
|
{
|
||||||
@@ -282,7 +278,7 @@ public class RenderSystem : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start recording commands
|
// Start recording commands
|
||||||
Debug.WriteLine($"GPU: Frame started.");
|
|
||||||
// TODO: How can we support async compute and async copy?
|
// TODO: How can we support async compute and async copy?
|
||||||
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
||||||
ref var renderRequests = ref frameResource.RenderRequests;
|
ref var renderRequests = ref frameResource.RenderRequests;
|
||||||
@@ -319,15 +315,15 @@ public class RenderSystem : IDisposable
|
|||||||
renderRequests.Clear();
|
renderRequests.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
_submittedFenceValue = nextFenceValue;
|
_submittedFenceValue++;
|
||||||
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(nextFenceValue);
|
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_submittedFenceValue);
|
||||||
frameResource.GpuReadyEvent.Set();
|
frameResource.GpuReadyEvent.Set();
|
||||||
|
|
||||||
_completedFenceValue = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
|
completedFenceValue = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
|
||||||
|
|
||||||
// End the frame and retire resources based on the freshest observed GPU progress.
|
// End the frame and retire resources based on the freshest observed GPU progress.
|
||||||
_resourceManager.EndFrame(_completedFenceValue);
|
_resourceManager.EndFrame(completedFenceValue);
|
||||||
r = _graphicsEngine.EndFrame(_completedFenceValue);
|
r = _graphicsEngine.EndFrame(completedFenceValue);
|
||||||
|
|
||||||
if (r.IsFailure)
|
if (r.IsFailure)
|
||||||
{
|
{
|
||||||
@@ -387,7 +383,9 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
internal bool TryAcquireCPUFrame()
|
internal bool TryAcquireCPUFrame()
|
||||||
{
|
{
|
||||||
ulong requiredGpuFence = _cpuFenceValue < _config.FrameBufferCount ? 0 : _cpuFenceValue - _config.FrameBufferCount + 1;
|
Debug.Assert(!_disposed, "Cannot acquire CPU frame on a disposed RenderSystem.");
|
||||||
|
|
||||||
|
var requiredGpuFence = _cpuFenceValue < _config.FrameBufferCount ? 0 : _cpuFenceValue - _config.FrameBufferCount + 1;
|
||||||
|
|
||||||
if (requiredGpuFence > 0 && _graphicsEngine.Device.GraphicsQueue.GetCompletedValue() < requiredGpuFence)
|
if (requiredGpuFence > 0 && _graphicsEngine.Device.GraphicsQueue.GetCompletedValue() < requiredGpuFence)
|
||||||
{
|
{
|
||||||
@@ -460,10 +458,10 @@ public class RenderSystem : IDisposable
|
|||||||
_renderPipeline.Dispose();
|
_renderPipeline.Dispose();
|
||||||
|
|
||||||
_resourceManager.Dispose();
|
_resourceManager.Dispose();
|
||||||
_graphicsEngine.Dispose();
|
|
||||||
|
|
||||||
_swapChainManager.Dispose();
|
_swapChainManager.Dispose();
|
||||||
|
|
||||||
|
_graphicsEngine.Dispose();
|
||||||
|
|
||||||
_shutdownEvent.Dispose();
|
_shutdownEvent.Dispose();
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Ghost.Graphics;
|
|||||||
|
|
||||||
public partial class ResourceManager
|
public partial class ResourceManager
|
||||||
{
|
{
|
||||||
private const ulong _DEFAULT_TRANSIENT_PAGE_SIZE = 16 * 1024 * 1024; // 16MB
|
public const ulong DEFAULT_TRANSIENT_PAGE_SIZE = 16 * 1024 * 1024; // 16MB
|
||||||
|
|
||||||
[DebuggerDisplay("Heap: {heap}, Offset: {offset}, HeapType: {heapType}, HeapFlags: {heapFlags}")]
|
[DebuggerDisplay("Heap: {heap}, Offset: {offset}, HeapType: {heapType}, HeapFlags: {heapFlags}")]
|
||||||
private struct Page
|
private struct Page
|
||||||
@@ -27,19 +27,11 @@ public partial class ResourceManager
|
|||||||
public ulong retireFrame;
|
public ulong retireFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnsafeList<Page> _activePages;
|
private UnsafeList<Page> _activePages = new UnsafeList<Page>(4, Allocator.Persistent);
|
||||||
private Queue<Page> _freePages = null!;
|
private UnsafeQueue<Page> _freePages = new UnsafeQueue<Page>(4, Allocator.Persistent);
|
||||||
private Queue<RetiringPage> _retiringPages = null!;
|
private UnsafeQueue<RetiringPage> _retiringPages = new UnsafeQueue<RetiringPage>(4, Allocator.Persistent);
|
||||||
|
|
||||||
private UnsafeList<Handle<GPUResource>> _oversizedTransientResources;
|
private UnsafeList<Handle<GPUResource>> _frameTransientResources = new UnsafeList<Handle<GPUResource>>(4, Allocator.Persistent);
|
||||||
|
|
||||||
private void InitializePool()
|
|
||||||
{
|
|
||||||
_activePages = new UnsafeList<Page>(4, Allocator.Persistent);
|
|
||||||
_freePages = new Queue<Page>(4);
|
|
||||||
_retiringPages = new Queue<RetiringPage>(4);
|
|
||||||
_oversizedTransientResources = new UnsafeList<Handle<GPUResource>>(4, Allocator.Persistent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsHeapFlagsCompatible(HeapFlags pageHeapFlags, HeapFlags requiredHeapFlags)
|
private static bool IsHeapFlagsCompatible(HeapFlags pageHeapFlags, HeapFlags requiredHeapFlags)
|
||||||
{
|
{
|
||||||
@@ -76,7 +68,7 @@ public partial class ResourceManager
|
|||||||
|
|
||||||
var allocationDesc = new AllocationDesc
|
var allocationDesc = new AllocationDesc
|
||||||
{
|
{
|
||||||
Size = _DEFAULT_TRANSIENT_PAGE_SIZE,
|
Size = DEFAULT_TRANSIENT_PAGE_SIZE,
|
||||||
Alignment = 65536, // 64KB
|
Alignment = 65536, // 64KB
|
||||||
HeapType = heapType,
|
HeapType = heapType,
|
||||||
HeapFlags = heapFlags,
|
HeapFlags = heapFlags,
|
||||||
@@ -104,12 +96,12 @@ public partial class ResourceManager
|
|||||||
var isRTOrDS = desc.Usage.HasFlag(TextureUsage.DepthStencil) || desc.Usage.HasFlag(TextureUsage.RenderTarget);
|
var isRTOrDS = desc.Usage.HasFlag(TextureUsage.DepthStencil) || desc.Usage.HasFlag(TextureUsage.RenderTarget);
|
||||||
var size = _resourceAllocator.GetSizeInfo(ResourceDesc.Texture(desc));
|
var size = _resourceAllocator.GetSizeInfo(ResourceDesc.Texture(desc));
|
||||||
|
|
||||||
if (size.Size > _DEFAULT_TRANSIENT_PAGE_SIZE)
|
if (size.Size > DEFAULT_TRANSIENT_PAGE_SIZE)
|
||||||
{
|
{
|
||||||
var texHandle = _resourceAllocator.CreateTexture(in desc, name);
|
var texHandle = _resourceAllocator.CreateTexture(in desc, name);
|
||||||
if (texHandle.IsValid)
|
if (texHandle.IsValid)
|
||||||
{
|
{
|
||||||
_oversizedTransientResources.Add(texHandle.AsResource());
|
_frameTransientResources.Add(texHandle.AsResource());
|
||||||
}
|
}
|
||||||
|
|
||||||
return texHandle;
|
return texHandle;
|
||||||
@@ -138,7 +130,7 @@ public partial class ResourceManager
|
|||||||
|
|
||||||
var proposedOffset = (p.offset + (size.Alignment - 1)) & ~(size.Alignment - 1);
|
var proposedOffset = (p.offset + (size.Alignment - 1)) & ~(size.Alignment - 1);
|
||||||
|
|
||||||
if (proposedOffset + size.Size <= _DEFAULT_TRANSIENT_PAGE_SIZE)
|
if (proposedOffset + size.Size <= DEFAULT_TRANSIENT_PAGE_SIZE)
|
||||||
{
|
{
|
||||||
foundPageIndex = i;
|
foundPageIndex = i;
|
||||||
alignedOffset = proposedOffset;
|
alignedOffset = proposedOffset;
|
||||||
@@ -168,7 +160,11 @@ public partial class ResourceManager
|
|||||||
Offset = alignedOffset,
|
Offset = alignedOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
page.offset = alignedOffset + size.Size;
|
if (handle.IsValid)
|
||||||
|
{
|
||||||
|
page.offset = alignedOffset + size.Size;
|
||||||
|
_frameTransientResources.Add(handle.AsResource());
|
||||||
|
}
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
@@ -176,12 +172,12 @@ public partial class ResourceManager
|
|||||||
public Handle<GPUBuffer> CreateTransientBuffer(ref readonly BufferDesc desc, string? name = null)
|
public Handle<GPUBuffer> CreateTransientBuffer(ref readonly BufferDesc desc, string? name = null)
|
||||||
{
|
{
|
||||||
var size = _resourceAllocator.GetSizeInfo(ResourceDesc.Buffer(desc));
|
var size = _resourceAllocator.GetSizeInfo(ResourceDesc.Buffer(desc));
|
||||||
if (size.Size > _DEFAULT_TRANSIENT_PAGE_SIZE)
|
if (size.Size > DEFAULT_TRANSIENT_PAGE_SIZE)
|
||||||
{
|
{
|
||||||
var bufHandle = _resourceAllocator.CreateBuffer(in desc, name);
|
var bufHandle = _resourceAllocator.CreateBuffer(in desc, name);
|
||||||
if (bufHandle.IsValid)
|
if (bufHandle.IsValid)
|
||||||
{
|
{
|
||||||
_oversizedTransientResources.Add(bufHandle.AsResource());
|
_frameTransientResources.Add(bufHandle.AsResource());
|
||||||
}
|
}
|
||||||
|
|
||||||
return bufHandle;
|
return bufHandle;
|
||||||
@@ -216,7 +212,7 @@ public partial class ResourceManager
|
|||||||
|
|
||||||
var proposedOffset = (p.offset + (size.Alignment - 1)) & ~(size.Alignment - 1);
|
var proposedOffset = (p.offset + (size.Alignment - 1)) & ~(size.Alignment - 1);
|
||||||
|
|
||||||
if (proposedOffset + size.Size <= _DEFAULT_TRANSIENT_PAGE_SIZE)
|
if (proposedOffset + size.Size <= DEFAULT_TRANSIENT_PAGE_SIZE)
|
||||||
{
|
{
|
||||||
foundPageIndex = i;
|
foundPageIndex = i;
|
||||||
alignedOffset = proposedOffset;
|
alignedOffset = proposedOffset;
|
||||||
@@ -246,12 +242,16 @@ public partial class ResourceManager
|
|||||||
Offset = alignedOffset,
|
Offset = alignedOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
page.offset = alignedOffset + size.Size;
|
if (handle.IsValid)
|
||||||
|
{
|
||||||
|
page.offset = alignedOffset + size.Size;
|
||||||
|
_frameTransientResources.Add(handle.AsResource());
|
||||||
|
}
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EndFramePool(ulong gpuFrame)
|
private void EndFramePool(ulong completedFrame)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < _activePages.Count; i++)
|
for (var i = 0; i < _activePages.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -265,7 +265,7 @@ public partial class ResourceManager
|
|||||||
|
|
||||||
_activePages.Clear();
|
_activePages.Clear();
|
||||||
|
|
||||||
while (_retiringPages.TryPeek(out var retiringPage) && retiringPage.retireFrame <= gpuFrame)
|
while (_retiringPages.TryPeek(out var retiringPage) && retiringPage.retireFrame < completedFrame)
|
||||||
{
|
{
|
||||||
_retiringPages.Dequeue();
|
_retiringPages.Dequeue();
|
||||||
|
|
||||||
@@ -274,16 +274,21 @@ public partial class ResourceManager
|
|||||||
_freePages.Enqueue(retiringPage.page);
|
_freePages.Enqueue(retiringPage.page);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < _oversizedTransientResources.Count; i++)
|
for (var i = 0; i < _frameTransientResources.Count; i++)
|
||||||
{
|
{
|
||||||
_resourceDatabase.ReleaseResource(_oversizedTransientResources[i]);
|
_resourceDatabase.ReleaseResource(_frameTransientResources[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
_oversizedTransientResources.Clear();
|
_frameTransientResources.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposePool()
|
private void DisposePool()
|
||||||
{
|
{
|
||||||
|
foreach (var resource in _frameTransientResources)
|
||||||
|
{
|
||||||
|
_resourceDatabase.ReleaseResourceImmediately(resource);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var page in _activePages)
|
foreach (var page in _activePages)
|
||||||
{
|
{
|
||||||
_resourceDatabase.ReleaseResourceImmediately(page.heap);
|
_resourceDatabase.ReleaseResourceImmediately(page.heap);
|
||||||
@@ -299,13 +304,9 @@ public partial class ResourceManager
|
|||||||
_resourceDatabase.ReleaseResourceImmediately(page.page.heap);
|
_resourceDatabase.ReleaseResourceImmediately(page.page.heap);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var resource in _oversizedTransientResources)
|
|
||||||
{
|
|
||||||
_resourceDatabase.ReleaseResourceImmediately(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
_activePages.Dispose();
|
_activePages.Dispose();
|
||||||
//_retiringPages.Dispose();
|
_freePages.Dispose();
|
||||||
_oversizedTransientResources.Dispose();
|
_retiringPages.Dispose();
|
||||||
|
_frameTransientResources.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
|
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
|
||||||
|
|
||||||
_materialPalettes = new MaterialPaletteStore();
|
_materialPalettes = new MaterialPaletteStore();
|
||||||
|
|
||||||
InitializePool();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~ResourceManager()
|
~ResourceManager()
|
||||||
@@ -65,9 +63,6 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
internal void EndFrame(ulong completedFrame)
|
internal void EndFrame(ulong completedFrame)
|
||||||
{
|
{
|
||||||
Debug.Assert(!_disposed);
|
Debug.Assert(!_disposed);
|
||||||
|
|
||||||
//_submittedFrame = submittedFrame;
|
|
||||||
|
|
||||||
EndFramePool(completedFrame);
|
EndFramePool(completedFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
47
src/Runtime/Ghost.Graphics/ResourceUploadBatch.cs
Normal file
47
src/Runtime/Ghost.Graphics/ResourceUploadBatch.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics;
|
||||||
|
|
||||||
|
public class ResourceUploadBatch
|
||||||
|
{
|
||||||
|
private readonly IRenderDevice _device;
|
||||||
|
|
||||||
|
private readonly ICommandAllocator _commandAllocator;
|
||||||
|
private readonly ICommandBuffer _commandBuffer;
|
||||||
|
|
||||||
|
internal ResourceUploadBatch(IGraphicsEngine engine)
|
||||||
|
{
|
||||||
|
_device = engine.Device;
|
||||||
|
|
||||||
|
_commandAllocator = engine.CreateCommandAllocator(CommandBufferType.Copy);
|
||||||
|
_commandBuffer = engine.CreateCommandBuffer(CommandBufferType.Copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Begin()
|
||||||
|
{
|
||||||
|
_commandBuffer.Begin(_commandAllocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result End()
|
||||||
|
{
|
||||||
|
var r = _commandBuffer.End();
|
||||||
|
if (r.IsFailure)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
_device.GraphicsQueue.Submit(_commandBuffer);
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitIdle()
|
||||||
|
{
|
||||||
|
_device.GraphicsQueue.WaitIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WaitAsync()
|
||||||
|
{
|
||||||
|
return _device.GraphicsQueue.WaitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -174,9 +174,9 @@ public static unsafe class MeshletUtility
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group, AllocationHandle handle)
|
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
|
||||||
{
|
{
|
||||||
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, handle);
|
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, Allocator.FreeList);
|
||||||
for (var j = 0; j < group.Count; j++)
|
for (var j = 0; j < group.Count; j++)
|
||||||
{
|
{
|
||||||
boundsList[j] = (clusters[group[j]].bounds);
|
boundsList[j] = (clusters[group[j]].bounds);
|
||||||
@@ -204,13 +204,13 @@ public static unsafe class MeshletUtility
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UnsafeList<Cluster> Clusterize(ref readonly ClodConfig config, ref readonly ClodMesh mesh, uint* indices, nuint indexCount, AllocationHandle handle)
|
private static UnsafeList<Cluster> Clusterize(ref readonly ClodConfig config, ref readonly ClodMesh mesh, uint* indices, nuint indexCount)
|
||||||
{
|
{
|
||||||
var maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles);
|
var maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles);
|
||||||
|
|
||||||
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, handle);
|
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, Allocator.FreeList);
|
||||||
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, handle);
|
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, Allocator.FreeList);
|
||||||
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, handle);
|
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, Allocator.FreeList);
|
||||||
|
|
||||||
var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
|
var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
|
||||||
var pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr();
|
var pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr();
|
||||||
@@ -238,7 +238,7 @@ public static unsafe class MeshletUtility
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var clusters = new UnsafeList<Cluster>((int)meshletCount, handle);
|
var clusters = new UnsafeList<Cluster>((int)meshletCount, Allocator.FreeList);
|
||||||
|
|
||||||
for (nuint i = 0; i < meshletCount; i++)
|
for (nuint i = 0; i < meshletCount; i++)
|
||||||
{
|
{
|
||||||
@@ -257,9 +257,9 @@ public static unsafe class MeshletUtility
|
|||||||
var cluster = new Cluster
|
var cluster = new Cluster
|
||||||
{
|
{
|
||||||
vertices = meshlet.vertex_count,
|
vertices = meshlet.vertex_count,
|
||||||
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), handle),
|
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), Allocator.FreeList),
|
||||||
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, handle),
|
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, Allocator.FreeList),
|
||||||
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), handle),
|
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), Allocator.FreeList),
|
||||||
group = -1,
|
group = -1,
|
||||||
refined = -1
|
refined = -1
|
||||||
};
|
};
|
||||||
@@ -326,12 +326,12 @@ public static unsafe class MeshletUtility
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UnsafeList<UnsafeList<int>> Partition(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeArray<uint> remap, AllocationHandle handle)
|
private static UnsafeList<UnsafeList<int>> Partition(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeArray<uint> remap)
|
||||||
{
|
{
|
||||||
if (pending.Count <= (int)config.partitionSize)
|
if (pending.Count <= (int)config.partitionSize)
|
||||||
{
|
{
|
||||||
var single = new UnsafeList<UnsafeList<int>>(1, handle);
|
var single = new UnsafeList<UnsafeList<int>>(1, Allocator.FreeList);
|
||||||
var pendingcpy = new UnsafeList<int>(pending.Count, handle);
|
var pendingcpy = new UnsafeList<int>(pending.Count, Allocator.FreeList);
|
||||||
|
|
||||||
pendingcpy.AddRange(pending.AsSpan());
|
pendingcpy.AddRange(pending.AsSpan());
|
||||||
single.Add(pendingcpy);
|
single.Add(pendingcpy);
|
||||||
@@ -345,8 +345,8 @@ public static unsafe class MeshletUtility
|
|||||||
totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
|
totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, handle);
|
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, Allocator.FreeList);
|
||||||
using var clusterCounts = new UnsafeList<uint>(pending.Count, handle);
|
using var clusterCounts = new UnsafeList<uint>(pending.Count, Allocator.FreeList);
|
||||||
|
|
||||||
nuint offset = 0;
|
nuint offset = 0;
|
||||||
for (var i = 0; i < pending.Count; i++)
|
for (var i = 0; i < pending.Count; i++)
|
||||||
@@ -361,7 +361,7 @@ public static unsafe class MeshletUtility
|
|||||||
offset += (nuint)cluster.indices.Count;
|
offset += (nuint)cluster.indices.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var clusterPart = new UnsafeArray<uint>(pending.Count, handle);
|
using var clusterPart = new UnsafeArray<uint>(pending.Count, Allocator.FreeList);
|
||||||
|
|
||||||
var partitionCount = MeshOptApi.PartitionClusters(
|
var partitionCount = MeshOptApi.PartitionClusters(
|
||||||
(uint*)clusterPart.GetUnsafePtr(),
|
(uint*)clusterPart.GetUnsafePtr(),
|
||||||
@@ -375,10 +375,10 @@ public static unsafe class MeshletUtility
|
|||||||
config.partitionSize
|
config.partitionSize
|
||||||
);
|
);
|
||||||
|
|
||||||
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, handle);
|
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, Allocator.FreeList);
|
||||||
for (nuint i = 0; i < partitionCount; i++)
|
for (nuint i = 0; i < partitionCount; i++)
|
||||||
{
|
{
|
||||||
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), handle));
|
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), Allocator.FreeList));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < pending.Count; i++)
|
for (var i = 0; i < pending.Count; i++)
|
||||||
@@ -389,9 +389,9 @@ public static unsafe class MeshletUtility
|
|||||||
return partitions;
|
return partitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback, AllocationHandle handle)
|
private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback)
|
||||||
{
|
{
|
||||||
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, handle);
|
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, Allocator.FreeList);
|
||||||
|
|
||||||
for (var i = 0; i < group.Count; i++)
|
for (var i = 0; i < group.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -425,10 +425,10 @@ public static unsafe class MeshletUtility
|
|||||||
public uint id;
|
public uint id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SimplifyFallback(ref UnsafeArray<uint> lod, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint target_count, float* error, AllocationHandle handle)
|
private static void SimplifyFallback(ref UnsafeArray<uint> lod, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint target_count, float* error)
|
||||||
{
|
{
|
||||||
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, handle);
|
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, Allocator.FreeList);
|
||||||
using var subset_locks = new UnsafeArray<byte>(indices.Count, handle);
|
using var subset_locks = new UnsafeArray<byte>(indices.Count, Allocator.FreeList);
|
||||||
|
|
||||||
lod.Resize(indices.Count);
|
lod.Resize(indices.Count);
|
||||||
|
|
||||||
@@ -462,9 +462,9 @@ public static unsafe class MeshletUtility
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UnsafeArray<uint> Simplify(ref readonly ClodConfig config, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint targetCount, float* error, AllocationHandle handle)
|
public static UnsafeArray<uint> Simplify(ref readonly ClodConfig config, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint targetCount, float* error)
|
||||||
{
|
{
|
||||||
var lod = new UnsafeArray<uint>(indices.Count, handle);
|
var lod = new UnsafeArray<uint>(indices.Count, Allocator.FreeList);
|
||||||
|
|
||||||
if (targetCount >= (nuint)indices.Count)
|
if (targetCount >= (nuint)indices.Count)
|
||||||
{
|
{
|
||||||
@@ -529,7 +529,7 @@ public static unsafe class MeshletUtility
|
|||||||
|
|
||||||
if ((nuint)lod.Length > targetCount && config.simplifyFallbackSloppy)
|
if ((nuint)lod.Length > targetCount && config.simplifyFallbackSloppy)
|
||||||
{
|
{
|
||||||
SimplifyFallback(ref lod, in mesh, indices, locks, targetCount, error, handle);
|
SimplifyFallback(ref lod, in mesh, indices, locks, targetCount, error);
|
||||||
*error *= config.simplifyErrorFactorSloppy;
|
*error *= config.simplifyErrorFactorSloppy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,13 +577,8 @@ public static unsafe class MeshletUtility
|
|||||||
{
|
{
|
||||||
Debug.Assert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
|
Debug.Assert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
|
||||||
|
|
||||||
using var pool = new MemoryPool<VirtualArena, VirtualArena.CreationOptions>(new VirtualArena.CreationOptions
|
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, Allocator.FreeList, AllocationOption.Clear); ;
|
||||||
{
|
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, Allocator.FreeList);
|
||||||
reserveCapacity = 256 * 1024 * 1024
|
|
||||||
});
|
|
||||||
|
|
||||||
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, pool.AllocationHandle, AllocationOption.Clear); ;
|
|
||||||
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, pool.AllocationHandle);
|
|
||||||
|
|
||||||
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
||||||
|
|
||||||
@@ -606,14 +601,14 @@ public static unsafe class MeshletUtility
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using var clusters = Clusterize(in config, in mesh, mesh.indices, mesh.indexCount, pool.AllocationHandle);
|
using var clusters = Clusterize(in config, in mesh, mesh.indices, mesh.indexCount);
|
||||||
|
|
||||||
for (var i = 0; i < clusters.Count; i++)
|
for (var i = 0; i < clusters.Count; i++)
|
||||||
{
|
{
|
||||||
clusters[i].bounds = ComputeBounds(in mesh, clusters[i].indices, 0.0f);
|
clusters[i].bounds = ComputeBounds(in mesh, clusters[i].indices, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
using var pending = new UnsafeList<int>(clusters.Count, pool.AllocationHandle);
|
using var pending = new UnsafeList<int>(clusters.Count, Allocator.FreeList);
|
||||||
for (var i = 0; i < clusters.Count; i++)
|
for (var i = 0; i < clusters.Count; i++)
|
||||||
{
|
{
|
||||||
pending.Add(i);
|
pending.Add(i);
|
||||||
@@ -623,14 +618,14 @@ public static unsafe class MeshletUtility
|
|||||||
|
|
||||||
while (pending.Count > 1)
|
while (pending.Count > 1)
|
||||||
{
|
{
|
||||||
using var groups = Partition(in config, in mesh, clusters, pending, remap, pool.AllocationHandle);
|
using var groups = Partition(in config, in mesh, clusters, pending, remap);
|
||||||
pending.Clear();
|
pending.Clear();
|
||||||
|
|
||||||
LockBoundary(locks, groups, clusters, remap, mesh.vertexLock);
|
LockBoundary(locks, groups, clusters, remap, mesh.vertexLock);
|
||||||
|
|
||||||
for (var i = 0; i < groups.Count; i++)
|
for (var i = 0; i < groups.Count; i++)
|
||||||
{
|
{
|
||||||
using var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, pool.AllocationHandle);
|
using var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, Allocator.FreeList);
|
||||||
for (var j = 0; j < groups[i].Count; j++)
|
for (var j = 0; j < groups[i].Count; j++)
|
||||||
{
|
{
|
||||||
var clusterIndices = clusters[groups[i][j]].indices;
|
var clusterIndices = clusters[groups[i][j]].indices;
|
||||||
@@ -638,28 +633,28 @@ public static unsafe class MeshletUtility
|
|||||||
}
|
}
|
||||||
|
|
||||||
var targetSize = (nuint)(merged.Count / 3 * config.simplifyRatio * 3.0f);
|
var targetSize = (nuint)(merged.Count / 3 * config.simplifyRatio * 3.0f);
|
||||||
var bounds = MergeBounds(clusters, groups[i], pool.AllocationHandle);
|
var bounds = MergeBounds(clusters, groups[i]);
|
||||||
|
|
||||||
var error = 0.0f;
|
var error = 0.0f;
|
||||||
using var simplified = Simplify(in config, in mesh, merged.AsReadOnly(), locks.AsReadOnly(), targetSize, &error, pool.AllocationHandle);
|
using var simplified = Simplify(in config, in mesh, merged.AsReadOnly(), locks.AsReadOnly(), targetSize, &error);
|
||||||
|
|
||||||
if ((nuint)simplified.Length > (nuint)(merged.Count * config.simplifyThreshold))
|
if ((nuint)simplified.Length > (nuint)(merged.Count * config.simplifyThreshold))
|
||||||
{
|
{
|
||||||
bounds.error = float.MaxValue;
|
bounds.error = float.MaxValue;
|
||||||
OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
|
OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive;
|
bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive;
|
||||||
|
|
||||||
var refined = OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
|
var refined = OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
|
||||||
|
|
||||||
for (var j = 0; j < groups[i].Count; j++)
|
for (var j = 0; j < groups[i].Count; j++)
|
||||||
{
|
{
|
||||||
clusters[groups[i][j]].Dispose();
|
clusters[groups[i][j]].Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
using var split = Clusterize(in config, in mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Length, pool.AllocationHandle);
|
using var split = Clusterize(in config, in mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Length);
|
||||||
for (var j = 0; j < split.Count; j++)
|
for (var j = 0; j < split.Count; j++)
|
||||||
{
|
{
|
||||||
split[j].refined = refined;
|
split[j].refined = refined;
|
||||||
@@ -681,7 +676,7 @@ public static unsafe class MeshletUtility
|
|||||||
{
|
{
|
||||||
var bounds = clusters[pending[0]].bounds;
|
var bounds = clusters[pending[0]].bounds;
|
||||||
bounds.error = float.MaxValue;
|
bounds.error = float.MaxValue;
|
||||||
OutputGroup(in config, in mesh, clusters, pending, bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
|
OutputGroup(in config, in mesh, clusters, pending, bounds, depth, outputContext, outputCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalClusterCount = (nuint)clusters.Count;
|
var finalClusterCount = (nuint)clusters.Count;
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ shader "MyShader/Standard"
|
|||||||
float b = float((hash & 0x0000FFu)) / 255.0;
|
float b = float((hash & 0x0000FFu)) / 255.0;
|
||||||
|
|
||||||
return float4(r, g, b, 1.0);
|
return float4(r, g, b, 1.0);
|
||||||
// return float4(input.normal, 1.0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
{
|
{
|
||||||
private class MeshletDebugPassData
|
private class MeshletDebugPassData
|
||||||
{
|
{
|
||||||
public Identifier<RGTexture> depth;
|
|
||||||
public Identifier<RGTexture> backbuffer;
|
|
||||||
public RenderList renderList;
|
public RenderList renderList;
|
||||||
public Handle<Material> material;
|
public Handle<Material> material;
|
||||||
public uint globalIndex;
|
public uint globalIndex;
|
||||||
@@ -72,7 +70,6 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
renderSystem.GraphicsEngine.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
renderSystem.GraphicsEngine.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static float3 IntersectFrustumPlanes(float4 p0, float4 p1, float4 p2)
|
private static float3 IntersectFrustumPlanes(float4 p0, float4 p1, float4 p2)
|
||||||
{
|
{
|
||||||
var n0 = p0.xyz;
|
var n0 = p0.xyz;
|
||||||
@@ -139,10 +136,6 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<GPUBuffer> instanceBufferHandle = default;
|
|
||||||
Handle<GPUBuffer> viewBufferHandle = default;
|
|
||||||
Handle<GPUBuffer> frameBufferHandle = default;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rtResult = _renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rt.AsResource());
|
var rtResult = _renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rt.AsResource());
|
||||||
@@ -226,7 +219,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
HeapType = HeapType.Upload, // Upload directly for simplicity in testing
|
HeapType = HeapType.Upload, // Upload directly for simplicity in testing
|
||||||
};
|
};
|
||||||
|
|
||||||
instanceBufferHandle = resourceManager.CreateTransientBuffer(in instanceBufferDesc, "Instance Buffer");
|
var instanceBufferHandle = resourceManager.CreateTransientBuffer(in instanceBufferDesc, "Instance Buffer");
|
||||||
var instanceBufferResource = instanceBufferHandle.AsResource();
|
var instanceBufferResource = instanceBufferHandle.AsResource();
|
||||||
|
|
||||||
var instanceDataArray = new InstanceData[instanceCount];
|
var instanceDataArray = new InstanceData[instanceCount];
|
||||||
@@ -266,7 +259,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
HeapType = HeapType.Upload,
|
HeapType = HeapType.Upload,
|
||||||
};
|
};
|
||||||
|
|
||||||
viewBufferHandle = resourceManager.CreateTransientBuffer(in viewBufferDesc, "View Buffer");
|
var viewBufferHandle = resourceManager.CreateTransientBuffer(in viewBufferDesc, "View Buffer");
|
||||||
var viewBufferResource = viewBufferHandle.AsResource();
|
var viewBufferResource = viewBufferHandle.AsResource();
|
||||||
|
|
||||||
var viewData = new ViewData
|
var viewData = new ViewData
|
||||||
@@ -294,7 +287,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
HeapType = HeapType.Upload,
|
HeapType = HeapType.Upload,
|
||||||
};
|
};
|
||||||
|
|
||||||
frameBufferHandle = resourceManager.CreateTransientBuffer(in frameBufferDesc, "Frame Buffer");
|
var frameBufferHandle = resourceManager.CreateTransientBuffer(in frameBufferDesc, "Frame Buffer");
|
||||||
var frameBufferResource = frameBufferHandle.AsResource();
|
var frameBufferResource = frameBufferHandle.AsResource();
|
||||||
|
|
||||||
var frameData = new FrameData
|
var frameData = new FrameData
|
||||||
@@ -329,10 +322,6 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_renderSystem.GraphicsEngine.ResourceDatabase.ReleaseResource(instanceBufferHandle.AsResource());
|
|
||||||
_renderSystem.GraphicsEngine.ResourceDatabase.ReleaseResource(viewBufferHandle.AsResource());
|
|
||||||
_renderSystem.GraphicsEngine.ResourceDatabase.ReleaseResource(frameBufferHandle.AsResource());
|
|
||||||
|
|
||||||
if (request.swapChainIndex >= 0)
|
if (request.swapChainIndex >= 0)
|
||||||
{
|
{
|
||||||
_renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
|
_renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
|
||||||
@@ -347,8 +336,6 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|||||||
{
|
{
|
||||||
var depth = builder.CreateTexture(RGTextureDesc.RelativeDepth(1.0f), "Depth Texture");
|
var depth = builder.CreateTexture(RGTextureDesc.RelativeDepth(1.0f), "Depth Texture");
|
||||||
|
|
||||||
passData.depth = depth;
|
|
||||||
passData.backbuffer = backbuffer;
|
|
||||||
passData.renderList = renderList;
|
passData.renderList = renderList;
|
||||||
passData.globalIndex = globalIndex;
|
passData.globalIndex = globalIndex;
|
||||||
passData.viewIndex = viewIndex;
|
passData.viewIndex = viewIndex;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ internal static class MeshUtility
|
|||||||
return Result.Failure("Unsupported file format. Only .obj and .fbx are supported.");
|
return Result.Failure("Unsupported file format. Only .obj and .fbx are supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var error = new ufbx_error();
|
||||||
var load_Opts = new ufbx_load_opts
|
var load_Opts = new ufbx_load_opts
|
||||||
{
|
{
|
||||||
target_axes = ufbx_coordinate_axes.left_handed_y_up,
|
target_axes = ufbx_coordinate_axes.left_handed_y_up,
|
||||||
@@ -48,15 +49,8 @@ internal static class MeshUtility
|
|||||||
handedness_conversion_axis = ufbx_mirror_axis.UFBX_MIRROR_AXIS_X,
|
handedness_conversion_axis = ufbx_mirror_axis.UFBX_MIRROR_AXIS_X,
|
||||||
space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY,
|
space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY,
|
||||||
};
|
};
|
||||||
var error = new ufbx_error();
|
|
||||||
|
|
||||||
using var pool = new MemoryPool<VirtualStack, VirtualStack.CreationOpts>(new VirtualStack.CreationOpts
|
using var str = new UnsafeArray<byte>(Encoding.UTF8.GetByteCount(filePath) + 1, Allocator.FreeList);
|
||||||
{
|
|
||||||
reserveCapacity = 256 * 1024 * 1024 // 256 MB should be enough for most models, adjust as needed. Note that this use virtual memory and does not actually consume physical memory until allocations are made.
|
|
||||||
});
|
|
||||||
|
|
||||||
using var scope0 = pool.Allocator.CreateScope(pool.AllocationHandle);
|
|
||||||
using var str = new UnsafeArray<byte>(Encoding.UTF8.GetByteCount(filePath) + 1, scope0.AllocationHandle);
|
|
||||||
var count = Encoding.UTF8.GetBytes(filePath, str.AsSpan());
|
var count = Encoding.UTF8.GetBytes(filePath, str.AsSpan());
|
||||||
str[count] = 0;
|
str[count] = 0;
|
||||||
|
|
||||||
@@ -66,7 +60,7 @@ internal static class MeshUtility
|
|||||||
return Result.Failure(error.description.ToString());
|
return Result.Failure(error.description.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
using var flatVertices = new UnsafeList<Vertex>(1024, scope0.AllocationHandle);
|
using var flatVertices = new UnsafeList<Vertex>(1024, Allocator.FreeList);
|
||||||
|
|
||||||
var needComputeNormals = false;
|
var needComputeNormals = false;
|
||||||
|
|
||||||
@@ -78,8 +72,6 @@ internal static class MeshUtility
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var scope1 = pool.Allocator.CreateScope(pool.AllocationHandle);
|
|
||||||
|
|
||||||
if (node->mesh != null)
|
if (node->mesh != null)
|
||||||
{
|
{
|
||||||
var pMesh = node->mesh;
|
var pMesh = node->mesh;
|
||||||
@@ -90,7 +82,7 @@ internal static class MeshUtility
|
|||||||
|
|
||||||
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
|
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
|
||||||
|
|
||||||
var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, scope1.AllocationHandle);
|
using var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, Allocator.FreeList);
|
||||||
|
|
||||||
for (var j = 0u; j < pMesh->num_faces; j++)
|
for (var j = 0u; j < pMesh->num_faces; j++)
|
||||||
{
|
{
|
||||||
@@ -141,8 +133,8 @@ internal static class MeshUtility
|
|||||||
|
|
||||||
var numIndices = (uint)flatVertices.Count;
|
var numIndices = (uint)flatVertices.Count;
|
||||||
|
|
||||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, scope0.AllocationHandle);
|
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, Allocator.FreeList);
|
||||||
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, scope0.AllocationHandle);
|
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, Allocator.FreeList);
|
||||||
|
|
||||||
var stream = new ufbx_vertex_stream
|
var stream = new ufbx_vertex_stream
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,14 +35,7 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
Panel.SizeChanged += SwapChainPanel_SizeChanged;
|
Panel.SizeChanged += SwapChainPanel_SizeChanged;
|
||||||
Panel.CompositionScaleChanged += SwapChainPanel_CompositionScaleChanged;
|
Panel.CompositionScaleChanged += SwapChainPanel_CompositionScaleChanged;
|
||||||
|
|
||||||
var opts = new AllocationManagerInitOpts
|
AllocationManager.Initialize(AllocationManagerInitOpts.Default);
|
||||||
{
|
|
||||||
ArenaCapacity = 1024 * 1024 * 1024, // 1GB
|
|
||||||
StackCapacity = 1024 * 1024 * 32, // 32MB
|
|
||||||
FreeListConcurrencyLevel = Environment.ProcessorCount,
|
|
||||||
};
|
|
||||||
|
|
||||||
AllocationManager.Initialize(opts);
|
|
||||||
|
|
||||||
//_jobScheduler = new JobScheduler(Environment.ProcessorCount - 1);
|
//_jobScheduler = new JobScheduler(Environment.ProcessorCount - 1);
|
||||||
}
|
}
|
||||||
@@ -90,7 +83,7 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
// Create Camera Entity
|
// Create Camera Entity
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
var camSet = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Camera>.Value, ComponentTypeID<LocalToWorld>.Value);
|
using var camSet = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Camera>.Value, ComponentTypeID<LocalToWorld>.Value);
|
||||||
var cameraEntity = _world.EntityManager.CreateEntity(camSet);
|
var cameraEntity = _world.EntityManager.CreateEntity(camSet);
|
||||||
|
|
||||||
_world.EntityManager.SetComponent(cameraEntity, new Camera
|
_world.EntityManager.SetComponent(cameraEntity, new Camera
|
||||||
@@ -126,12 +119,10 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
|
|
||||||
directCmd.End().ThrowIfFailed();
|
directCmd.End().ThrowIfFailed();
|
||||||
|
|
||||||
// FIX: This will bump the complete value of the queue and cause the render thread render the first frame twice, which is not expected. We should have a better way to handle this.
|
// Maybe async upload support in the future?
|
||||||
// Maybe a async upload support in the future?
|
|
||||||
_renderSystem.GraphicsEngine.Device.GraphicsQueue.Submit(directCmd);
|
_renderSystem.GraphicsEngine.Device.GraphicsQueue.Submit(directCmd);
|
||||||
_renderSystem.GraphicsEngine.Device.GraphicsQueue.WaitIdle();
|
|
||||||
|
|
||||||
var meshSet = new ComponentSet(scope.AllocationHandle, ComponentTypeID<MeshInstance>.Value, ComponentTypeID<LocalToWorld>.Value);
|
using var meshSet = new ComponentSet(scope.AllocationHandle, ComponentTypeID<MeshInstance>.Value, ComponentTypeID<LocalToWorld>.Value);
|
||||||
var meshEntity = _world.EntityManager.CreateEntity(meshSet);
|
var meshEntity = _world.EntityManager.CreateEntity(meshSet);
|
||||||
_world.EntityManager.SetComponent(meshEntity, new MeshInstance
|
_world.EntityManager.SetComponent(meshEntity, new MeshInstance
|
||||||
{
|
{
|
||||||
@@ -208,7 +199,6 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
|
|
||||||
if (_renderSystem.TryAcquireCPUFrame())
|
if (_renderSystem.TryAcquireCPUFrame())
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"CPU: Frame started.");
|
|
||||||
_world.SystemManager.UpdateAll(default);
|
_world.SystemManager.UpdateAll(default);
|
||||||
_renderSystem.SignalCPUReady();
|
_renderSystem.SignalCPUReady();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,20 +117,25 @@ internal sealed unsafe class NvttBindingTest : ITest
|
|||||||
pOutOpts->SetSrgbFlag(true);
|
pOutOpts->SetSrgbFlag(true);
|
||||||
pOutOpts->SetContainer(NvttContainer.NVTT_Container_DDS10);
|
pOutOpts->SetContainer(NvttContainer.NVTT_Container_DDS10);
|
||||||
|
|
||||||
pOutOpts->SetOutputHandler(
|
using var handler = new NvttOutputHandler
|
||||||
(size, w, h, d, face, mip) =>
|
{
|
||||||
|
beginImageHandler = (size, w, h, d, face, mip) =>
|
||||||
{
|
{
|
||||||
imagesBegun++;
|
imagesBegun++;
|
||||||
},
|
},
|
||||||
(ptr, len) =>
|
outputHandler = (ptr, len) =>
|
||||||
{
|
{
|
||||||
totalBytesReceived += len;
|
totalBytesReceived += len;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
null
|
endImageHandler = null,
|
||||||
);
|
errorHandler = (err) =>
|
||||||
pOutOpts->SetErrorHandler(err =>
|
{
|
||||||
Console.WriteLine($"/n [NVTT Error] {err}"));
|
Console.WriteLine($"/n [NVTT Error] {err}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pOutOpts->SetOutputHandler(handler);
|
||||||
|
|
||||||
var pCtx = NvttContext.Create();
|
var pCtx = NvttContext.Create();
|
||||||
pCtx->SetCudaAcceleration(false); // CPU only for the test
|
pCtx->SetCudaAcceleration(false); // CPU only for the test
|
||||||
@@ -214,7 +219,7 @@ internal sealed unsafe class NvttBindingTest : ITest
|
|||||||
{
|
{
|
||||||
(*(int*)userData)++;
|
(*(int*)userData)++;
|
||||||
|
|
||||||
int i = 0;
|
var i = 0;
|
||||||
while (msg[i] != 0)
|
while (msg[i] != 0)
|
||||||
{
|
{
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
@@ -8,24 +8,39 @@ public delegate void EndImageDelegate();
|
|||||||
|
|
||||||
public delegate void ErrorDelegate(NvttError error);
|
public delegate void ErrorDelegate(NvttError error);
|
||||||
|
|
||||||
|
public class NvttOutputHandler : IDisposable
|
||||||
|
{
|
||||||
|
public BeginImageDelegate? beginImageHandler;
|
||||||
|
public OutputDelegate? outputHandler;
|
||||||
|
public EndImageDelegate? endImageHandler;
|
||||||
|
public ErrorDelegate? errorHandler;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
beginImageHandler = null;
|
||||||
|
outputHandler = null;
|
||||||
|
endImageHandler = null;
|
||||||
|
errorHandler = null;
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe partial struct NvttOutputOptions
|
public unsafe partial struct NvttOutputOptions
|
||||||
{
|
{
|
||||||
public void SetOutputHandler(BeginImageDelegate? beginImageHandler, OutputDelegate? outputHandler, EndImageDelegate? endImageHandler)
|
public void SetOutputHandler(NvttOutputHandler handler)
|
||||||
{
|
{
|
||||||
var beginPtr = beginImageHandler == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(beginImageHandler);
|
var beginPtr = handler.beginImageHandler == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(handler.beginImageHandler);
|
||||||
var outputPtr = outputHandler == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(outputHandler);
|
var outputPtr = handler.outputHandler == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(handler.outputHandler);
|
||||||
var endPtr = endImageHandler == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(endImageHandler);
|
var endPtr = handler.endImageHandler == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(handler.endImageHandler);
|
||||||
|
|
||||||
Api.nvttSetOutputOptionsOutputHandler(
|
Api.nvttSetOutputOptionsOutputHandler(
|
||||||
(NvttOutputOptions*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this),
|
(NvttOutputOptions*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this),
|
||||||
(delegate* unmanaged[Cdecl]<int, int, int, int, int, int, void>)beginPtr,
|
(delegate* unmanaged[Cdecl]<int, int, int, int, int, int, void>)beginPtr,
|
||||||
(delegate* unmanaged[Cdecl]<void*, int, NvttBoolean>)outputPtr,
|
(delegate* unmanaged[Cdecl]<void*, int, NvttBoolean>)outputPtr,
|
||||||
endPtr);
|
endPtr);
|
||||||
}
|
|
||||||
|
|
||||||
public void SetErrorHandler(ErrorDelegate? errorHandler)
|
var errorPtr = handler.errorHandler == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(handler.errorHandler);
|
||||||
{
|
|
||||||
var errorPtr = errorHandler == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(errorHandler);
|
|
||||||
|
|
||||||
Api.nvttSetOutputOptionsErrorHandler(
|
Api.nvttSetOutputOptionsErrorHandler(
|
||||||
(NvttOutputOptions*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this),
|
(NvttOutputOptions*)System.Runtime.CompilerServices.Unsafe.AsPointer(ref this),
|
||||||
|
|||||||
Reference in New Issue
Block a user