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

@@ -79,19 +79,12 @@ public readonly unsafe ref struct RenderContext
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);
}
finally
{
ResourceDatabase.ReleaseResource(uploadHandle.AsResource());
}
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
}
}
@@ -280,25 +273,18 @@ public readonly unsafe ref struct RenderContext
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,
slicePitch = slicePitch
};
pData = pData,
rowPitch = rowPitch,
slicePitch = slicePitch
};
_cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
}
}
finally
{
ResourceDatabase.ReleaseResource(uploadHandle.AsResource());
_cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
}
}
}

View File

@@ -65,7 +65,7 @@ public sealed class RenderGraph : IDisposable
);
_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);
_blackboard = new RenderGraphBlackboard();
@@ -190,12 +190,14 @@ public sealed class RenderGraph : IDisposable
_resources.ResolveTextureSizes(in viewState);
var graphHash = RenderGraphHasher.ComputeGraphHash(_passes, _resources);
var error = _compiler.Compile(in viewState, graphHash, _passes, _compiledPasses, _nativePasses, _compiledBarriers);
if (error != Error.None)
var result = _compiler.Compile(in viewState, graphHash, _passes, _compiledPasses, _nativePasses, _compiledBarriers);
if (result.IsFailure)
{
return error;
return result.Error;
}
_context.RelativeScale = result.Value;
_compiled = true;
return Error.None;
}

View File

