feat(resource): refactor heap management & suballocation

Major overhaul of GPU resource/heap management:
- Replace resource pooling and upload buffer logic with transient heap/page-based suballocation in ResourceManager.
- Add support for suballocation and heap flags/types, with D3D12 helpers.
- Remove ICommandBuffer.UploadBuffer/UploadTexture; add UpdateSubResources and CopyBuffer, move upload logic to RenderingContext.
- Refactor D3D12ResourceAllocator/Database for suballocation, heap flags, and mapping.
- Standardize on Handle<GPUBuffer> usage.
- Update meshlet/mesh utilities for new allocation handles and memory pools.
- Refactor RenderGraph and docs to use "heap" terminology.
- Use cpuFrame/gpuFrame consistently for frame sync.
- Add s2h.hlsl, s2h_3d.hlsl, s2h_scatter.hlsl shader debug libs.
- Miscellaneous fixes, cleanup, and dependency updates.

BREAKING CHANGE: Resource pooling and upload APIs replaced with new heap/page-based suballocation system. Update all buffer/texture creation and upload code to use new ResourceManager and ICommandBuffer methods.
This commit is contained in:
2026-04-03 01:48:49 +09:00
parent d03eb659fa
commit 6321b36ef5
47 changed files with 2552 additions and 526 deletions

View File

@@ -22,7 +22,7 @@
<ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.6" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.8" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.3">
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -6,6 +6,7 @@ using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Gdiplus;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D_Alias;
@@ -635,7 +636,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
pNativeObject->SetPipelineState(psor.Value);
}
public void SetConstantBufferView(uint slot, Handle<RHI.GPUBuffer> buffer)
public void SetConstantBufferView(uint slot, Handle<GPUBuffer> buffer)
{
AssertNotDisposed();
ThrowIfNotRecording();
@@ -651,7 +652,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
pNativeObject->SetGraphicsRootConstantBufferView(slot, resource.Get()->GetGPUVirtualAddress());
}
public void SetVertexBuffer(uint slot, Handle<RHI.GPUBuffer> buffer, ulong offset = 0)
public void SetVertexBuffer(uint slot, Handle<GPUBuffer> buffer, ulong offset = 0)
{
AssertNotDisposed();
ThrowIfNotRecording();
@@ -681,7 +682,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
pNativeObject->IASetVertexBuffers(slot, 1, &vbView);
}
public void SetIndexBuffer(Handle<RHI.GPUBuffer> buffer, IndexType type, ulong offset = 0)
public void SetIndexBuffer(Handle<GPUBuffer> buffer, IndexType type, ulong offset = 0)
{
AssertNotDisposed();
ThrowIfNotRecording();
@@ -821,7 +822,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
throw new NotImplementedException();
}
public void ExecuteIndirect(Handle<RHI.GPUBuffer> argumentBuffer, ulong argumentOffset, Handle<RHI.GPUBuffer> countBuffer, ulong countBufferOffset)
public void ExecuteIndirect(Handle<GPUBuffer> argumentBuffer, ulong argumentOffset, Handle<GPUBuffer> countBuffer, ulong countBufferOffset)
{
throw new NotImplementedException();
@@ -845,57 +846,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
}
public void UploadBuffer<T>(Handle<RHI.GPUBuffer> buffer, params ReadOnlySpan<T> data)
where T : unmanaged
{
static void Map(T* pData, nuint size, ulong offset, SharedPtr<ID3D12Resource> resource)
{
void* pMappedData;
resource.Get()->Map(0, null, &pMappedData);
MemoryUtility.MemCpy((byte*)pMappedData + offset, pData, size);
resource.Get()->Unmap(0, null);
}
AssertNotDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != Error.None)
{
return;
}
#endif
IncrementCommandCount();
var pResource = _resourceDatabase.GetResource(buffer.AsResource());
D3D12_HEAP_PROPERTIES properties;
D3D12_HEAP_FLAGS flags;
ThrowIfFailed(pResource.Get()->GetHeapProperties(&properties, &flags));
if (properties.Type == D3D12_HEAP_TYPE_UPLOAD)
{
fixed (T* pData = data)
{
Map(pData, (nuint)(data.Length * sizeof(T)), 0, pResource);
}
}
else
{
var sizeInBytes = (uint)(data.Length * sizeof(T));
var uploadHandle = _resourceAllocator.CreateTempUploadBuffer(sizeInBytes, out var offset);
var uploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
fixed (T* pData = data)
{
Map(pData, sizeInBytes, offset, uploadResource);
}
pNativeObject->CopyBufferRegion(pResource, 0, uploadResource, offset, sizeInBytes);
}
}
public void UploadTexture(Handle<GPUTexture> texture, params ReadOnlySpan<SubResourceData> subresources)
public void CopyBuffer(Handle<GPUBuffer> dst, Handle<GPUBuffer> src, ulong dstOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
{
AssertNotDisposed();
ThrowIfNotRecording();
@@ -905,47 +856,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
return;
}
#endif
IncrementCommandCount();
var resource = _resourceDatabase.GetResource(texture.AsResource());
var resourceDesc = resource.Get()->GetDesc();
var requiredSize = GetRequiredIntermediateSize(resource, 0, (uint)subresources.Length);
var uploadHandle = _resourceAllocator.CreateTempUploadBuffer(requiredSize, out var offset);
var pUploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
var d3d12Subresources = stackalloc D3D12_SUBRESOURCE_DATA[subresources.Length];
for (var i = 0; i < subresources.Length; i++)
{
d3d12Subresources[i] = new D3D12_SUBRESOURCE_DATA
{
pData = subresources[i].pData,
RowPitch = (nint)subresources[i].rowPitch,
SlicePitch = (nint)subresources[i].slicePitch
};
}
UpdateSubresources(
(ID3D12GraphicsCommandList*)pNativeObject,
resource,
pUploadResource,
offset,
0,
(uint)subresources.Length,
d3d12Subresources);
}
public void CopyBuffer(Handle<RHI.GPUBuffer> dst, Handle<RHI.GPUBuffer> src, ulong dstOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
{
AssertNotDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != Error.None)
{
return;
}
#endif
if (dst == src)
{
return;
@@ -971,6 +882,48 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
}
}
public void UpdateSubResources(Handle<GPUResource> resource, Handle<GPUResource> intermediate, params ReadOnlySpan<SubResourceData> subResources)
{
AssertNotDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != Error.None)
{
return;
}
#endif
IncrementCommandCount();
var d3d12Resource = _resourceDatabase.GetResource(resource);
var d3d12Intermediate = _resourceDatabase.GetResource(intermediate);
if (d3d12Intermediate == null || d3d12Resource == null)
{
RecordError(nameof(UpdateSubResources), Error.InvalidArgument);
return;
}
var d3d12Subresources = stackalloc D3D12_SUBRESOURCE_DATA[subResources.Length];
for (var i = 0; i < subResources.Length; i++)
{
d3d12Subresources[i] = new D3D12_SUBRESOURCE_DATA
{
pData = subResources[i].pData,
RowPitch = (nint)subResources[i].rowPitch,
SlicePitch = (nint)subResources[i].slicePitch
};
}
UpdateSubresources(
(ID3D12GraphicsCommandList*)pNativeObject,
d3d12Resource,
d3d12Intermediate,
0,
0,
(uint)subResources.Length,
d3d12Subresources);
}
private D3D12_TEXTURE_COPY_LOCATION GetTextureCopyLocation(SharedPtr<ID3D12Resource> texture, TextureSubresource subres)
{
var flatIndex = subres.MipLevel + subres.ArrayLayer * texture.Get()->GetDesc().MipLevels;

View File

@@ -75,7 +75,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
private readonly Queue<ICommandBuffer> _commandBufferPool;
private readonly Queue<CommandBufferReturnEntry> _commandBufferReturnQueue;
private ulong _currentFrame;
private ulong _cpuFrame;
private bool _disposed;
public IRenderDevice Device => _device;
@@ -147,7 +147,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
public void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer)
{
_commandBufferReturnQueue.Enqueue(new CommandBufferReturnEntry(commandBuffer, _currentFrame));
_commandBufferReturnQueue.Enqueue(new CommandBufferReturnEntry(commandBuffer, _cpuFrame));
}
public ISwapChain CreateSwapChain(SwapChainDesc desc)
@@ -156,22 +156,22 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
return new DXGISwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount);
}
public Result BeginFrame(ulong currentFrame)
public Result BeginFrame(ulong cpuFrame)
{
ThrowIfDisposed();
_currentFrame = currentFrame;
_resourceDatabase.BeginFrame(currentFrame);
_cpuFrame = cpuFrame;
_resourceDatabase.BeginFrame(cpuFrame);
return Result.Success();
}
public Result EndFrame(ulong completedFrame)
public Result EndFrame(ulong gpuFrame)
{
ThrowIfDisposed();
_resourceDatabase.EndFrame(completedFrame);
_resourceDatabase.EndFrame(gpuFrame);
while (_commandBufferReturnQueue.TryPeek(out var entry) && entry.returnFrame <= completedFrame)
while (_commandBufferReturnQueue.TryPeek(out var entry) && entry.returnFrame <= gpuFrame)
{
_commandBufferPool.Enqueue(entry.commandBuffer);
_commandBufferReturnQueue.Dequeue();

View File

@@ -1,3 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;

View File

@@ -462,10 +462,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12PipelineLibrary _pipelineLibrary;
// TODO: We should use ring buffer pool in d3d12ma for upload buffer.
private readonly Handle<RHI.GPUBuffer> _uploadBatch;
private ulong _uploadBatchOffset;
private bool _disposed;
public D3D12ResourceAllocator(
@@ -489,17 +485,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
_descriptorAllocator = descriptorAllocator;
_resourceDatabase = resourceDatabase;
_pipelineLibrary = pipelineLibrary;
// Create an upload batch
var uploadDesc = new BufferDesc
{
Size = _UPLOAD_BATCH_SIZE,
Usage = BufferUsage.Upload,
MemoryType = ResourceMemoryType.Upload,
};
_uploadBatch = CreateBuffer(in uploadDesc, "D3D12ResourceAllocator_UploadBatch");
_uploadBatchOffset = 0;
}
~D3D12ResourceAllocator()
@@ -507,18 +492,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Handle<GPUResource> TrackAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData barrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp)
{
var handle = _resourceDatabase.AddAllocation(allocation, barrierData, resourceDescriptor, desc, name);
if (isTemp)
{
_resourceDatabase.ReleaseResource(handle);
}
return handle;
}
private HRESULT CreateResource(D3D12MA_ALLOCATION_DESC* pAllocationDesc, D3D12_RESOURCE_DESC* pResourceDesc, D3D12_RESOURCE_STATES initialState, CreationOptions options, Guid* riid, void** ppv)
{
HRESULT hr;
@@ -563,27 +536,13 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
};
}
public Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string name)
public Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string? name = null)
{
var allocDesc = new D3D12MA_ALLOCATION_DESC
{
HeapType = desc.HeapType switch
{
HeapType.Default => D3D12_HEAP_TYPE_DEFAULT,
HeapType.Upload => D3D12_HEAP_TYPE_UPLOAD,
HeapType.Readback => D3D12_HEAP_TYPE_READBACK,
_ => D3D12_HEAP_TYPE_DEFAULT
},
HeapType = desc.HeapType.ToD3D12HeapType(),
Flags = D3D12MA_ALLOCATION_FLAG_COMMITTED,
ExtraHeapFlags = desc.HeapFlags switch
{
HeapFlags.None => D3D12_HEAP_FLAG_NONE,
HeapFlags.AllowBuffers => D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
HeapFlags.AllowTextures => D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES,
HeapFlags.AllowRTAndDS => D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
HeapFlags.AlowBufferAndTexture => D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES,
_ => D3D12_HEAP_FLAG_NONE
}
ExtraHeapFlags = desc.HeapFlags.ToD3D12HeapFlags()
};
// SizeInBytes must be aligned to 64KB for committed resources
@@ -606,10 +565,10 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
sync = BarrierSync.None
};
return TrackAllocation(alloc, barrierData, ResourceViewGroup.Invalid, default, name, false);
return _resourceDatabase.AddAllocation(alloc, barrierData, ResourceViewGroup.Invalid, default, name);
}
public Handle<GPUTexture> CreateTexture(ref readonly TextureDesc desc, string name, CreationOptions options = default)
public Handle<GPUTexture> CreateTexture(ref readonly TextureDesc desc, string? name = null, CreationOptions options = default)
{
Debug.Assert(!_disposed);
@@ -634,7 +593,10 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
else
{
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, null, (void**)&pAllocation);
pResource = pAllocation->GetResource();
if (hr.SUCCEEDED)
{
pResource = pAllocation->GetResource();
}
}
if (hr.FAILED)
@@ -645,7 +607,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
return Handle<GPUTexture>.Invalid;
}
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
{
@@ -701,13 +662,13 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
}
else
{
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp);
resource = _resourceDatabase.AddAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Texture(desc), name);
}
return resource.AsTexture();
}
public Handle<GPUTexture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string name, CreationOptions options = default)
public Handle<GPUTexture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string? name = null, CreationOptions options = default)
{
Debug.Assert(!_disposed);
@@ -715,7 +676,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
return CreateTexture(in textureDesc, name, options);
}
public Handle<RHI.GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string name, CreationOptions options = default)
public Handle<GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string? name = null, CreationOptions options = default)
{
Debug.Assert(!_disposed);
CheckBufferSize(desc.Size);
@@ -760,10 +721,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
#if DEBUG
ThrowIfFailed(hr);
#endif
return Handle<RHI.GPUBuffer>.Invalid;
return Handle<GPUBuffer>.Invalid;
}
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(BufferUsage.Constant))
@@ -814,40 +774,10 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
}
else
{
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp);
resource = _resourceDatabase.AddAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Buffer(desc), name);
}
return resource.AsGraphicsBuffer();
}
public Handle<RHI.GPUBuffer> CreateTempUploadBuffer(ulong sizeInBytes, out ulong offset)
{
if (sizeInBytes <= _MAX_RESOURCE_SIZE_TO_FIT_IN_UPLOAD_BATCH && sizeInBytes + _uploadBatchOffset <= _UPLOAD_BATCH_SIZE)
{
offset = _uploadBatchOffset;
_uploadBatchOffset += sizeInBytes;
return _uploadBatch;
}
else
{
var bufferDesc = new BufferDesc
{
Size = (uint)sizeInBytes,
Usage = BufferUsage.Upload,
MemoryType = ResourceMemoryType.Upload,
};
var options = new CreationOptions
{
AllocationType = ResourceAllocationType.Temporary,
};
offset = 0;
var handle = CreateBuffer(in bufferDesc, "TempUploadBuffer", options);
_resourceDatabase.ReleaseResource(handle.AsResource());
return handle;
}
return resource.AsBuffer();
}
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc)
@@ -887,7 +817,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
return;
}
_resourceDatabase.ReleaseResourceImmediately(_uploadBatch.AsResource());
_d3d12MA.Dispose();
_disposed = true;

