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:
2026-04-05 17:54:23 +09:00
parent 92970f85ef
commit effd33b285
35 changed files with 472 additions and 494 deletions

View File

@@ -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>

View File

@@ -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);

View File

@@ -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();
} }
} }

View File

@@ -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();
} }
} }

View File

@@ -197,6 +197,7 @@ public unsafe partial struct EntityQuery
public readonly void Dispose() public readonly void Dispose()
{ {
_changedComponentIDs.Dispose();
_scope.Dispose(); _scope.Dispose();
} }
} }

View File

@@ -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();
} }
} }

View File

@@ -198,6 +198,7 @@ public unsafe partial struct EntityQuery
public readonly void Dispose() public readonly void Dispose()
{ {
_changedComponentIDs.Dispose();
_scope.Dispose(); _scope.Dispose();
} }
} }

View File

@@ -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>

View File

@@ -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;
@@ -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)

View File

@@ -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();
} }
} }

View File

@@ -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();

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();
} }

View File

@@ -79,8 +79,6 @@ 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);
@@ -88,11 +86,6 @@ public readonly unsafe ref struct RenderContext
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes); _cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
} }
finally
{
ResourceDatabase.ReleaseResource(uploadHandle.AsResource());
}
}
} }
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh) public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
@@ -280,8 +273,6 @@ 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)
@@ -296,9 +287,4 @@ public readonly unsafe ref struct RenderContext
_cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData); _cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
} }
} }
finally
{
ResourceDatabase.ReleaseResource(uploadHandle.AsResource());
}
}
} }

View File

@@ -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;
} }

View File

@@ -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);
} }

View File

@@ -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);
}
} }

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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);
} }
} }

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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;

View File

@@ -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,
}); });
if (handle.IsValid)
{
page.offset = alignedOffset + size.Size; 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,
}); });
if (handle.IsValid)
{
page.offset = alignedOffset + size.Size; 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();
} }
} }

View File

@@ -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);
} }

View 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();
}
}

View File

@@ -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;

View File

@@ -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);
} }
} }

View File

@@ -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;

View File

@@ -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
{ {

View File

@@ -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();
} }

View File

@@ -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++;

View File

@@ -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),