@@ -11,97 +11,39 @@ internal enum BarrierFlags
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
{
public int resourceIndex;
public ResourceBarrierData currentState;
public int lastAccessPass = -1;
public ResourceBarrierData currentState;
public void Reset()
{
resourceIndex = -1;
currentState = default;
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
{
public int PassIndex;
public Identifier<RGResource> Resource;
public ResourceBarrierData TargetState;
public Identifier<RGResource> AliasingPredecessor; // Invalid if not aliasing
public BarrierFlags Flags;
public RenderGraphResourceType ResourceType;
public int passIndex;
public Identifier<RGResource> resource;
public ResourceBarrierData targetState;
public Identifier<RGResource> aliasingPredecessor; // Invalid if not aliasing
public BarrierFlags flags;
public RenderGraphResourceType resourceType;
public override readonly string ToString()
{
return AliasingPredecessor.IsValid
? $"[Pass {PassIndex}] Aliasing: {AliasingPredecessor.Value}->{Resource.Value} -> {TargetState.layout}"
: $"[Pass {PassIndex}] Transition: {Resource.Value} -> {TargetState.layout}";
return aliasingPredecessor.IsValid
? $"[Pass {passIndex}] Aliasing: {aliasingPredecessor.Value}->{resource.Value} -> {targetState.layout}"
: $"[Pass {passIndex}] Transition: {resource.Value} -> {targetState.layout}";
}
}
/// <summary>
/// Static class containing barrier compilation logic.
/// Compiles barriers at graph compilation time, storing only target states.
/// </summary>
internal static class RenderGraphBarriers
{
/// <summary>
/// Compiles all barriers needed for execution, storing only target states.
/// Barriers include aliasing barriers and implicit state transitions.
/// </summary>
public static void CompileBarriers(
List<RenderGraphPassBase> compiledPasses,
List<CompiledBarrier> compiledBarriers,
@@ -123,9 +65,6 @@ internal static class RenderGraphBarriers
}
}
/// <summary>
/// Inserts aliasing barriers when a placed heap is reused.
/// </summary>
private static void InsertAliasingBarriers(
RenderGraphPassBase pass,
int passIdx,
@@ -189,12 +128,12 @@ internal static class RenderGraphBarriers
var targetState = new ResourceBarrierData(BarrierLayout.Undefined, BarrierAccess.NoAccess, BarrierSync.None);
var barrier = new CompiledBarrier
{
PassIndex = passIdx,
Resource = id,
TargetState = targetState,
AliasingPredecessor = resourceBefore,
Flags = BarrierFlags.FirstUsage | BarrierFlags.Discard,
ResourceType = resource.type
passIndex = passIdx,
resource = id,
targetState = targetState,
aliasingPredecessor = resourceBefore,
flags = BarrierFlags.FirstUsage | BarrierFlags.Discard,
resourceType = resource.type
};
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(
RenderGraphPassBase pass,
int passIdx,
@@ -221,12 +156,12 @@ internal static class RenderGraphBarriers
var resource = resources.GetResource(id);
var barrier = new CompiledBarrier
{
PassIndex = passIdx,
Resource = id,
TargetState = targetState,
AliasingPredecessor = Identifier<RGResource>.Invalid,
Flags = BarrierFlags.None,
ResourceType = resource.type
passIndex = passIdx,
resource = id,
targetState = targetState,
aliasingPredecessor = Identifier<RGResource>.Invalid,
flags = BarrierFlags.None,
resourceType = resource.type
};
compiledBarriers.Add(barrier);
}

View File

@@ -4,10 +4,6 @@ using System.Diagnostics.CodeAnalysis;
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
{
// 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
{
public int index;
@@ -56,40 +49,24 @@ internal struct PlacedResourceData
public int lastUsePass;
}
/// <summary>
/// Manages compilation caching for render graphs.
/// Stores compiled results and allows cache hits when graph structure is unchanged.
/// </summary>
internal sealed class RenderGraphCompilationCache
{
private readonly CachedCompilation _cached = new();
private ulong _cachedHash;
private bool _hasCachedData;
// Statistics
public int CacheHits { get; private set; }
public int CacheMisses { get; private set; }
/// <summary>
/// Attempts to retrieve cached compilation results.
/// </summary>
public bool TryGetCached(ulong hash, [MaybeNullWhen(false)] out CachedCompilation result)
{
if (_hasCachedData && _cachedHash == hash)
{
result = _cached;
CacheHits++;
return true;
}
result = null;
CacheMisses++;
return false;
}
/// <summary>
/// Stores compilation results in the cache.
/// </summary>
public void Store(ulong hash, CachedCompilation data)
{
_cachedHash = hash;
@@ -112,9 +89,6 @@ internal sealed class RenderGraphCompilationCache
_cached.backingResources.AddRange(data.backingResources);
}
/// <summary>
/// Invalidates the cache, forcing recompilation on next Compile().
/// </summary>
public void Invalidate()
{
_hasCachedData = false;
@@ -131,14 +105,4 @@ internal sealed class RenderGraphCompilationCache
_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.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.RenderGraphModule;
@@ -9,7 +10,6 @@ namespace Ghost.Graphics.RenderGraphModule;
/// </summary>
internal sealed class RenderGraphCompiler
{
private readonly ResourceManager _resourceManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly IResourceAllocator _resourceAllocator;
private readonly RenderGraphResourceRegistry _resources;
@@ -20,7 +20,6 @@ internal sealed class RenderGraphCompiler
private Handle<GPUResource> _resourceHeap;
public RenderGraphCompiler(
ResourceManager resourceManager,
IResourceDatabase resourceDatabase,
IResourceAllocator resourceAllocator,
RenderGraphResourceRegistry resources,
@@ -28,7 +27,6 @@ internal sealed class RenderGraphCompiler
RenderGraphNativePassBuilder nativePassBuilder,
RenderGraphCompilationCache compilationCache)
{
_resourceManager = resourceManager;
_resourceDatabase = resourceDatabase;
_resourceAllocator = resourceAllocator;
_resources = resources;
@@ -41,7 +39,7 @@ internal sealed class RenderGraphCompiler
/// <summary>
/// Compiles the render graph by culling passes, allocating resources, and preparing barriers.
/// </summary>
public Error Compile(
public Result<float2, Error> Compile(
in ViewState viewState,
ulong graphHash,
List<RenderGraphPassBase> passes,
@@ -55,7 +53,8 @@ internal sealed class RenderGraphCompiler
if (_compilationCache.TryGetCached(graphHash, out var cached))
{
// 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
_resources.ResolveTextureSizes(in viewState);
@@ -69,14 +68,15 @@ internal sealed class RenderGraphCompiler
}
cached.viewState = viewState;
return float2.one;
}
else
{
// Perfect cache hit - restore everything
RestoreFromCache(cached, compiledPasses, passes, nativePasses, compiledBarriers);
return scale;
}
return Error.None;
}
// Fresh compilation needed
@@ -109,7 +109,7 @@ internal sealed class RenderGraphCompiler
_nativePassBuilder.BuildNativeRenderPasses(compiledPasses, nativePasses, compiledBarriers);
StoreInCache(graphHash, viewState, compiledPasses, passes, compiledBarriers);
return Error.None;
return float2.one;
}
private void MarkPassesWithSideEffects(List<RenderGraphPassBase> passes)

View File

@@ -10,13 +10,12 @@ public interface IRenderGraphContext
ResourceManager ResourceManager { get; }
IResourceDatabase ResourceDatabase { get; }
float2 RelativeScale { get; }
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
Handle<GPUTexture> GetActualTexture(Identifier<RGTexture> texture);
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();
}
@@ -53,7 +52,6 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
private readonly IShaderCompiler _shaderCompiler;
private readonly RenderGraphResourceRegistry _resources;
private uint _frameIndex;
private ICommandBuffer _commandBuffer;
private readonly TextureFormat[] _rtvFormats;
@@ -74,6 +72,11 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
public int ActiveMeshIndexCount => _activeMeshIndexCount;
public float2 RelativeScale
{
get; set;
}
internal RenderGraphContext(ResourceManager resourceManager, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, IShaderCompiler shaderCompiler, RenderGraphResourceRegistry resources)
{
_resourceManager = resourceManager;
@@ -88,9 +91,8 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
_dsvFormat = TextureFormat.Unknown;
}
internal void BeginNewFrame(uint frameIndex, ICommandBuffer commandBuffer)
internal void BeginNewFrame(ICommandBuffer commandBuffer)
{
_frameIndex = frameIndex;
_commandBuffer = commandBuffer;
}
@@ -125,38 +127,6 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
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)
{
_commandBuffer.SetViewport(desc);

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using System.Diagnostics;
using TerraFX.Interop.Windows;
@@ -12,8 +13,6 @@ internal sealed class RenderGraphExecutor
private readonly RenderGraphResourceRegistry _resources;
private readonly RenderGraphContext _context;
private uint _frameIndex;
public RenderGraphExecutor(
ResourceManager resourceManager,
IResourceDatabase resourceDatabase,
@@ -89,7 +88,7 @@ internal sealed class RenderGraphExecutor
var nativePassIndex = 0;
var logicalPassIndex = 0;
_context.BeginNewFrame(_frameIndex++, commandBuffer);
_context.BeginNewFrame(commandBuffer);
var pPassRTDescs = stackalloc PassRenderTargetDesc[8];
var pRtFormats = stackalloc TextureFormat[8];
@@ -218,10 +217,10 @@ internal sealed class RenderGraphExecutor
// 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.
while (barrierIndex < compiledBarriers.Count && compiledBarriers[barrierIndex].PassIndex == passIndex)
while (barrierIndex < compiledBarriers.Count && compiledBarriers[barrierIndex].passIndex == passIndex)
{
var compiledBarrier = compiledBarriers[barrierIndex++];
var resource = _resources.GetResource(compiledBarrier.Resource);
var resource = _resources.GetResource(compiledBarrier.resource);
var resourceHandle = resource.backingResource;
// Always query the before state from ResourceManager (single source of truth)
@@ -232,21 +231,21 @@ internal sealed class RenderGraphExecutor
}
var currentState = currentStateResult.Value;
var target = compiledBarrier.TargetState;
var target = compiledBarrier.targetState;
// Create barrier descriptor
BarrierDesc desc;
if (compiledBarrier.ResourceType == RenderGraphResourceType.Texture)
if (compiledBarrier.resourceType == RenderGraphResourceType.Texture)
{
desc = BarrierDesc.Texture(resourceHandle, target.sync, target.access, target.layout,
discard: compiledBarrier.Flags.HasFlag(BarrierFlags.Discard));
discard: compiledBarrier.flags.HasFlag(BarrierFlags.Discard));
}
else
{
desc = BarrierDesc.Buffer(resourceHandle, target.sync, target.access);
}
if (compiledBarrier.AliasingPredecessor.IsValid)
if (compiledBarrier.aliasingPredecessor.IsValid)
{
desc.IsAliasing = true;
}

View File

@@ -5,7 +5,7 @@ using System.IO.Hashing;
namespace Ghost.Graphics.RenderGraphModule;
internal static class RenderGraphHasher
internal static unsafe class RenderGraphHasher
{
/// <summary>
/// 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)
{
using var scope = AllocationManager.CreateStackScope();
var writer = new BufferWriter(2048, scope.AllocationHandle);
using var writer = new BufferWriter(2048, scope.AllocationHandle);
// Hash pass count
writer.Write(passes.Count);
@@ -29,14 +29,14 @@ internal static class RenderGraphHasher
writer.Write(pass.asyncCompute);
// Hash depth attachment
ComputeTextureHash(ref writer, pass.depthAccess.id, resources);
ComputeTextureHash(&writer, pass.depthAccess.id, resources);
writer.Write(pass.depthAccess.accessFlags);
writer.Write(pass.maxColorIndex);
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);
}
@@ -82,7 +82,7 @@ internal static class RenderGraphHasher
/// For imported textures, hashes the backing handle.
/// For transient textures, hashes the descriptor (respecting size mode).
/// </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)
{
@@ -92,39 +92,39 @@ internal static class RenderGraphHasher
var resource = resources.GetResource(texture.AsResource());
// Hash imported flag
writer.Write(resource.isImported);
writer->Write(resource.isImported);
// For imported textures, hash the backing heap handle
if (resource.isImported)
{
writer.Write(resource.backingResource.GetHashCode());
writer->Write(resource.backingResource.GetHashCode());
return;
}
var desc = resource.rgTextureDesc;
writer.Write(desc.format);
writer.Write(desc.sizeMode);
writer->Write(desc.format);
writer->Write(desc.sizeMode);
// Hash size specification based on mode
if (desc.sizeMode == RGTextureSizeMode.Absolute)
{
// Absolute mode: hash actual dimensions
writer.Write(desc.width);
writer.Write(desc.height);
writer->Write(desc.width);
writer->Write(desc.height);
}
else
{
// Relative mode: hash scale factors (NOT resolved dimensions)
writer.Write(desc.scaleX);
writer.Write(desc.scaleY);
writer->Write(desc.scaleX);
writer->Write(desc.scaleY);
}
// Hash other structural properties
writer.Write(desc.dimension);
writer.Write(desc.mipLevels);
writer.Write(desc.usage);
writer.Write(desc.clearAtFirstUse);
writer.Write(desc.discardAtLastUse);
writer->Write(desc.dimension);
writer->Write(desc.mipLevels);
writer->Write(desc.usage);
writer->Write(desc.clearAtFirstUse);
writer->Write(desc.discardAtLastUse);
}
}

View File

@@ -221,16 +221,16 @@ internal sealed class RenderGraphNativePassBuilder
// Check if any compiled barriers for passB affect render targets
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
if (renderTargets.Contains(compiledBarriers[i].Resource))
if (renderTargets.Contains(compiledBarriers[i].resource))
{
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
}

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.RenderGraphModule;
@@ -48,6 +49,14 @@ public struct ViewState : IEquatable<ViewState>
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)
{
return viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight

View File

@@ -106,7 +106,6 @@ public class RenderSystem : IDisposable
private uint _frameIndex;
private ulong _cpuFenceValue;
private ulong _submittedFenceValue;
private ulong _completedFenceValue;
private bool _isRunning;
private bool _disposed;
@@ -119,7 +118,6 @@ public class RenderSystem : IDisposable
public ulong CPUFenceValue => _cpuFenceValue;
public ulong SubmittedFenceValue => _submittedFenceValue;
public ulong CompletedFenceValue => _completedFenceValue;
public uint FrameIndex => _frameIndex;
public uint MaxFrameLatency => _config.FrameBufferCount;
@@ -260,20 +258,18 @@ public class RenderSystem : IDisposable
}
}
}
_completedFenceValue = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
if (_submittedFenceValue < _completedFenceValue)
{
_submittedFenceValue = _completedFenceValue;
}
var nextFenceValue = _submittedFenceValue + 1;
var completedFenceValue = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
if (_submittedFenceValue < completedFenceValue)
{
_submittedFenceValue = completedFenceValue;
}
// Begin rendering for this frame
frameResource.CommandAllocator.Reset();
_resourceManager.BeginFrame(nextFenceValue);
var r = _graphicsEngine.BeginFrame(nextFenceValue);
_resourceManager.BeginFrame(_submittedFenceValue);
var r = _graphicsEngine.BeginFrame(_submittedFenceValue);
if (r.IsFailure)
{
@@ -282,7 +278,7 @@ public class RenderSystem : IDisposable
}
// Start recording commands
Debug.WriteLine($"GPU: Frame started.");
// TODO: How can we support async compute and async copy?
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
ref var renderRequests = ref frameResource.RenderRequests;
@@ -319,15 +315,15 @@ public class RenderSystem : IDisposable
renderRequests.Clear();
}
_submittedFenceValue = nextFenceValue;
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(nextFenceValue);
_submittedFenceValue++;
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_submittedFenceValue);
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.
_resourceManager.EndFrame(_completedFenceValue);
r = _graphicsEngine.EndFrame(_completedFenceValue);
_resourceManager.EndFrame(completedFenceValue);
r = _graphicsEngine.EndFrame(completedFenceValue);
if (r.IsFailure)
{
@@ -387,7 +383,9 @@ public class RenderSystem : IDisposable
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)
{
@@ -460,9 +458,9 @@ public class RenderSystem : IDisposable
_renderPipeline.Dispose();
_resourceManager.Dispose();
_graphicsEngine.Dispose();
_swapChainManager.Dispose();
_graphicsEngine.Dispose();
_shutdownEvent.Dispose();

View File

@@ -8,7 +8,7 @@ namespace Ghost.Graphics;
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}")]
private struct Page
@@ -27,19 +27,11 @@ public partial class ResourceManager
public ulong retireFrame;
}
private UnsafeList<Page> _activePages;
private Queue<Page> _freePages = null!;
private Queue<RetiringPage> _retiringPages = null!;
private UnsafeList<Page> _activePages = new UnsafeList<Page>(4, Allocator.Persistent);
private UnsafeQueue<Page> _freePages = new UnsafeQueue<Page>(4, Allocator.Persistent);
private UnsafeQueue<RetiringPage> _retiringPages = new UnsafeQueue<RetiringPage>(4, Allocator.Persistent);
private UnsafeList<Handle<GPUResource>> _oversizedTransientResources;
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 UnsafeList<Handle<GPUResource>> _frameTransientResources = new UnsafeList<Handle<GPUResource>>(4, Allocator.Persistent);
private static bool IsHeapFlagsCompatible(HeapFlags pageHeapFlags, HeapFlags requiredHeapFlags)
{
@@ -76,7 +68,7 @@ public partial class ResourceManager
var allocationDesc = new AllocationDesc
{
Size = _DEFAULT_TRANSIENT_PAGE_SIZE,
Size = DEFAULT_TRANSIENT_PAGE_SIZE,
Alignment = 65536, // 64KB
HeapType = heapType,
HeapFlags = heapFlags,
@@ -104,12 +96,12 @@ public partial class ResourceManager
var isRTOrDS = desc.Usage.HasFlag(TextureUsage.DepthStencil) || desc.Usage.HasFlag(TextureUsage.RenderTarget);
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);
if (texHandle.IsValid)
{
_oversizedTransientResources.Add(texHandle.AsResource());
_frameTransientResources.Add(texHandle.AsResource());
}
return texHandle;
@@ -138,7 +130,7 @@ public partial class ResourceManager
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;
alignedOffset = proposedOffset;
@@ -168,7 +160,11 @@ public partial class ResourceManager
Offset = alignedOffset,
});
page.offset = alignedOffset + size.Size;
if (handle.IsValid)
{
page.offset = alignedOffset + size.Size;
_frameTransientResources.Add(handle.AsResource());
}
return handle;
}
@@ -176,12 +172,12 @@ public partial class ResourceManager
public Handle<GPUBuffer> CreateTransientBuffer(ref readonly BufferDesc desc, string? name = null)
{
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);
if (bufHandle.IsValid)
{
_oversizedTransientResources.Add(bufHandle.AsResource());
_frameTransientResources.Add(bufHandle.AsResource());
}
return bufHandle;
@@ -216,7 +212,7 @@ public partial class ResourceManager
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;
alignedOffset = proposedOffset;
@@ -246,12 +242,16 @@ public partial class ResourceManager
Offset = alignedOffset,
});
page.offset = alignedOffset + size.Size;
if (handle.IsValid)
{
page.offset = alignedOffset + size.Size;
_frameTransientResources.Add(handle.AsResource());
}
return handle;
}
private void EndFramePool(ulong gpuFrame)
private void EndFramePool(ulong completedFrame)
{
for (var i = 0; i < _activePages.Count; i++)
{
@@ -265,7 +265,7 @@ public partial class ResourceManager
_activePages.Clear();
while (_retiringPages.TryPeek(out var retiringPage) && retiringPage.retireFrame <= gpuFrame)
while (_retiringPages.TryPeek(out var retiringPage) && retiringPage.retireFrame < completedFrame)
{
_retiringPages.Dequeue();
@@ -274,16 +274,21 @@ public partial class ResourceManager
_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()
{
foreach (var resource in _frameTransientResources)
{
_resourceDatabase.ReleaseResourceImmediately(resource);
}
foreach (var page in _activePages)
{
_resourceDatabase.ReleaseResourceImmediately(page.heap);
@@ -299,13 +304,9 @@ public partial class ResourceManager
_resourceDatabase.ReleaseResourceImmediately(page.page.heap);
}
foreach (var resource in _oversizedTransientResources)
{
_resourceDatabase.ReleaseResourceImmediately(resource);
}
_activePages.Dispose();
//_retiringPages.Dispose();
_oversizedTransientResources.Dispose();
_freePages.Dispose();
_retiringPages.Dispose();
_frameTransientResources.Dispose();
}
}

View File

@@ -47,8 +47,6 @@ public sealed partial class ResourceManager : IDisposable
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
_materialPalettes = new MaterialPaletteStore();
InitializePool();
}
~ResourceManager()
@@ -65,9 +63,6 @@ public sealed partial class ResourceManager : IDisposable
internal void EndFrame(ulong completedFrame)
{
Debug.Assert(!_disposed);
//_submittedFrame = submittedFrame;
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++)
{
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);
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, handle);
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, handle);
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, handle);
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, Allocator.FreeList);
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, Allocator.FreeList);
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, Allocator.FreeList);
var pMeshlets = (meshopt_Meshlet*)meshlets.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++)
{
@@ -257,9 +257,9 @@ public static unsafe class MeshletUtility
var cluster = new Cluster
{
vertices = meshlet.vertex_count,
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), handle),
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, handle),
localIndices = new UnsafeList<byte>((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, Allocator.FreeList),
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), Allocator.FreeList),
group = -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)
{
var single = new UnsafeList<UnsafeList<int>>(1, handle);
var pendingcpy = new UnsafeList<int>(pending.Count, handle);
var single = new UnsafeList<UnsafeList<int>>(1, Allocator.FreeList);
var pendingcpy = new UnsafeList<int>(pending.Count, Allocator.FreeList);
pendingcpy.AddRange(pending.AsSpan());
single.Add(pendingcpy);
@@ -345,8 +345,8 @@ public static unsafe class MeshletUtility
totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
}
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, handle);
using var clusterCounts = new UnsafeList<uint>(pending.Count, handle);
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, Allocator.FreeList);
using var clusterCounts = new UnsafeList<uint>(pending.Count, Allocator.FreeList);
nuint offset = 0;
for (var i = 0; i < pending.Count; i++)
@@ -361,7 +361,7 @@ public static unsafe class MeshletUtility
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(
(uint*)clusterPart.GetUnsafePtr(),
@@ -375,10 +375,10 @@ public static unsafe class MeshletUtility
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++)
{
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++)
@@ -389,9 +389,9 @@ public static unsafe class MeshletUtility
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++)
{
@@ -425,10 +425,10 @@ public static unsafe class MeshletUtility
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_locks = new UnsafeArray<byte>(indices.Count, handle);
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, Allocator.FreeList);
using var subset_locks = new UnsafeArray<byte>(indices.Count, Allocator.FreeList);
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)
{
@@ -529,7 +529,7 @@ public static unsafe class MeshletUtility
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;
}
@@ -577,13 +577,8 @@ public static unsafe class MeshletUtility
{
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
{
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);
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, Allocator.FreeList, AllocationOption.Clear); ;
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, Allocator.FreeList);
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++)
{
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++)
{
pending.Add(i);
@@ -623,14 +618,14 @@ public static unsafe class MeshletUtility
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();
LockBoundary(locks, groups, clusters, remap, mesh.vertexLock);
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++)
{
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 bounds = MergeBounds(clusters, groups[i], pool.AllocationHandle);
var bounds = MergeBounds(clusters, groups[i]);
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))
{
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;
}
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++)
{
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++)
{
split[j].refined = refined;
@@ -681,7 +676,7 @@ public static unsafe class MeshletUtility
{
var bounds = clusters[pending[0]].bounds;
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;

View File

@@ -135,13 +135,12 @@ shader "MyShader/Standard"
// return perMaterialData.color * blendedColor + input.color;
uint hash = PCGHash(input.meshletID);
float r = float((hash & 0xFF0000u) >> 16) / 255.0;
float g = float((hash & 0x00FF00u) >> 8) / 255.0;
float b = float((hash & 0x0000FFu)) / 255.0;
return float4(r, g, b, 1.0);
// return float4(input.normal, 1.0);
}
}