View File

@@ -4,14 +4,14 @@ using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.D3D12;
// TODO: Thread safety
internal class D3D12ResourceDatabase : IResourceDatabase
internal unsafe class D3D12ResourceDatabase : IResourceDatabase
{
internal unsafe record struct ResourceRecord
{
@@ -109,7 +109,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
private int _writeLock;
private ulong _currentFrame;
private ulong _cpuFrame;
private bool _disposed;
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
@@ -323,12 +323,12 @@ internal class D3D12ResourceDatabase : IResourceDatabase
{
Debug.Assert(!_disposed);
if (_resources.TryGetElementAt(handle.ID, handle.Generation, out var record))
if (!_resources.TryGetElementAt(handle.ID, handle.Generation, out var record))
{
return;
}
var entry = new ReleaseEntry(record, _currentFrame);
var entry = new ReleaseEntry(record, _cpuFrame);
_releaseQueue.Enqueue(entry);
_resources.Remove(handle.ID, handle.Generation);
@@ -399,20 +399,52 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return Error.None;
}
public void BeginFrame(ulong currentFrame)
public Error Map(Handle<GPUResource> handle, uint subResource, ResourceRange? readRange, ResourceRange? writeRange, void* pData, nuint size)
{
Debug.Assert(!_disposed);
_currentFrame = currentFrame;
var r = GetResourceRecord(handle);
if (r.IsFailure)
{
return r.Error;
}
var resource = r.Value.ResourcePtr;
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;
void * mappedData = null;
resource.Get()->Map(subResource, readRange.HasValue ? &rRange : null, &mappedData);
MemoryUtility.MemCpy(mappedData, pData, size);
resource.Get()->Unmap(subResource, writeRange.HasValue ? &wRange : null);
return Error.None;
}
public void EndFrame(ulong completedFrame)
public ulong GetIntermediateResourceSize(Handle<GPUResource> resource, uint firstSubResource, uint numSubResources)
{
var r = GetResourceRecord(resource);
if (r.IsFailure)
{
return 0;
}
return GetRequiredIntermediateSize(r.Value.ResourcePtr.Get(), firstSubResource, numSubResources);
}
public void BeginFrame(ulong cpuFrame)
{
Debug.Assert(!_disposed);
_cpuFrame = cpuFrame;
}
public void EndFrame(ulong gpuFrame)
{
Debug.Assert(!_disposed);
while (_releaseQueue.Count > 0)
{
var toRelease = _releaseQueue.Peek();
if (toRelease.fenceValue > completedFrame)
if (toRelease.fenceValue > gpuFrame)
{
break;
}
@@ -431,6 +463,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase
{
record.Release(_descriptorAllocator);
}
_resources.Clear();
}
public void Dispose()

View File

@@ -386,6 +386,31 @@ internal static unsafe class D3D12Utility
return flags;
}
public static D3D12_HEAP_TYPE ToD3D12HeapType(this HeapType heapType)
{
return heapType switch
{
HeapType.Default => D3D12_HEAP_TYPE_DEFAULT,
HeapType.Upload => D3D12_HEAP_TYPE_UPLOAD,
HeapType.Readback => D3D12_HEAP_TYPE_READBACK,
_ => throw new ArgumentException($"Unknown heap type: {heapType}")
};
}
public static D3D12_HEAP_FLAGS ToD3D12HeapFlags(this HeapFlags flags)
{
return flags switch
{
HeapFlags.None => D3D12_HEAP_FLAG_NONE,
HeapFlags.Shared => D3D12_HEAP_FLAG_SHARED,
HeapFlags.AllowOnlyBuffers => D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
HeapFlags.AllowOnlyTextures => D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES,
HeapFlags.AllowOnlyRTAndDS => D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
HeapFlags.AllowAllBufferAndTexture => D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES,
_ => throw new ArgumentException($"Unknown heap flags: {flags}")
};
}
public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in TextureDesc desc)
{
var dxgiFormat = desc.Format.ToDXGIFormat();

View File

@@ -152,6 +152,19 @@ public record struct Vertex
public float2 uv;
}
public struct ResourceRange
{
public nuint Start
{
get; set;
}
public nuint End
{
get; set;
}
}
public readonly struct ShaderVariant;
public readonly struct GraphicsPipeline;

View File

@@ -185,24 +185,6 @@ public interface ICommandBuffer : IDisposable
// TODO: This method is not supported yet.
void DispatchRay();
/// <summary>
/// Uploads the specified data to the buffer represented by the given handle.
/// </summary>
/// <typeparam name="T">The unmanaged Value space of the elements to upload to the buffer.</typeparam>
/// <param name="buffer">A handle to the buffer that will receive the uploaded data.</param>
/// <param name="data">A read-only span containing the data to upload to the buffer. The span must contain elements of space
/// <typeparamref name="T"/>.</param>
void UploadBuffer<T>(Handle<GPUBuffer> buffer, params ReadOnlySpan<T> data)
where T : unmanaged;
/// <summary>
/// Uploads texture data to the specified texture resource starting at the given subresource index.
/// </summary>
/// <param name="texture">The texture resource to which the subresource data will be uploaded. Must be a valid, initialized texture handle.</param>
/// <param name="subresources">A reference to the structure containing the subresource data to upload. The data must match the Format and layout expected by the texture.</param>
/// Must be greater than zero and not exceed the remaining subresources in the texture.</param>
void UploadTexture(Handle<GPUTexture> texture, params ReadOnlySpan<SubResourceData> subresources);
/// <summary>
/// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer.
/// </summary>
@@ -221,4 +203,6 @@ public interface ICommandBuffer : IDisposable
/// <param name="src">The handle to the source texture from which data will be read.</param>
/// <param name="srcRegion">The region of the source texture to copy from. If null, the entire texture will be used.</param>
void CopyTexture(Handle<GPUTexture> dst, TextureRegion? dstRegion, Handle<GPUTexture> src, TextureRegion? srcRegion);
void UpdateSubResources(Handle<GPUResource> resource, Handle<GPUResource> intermediate, params ReadOnlySpan<SubResourceData> subResources);
}

View File

@@ -74,14 +74,14 @@ public interface IGraphicsEngine : IDisposable
/// <summary>
/// Begin the current frame.
/// </summary>
/// <param name="currentFrame">CPU fence value for synchronization</param>
/// <param name="cpuFrame">CPU fence value for synchronization</param>
/// <returns>Result of the begin frame operation</returns>
Result BeginFrame(ulong currentFrame);
Result BeginFrame(ulong cpuFrame);
/// <summary>
/// End the current frame.
/// </summary>
/// <param name="completedFrame">GPU fence value for synchronization</param>
/// <param name="gpuFrame">GPU fence value for synchronization</param>
/// <returns>Result of the end frame operation</returns>
Result EndFrame(ulong completedFrame);
Result EndFrame(ulong gpuFrame);
}

View File

@@ -1,3 +1,5 @@
using Ghost.Core;
namespace Ghost.Graphics.RHI;
[Flags]

View File

@@ -5,7 +5,6 @@ namespace Ghost.Graphics.RHI;
public enum ResourceAllocationType
{
Default,
Temporary,
Suballocation,
}
@@ -36,11 +35,12 @@ public enum HeapType
public enum HeapFlags
{
None = 0,
AllowBuffers,
AllowTextures,
AllowRTAndDS,
AlowBufferAndTexture,
None,
Shared,
AllowOnlyBuffers,
AllowOnlyTextures,
AllowOnlyRTAndDS,
AllowAllBufferAndTexture,
}
public struct AllocationDesc
@@ -89,7 +89,7 @@ public interface IResourceAllocator : IDisposable
/// <param name="desc">Allocation description</param>
/// <param name="name">Debug name of the allocation</param>
/// <returns>An <see cref="Handle{GPUResource}"/> point to the allocated memory</returns>
Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string name);
Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string? name = null);
/// <summary>
/// Creates a texture resource
@@ -98,7 +98,7 @@ public interface IResourceAllocator : IDisposable
/// <param name="name">Debug name of the resource</param>
/// <param name="options">Additional options of the resource allocation</param>
/// <returns>An <see cref="Handle{Texture}"/> point to the resource</returns>
Handle<GPUTexture> CreateTexture(ref readonly TextureDesc desc, string name, CreationOptions options = default);
Handle<GPUTexture> CreateTexture(ref readonly TextureDesc desc, string? name = null, CreationOptions options = default);
/// <summary>
/// Creates a render Target for off-screen rendering
@@ -107,7 +107,7 @@ public interface IResourceAllocator : IDisposable
/// <param name="name">Debug name of the resource</param>
/// <param name="options">Additional options of the resource allocation</param>
/// <returns>An <see cref="Handle{Texture}"/> point to the resource</returns>
Handle<GPUTexture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string name, CreationOptions options = default);
Handle<GPUTexture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string? name = null, CreationOptions options = default);
/// <summary>
/// Creates a buffer resource
@@ -116,18 +116,7 @@ public interface IResourceAllocator : IDisposable
/// <param name="name">Debug name of the resource</param>
/// <param name="options">Additional options of the resource allocation</param>
/// <returns>An <see cref="Handle{GraphicsBuffer}"/> point to the resource</returns>
Handle<GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string name, CreationOptions options = default);
/// <summary>
/// Creates a temporary upload buffer of the specified size in bytes.
/// </summary>
/// <remarks>
/// This method has been optimized for frequent calls during frame updates. It efficiently manages memory to minimize fragmentation and overhead.
/// </remarks>
/// <param name="sizeInBytes">The size of the upload buffer to create, in bytes.</param>
/// <param name="offset">The offset within the upload buffer where the allocation begins.</param>
/// <returns>An <see cref="Handle{GraphicsBuffer}"/> pointing to the created upload buffer.</returns>
Handle<GPUBuffer> CreateTempUploadBuffer(ulong sizeInBytes, out ulong offset);
Handle<GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string? name = null, CreationOptions options = default);
/// <summary>
/// Creates a new sampler object using the specified sampler description.

View File

@@ -31,22 +31,8 @@ public enum BindlessAccess
UnorderedAccess,
}
// TODO: Consider adding methods for resource enumeration, statistics, and bulk operations.
// TODO: Consider adding async resource loading and streaming support.
public interface IResourceDatabase : IDisposable
public unsafe interface IResourceDatabase : IDisposable
{
/*
/// <summary>
/// Imports an external unmanaged resource and returns a handle for use within the resource management system.
/// </summary>
/// <typeparam name="T">The space of the unmanaged resource pointer to import.</typeparam>
/// <param name="resourcePtr">A pointer to the external unmanaged resource to be imported. Must remain valid for the duration of the resource's usage.</param>
/// <param name="initialState">The initial state to assign to the imported resource.</param>
/// <returns>A handle representing the imported resource, which can be used for subsequent operations.</returns>
unsafe Handle<GPUResource> ImportExternalResource<T>(T resourcePtr, ResourceState initialState, string? name = null)
where T : unmanaged;
*/
void EnterParallelRead();
void ExitParallelRead();
@@ -139,4 +125,8 @@ public interface IResourceDatabase : IDisposable
/// <param name="handleB">The second handle whose associated resource is to be swapped.</param>
/// <returns>An Error indicating the success or failure of the swap operation.</returns>
Error Swap(Handle<GPUResource> handleA, Handle<GPUResource> handleB);
Error Map(Handle<GPUResource> handle, uint subResource, ResourceRange? readRange, ResourceRange? writeRange, void* pData, nuint size);
ulong GetIntermediateResourceSize(Handle<GPUResource> resource, uint firstSubResource, uint numSubResources);
}

View File

@@ -48,7 +48,7 @@ public interface ISwapChain : IDisposable
/// <summary>
/// Gets all back buffer textures
/// </summary>
/// <returns>AlowBufferAndTexture back buffer textures</returns>
/// <returns>AllowAllBufferAndTexture back buffer textures</returns>
ReadOnlySpan<Handle<GPUTexture>> GetBackBuffers();
/// <summary>

View File

@@ -25,7 +25,7 @@ public static class ResourceHandleExtensions
return new Handle<GPUTexture>(resource.ID, resource.Generation);
}
public static Handle<GPUBuffer> AsGraphicsBuffer(this Handle<GPUResource> resource)
public static Handle<GPUBuffer> AsBuffer(this Handle<GPUResource> resource)
{
return new Handle<GPUBuffer>(resource.ID, resource.Generation);
}

View File

@@ -11,16 +11,16 @@ namespace Ghost.Graphics.Core;
internal struct CBufferCache : IResourceReleasable
{
private UnsafeArray<byte> _cpuData;
private Handle<RHI.GPUBuffer> _gpuResource;
private Handle<GPUBuffer> _gpuResource;
private uint _size;
public readonly UnsafeArray<byte> CpuData => _cpuData;
public readonly Handle<RHI.GPUBuffer> GpuResource => _gpuResource;
public readonly Handle<GPUBuffer> GpuResource => _gpuResource;
public readonly uint Size => _size;
public readonly bool IsCreated => _size != 0 && _gpuResource.IsValid && _cpuData.IsCreated;
public CBufferCache(Handle<RHI.GPUBuffer> buffer, uint bufferSize)
public CBufferCache(Handle<GPUBuffer> buffer, uint bufferSize)
{
_size = bufferSize;
_cpuData = new UnsafeArray<byte>((int)bufferSize, Allocator.Persistent);
@@ -37,7 +37,7 @@ internal struct CBufferCache : IResourceReleasable
_cpuData.Dispose();
database.ReleaseResource(_gpuResource.AsResource());
_gpuResource = Handle<RHI.GPUBuffer>.Invalid;
_gpuResource = Handle<GPUBuffer>.Invalid;
_size = 0;
}
}

View File

@@ -1,7 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics;
@@ -138,7 +137,7 @@ public struct Mesh : IResourceReleasable
/// <summary>
/// Gets the handle to the vertex buffer on the GPU.
/// </summary>
public Handle<RHI.GPUBuffer> VertexBuffer
public Handle<GPUBuffer> VertexBuffer
{
get; internal set;
}
@@ -146,7 +145,7 @@ public struct Mesh : IResourceReleasable
/// <summary>
/// Gets the handle to the index buffer on the GPU.
/// </summary>
public Handle<RHI.GPUBuffer> IndexBuffer
public Handle<GPUBuffer> IndexBuffer
{
get; internal set;
}
@@ -154,7 +153,7 @@ public struct Mesh : IResourceReleasable
/// <summary>
/// Gets the handle to the meshlet buffer on the GPU.
/// </summary>
public Handle<RHI.GPUBuffer> MeshLetBuffer
public Handle<GPUBuffer> MeshLetBuffer
{
get; internal set;
}
@@ -162,7 +161,7 @@ public struct Mesh : IResourceReleasable
/// <summary>
/// Gets the handle to the meshlet vertices buffer on the GPU.
/// </summary>
public Handle<RHI.GPUBuffer> MeshletVerticesBuffer
public Handle<GPUBuffer> MeshletVerticesBuffer
{
get; internal set;
}
@@ -170,7 +169,7 @@ public struct Mesh : IResourceReleasable
/// <summary>
/// Gets the handle to the meshlet triangles buffer on the GPU.
/// </summary>
public Handle<RHI.GPUBuffer> MeshletTrianglesBuffer
public Handle<GPUBuffer> MeshletTrianglesBuffer
{
get; internal set;
}
@@ -178,12 +177,12 @@ public struct Mesh : IResourceReleasable
/// <summary>
/// Gets the handle to the mesh data buffer on the GPU.
/// </summary>
public Handle<RHI.GPUBuffer> ObjectDataBuffer
public Handle<GPUBuffer> ObjectDataBuffer
{
get; internal set;
}
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<RHI.GPUBuffer> vertexBuffer, Handle<RHI.GPUBuffer> indexBuffer)
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GPUBuffer> vertexBuffer, Handle<GPUBuffer> indexBuffer)
{
Vertices = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
Indices = new UnsafeList<uint>(indices.Length, Allocator.Persistent);

View File

@@ -2,10 +2,11 @@ using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
namespace Ghost.Graphics.Core;
// TODO: Temporary rendering context for resource creation and data upload. We will refactor it later when we have a better understanding of the engine architecture.
// TODO: Temporary rendering context for heap creation and data upload. We will refactor it later when we have a better understanding of the engine architecture.
public readonly unsafe ref struct RenderingContext
{
private readonly IGraphicsEngine _engine;
@@ -82,6 +83,49 @@ public readonly unsafe ref struct RenderingContext
ResourceDatabase.SetResourceBarrierData(resource, new ResourceBarrierData(newLayout, newAccess, newSync));
}
private void UploadBuffer<T>(Handle<GPUBuffer> buffer, params ReadOnlySpan<T> data)
where T : unmanaged
{
var r = _engine.ResourceDatabase.GetResourceDescription(buffer.AsResource());
if (r.IsFailure)
{
return;
}
Debug.Assert(r.Value.Type == ResourceType.Buffer);
var sizeInBytes = (nuint)(data.Length * sizeof(T));
var memoryType = r.Value.BufferDescription.MemoryType;
if (memoryType == ResourceMemoryType.Upload)
{
fixed (T* pData = data)
{
ResourceDatabase.Map(buffer.AsResource(), 0, null, null, pData, sizeInBytes);
}
}
else
{
//var uploadHandle = _resourceAllocator.CreateTempUploadBuffer(sizeInBytes, out var offset);
//var uploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
var uploadDesc = new BufferDesc
{
Size = sizeInBytes,
Usage = BufferUsage.Upload,
MemoryType = ResourceMemoryType.Upload,
};
var uploadHandle = _resourceManager.CreateTransientBuffer(in uploadDesc);
fixed (T* pData = data)
{
ResourceDatabase.Map(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
}
_directCmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
}
}
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
{
var mesh = _resourceManager.CreateMesh(vertices, indices);
@@ -98,8 +142,8 @@ public readonly unsafe ref struct RenderingContext
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
@@ -147,8 +191,8 @@ public readonly unsafe ref struct RenderingContext
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
_directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
@@ -204,9 +248,9 @@ public readonly unsafe ref struct RenderingContext
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
_directCmd.UploadBuffer(meshRef.MeshLetBuffer, meshletData.meshlets.AsSpan());
_directCmd.UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan());
_directCmd.UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan());
UploadBuffer(meshRef.MeshLetBuffer, meshletData.meshlets.AsSpan());
UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan());
UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan());
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
@@ -236,7 +280,7 @@ public readonly unsafe ref struct RenderingContext
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
_directCmd.UploadBuffer(meshData.ObjectDataBuffer, data);
UploadBuffer(meshData.ObjectDataBuffer, data);
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading);
}
@@ -252,16 +296,29 @@ public readonly unsafe ref struct RenderingContext
public void UploadTexture<T>(Handle<GPUTexture> texture, ReadOnlySpan<T> data)
where T : unmanaged
{
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
//var size = ResourceAllocator.GetSizeInfo(desc).Size;
//if ((ulong)(data.Length * sizeof(T)) != ResourceAllocator.GetSizeInfo(desc).Size)
//{
// throw new ArgumentException("Data size does not match texture size.");
//}
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
var requiredSize = ResourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
var uploadDesc = new BufferDesc
{
Size = requiredSize,
Usage = BufferUsage.Upload,
MemoryType = ResourceMemoryType.Upload,
};
var uploadHandle = _resourceManager.CreateTransientBuffer(in uploadDesc);
if (uploadHandle.IsInvalid)
{
throw new OutOfMemoryException("Failed to create upload buffer for texture data.");
}
TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy);
fixed (T* pData = data)
@@ -273,7 +330,7 @@ public readonly unsafe ref struct RenderingContext
slicePitch = slicePitch
};
_directCmd.UploadTexture(texture, subresourceData);
_directCmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
}
}
}

View File

@@ -5,7 +5,7 @@ using System.Diagnostics;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Main render graph class that manages resource allocation and pass execution.
/// Main render graph class that manages heap allocation and pass execution.
/// </summary>
public sealed class RenderGraph : IDisposable
{
@@ -125,7 +125,7 @@ public sealed class RenderGraph : IDisposable
/// </summary>
/// <param name="buffer">The external buffer handle.</param>
/// <returns>The identifier of the imported render graph buffer. Invalid if import fails.</returns>
public Identifier<RGBuffer> ImportBuffer(Handle<RHI.GPUBuffer> buffer, string name)
public Identifier<RGBuffer> ImportBuffer(Handle<GPUBuffer> buffer, string name)
{
var r = _resourceDatabase.GetResourceDescription(buffer.AsResource());
if (r.IsFailure)
@@ -178,7 +178,7 @@ public sealed class RenderGraph : IDisposable
}
/// <summary>
/// Compiles the render graph by culling unused passes and determining resource lifetimes.
/// Compiles the render graph by culling unused passes and determining heap lifetimes.
/// </summary>
public Error Compile(ViewState viewState)
{

View File

@@ -12,7 +12,7 @@ internal struct MemoryBlock
public bool isFree;
public int firstUsePass;
public int lastUsePass;
public int logicalResourceIndex; // Which logical resource is currently using this block
public int logicalResourceIndex; // Which logical heap is currently using this block
public MemoryBlock(ulong offset, ulong size)
{
@@ -267,7 +267,7 @@ internal sealed class ResourceAliasingManager
private readonly ResourceHeap _heap;
private readonly List<PlacedResource> _placedResources;
// Mapping from logical resource index to placed resource index
// Mapping from logical heap index to placed heap index
private readonly Dictionary<int, int> _logicalToPlaced;
private const ulong _DEFAULT_TEXTURE_ALIGNMENT = 65536; // 64KB
@@ -373,7 +373,7 @@ internal sealed class ResourceAliasingManager
_heap.size = peakMemoryUsage;
_heap.Reset();
// Allocate each logical resource in the heap
// Allocate each logical heap in the heap
foreach (var (logicalIndex, logicalResource) in logicalResources)
{
// TODO: Currently we are recalculating the aliasing candidates in the real allocation pass.
@@ -414,7 +414,7 @@ internal sealed class ResourceAliasingManager
}
// Second pass: Populate aliasedLogicalResources lists
// For each placed resource, find all OTHER placed resources at the same offset
// For each placed heap, find all OTHER placed resources at the same offset
for (var i = 0; i < _placedResources.Count; i++)
{
var placed = _placedResources[i];
@@ -432,7 +432,7 @@ internal sealed class ResourceAliasingManager
// Check if they're at the same offset
if (other.heapOffset == placed.heapOffset)
{
// Add the other's logical resource to this one's aliased list
// Add the other's logical heap to this one's aliased list
var otherLogicalIndex = other.aliasedLogicalResources[0]; // Each has exactly one at this point
if (!placed.aliasedLogicalResources.Contains(otherLogicalIndex))
{

View File

@@ -12,7 +12,7 @@ internal enum BarrierFlags
}
/// <summary>
/// Represents a resource barrier requirement that needs to be resolved at runtime.
/// Represents a heap barrier requirement that needs to be resolved at runtime.
/// </summary>
internal struct ResourceBarrier
{
@@ -55,7 +55,7 @@ internal struct ResourceBarrier
}
/// <summary>
/// Tracks the current state of a resource across passes during compilation.
/// Tracks the current state of a heap across passes during compilation.
/// </summary>
internal sealed class ResourceStateTracker
{
@@ -124,7 +124,7 @@ internal static class RenderGraphBarriers
}
/// <summary>
/// Inserts aliasing barriers when a placed resource is reused.
/// Inserts aliasing barriers when a placed heap is reused.
/// </summary>
private static void InsertAliasingBarriers(
RenderGraphPassBase pass,
@@ -148,20 +148,20 @@ internal static class RenderGraphBarriers
continue;
}
// Check if this is the first use of this logical resource
// Check if this is the first use of this logical heap
if (resource.firstUsePass == pass.index)
{
// Get the placed resource
// Get the placed heap
var placedIndex = aliasingManager.GetPlacedResourceIndex(id.Value);
if (placedIndex >= 0)
{
var placed = aliasingManager.GetPlacedResource(placedIndex);
// If this placed resource has multiple aliased resources,
// If this placed heap has multiple aliased resources,
// we need an aliasing barrier when switching between them
if (placed != null && placed.aliasedLogicalResources.Count > 1)
{
// Find the resource that used this placed memory most recently before this pass
// Find the heap that used this placed memory most recently before this pass
Identifier<RGResource> resourceBefore = default;
var mostRecentLastUse = -1;
@@ -169,10 +169,10 @@ internal static class RenderGraphBarriers
{
if (otherLogicalIndex != id.Value)
{
// Get resource by global index
// Get heap by global index
var otherResource = resources.GetResourceByIndex(otherLogicalIndex);
// Check if this resource finished before our resource starts
// Check if this heap finished before our heap starts
if (otherResource.lastUsePass < pass.index &&
otherResource.lastUsePass > mostRecentLastUse)
{
@@ -182,7 +182,7 @@ internal static class RenderGraphBarriers
}
}
// If we found a previous resource, insert aliasing barrier
// If we found a previous heap, insert aliasing barrier
if (mostRecentLastUse >= 0)
{
// Aliasing Requirement: Transition to Undefined, Sync with Predecessor
@@ -215,7 +215,7 @@ internal static class RenderGraphBarriers
List<CompiledBarrier> compiledBarriers,
RenderGraphResourceRegistry resources)
{
// Helper to add a compiled barrier for a resource transition
// Helper to add a compiled barrier for a heap transition
void AddTransition(Identifier<RGResource> id, ResourceBarrierData targetState)
{
var resource = resources.GetResource(id);

View File

@@ -22,7 +22,7 @@ public enum ResourceExtractionFlags : byte
{
None = 0,
/// <summary>
/// Releases the old resource after extraction.
/// Releases the old heap after extraction.
/// </summary>
ReleaseAfterExtract = 1 << 0,
}
@@ -36,19 +36,19 @@ public interface IRenderGraphBuilder : IDisposable
void AllowPassCulling(bool value);
/// <summary>
/// Creates a new texture resource based on the specified desc.
/// Creates a new texture heap based on the specified desc.
/// </summary>
/// <param name="desc">A structure that defines the properties and configuration of the texture to create.</param>
/// <param name="name">The name of the texture resource.</param>
/// <returns>An identifier for the newly created texture resource.</returns>
/// <param name="name">The name of the texture heap.</param>
/// <returns>An identifier for the newly created texture heap.</returns>
Identifier<RGTexture> CreateTexture(in RGTextureDesc desc, string name);
/// <summary>
/// Creates a new buffer resource based on the specified desc.
/// Creates a new buffer heap based on the specified desc.
/// </summary>
/// <param name="desc">A structure that defines the properties and configuration of the buffer to create.</param>
/// <param name="name">The name of the buffer resource.</param>
/// <returns>An identifier for the newly created buffer resource.</returns>
/// <param name="name">The name of the buffer heap.</param>
/// <returns>An identifier for the newly created buffer heap.</returns>
Identifier<RGBuffer> CreateBuffer(in BufferDesc desc, string name);
/// <summary>
@@ -69,17 +69,17 @@ public interface IRenderGraphBuilder : IDisposable
Identifier<RGBuffer> UseBuffer(Identifier<RGBuffer> buffer, AccessFlags accessMode);
/// <summary>
/// Extracts the actual texture resource associated with the given identifier for use in outside of the render graph execution context.
/// Extracts the actual texture heap associated with the given identifier for use in outside of the render graph execution context.
/// </summary>
/// <param name="src">The identifier of the texture to be extracted.</param>
/// <param name="dst">A handle to receive the actual GPU texture resource.</param>
/// <param name="dst">A handle to receive the actual GPU texture heap.</param>
void QueryTextureExtraction(Identifier<RGTexture> src, Handle<GPUTexture> dst, ResourceExtractionFlags flags = ResourceExtractionFlags.ReleaseAfterExtract);
/// <summary>
/// Extracts the actual buffer resource associated with the given identifier for use in outside of the render graph execution context.
/// Extracts the actual buffer heap associated with the given identifier for use in outside of the render graph execution context.
/// </summary>
/// <param name="src">The identifier of the buffer to be extracted.</param>
/// <param name="dst">A handle to receive the actual GPU buffer resource.</param>
/// <param name="dst">A handle to receive the actual GPU buffer heap.</param>
void QueryBufferExtraction(Identifier<RGBuffer> src, Handle<GPUBuffer> dst, ResourceExtractionFlags flags = ResourceExtractionFlags.ReleaseAfterExtract);
}
@@ -94,7 +94,7 @@ public interface IRasterRenderGraphBuilder : IRenderGraphBuilder
/// <summary>
/// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
/// </summary>
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer resource.</param>
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer heap.</param>
/// <returns>An identifier for the buffer.</returns>
Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer);
@@ -150,7 +150,7 @@ public interface IUnsafeRenderGraphBuilder : IRenderGraphBuilder
/// <summary>
/// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
/// </summary>
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer resource.</param>
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer heap.</param>
/// <returns>An identifier for the buffer.</returns>
Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer);

View File

@@ -16,16 +16,16 @@ internal sealed class CachedCompilation
// Culling decisions for each pass
public readonly List<bool> passCulledFlags = new(64);
// Physical resource aliasing mappings (logical index -> physical index)
// Physical heap aliasing mappings (logical index -> physical index)
public readonly Dictionary<int, int> logicalToPhysical = new(128);
// Placed resource metadata
// Placed heap metadata
public readonly List<PlacedResourceData> placedResources = new(32);
// Compiled barriers (stores only target states, queries before state from ResourceManager)
public readonly List<CompiledBarrier> compiledBarriers = new(128);
// Real gpu resource
// Real gpu heap
public readonly List<Handle<GPUResource>> backingResources = new(32);
// View state used for this compilation
@@ -44,7 +44,7 @@ internal sealed class CachedCompilation
}
/// <summary>
/// Placed resource data for caching.
/// Placed heap data for caching.
/// </summary>
internal struct PlacedResourceData
{

View File

@@ -4,7 +4,7 @@ using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Handles compilation of the render graph including pass culling, resource allocation,
/// Handles compilation of the render graph including pass culling, heap allocation,
/// barrier compilation, and cache management.
/// </summary>
internal sealed class RenderGraphCompiler
@@ -229,7 +229,7 @@ internal sealed class RenderGraphCompiler
{
Size = _aliasingManager.Heap.size + 64 * 1024, // Add 64KB padding to avoid potential overflows
Alignment = ResourceHeap.DEFAULT_ALIGNMENT,
HeapFlags = HeapFlags.AlowBufferAndTexture,
HeapFlags = HeapFlags.AllowAllBufferAndTexture,
HeapType = HeapType.Default
};

View File

@@ -12,10 +12,10 @@ public interface IRenderGraphContext
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
Handle<GPUTexture> GetActualTexture(Identifier<RGTexture> texture);
Handle<RHI.GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer);
Handle<GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer);
Handle<GPUTexture> GetHistoryTexture(ReadOnlySpan<Identifier<RGTexture>> texture, int historyOffset);
Handle<RHI.GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffer, int historyOffset);
Handle<GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffer, int historyOffset);
ICommandBuffer GetCommandBufferUnsafe();
}
@@ -60,8 +60,8 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
private TextureFormat _dsvFormat;
private int _rtvCount;
private Handle<RHI.GPUBuffer> _activePerMaterialData;
private Handle<RHI.GPUBuffer> _activePerMeshData;
private Handle<GPUBuffer> _activePerMaterialData;
private Handle<GPUBuffer> _activePerMeshData;
private int _activeMeshIndexCount;
private uint _activeFrameBuffer;
@@ -120,9 +120,9 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
return _resources.GetResource(texture.AsResource()).backingResource.AsTexture();
}
public Handle<RHI.GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer)
public Handle<GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer)
{
return _resources.GetResource(buffer.AsResource()).backingResource.AsGraphicsBuffer();
return _resources.GetResource(buffer.AsResource()).backingResource.AsBuffer();
}
public Handle<GPUTexture> GetHistoryTexture(ReadOnlySpan<Identifier<RGTexture>> textures, int historyOffset)
@@ -141,11 +141,11 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
return GetActualTexture(textures[index]);
}
public Handle<RHI.GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffers, int historyOffset)
public Handle<GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffers, int historyOffset)
{
if (historyOffset < 0 || historyOffset >= buffers.Length)
{
return Handle<RHI.GPUBuffer>.Invalid;
return Handle<GPUBuffer>.Invalid;
}
var index = (int)(_frameIndex % buffers.Length) - historyOffset;
@@ -172,7 +172,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
var r = _resourceManager.GetMaterialReference(material);
if (r.IsFailure)
{
_activePerMaterialData = Handle<RHI.GPUBuffer>.Invalid;
_activePerMaterialData = Handle<GPUBuffer>.Invalid;
return;
}
@@ -185,7 +185,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
var shaderResult = _resourceManager.GetShaderReference(material.Shader);
if (shaderResult.IsFailure)
{
_activePerMaterialData = Handle<RHI.GPUBuffer>.Invalid;
_activePerMaterialData = Handle<GPUBuffer>.Invalid;
return;
}
@@ -230,7 +230,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
var r = _resourceManager.GetMeshReference(mesh);
if (r.IsFailure)
{
_activePerMeshData = Handle<RHI.GPUBuffer>.Invalid;
_activePerMeshData = Handle<GPUBuffer>.Invalid;
_activeMeshIndexCount = 0;
return;
}

View File

@@ -78,7 +78,7 @@ internal static class RenderGraphHasher
}
/// <summary>
/// Computes a hash of a texture resource's structural properties.
/// Computes a hash of a texture heap's structural properties.
/// For imported textures, hashes the backing handle.
/// For transient textures, hashes the descriptor (respecting size mode).
/// </summary>
@@ -94,7 +94,7 @@ internal static class RenderGraphHasher
// Hash imported flag
writer.Write(resource.isImported);
// For imported textures, hash the backing resource handle
// For imported textures, hash the backing heap handle
if (resource.isImported)
{
writer.Write(resource.backingResource.GetHashCode());

View File

@@ -204,7 +204,7 @@ internal sealed class RenderGraphNativePassBuilder
{
var laterPass = compiledPasses[passB];
// Build a set of render target resource IDs (color + depth)
// Build a set of render target heap IDs (color + depth)
var renderTargets = new HashSet<Identifier<RGResource>>();
for (var i = 0; i <= laterPass.maxColorIndex; i++)
{
@@ -241,7 +241,7 @@ internal sealed class RenderGraphNativePassBuilder
/// <summary>
/// Infers optimal load/store operations for all attachments in a native render pass.
/// Uses resource lifetime information to minimize memory bandwidth (critical for TBDR GPUs).
/// Uses heap lifetime information to minimize memory bandwidth (critical for TBDR GPUs).
/// </summary>
private void InferLoadStoreOps(NativeRenderPass nativePass)
{

View File

@@ -211,7 +211,7 @@ internal sealed class RenderGraphResourceRegistry
return new Identifier<RGTexture>(resource.index);
}
public Identifier<RGBuffer> ImportBuffer(ref readonly BufferDesc desc, Handle<RHI.GPUBuffer> buffer, string name)
public Identifier<RGBuffer> ImportBuffer(ref readonly BufferDesc desc, Handle<GPUBuffer> buffer, string name)
{
var resource = _pool.Rent<RenderGraphResource>();
resource.name = name;
@@ -256,7 +256,7 @@ internal sealed class RenderGraphResourceRegistry
}
/// <summary>
/// Gets resource by global index. Use this when iterating over all resources.
/// Gets heap by global index. Use this when iterating over all resources.
/// </summary>
public RenderGraphResource GetResourceByIndex(int index)
{

View File

@@ -163,7 +163,7 @@ public class RenderSystem : IDisposable
throw new NotSupportedException($"The specified graphics API '{desc.GraphicsAPI}' is not supported.");
}
_resourceManager = new ResourceManager(_graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
_resourceManager = new ResourceManager(_graphicsEngine.Device, _graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
_swapChainManager = new SwapChainManager(_graphicsEngine);
// Create frame resources for synchronization
@@ -246,7 +246,7 @@ public class RenderSystem : IDisposable
var flushFence = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue);
_graphicsEngine.Device.GraphicsQueue.WaitForValue(flushFence);
// Sync the current frame resource to this new fence to keep state consistent
// Sync the current frame heap to this new fence to keep state consistent
frameResource.FenceValue = flushFence;
foreach (var resource in _frameResources)
@@ -322,7 +322,7 @@ public class RenderSystem : IDisposable
}
// End the frame and present
_resourceManager.EndFrame(_cpuFenceValue);
_resourceManager.EndFrame(_gpuFenceValue);
r = _graphicsEngine.EndFrame(_gpuFenceValue);
if (r.IsFailure)
@@ -426,9 +426,11 @@ public class RenderSystem : IDisposable
_renderPipeline.Dispose();
_swapChainManager.Dispose();
_resourceManager.Dispose();
_graphicsEngine.Dispose();
_swapChainManager.Dispose();
_shutdownEvent.Dispose();
_disposed = true;

View File

@@ -0,0 +1,271 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
namespace Ghost.Graphics;
public partial class ResourceManager
{
private const ulong _DEFAULT_TRANSIENT_PAGE_SIZE = 16 * 1024 * 1024; // 16MB
private struct Page
{
public Handle<GPUResource> heap;
public ulong offset;
public HeapFlags heapFlags;
public HeapType heapType;
}
private struct RetiringPage
{
public Page page;
public ulong retireFrame;
}
private UnsafeList<Page> _activePages;
private UnsafeQueue<RetiringPage> _retiringPages;
private UnsafeList<Handle<GPUResource>> _oversizedTransientResources;
private void InitializePool()
{
_activePages = new UnsafeList<Page>(4, Allocator.Persistent);
_retiringPages = new UnsafeQueue<RetiringPage>(4, Allocator.Persistent);
_oversizedTransientResources = new UnsafeList<Handle<GPUResource>>(4, Allocator.Persistent);
}
private Error CreateNewActivePage(HeapType heapType, HeapFlags heapFlags)
{
var allocationDesc = new AllocationDesc
{
Size = _DEFAULT_TRANSIENT_PAGE_SIZE,
Alignment = 65536, // 64KB
HeapType = heapType,
HeapFlags = heapFlags,
};
var buffer = _resourceAllocator.Allocate(in allocationDesc, $"Page {_activePages.Count + _retiringPages.Count}");
if (buffer.IsInvalid)
{
return Error.OutOfMemory;
}
_activePages.Add(new Page
{
heap = buffer,
offset = 0,
heapFlags = heapFlags,
heapType = heapType,
});
return Error.None;
}
public Handle<GPUTexture> CreateTransientTexture(ref readonly TextureDesc desc, string? name = null)
{
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)
{
var texHandle = _resourceAllocator.CreateTexture(in desc, name);
if (texHandle.IsValid)
{
_oversizedTransientResources.Add(texHandle.AsResource());
}
return texHandle;
}
var requiredHeapFlags = _renderDevice.FeatureSupport.HasFlag(FeatureSupport.AliasBuffersAndTextures) ?
HeapFlags.AllowAllBufferAndTexture :
isRTOrDS ? HeapFlags.AllowOnlyRTAndDS : HeapFlags.AllowOnlyTextures;
var foundPageIndex = -1;
var alignedOffset = 0UL;
for (var i = 0; i < _activePages.Count; i++)
{
ref var p = ref _activePages[i];
if (p.heapType != HeapType.Default)
{
continue;
}
if (p.heapFlags != requiredHeapFlags && p.heapFlags != HeapFlags.AllowAllBufferAndTexture)
{
continue;
}
var proposedOffset = (p.offset + (size.Alignment - 1)) & ~(size.Alignment - 1);
if (proposedOffset + size.Size <= _DEFAULT_TRANSIENT_PAGE_SIZE)
{
foundPageIndex = i;
alignedOffset = proposedOffset;
break;
}
}
if (foundPageIndex == -1)
{
var error = CreateNewActivePage(HeapType.Default, requiredHeapFlags);
if (error != Error.None)
{
Debug.Fail($"Failed to create a new page for transient texture: {error}");
return Handle<GPUTexture>.Invalid;
}
foundPageIndex = _activePages.Count - 1;
alignedOffset = 0;
}
ref var page = ref _activePages[foundPageIndex];
var handle = _resourceAllocator.CreateTexture(in desc, name, new CreationOptions
{
AllocationType = ResourceAllocationType.Suballocation,
Heap = page.heap,
Offset = alignedOffset,
});
page.offset = alignedOffset + size.Size;
return handle;
}
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)
{
var bufHandle = _resourceAllocator.CreateBuffer(in desc, name);
if (bufHandle.IsValid)
{
_oversizedTransientResources.Add(bufHandle.AsResource());
}
return bufHandle;
}
var requiredHeapType = desc.MemoryType switch
{
ResourceMemoryType.Upload => HeapType.Upload,
ResourceMemoryType.Readback => HeapType.Readback,
_ => HeapType.Default
};
var requiredHeapFlags = _renderDevice.FeatureSupport.HasFlag(FeatureSupport.AliasBuffersAndTextures) ?
HeapFlags.AllowAllBufferAndTexture : HeapFlags.AllowOnlyBuffers;
var foundPageIndex = -1;
var alignedOffset = 0UL;
for (var i = 0; i < _activePages.Count; i++)
{
ref var p = ref _activePages[i];
if (p.heapType != requiredHeapType)
{
continue;
}
if (p.heapFlags != requiredHeapFlags && p.heapFlags != HeapFlags.AllowAllBufferAndTexture)
{
continue;
}
var proposedOffset = (p.offset + (size.Alignment - 1)) & ~(size.Alignment - 1);
if (proposedOffset + size.Size <= _DEFAULT_TRANSIENT_PAGE_SIZE)
{
foundPageIndex = i;
alignedOffset = proposedOffset;
break;
}
}
if (foundPageIndex == -1)
{
var error = CreateNewActivePage(requiredHeapType, requiredHeapFlags);
if (error != Error.None)
{
Debug.Fail($"Failed to create a new page for transient buffer: {error}");
return Handle<GPUBuffer>.Invalid;
}
foundPageIndex = _activePages.Count - 1;
alignedOffset = 0;
}
ref var page = ref _activePages[foundPageIndex];
var handle = _resourceAllocator.CreateBuffer(in desc, name, new CreationOptions
{
AllocationType = ResourceAllocationType.Suballocation,
Heap = page.heap,
Offset = alignedOffset,
});
page.offset = alignedOffset + size.Size;
return handle;
}
private void EndFramePool(ulong gpuFrame)
{
for (var i = 0; i < _activePages.Count; i++)
{
ref var page = ref _activePages[i];
_retiringPages.Enqueue(new RetiringPage
{
page = page,
retireFrame = _cpuFrame
});
}
_activePages.Clear();
while (_retiringPages.TryPeek(out var retiringPage) && retiringPage.retireFrame <= gpuFrame)
{
_retiringPages.Dequeue();
// Reset the page for reuse
retiringPage.page.offset = 0;
_activePages.Add(retiringPage.page);
}
for (var i = 0; i < _oversizedTransientResources.Count; i++)
{
_resourceDatabase.ReleaseResource(_oversizedTransientResources[i]);
}
_oversizedTransientResources.Clear();
}
private void DisposePool()
{
foreach (var page in _activePages)
{
_resourceDatabase.ReleaseResourceImmediately(page.heap);
}
foreach (var page in _retiringPages)
{
_resourceDatabase.ReleaseResourceImmediately(page.page.heap);
}
foreach (var resource in _oversizedTransientResources)
{
_resourceDatabase.ReleaseResourceImmediately(resource);
}
_activePages.Dispose();
_retiringPages.Dispose();
_oversizedTransientResources.Dispose();
}
}

View File

@@ -8,7 +8,7 @@ using System.Diagnostics;
namespace Ghost.Graphics;
public sealed class ResourceManager : IDisposable
public sealed partial class ResourceManager : IDisposable
{
private readonly struct ResourceReturnEntry
{
@@ -22,6 +22,7 @@ public sealed class ResourceManager : IDisposable
}
}
private readonly IRenderDevice _renderDevice;
private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase;
@@ -31,15 +32,13 @@ public sealed class ResourceManager : IDisposable
private readonly MaterialPaletteStore _materialPalettes;
private ulong _currentFrame;
private UnsafeHashMap<ResourceDesc, UnsafeQueue<Handle<GPUResource>>> _resourceCache;
private UnsafeQueue<ResourceReturnEntry> _resourceReturnQueue;
private ulong _cpuFrame;
private bool _disposed;
public ResourceManager(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
public ResourceManager(IRenderDevice renderDevice, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
{
_renderDevice = renderDevice;
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
@@ -48,9 +47,8 @@ public sealed class ResourceManager : IDisposable
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
_materialPalettes = new MaterialPaletteStore();
_resourceCache = new UnsafeHashMap<ResourceDesc, UnsafeQueue<Handle<GPUResource>>>(32, Allocator.Persistent);
_resourceReturnQueue = new UnsafeQueue<ResourceReturnEntry>(32, Allocator.Persistent);
InitializePool();
}
~ResourceManager()
@@ -58,30 +56,16 @@ public sealed class ResourceManager : IDisposable
Dispose();
}
internal void BeginFrame(ulong currentFrame)
internal void BeginFrame(ulong cpuFrame)
{
Debug.Assert(!_disposed);
_currentFrame = currentFrame;
_cpuFrame = cpuFrame;
}
internal void EndFrame(ulong completedFrame)
internal void EndFrame(ulong gpuFrame)
{
Debug.Assert(!_disposed);
while (_resourceReturnQueue.TryPeek(out var entry) && entry.returnFrame <= completedFrame)
{
_resourceReturnQueue.Dequeue();
var result = _resourceDatabase.GetResourceDescription(entry.handle);
Debug.Assert(result.IsSuccess);
ref var queue = ref _resourceCache.GetValueRefOrAddDefault(result.Value, out var exist);
if (!exist)
{
queue = new UnsafeQueue<Handle<GPUResource>>(4, Allocator.Persistent);
}
queue.Enqueue(entry.handle);
}
EndFramePool(gpuFrame);
}
/// <summary>
@@ -198,7 +182,7 @@ public sealed class ResourceManager : IDisposable
}
/// <summary>
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
/// Releases the mesh heap associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
/// </summary>
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
public void ReleaseMesh(Handle<Mesh> handle)
@@ -364,38 +348,6 @@ public sealed class ResourceManager : IDisposable
shader.ReleaseResource(_resourceDatabase);
}
public Handle<GPUResource> GetPooledResource(in ResourceDesc desc)
{
Debug.Assert(!_disposed);
ref var queue = ref _resourceCache.GetValueRef(desc, out var exist);
if (exist && queue.TryDequeue(out Handle<GPUResource> handle))
{
return handle;
}
handle = desc.Type switch
{
ResourceType.Buffer => _resourceAllocator.CreateBuffer(in desc.BufferDescription, "PooledBuffer").AsResource(),
ResourceType.Texture => _resourceAllocator.CreateTexture(in desc.TextureDescription, "PooledTexture").AsResource(),
_ => throw new ArgumentException("Invalid resource type.", nameof(desc)),
};
return handle;
}
public void ReturnPooledResource(Handle<GPUResource> handle)
{
Debug.Assert(!_disposed);
if (handle.IsInvalid)
{
return;
}
_resourceReturnQueue.Enqueue(new ResourceReturnEntry(handle, _currentFrame));
}
public void Dispose()
{
if (_disposed)
@@ -423,19 +375,7 @@ public sealed class ResourceManager : IDisposable
_shaders.Dispose();
_materialPalettes.Dispose();
foreach (var kvp in _resourceCache)
{
var queue = kvp.Value;
while (queue.TryDequeue(out var handle))
{
_resourceDatabase.ReleaseResource(handle);
}
queue.Dispose();
}
_resourceCache.Dispose();
_resourceReturnQueue.Dispose();
DisposePool();
_disposed = true;
GC.SuppressFinalize(this);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,354 @@
//////////////////////////////////////////////////////////////////////////
// Shader To Human (S2H) - HLSL/GLSL library for debugging shaders //
// Copyright (c) 2024-2025 Electronic Arts Inc. All rights reserved. //
//////////////////////////////////////////////////////////////////////////
// Example:
// #include "s2h.h"
// #include "s2h_3d.h"
// {
// struct Context3D context;
// // todo
// s2h_init(context);
// }
#ifndef S2H_3D_INCLUDE
#define S2H_3D_INCLUDE
struct Context3D
{
// ray origin
float3 ro;
// ray direction, normalized?
float3 rd;
// surfacePos = ro + rd * depth
float depth;
//
float4 dstColor;
};
//
void s2h_init(out Context3D context, float3 ro, float3 rd);
// @param thickness e.g. 0.09f
void s2h_drawLineWS(inout Context3D context, float3 from, float3 to, float4 color, float thickness);
// AABB: Axis Aligned Bounding Box
void s2h_drawAABB(inout Context3D context, float3 center, float3 halfSize, float4 color);
//
void s2h_drawArrowWS(inout Context3D context, float3 from, float3 to, float4 color, float thickness);
// @param worldFromObject aka objectToWorld
// @param r in world space units
void s2h_drawBasis(inout Context3D context, float4x4 worldFromObject, float r);
// @param radius e.g. 0.1f
void s2h_drawSphereWS(inout Context3D context, float3 pos, float4 color, float radius);
// 8x8 checker board with X (red) and Z (blue) around offset pointing up (Y+)
void s2h_drawCheckerBoard(inout Context3D context, float3 offset);
// infinitely far, +/- X/Z and horizon, useful to having some background for the user to orient themselves
void s2h_drawSkybox(inout Context3D context);
// implementation ----------------------------------------------------------------------
// @param ro ray origin
// @param ro ray direction
void s2h_init(out Context3D context, float3 ro, float3 rd)
{
context.ro = ro;
context.rd = rd;
context.depth = S2H_FLT_MAX;
context.dstColor = float4(0, 0, 0, 0);
}
// Inigo Quilez sphere ray intersection https://iquilezles.org/articles/intersectors
// sphere of size ra centered at point ce
float2 s2h_sphIntersect( in float3 ro, in float3 rd, in float3 ce, float ra )
{
float3 oc = ro - ce;
float b = dot( oc, rd );
float c = dot( oc, oc ) - ra*ra;
float h = b*b - c;
if( h<0.0 ) return float2(-1.0, -1.0); // no intersection
h = sqrt( h );
return float2( -b-h, -b+h );
}
// Inigo Quilez box ray intersection https://iquilezles.org/articles/intersectors
// axis aligned box centered at the origin, with size boxSize
float2 s2h_boxIntersection( in float3 ro, in float3 rd, float3 boxSize, out float3 outNormal )
{
float3 m = 1.0/rd; // can precompute if traversing a set of aligned boxes
float3 n = m*ro; // can precompute if traversing a set of aligned boxes
float3 k = abs(m)*boxSize;
float3 t1 = -n - k;
float3 t2 = -n + k;
float tN = max( max( t1.x, t1.y ), t1.z );
float tF = min( min( t2.x, t2.y ), t2.z );
if( tN>tF || tF<0.0) return float2(-1.0, -1.0); // no intersection
outNormal = (tN>0.0) ? step(float3(tN, tN, tN),t1) : // ro outside the box
step(t2,float3(tF, tF, tF)); // ro inside the box
outNormal *= -sign(rd);
return float2( tN, tF );
}
// Inigo Quilez box cylinder intersection https://iquilezles.org/articles/intersectors
// cylinder defined by extremes a and b, and radious ra
float4 s2h_cylIntersect( in float3 ro, in float3 rd, in float3 a, in float3 b, float ra )
{
float3 ba = b - a;
float3 oc = ro - a;
float baba = dot(ba,ba);
float bard = dot(ba,rd);
float baoc = dot(ba,oc);
float k2 = baba - bard*bard;
float k1 = baba*dot(oc,rd) - baoc*bard;
float k0 = baba*dot(oc,oc) - baoc*baoc - ra*ra*baba;
float h = k1*k1 - k2*k0;
if( h<0.0 ) return float4(-1.0, -1.0, -1.0, -1.0);//no intersection
h = sqrt(h);
float t = (-k1-h)/k2;
// body
float y = baoc + t*bard;
if( y>0.0 && y<baba ) return float4( t, (oc+t*rd - ba*y/baba)/ra );
// caps
t = ( ((y<0.0) ? 0.0 : baba) - baoc)/bard;
if( abs(k1+k2*t)<h )
{
return float4( t, ba*sign(y)/sqrt(baba) );
}
return float4(-1.0, -1.0, -1.0, -1.0);//no intersection
}
// normal at point p of cylinder (a,b,ra), see above
float3 s2h_cylNormal( in float3 p, in float3 a, in float3 b, float ra )
{
float3 pa = p - a;
float3 ba = b - a;
float baba = dot(ba,ba);
float paba = dot(pa,ba);
float h = dot(pa,ba)/baba;
return (pa - ba*h)/ra;
}
float s2h_dot2(float3 p)
{
return dot(p,p);
}
// cone defined by extremes pa and pb, and radious ra and rb
// Only one square root and one division is emplyed in the worst case. s2h_dot2(v) is dot(v,v)
// @param float4(t, normal)
float4 s2h_coneIntersect( in float3 ro, in float3 rd, in float3 pa, in float3 pb, in float ra, in float rb )
{
float3 ba = pb - pa;
float3 oa = ro - pa;
float3 ob = ro - pb;
float m0 = dot(ba,ba);
float m1 = dot(oa,ba);
float m2 = dot(rd,ba);
float m3 = dot(rd,oa);
float m5 = dot(oa,oa);
float m9 = dot(ob,ba);
// caps
if( m1<0.0 )
{
if( s2h_dot2(oa*m2-rd*m1)<(ra*ra*m2*m2) ) // delayed division
return float4(-m1/m2,-ba*rsqrt(m0));
}
else if( m9>0.0 )
{
float t = -m9/m2; // NOT delayed division
if( s2h_dot2(ob+rd*t)<(rb*rb) )
return float4(t,ba*rsqrt(m0));
}
// body
float rr = ra - rb;
float hy = m0 + rr*rr;
float k2 = m0*m0 - m2*m2*hy;
float k1 = m0*m0*m3 - m1*m2*hy + m0*ra*(rr*m2*1.0 );
float k0 = m0*m0*m5 - m1*m1*hy + m0*ra*(rr*m1*2.0 - m0*ra);
float h = k1*k1 - k2*k0;
if( h<0.0 ) return float4(-1.0, -1.0, -1.0, -1.0); //no intersection
float t = (-k1-sqrt(h))/k2;
float y = m1 + t*m2;
if( y<0.0 || y>m0 ) return float4(-1.0, -1.0, -1.0, -1.0); //no intersection
return float4(t, normalize(m0*(m0*(oa+t*rd)+rr*ba*ra)-ba*hy*y));
}
void s2h_drawAABB(inout Context3D context, float3 center, float3 halfSize, float4 color)
{
float3 normal;
float2 hit = s2h_boxIntersection(context.ro - center, context.rd, halfSize, normal);
if(hit.y > 0.0f && hit.x < context.depth)
{
context.depth = hit.x;
context.dstColor = color;
context.dstColor = lerp(context.dstColor, float4(normal * 0.5f + 0.5f, 1), 0.3f);
}
}
void s2h_drawLineWS(inout Context3D context, float3 from, float3 to, float4 color, float thickness)
{
float2 hit = s2h_cylIntersect(context.ro, context.rd, from, to, thickness).xy;
if(hit.x > 0.0 && hit.x < context.depth)
{
context.depth = hit.x;
float3 p = context.ro + context.depth * context.rd;
float3 normal = s2h_cylNormal(p, from, to, thickness);
// todo: refine shading
color.rgb = lerp(color.rgb, normal * 0.5f + 0.5f, 0.3f);
context.dstColor = color;
}
}
void s2h_drawArrowWS(inout Context3D context, float3 from, float3 to, float4 color, float thickness)
{
float4 hit = s2h_coneIntersect(context.ro, context.rd, from, to, thickness, 0.0f);
if(hit.x > 0.0 && hit.x < context.depth)
{
context.depth = hit.x;
float3 normal = hit.yzw;
// todo: refine shading
color.rgb = lerp(color.rgb, normal * 0.5 + 0.5, 0.3);
context.dstColor = color;
}
}
void s2h_drawBasis(inout Context3D context, float4x4 worldFromObject, float r)
{
float4 oHom = mul(worldFromObject, float4(0, 0, 0, 1));
float4 xHom = mul(worldFromObject, float4(r, 0, 0, 1));
float4 yHom = mul(worldFromObject, float4(0, r, 0, 1));
float4 zHom = mul(worldFromObject, float4(0, 0, r, 1));
float3 o = oHom.xyz / oHom.w;
float3 x = xHom.xyz / xHom.w;
float3 y = yHom.xyz / yHom.w;
float3 z = zHom.xyz / zHom.w;
s2h_drawArrowWS(context, o, x, float4(1, 0, 0, 1), 0.09f);
s2h_drawArrowWS(context, o, y, float4(0, 1, 0, 1), 0.09f);
s2h_drawArrowWS(context, o, z, float4(0, 0, 1, 1), 0.09f);
}
void s2h_drawSphereWS(inout Context3D context, float3 pos, float4 color, float radius)
{
float2 hit = s2h_sphIntersect(context.ro, context.rd, pos, radius);
if(hit.x > 0.0 && hit.x < context.depth)
{
float3 hitPos = context.ro + hit.x * context.rd;
float3 normal = normalize(hitPos - pos);
context.depth = hit.x;
// todo: refine shading
color.rgb = lerp(color.rgb, normal * 0.5 + 0.5, 0.3);
context.dstColor = color;
}
}
void s2h_drawCheckerBoard(inout Context3D context, float3 offset)
{
float3 pos = float3(0, -0.2, 0) + offset;
float3 size = float3(4.4, 0.2, 4.4);
float3 normal;
float2 hit = s2h_boxIntersection(context.ro - pos, context.rd, size, normal);
if(hit.y > 0.0f && hit.x < context.depth)
{
context.depth = hit.x;
float3 hitPos = context.ro + hit.x * context.rd;
float2 uv = hitPos.zx;
float value = 1.0f;
if(abs(uv.x) < 4.0f && abs(uv.y) < 4.0f)
value = frac(floor(uv.x) * 0.5f + floor(uv.y) * 0.5f) > 0.25f ? 0.4f : 0.6f;
context.dstColor = float4(value, value, value, 1);
if(abs(uv.x) < (4.0f - uv.y) * 0.1f && uv.y > 0.0f)
context.dstColor.rgb = float3(1,0,0);
if(abs(uv.y) < (4.0f - uv.x) * 0.1f && uv.x > 0.0f)
context.dstColor.rgb = float3(0,0,1);
if(dot(uv, uv) < 0.25f)
context.dstColor.rgb = float3(0,1,0);
context.dstColor = lerp(context.dstColor, float4(normal * 0.5f + 0.5f, 1), 0.3f);
}
}
void s2h_drawSkybox(inout Context3D context)
{
if(context.depth == S2H_FLT_MAX)
{
float3 d = context.rd;
float pi = 3.14159265f;
// assuming normalized rd
float2 uv = float2(-atan2(d.z, d.x) / pi + 1.0, acos(d.y) / pi);
float2 px = uv * float2(s2h_fontSize() * 8.0, s2h_fontSize() * 4.0);
float tileX = s2h_fontSize() * 4.0;
// 4*4 characters around the x axis
ContextGather ui;
s2h_init(ui, float2(frac(px.x / tileX + 0.5f) * tileX, px.y));
// horizon
ui.dstColor.rgb = float3(1,1,1) * saturate(1.0f - pow(abs(d.y), 0.2f));
ui.dstColor.a = 1.0f;
// grid
{
float2 gridXY = frac(ui.pxPos);
gridXY = min(gridXY, float2(1.0f, 1.0f) - gridXY);
// 0 .. 0.5
float grid = min(gridXY.x, gridXY.y);
ui.dstColor.rgb = lerp(ui.dstColor.rgb, float3(1,1,1), 0.07f * saturate(1.0f - grid * 30.0f));
}
bool xzAxis = abs(d.x) > abs(d.z);
bool posAxis = xzAxis ? (d.x > 0.0f) : (d.z > 0.0f);
s2h_setCursor(ui, float2(0, 12));
ui.textColor.rgb = xzAxis ? float3(1, 0, 0) : float3(0, 0, 1);
ui.textColor.a = 0.4f;
s2h_printTxt(ui, _SPACE);
s2h_printTxt(ui, posAxis ? _PLUS : _MINUS);
s2h_printTxt(ui, xzAxis ? _X : _Z);
context.dstColor = ui.dstColor;
}
}
void scene(inout Context3D context);
void sceneWithShadows(inout Context3D context)
{
scene(context);
float4 litScene = context.dstColor;
// shadow ray, experiment, todo: expose light direction
if(context.depth < S2H_FLT_MAX)
{
// 0..1
float visible;
{
const float bias = 0.001;
Context3D shadowContext;
s2h_init(shadowContext, context.ro + context.depth * context.rd, normalize(float3(1.0,3.0,2.0)));
shadowContext.ro += bias * shadowContext.rd;
scene(shadowContext);
visible = shadowContext.depth == S2H_FLT_MAX ? 1.0 : 0.0;
}
// shadows are grey, todo: expose ambient color
float shadowFactor = 0.5 - visible * 0.5;
context.dstColor.rgb = lerp(litScene.rgb, float3(0.0, 0.0, 0.0), shadowFactor);
}
}
#endif // S2H_3D_INCLUDE

View File

@@ -0,0 +1,245 @@
//////////////////////////////////////////////////////////////////////////
// Shader To Human (S2H) - HLSL/GLSL library for debugging shaders //
// Copyright (c) 2024-2025 Electronic Arts Inc. All rights reserved. //
//////////////////////////////////////////////////////////////////////////
// Example:
// #include "s2h.h"
// #include "s2h_scatter.h"
// {
// struct ContextScatter ui;
// s2h_init(ui);
// s2h_printTxt(ui, _A, _B);
// }
// void onGfxForAllScatter(int2 pxPos, float4 color)
// {
// g_computeOutput[pxPos] = color;
// }
#ifndef S2H_SCATTER_INCLUDE
#define S2H_SCATTER_INCLUDE
// documentation:
struct ContextScatter
{
// RGBA, alpha 1 is assumed to be opaque
float4 textColor;
// private, for internal use, might change --------
// in pixels
int2 pxCursor;
// window left top, set by s2h_init()
int pxLeftX;
// 1/2/3/4, call s2h_setScale()
int scale;
};
// first call this
void s2h_init(out ContextScatter ui);
// set text cursor position, next printLF() will reset to this x position
void s2h_setCursor(inout ContextScatter ui, float2 inpxLeftTop);
// @param scale 1:pixel perfect, 2:2x, 3:3x, ..
void s2h_setScale(inout ContextScatter ui, uint scale);
// e.g. ui.s2h_printTxt('I', ' ', 'a', 'm');
// @param a ascii character or 0
void s2h_printTxt(inout ContextScatter ui, uint a, uint b, uint c, uint d, uint e, uint f);
// jump to next line
void s2h_printLF(inout ContextScatter ui);
// @param value e.g. 123, 0
void s2h_printInt(inout ContextScatter ui, int value);
// print hexadecimal e.g. "0000aa34"
// @param value 32bit e.g. 0x123, 0xff00
void s2h_printHex(inout ContextScatter ui, uint value);
// @param output e.g. g_output from RWTexture2D<float3> g_output : register(u0, space0);
// @param pos in pixels from left top, left top of the printout
// @param value
void s2h_printFloat(inout ContextScatter ui, float value);
// block in a 8x8 character
void s2h_printBlock(inout ContextScatter ui, float4 color);
// circle in a 8x8 character
void s2h_printDisc(inout ContextScatter ui, float4 color);
// don't use directly
void s2h_printCharacter(inout ContextScatter ui, uint ascii);
// no AA
void s2h_drawCrosshair(inout ContextScatter ui, float2 pxCenter, float pxRadius, float4 color);
// implementation ----------------------------------------------------------------------
void s2h_init(out ContextScatter ui)
{
// white, opaque
ui.textColor = float4(1, 1, 1, 1);
ui.pxCursor = int2(0, 0);
ui.pxLeftX = ui.pxCursor.x;
ui.scale = 1;
}
void s2h_setCursor(inout ContextScatter ui, float2 inpxLeftTop)
{
ui.pxCursor = inpxLeftTop;
ui.pxLeftX = inpxLeftTop.x;
}
void s2h_setScale(inout ContextScatter ui, uint scale)
{
ui.scale = scale;
}
// implement this in your code
void onGfxForAllScatter(int2 pxPos, float4 color);
void s2h_printCharacter(inout ContextScatter ui, uint ascii)
{
[loop] for(int y = 0; y < 8 * ui.scale; ++y)
[loop] for(int x = 0; x < 8 * ui.scale; ++x)
if(s2h_fontLookup(ascii, int2(x, y) / ui.scale))
onGfxForAllScatter(ui.pxCursor + int2(x, y), ui.textColor);
ui.pxCursor.x += 8 * ui.scale;
}
void s2h_drawCrosshair(inout ContextScatter ui, float2 pxCenter, float pxRadius, float4 color)
{
// avoiding int math for better performance
onGfxForAllScatter(pxCenter, ui.textColor);
[loop] for(float i = 1; i < pxRadius; ++i)
{
onGfxForAllScatter(pxCenter + float2( i, 0), color);
onGfxForAllScatter(pxCenter+ float2(-i, 0), color);
onGfxForAllScatter(pxCenter+ float2( 0, i), color);
onGfxForAllScatter(pxCenter + float2( 0, -i), color);
}
}
void s2h_printTxt(inout ContextScatter ui, uint a)
{
s2h_printCharacter(ui, a);
}
// glsl has no default arguments to we implement multiple functions instead making porting easier
void s2h_printTxt(inout ContextScatter ui, uint a, uint b)
{ s2h_printTxt(ui, a); s2h_printCharacter(ui, b); }
void s2h_printTxt(inout ContextScatter ui, uint a, uint b, uint c)
{ s2h_printTxt(ui, a, b); s2h_printCharacter(ui, c); }
void s2h_printTxt(inout ContextScatter ui, uint a, uint b, uint c, uint d)
{ s2h_printTxt(ui, a, b, c); s2h_printCharacter(ui, d); }
void s2h_printTxt(inout ContextScatter ui, uint a, uint b, uint c, uint d, uint e)
{ s2h_printTxt(ui, a, b, c, d); s2h_printCharacter(ui, e); }
void s2h_printTxt(inout ContextScatter ui, uint a, uint b, uint c, uint d, uint e, uint f)
{ s2h_printTxt(ui, a, b, c, d, e); s2h_printCharacter(ui, f); }
void s2h_printLF(inout ContextScatter ui)
{
ui.pxCursor.x = ui.pxLeftX;
ui.pxCursor.y += 8 * ui.scale;
}
void s2h_printInt(inout ContextScatter ui, int value)
{
// leading '-'
if (value < 0)
{
s2h_printCharacter(ui, '-');
value = -value;
}
if (value == 0)
{
s2h_printCharacter(ui, '0');
return;
}
// move to right depending on number length
{
uint tmp = (uint)value;
while (tmp != 0u)
{
ui.pxCursor.x += 8 * ui.scale;
tmp /= 10u;
}
}
// digits
{
float backup = ui.pxCursor.x;
uint tmp = (uint)value;
while (tmp != 0u)
{
// 0..9
uint digit = tmp % 10u;
tmp /= 10u;
// go backwards
ui.pxCursor.x -= 8 * ui.scale;
s2h_printCharacter(ui, '0' + digit);
// counter +=8 from printCharacter ()
ui.pxCursor.x -= 8 * ui.scale;
}
ui.pxCursor.x = backup;
}
}
void s2h_printHex(inout ContextScatter ui, uint value)
{
// 8 nibbles
for(int i = 7; i >= 0; --i)
{
// 0..15
uint nibble = (value >> (i * 4)) & 0xf;
uint start = (nibble < 10) ? '0' : ('A' - 10);
s2h_printCharacter(ui, start + nibble);
}
}
void s2h_printFloat(inout ContextScatter ui, float value)
{
s2h_printInt(ui, int(value));
float fractional = frac(abs(value));
s2h_printCharacter(ui, '.');
uint digitCount = 3u;
// todo: unit tests, this is likely wrong at lower precision
// fractional digits
for(uint i = 0u; i < digitCount; ++i)
{
fractional *= 10.0f;
// 0..9
uint digit = uint(fractional);
fractional = frac(fractional);
s2h_printCharacter(ui, '0' + digit);
}
}
void s2h_printBlock(inout ContextScatter ui, float4 color)
{
[loop] for(int y = 0; y < 8 * ui.scale; ++y)
[loop] for(int x = 0; x < 8 * ui.scale; ++x)
{
float2 pxLocal = (float2(x, y) ) / ui.scale - float2(3.5f, 3.5f);
float mask = saturate(4 - max(abs(pxLocal.x), abs(pxLocal.y)));
if(mask)
onGfxForAllScatter(ui.pxCursor + int2(x,y), color);
}
ui.pxCursor.x += 8 * ui.scale;
}
void s2h_printDisc(inout ContextScatter ui, float4 color)
{
[loop] for(int y = 0; y < 8 * ui.scale; ++y)
[loop] for(int x = 0; x < 8 * ui.scale; ++x)
{
float2 pxLocal = (float2(x, y) ) / ui.scale - float2(3.5f, 3.5f);
float mask = saturate(4 - length(pxLocal));
if(mask)
onGfxForAllScatter(ui.pxCursor + int2(x,y), color);
}
ui.pxCursor.x += 8 * ui.scale;
}
#endif // S2H_SCATTER_INCLUDE

View File

@@ -1,4 +1,6 @@
using Ghost.Graphics.Core;
// Source: https://github.com/zeux/meshoptimizer/blob/master/demo/clusterlod.h
// Translated from C++ to C#.
using Ghost.MeshOptimizer;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
@@ -172,9 +174,9 @@ public static unsafe class MeshletUtility
};
}
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group, AllocationHandle handle)
{
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, Allocator.FreeList);
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, handle);
for (var j = 0; j < group.Count; j++)
{
boundsList[j] = (clusters[group[j]].bounds);
@@ -202,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, Allocator allocator)
private static UnsafeList<Cluster> Clusterize(ref readonly ClodConfig config, ref readonly ClodMesh mesh, uint* indices, nuint indexCount, AllocationHandle handle)
{
var maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles);
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);
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);
var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
var pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr();
@@ -236,7 +238,7 @@ public static unsafe class MeshletUtility
);
}
var clusters = new UnsafeList<Cluster>((int)meshletCount, allocator);
var clusters = new UnsafeList<Cluster>((int)meshletCount, handle);
for (nuint i = 0; i < meshletCount; i++)
{
@@ -255,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), Allocator.FreeList),
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, Allocator.FreeList),
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), Allocator.FreeList),
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),
group = -1,
refined = -1
};
@@ -324,16 +326,16 @@ 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, Allocator allocator)
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)
{
if (pending.Count <= (int)config.partitionSize)
{
var single = new UnsafeList<UnsafeList<int>>(1, allocator);
var pendingcpy = new UnsafeList<int>(pending.Count, Allocator.FreeList);
var single = new UnsafeList<UnsafeList<int>>(1, handle);
var pendingcpy = new UnsafeList<int>(pending.Count, handle);
pendingcpy.AddRange(pending.AsSpan());
single.Add(pendingcpy);
return single;
}
@@ -343,8 +345,8 @@ public static unsafe class MeshletUtility
totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
}
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, Allocator.FreeList);
using var clusterCounts = new UnsafeList<uint>(pending.Count, Allocator.FreeList);
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, handle);
using var clusterCounts = new UnsafeList<uint>(pending.Count, handle);
nuint offset = 0;
for (var i = 0; i < pending.Count; i++)
@@ -359,7 +361,7 @@ public static unsafe class MeshletUtility
offset += (nuint)cluster.indices.Count;
}
using var clusterPart = new UnsafeArray<uint>(pending.Count, Allocator.FreeList);
using var clusterPart = new UnsafeArray<uint>(pending.Count, handle);
var partitionCount = MeshOptApi.PartitionClusters(
(uint*)clusterPart.GetUnsafePtr(),
@@ -373,10 +375,10 @@ public static unsafe class MeshletUtility
config.partitionSize
);
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, allocator);
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, handle);
for (nuint i = 0; i < partitionCount; i++)
{
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), allocator));
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), handle));
}
for (var i = 0; i < pending.Count; i++)
@@ -387,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)
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)
{
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, Allocator.FreeList);
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, handle);
for (var i = 0; i < group.Count; i++)
{
@@ -423,35 +425,35 @@ 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)
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)
{
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, Allocator.FreeList);
using var subset_locks = new UnsafeArray<byte>(indices.Count, Allocator.FreeList);
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, handle);
using var subset_locks = new UnsafeArray<byte>(indices.Count, handle);
lod.Resize(indices.Count);
var positions_stride = mesh.vertexPositionsStride / sizeof(float);
var positions_stride = mesh.vertexPositionsStride / sizeof(float);
// deindex the mesh subset to avoid calling simplifySloppy on the entire vertex buffer (which is prohibitively expensive without sparsity)
for (var i = 0; i<indices.Count; ++i)
{
var v = indices[i];
Debug.Assert(v<mesh.vertexCount);
// deindex the mesh subset to avoid calling simplifySloppy on the entire vertex buffer (which is prohibitively expensive without sparsity)
for (var i = 0; i < indices.Count; ++i)
{
var v = indices[i];
Debug.Assert(v < mesh.vertexCount);
subset[i].x = mesh.vertexPositions[v * positions_stride + 0];
subset[i].y = mesh.vertexPositions[v * positions_stride + 1];
subset[i].z = mesh.vertexPositions[v * positions_stride + 2];
subset[i].id = v;
subset[i].y = mesh.vertexPositions[v * positions_stride + 1];
subset[i].z = mesh.vertexPositions[v * positions_stride + 2];
subset[i].id = v;
subset_locks[i] = locks[v];
lod[i] = (uint)i;
subset_locks[i] = locks[v];
lod[i] = (uint)i;
}
var newSize = MeshOptApi.SimplifySloppy((uint*)lod.GetUnsafePtr(), (uint*)lod.GetUnsafePtr(), (nuint)lod.Count, (float*)subset.GetUnsafePtr(), (nuint)subset.Count, (nuint)sizeof(SloppyVertex), (byte*)subset_locks.GetUnsafePtr(), target_count, float.MaxValue, error);
lod.Resize((int)newSize);
// convert error to absolute
* error *= MeshOptApi.SimplifyScale((float*)subset.GetUnsafePtr(), (nuint)subset.Count, (nuint)sizeof(SloppyVertex));
*error *= MeshOptApi.SimplifyScale((float*)subset.GetUnsafePtr(), (nuint)subset.Count, (nuint)sizeof(SloppyVertex));
// restore original vertex indices
for (var i = 0; i < lod.Count; ++i)
@@ -460,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, Allocator allocator)
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)
{
var lod = new UnsafeArray<uint>(indices.Count, allocator);
var lod = new UnsafeArray<uint>(indices.Count, handle);
if (targetCount >= (nuint)indices.Count)
{
@@ -527,7 +529,7 @@ public static unsafe class MeshletUtility
if ((nuint)lod.Length > targetCount && config.simplifyFallbackSloppy)
{
SimplifyFallback(ref lod, in mesh, indices, locks, targetCount, error);
SimplifyFallback(ref lod, in mesh, indices, locks, targetCount, error, handle);
*error *= config.simplifyErrorFactorSloppy;
}
@@ -575,8 +577,13 @@ public static unsafe class MeshletUtility
{
Debug.Assert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, Allocator.FreeList, AllocationOption.Clear);
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, Allocator.FreeList);
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);
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
@@ -599,14 +606,14 @@ public static unsafe class MeshletUtility
}
}
using var clusters = Clusterize(in config, in mesh, mesh.indices, mesh.indexCount, Allocator.FreeList);
using var clusters = Clusterize(in config, in mesh, mesh.indices, mesh.indexCount, pool.AllocationHandle);
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, Allocator.FreeList);
using var pending = new UnsafeList<int>(clusters.Count, pool.AllocationHandle);
for (var i = 0; i < clusters.Count; i++)
{
pending.Add(i);
@@ -616,14 +623,14 @@ public static unsafe class MeshletUtility
while (pending.Count > 1)
{
using var groups = Partition(in config, in mesh, clusters, pending, remap, Allocator.FreeList);
using var groups = Partition(in config, in mesh, clusters, pending, remap, pool.AllocationHandle);
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, Allocator.FreeList);
using var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, pool.AllocationHandle);
for (var j = 0; j < groups[i].Count; j++)
{
var clusterIndices = clusters[groups[i][j]].indices;
@@ -631,28 +638,28 @@ public static unsafe class MeshletUtility
}
var targetSize = (nuint)(merged.Count / 3 * config.simplifyRatio * 3.0f);
var bounds = MergeBounds(clusters, groups[i]);
var bounds = MergeBounds(clusters, groups[i], pool.AllocationHandle);
var error = 0.0f;
using var simplified = Simplify(in config, in mesh, merged.AsReadOnly(), locks.AsReadOnly(), targetSize, &error, Allocator.FreeList);
using var simplified = Simplify(in config, in mesh, merged.AsReadOnly(), locks.AsReadOnly(), targetSize, &error, pool.AllocationHandle);
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);
OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
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);
var refined = OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
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, Allocator.FreeList);
using var split = Clusterize(in config, in mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Length, pool.AllocationHandle);
for (var j = 0; j < split.Count; j++)
{
split[j].refined = refined;
@@ -674,7 +681,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);
OutputGroup(in config, in mesh, clusters, pending, bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
}
var finalClusterCount = (nuint)clusters.Count;

View File

@@ -53,11 +53,11 @@ shader "MyShader/Standard"
MeshData meshData = LoadData<MeshData>(instanceData.meshBuffer, 0);
ByteAddressBuffer meshletBuffer = GET_BUFFER(meshData.meshletBuffer);
Meshlet m = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
Meshlet meshlet = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
s_Payload.meshletIndex = groupID.x;
uint lodLevel = (m.packedCounts >> 24) & 0xFFu;
uint lodLevel = (meshlet.packedCounts >> 24) & 0xFFu;
uint emitMeshlet = lodLevel == 0u ? 1u : 0u;
DispatchMesh(emitMeshlet, 1u, 1u, s_Payload);
}
@@ -74,10 +74,10 @@ shader "MyShader/Standard"
MeshData meshData = LoadData<MeshData>(instanceData.meshBuffer, 0);
ByteAddressBuffer meshletBuffer = GET_BUFFER(meshData.meshletBuffer);
Meshlet m = meshletBuffer.Load<Meshlet>(meshPayload.meshletIndex * sizeof(Meshlet));
Meshlet meshlet = meshletBuffer.Load<Meshlet>(meshPayload.meshletIndex * sizeof(Meshlet));
uint vertexCount = m.packedCounts & 0xFFu;
uint triangleCount = (m.packedCounts >> 8) & 0xFFu;
uint vertexCount = meshlet.packedCounts & 0xFFu;
uint triangleCount = (meshlet.packedCounts >> 8) & 0xFFu;
SetMeshOutputCounts(vertexCount, triangleCount);
ByteAddressBuffer meshletVerticesBuffer = GET_BUFFER(meshData.meshletVerticesBuffer);
@@ -86,7 +86,7 @@ shader "MyShader/Standard"
// Write vertex output
if (groupThreadID.x < vertexCount)
{
uint vertexIndex = meshletVerticesBuffer.Load((m.vertexOffset + groupThreadID.x) * 4);
uint vertexIndex = meshletVerticesBuffer.Load((meshlet.vertexOffset + groupThreadID.x) * 4);
ByteAddressBuffer vertices = GET_BUFFER(meshData.vertexBuffer);
Vertex v = vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
@@ -112,7 +112,7 @@ shader "MyShader/Standard"
uint triangleIndex = groupThreadID.x;
// Load the packed 32-bit integer containing the 3 local indices
uint packedIndices = meshletTrianglesBuffer.Load((m.triangleOffset + triangleIndex) * 4);
uint packedIndices = meshletTrianglesBuffer.Load((meshlet.triangleOffset + triangleIndex) * 4);
uint i0 = packedIndices & 0xFF;
uint i1 = (packedIndices >> 8) & 0xFF;