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

@@ -209,7 +209,7 @@ public class ShaderVisitor : GhostShaderParserBaseVisitor<object>
if (stop >= start) if (stop >= start)
{ {
var input = context.Start.InputStream; var input = context.Start.InputStream;
hlsl.Code = input.GetText(new Antlr4.Runtime.Misc.Interval(start, stop)); hlsl.Code = input.GetText(new Interval(start, stop));
} }
return hlsl; return hlsl;

View File

@@ -13,7 +13,7 @@ namespace Ghost.Editor.Properties {
/// <summary> /// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc. /// A strongly-typed heap class, for looking up localized strings, etc.
/// </summary> /// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder // This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
@@ -48,7 +48,7 @@ namespace Ghost.Editor.Properties {
/// <summary> /// <summary>
/// Overrides the current thread's CurrentUICulture property for all /// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class. /// heap lookups using this strongly typed heap class.
/// </summary> /// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture { internal static global::System.Globalization.CultureInfo Culture {

View File

@@ -1,6 +1,4 @@
<ResourceDictionary <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/View/Controls/Docking/DockingLayout.xaml" /> <ResourceDictionary Source="/View/Controls/Docking/DockingLayout.xaml" />

View File

@@ -18,7 +18,7 @@ public partial class AssetPathToGlyphConverter : IValueConverter
var extension = Path.GetExtension(path).ToLowerInvariant(); var extension = Path.GetExtension(path).ToLowerInvariant();
// TODO: Use resource dictionary for icons. // TODO: Use heap dictionary for icons.
return extension switch return extension switch
{ {
".ghostscene" => "\uF159", ".ghostscene" => "\uF159",

View File

@@ -10,9 +10,10 @@
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="local:DockDocument"> <ControlTemplate TargetType="local:DockDocument">
<Border Background="Transparent"> <Border Background="Transparent">
<ContentPresenter Content="{TemplateBinding Content}" <ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}" />
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>

View File

@@ -157,7 +157,7 @@ public partial class DockGroup : DockContainer
Content = doc Content = doc
}; };
existingTab.SetBinding(TabViewItem.HeaderProperty, new Microsoft.UI.Xaml.Data.Binding existingTab.SetBinding(TabViewItem.HeaderProperty, new Binding
{ {
Source = doc, Source = doc,
Path = new PropertyPath(nameof(DockDocument.Title)), Path = new PropertyPath(nameof(DockDocument.Title)),

View File

@@ -7,7 +7,12 @@
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="local:DockRegionHighlight"> <ControlTemplate TargetType="local:DockRegionHighlight">
<Border Background="{ThemeResource SystemControlHighlightAccentBrush}" Opacity="0.25" BorderBrush="{ThemeResource SystemControlHighlightAccentBrush}" BorderThickness="2" CornerRadius="4" /> <Border
Background="{ThemeResource SystemControlHighlightAccentBrush}"
BorderBrush="{ThemeResource SystemControlHighlightAccentBrush}"
BorderThickness="2"
CornerRadius="4"
Opacity="0.25" />
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
</Setter> </Setter>

View File

@@ -108,7 +108,7 @@ internal sealed partial class ProjectBrowser : UserControl
_isUpdatingSelection = false; _isUpdatingSelection = false;
} }
private async void PART_FilesView_DoubleTapped(object sender, Microsoft.UI.Xaml.Input.DoubleTappedRoutedEventArgs e) private async void PART_FilesView_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{ {
if (PART_FilesView.SelectedItem is ExplorerItem selectedItem) if (PART_FilesView.SelectedItem is ExplorerItem selectedItem)
{ {

View File

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

View File

@@ -6,6 +6,7 @@ using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Gdiplus;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D_Alias; using static TerraFX.Aliases.D3D_Alias;
@@ -635,7 +636,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
pNativeObject->SetPipelineState(psor.Value); pNativeObject->SetPipelineState(psor.Value);
} }
public void SetConstantBufferView(uint slot, Handle<RHI.GPUBuffer> buffer) public void SetConstantBufferView(uint slot, Handle<GPUBuffer> buffer)
{ {
AssertNotDisposed(); AssertNotDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
@@ -651,7 +652,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
pNativeObject->SetGraphicsRootConstantBufferView(slot, resource.Get()->GetGPUVirtualAddress()); 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(); AssertNotDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
@@ -681,7 +682,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
pNativeObject->IASetVertexBuffers(slot, 1, &vbView); 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(); AssertNotDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
@@ -821,7 +822,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
throw new NotImplementedException(); 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(); 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) public void CopyBuffer(Handle<GPUBuffer> dst, Handle<GPUBuffer> src, ulong dstOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
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)
{ {
AssertNotDisposed(); AssertNotDisposed();
ThrowIfNotRecording(); ThrowIfNotRecording();
@@ -905,47 +856,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
return; return;
} }
#endif #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) if (dst == src)
{ {
return; 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) private D3D12_TEXTURE_COPY_LOCATION GetTextureCopyLocation(SharedPtr<ID3D12Resource> texture, TextureSubresource subres)
{ {
var flatIndex = subres.MipLevel + subres.ArrayLayer * texture.Get()->GetDesc().MipLevels; 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<ICommandBuffer> _commandBufferPool;
private readonly Queue<CommandBufferReturnEntry> _commandBufferReturnQueue; private readonly Queue<CommandBufferReturnEntry> _commandBufferReturnQueue;
private ulong _currentFrame; private ulong _cpuFrame;
private bool _disposed; private bool _disposed;
public IRenderDevice Device => _device; public IRenderDevice Device => _device;
@@ -147,7 +147,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
public void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer) public void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer)
{ {
_commandBufferReturnQueue.Enqueue(new CommandBufferReturnEntry(commandBuffer, _currentFrame)); _commandBufferReturnQueue.Enqueue(new CommandBufferReturnEntry(commandBuffer, _cpuFrame));
} }
public ISwapChain CreateSwapChain(SwapChainDesc desc) public ISwapChain CreateSwapChain(SwapChainDesc desc)
@@ -156,22 +156,22 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
return new DXGISwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount); return new DXGISwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount);
} }
public Result BeginFrame(ulong currentFrame) public Result BeginFrame(ulong cpuFrame)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
_currentFrame = currentFrame; _cpuFrame = cpuFrame;
_resourceDatabase.BeginFrame(currentFrame); _resourceDatabase.BeginFrame(cpuFrame);
return Result.Success(); return Result.Success();
} }
public Result EndFrame(ulong completedFrame) public Result EndFrame(ulong gpuFrame)
{ {
ThrowIfDisposed(); 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); _commandBufferPool.Enqueue(entry.commandBuffer);
_commandBufferReturnQueue.Dequeue(); _commandBufferReturnQueue.Dequeue();

View File

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

View File

@@ -462,10 +462,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12PipelineLibrary _pipelineLibrary; 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; private bool _disposed;
public D3D12ResourceAllocator( public D3D12ResourceAllocator(
@@ -489,17 +485,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
_descriptorAllocator = descriptorAllocator; _descriptorAllocator = descriptorAllocator;
_resourceDatabase = resourceDatabase; _resourceDatabase = resourceDatabase;
_pipelineLibrary = pipelineLibrary; _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() ~D3D12ResourceAllocator()
@@ -507,18 +492,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
Dispose(); 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) private HRESULT CreateResource(D3D12MA_ALLOCATION_DESC* pAllocationDesc, D3D12_RESOURCE_DESC* pResourceDesc, D3D12_RESOURCE_STATES initialState, CreationOptions options, Guid* riid, void** ppv)
{ {
HRESULT hr; 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 var allocDesc = new D3D12MA_ALLOCATION_DESC
{ {
HeapType = desc.HeapType switch HeapType = desc.HeapType.ToD3D12HeapType(),
{
HeapType.Default => D3D12_HEAP_TYPE_DEFAULT,
HeapType.Upload => D3D12_HEAP_TYPE_UPLOAD,
HeapType.Readback => D3D12_HEAP_TYPE_READBACK,
_ => D3D12_HEAP_TYPE_DEFAULT
},
Flags = D3D12MA_ALLOCATION_FLAG_COMMITTED, Flags = D3D12MA_ALLOCATION_FLAG_COMMITTED,
ExtraHeapFlags = desc.HeapFlags switch ExtraHeapFlags = desc.HeapFlags.ToD3D12HeapFlags()
{
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
}
}; };
// SizeInBytes must be aligned to 64KB for committed resources // SizeInBytes must be aligned to 64KB for committed resources
@@ -606,10 +565,10 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
sync = BarrierSync.None 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); Debug.Assert(!_disposed);
@@ -634,8 +593,11 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
else else
{ {
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, null, (void**)&pAllocation); hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, null, (void**)&pAllocation);
if (hr.SUCCEEDED)
{
pResource = pAllocation->GetResource(); pResource = pAllocation->GetResource();
} }
}
if (hr.FAILED) if (hr.FAILED)
{ {
@@ -645,7 +607,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
return Handle<GPUTexture>.Invalid; return Handle<GPUTexture>.Invalid;
} }
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
var resourceDescriptor = ResourceViewGroup.Invalid; var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(TextureUsage.ShaderResource)) if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
{ {
@@ -701,13 +662,13 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
} }
else else
{ {
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp); resource = _resourceDatabase.AddAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Texture(desc), name);
} }
return resource.AsTexture(); 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); Debug.Assert(!_disposed);
@@ -715,7 +676,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
return CreateTexture(in textureDesc, name, options); 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); Debug.Assert(!_disposed);
CheckBufferSize(desc.Size); CheckBufferSize(desc.Size);
@@ -760,10 +721,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
#if DEBUG #if DEBUG
ThrowIfFailed(hr); ThrowIfFailed(hr);
#endif #endif
return Handle<RHI.GPUBuffer>.Invalid; return Handle<GPUBuffer>.Invalid;
} }
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
var resourceDescriptor = ResourceViewGroup.Invalid; var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(BufferUsage.Constant)) if (desc.Usage.HasFlag(BufferUsage.Constant))
@@ -814,40 +774,10 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
} }
else else
{ {
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp); resource = _resourceDatabase.AddAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Buffer(desc), name);
} }
return resource.AsGraphicsBuffer(); return resource.AsBuffer();
}
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;
}
} }
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc) public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc)
@@ -887,7 +817,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
return; return;
} }
_resourceDatabase.ReleaseResourceImmediately(_uploadBatch.AsResource());
_d3d12MA.Dispose(); _d3d12MA.Dispose();
_disposed = true; _disposed = true;

View File

@@ -4,14 +4,14 @@ using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
// TODO: Thread safety internal unsafe class D3D12ResourceDatabase : IResourceDatabase
internal class D3D12ResourceDatabase : IResourceDatabase
{ {
internal unsafe record struct ResourceRecord internal unsafe record struct ResourceRecord
{ {
@@ -109,7 +109,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase
private int _writeLock; private int _writeLock;
private ulong _currentFrame; private ulong _cpuFrame;
private bool _disposed; private bool _disposed;
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator) public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
@@ -323,12 +323,12 @@ internal class D3D12ResourceDatabase : IResourceDatabase
{ {
Debug.Assert(!_disposed); Debug.Assert(!_disposed);
if (_resources.TryGetElementAt(handle.ID, handle.Generation, out var record)) if (!_resources.TryGetElementAt(handle.ID, handle.Generation, out var record))
{ {
return; return;
} }
var entry = new ReleaseEntry(record, _currentFrame); var entry = new ReleaseEntry(record, _cpuFrame);
_releaseQueue.Enqueue(entry); _releaseQueue.Enqueue(entry);
_resources.Remove(handle.ID, handle.Generation); _resources.Remove(handle.ID, handle.Generation);
@@ -399,20 +399,52 @@ internal class D3D12ResourceDatabase : IResourceDatabase
return Error.None; 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); var r = GetResourceRecord(handle);
_currentFrame = currentFrame; if (r.IsFailure)
{
return r.Error;
} }
public void EndFrame(ulong completedFrame) 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 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); Debug.Assert(!_disposed);
while (_releaseQueue.Count > 0) while (_releaseQueue.Count > 0)
{ {
var toRelease = _releaseQueue.Peek(); var toRelease = _releaseQueue.Peek();
if (toRelease.fenceValue > completedFrame) if (toRelease.fenceValue > gpuFrame)
{ {
break; break;
} }
@@ -431,6 +463,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase
{ {
record.Release(_descriptorAllocator); record.Release(_descriptorAllocator);
} }
_resources.Clear();
} }
public void Dispose() public void Dispose()

View File

@@ -386,6 +386,31 @@ internal static unsafe class D3D12Utility
return flags; 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) public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in TextureDesc desc)
{ {
var dxgiFormat = desc.Format.ToDXGIFormat(); var dxgiFormat = desc.Format.ToDXGIFormat();

View File

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

View File

@@ -185,24 +185,6 @@ public interface ICommandBuffer : IDisposable
// TODO: This method is not supported yet. // TODO: This method is not supported yet.
void DispatchRay(); 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> /// <summary>
/// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer. /// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer.
/// </summary> /// </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="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> /// <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 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> /// <summary>
/// Begin the current frame. /// Begin the current frame.
/// </summary> /// </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> /// <returns>Result of the begin frame operation</returns>
Result BeginFrame(ulong currentFrame); Result BeginFrame(ulong cpuFrame);
/// <summary> /// <summary>
/// End the current frame. /// End the current frame.
/// </summary> /// </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> /// <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; namespace Ghost.Graphics.RHI;
[Flags] [Flags]

View File

@@ -5,7 +5,6 @@ namespace Ghost.Graphics.RHI;
public enum ResourceAllocationType public enum ResourceAllocationType
{ {
Default, Default,
Temporary,
Suballocation, Suballocation,
} }
@@ -36,11 +35,12 @@ public enum HeapType
public enum HeapFlags public enum HeapFlags
{ {
None = 0, None,
AllowBuffers, Shared,
AllowTextures, AllowOnlyBuffers,
AllowRTAndDS, AllowOnlyTextures,
AlowBufferAndTexture, AllowOnlyRTAndDS,
AllowAllBufferAndTexture,
} }
public struct AllocationDesc public struct AllocationDesc
@@ -89,7 +89,7 @@ public interface IResourceAllocator : IDisposable
/// <param name="desc">Allocation description</param> /// <param name="desc">Allocation description</param>
/// <param name="name">Debug name of the allocation</param> /// <param name="name">Debug name of the allocation</param>
/// <returns>An <see cref="Handle{GPUResource}"/> point to the allocated memory</returns> /// <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> /// <summary>
/// Creates a texture resource /// Creates a texture resource
@@ -98,7 +98,7 @@ public interface IResourceAllocator : IDisposable
/// <param name="name">Debug name of the resource</param> /// <param name="name">Debug name of the resource</param>
/// <param name="options">Additional options of the resource allocation</param> /// <param name="options">Additional options of the resource allocation</param>
/// <returns>An <see cref="Handle{Texture}"/> point to the resource</returns> /// <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> /// <summary>
/// Creates a render Target for off-screen rendering /// 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="name">Debug name of the resource</param>
/// <param name="options">Additional options of the resource allocation</param> /// <param name="options">Additional options of the resource allocation</param>
/// <returns>An <see cref="Handle{Texture}"/> point to the resource</returns> /// <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> /// <summary>
/// Creates a buffer resource /// Creates a buffer resource
@@ -116,18 +116,7 @@ public interface IResourceAllocator : IDisposable
/// <param name="name">Debug name of the resource</param> /// <param name="name">Debug name of the resource</param>
/// <param name="options">Additional options of the resource allocation</param> /// <param name="options">Additional options of the resource allocation</param>
/// <returns>An <see cref="Handle{GraphicsBuffer}"/> point to the resource</returns> /// <returns>An <see cref="Handle{GraphicsBuffer}"/> point to the resource</returns>
Handle<GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string name, CreationOptions options = default); Handle<GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string? name = null, 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);
/// <summary> /// <summary>
/// Creates a new sampler object using the specified sampler description. /// Creates a new sampler object using the specified sampler description.

View File

@@ -31,22 +31,8 @@ public enum BindlessAccess
UnorderedAccess, UnorderedAccess,
} }
// TODO: Consider adding methods for resource enumeration, statistics, and bulk operations. public unsafe interface IResourceDatabase : IDisposable
// TODO: Consider adding async resource loading and streaming support.
public 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 EnterParallelRead();
void ExitParallelRead(); void ExitParallelRead();
@@ -139,4 +125,8 @@ public interface IResourceDatabase : IDisposable
/// <param name="handleB">The second handle whose associated resource is to be swapped.</param> /// <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> /// <returns>An Error indicating the success or failure of the swap operation.</returns>
Error Swap(Handle<GPUResource> handleA, Handle<GPUResource> handleB); 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> /// <summary>
/// Gets all back buffer textures /// Gets all back buffer textures
/// </summary> /// </summary>
/// <returns>AlowBufferAndTexture back buffer textures</returns> /// <returns>AllowAllBufferAndTexture back buffer textures</returns>
ReadOnlySpan<Handle<GPUTexture>> GetBackBuffers(); ReadOnlySpan<Handle<GPUTexture>> GetBackBuffers();
/// <summary> /// <summary>

View File

@@ -25,7 +25,7 @@ public static class ResourceHandleExtensions
return new Handle<GPUTexture>(resource.ID, resource.Generation); 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); return new Handle<GPUBuffer>(resource.ID, resource.Generation);
} }

View File

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

View File

@@ -1,7 +1,6 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities; using Ghost.Graphics.Utilities;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
@@ -138,7 +137,7 @@ public struct Mesh : IResourceReleasable
/// <summary> /// <summary>
/// Gets the handle to the vertex buffer on the GPU. /// Gets the handle to the vertex buffer on the GPU.
/// </summary> /// </summary>
public Handle<RHI.GPUBuffer> VertexBuffer public Handle<GPUBuffer> VertexBuffer
{ {
get; internal set; get; internal set;
} }
@@ -146,7 +145,7 @@ public struct Mesh : IResourceReleasable
/// <summary> /// <summary>
/// Gets the handle to the index buffer on the GPU. /// Gets the handle to the index buffer on the GPU.
/// </summary> /// </summary>
public Handle<RHI.GPUBuffer> IndexBuffer public Handle<GPUBuffer> IndexBuffer
{ {
get; internal set; get; internal set;
} }
@@ -154,7 +153,7 @@ public struct Mesh : IResourceReleasable
/// <summary> /// <summary>
/// Gets the handle to the meshlet buffer on the GPU. /// Gets the handle to the meshlet buffer on the GPU.
/// </summary> /// </summary>
public Handle<RHI.GPUBuffer> MeshLetBuffer public Handle<GPUBuffer> MeshLetBuffer
{ {
get; internal set; get; internal set;
} }
@@ -162,7 +161,7 @@ public struct Mesh : IResourceReleasable
/// <summary> /// <summary>
/// Gets the handle to the meshlet vertices buffer on the GPU. /// Gets the handle to the meshlet vertices buffer on the GPU.
/// </summary> /// </summary>
public Handle<RHI.GPUBuffer> MeshletVerticesBuffer public Handle<GPUBuffer> MeshletVerticesBuffer
{ {
get; internal set; get; internal set;
} }
@@ -170,7 +169,7 @@ public struct Mesh : IResourceReleasable
/// <summary> /// <summary>
/// Gets the handle to the meshlet triangles buffer on the GPU. /// Gets the handle to the meshlet triangles buffer on the GPU.
/// </summary> /// </summary>
public Handle<RHI.GPUBuffer> MeshletTrianglesBuffer public Handle<GPUBuffer> MeshletTrianglesBuffer
{ {
get; internal set; get; internal set;
} }
@@ -178,12 +177,12 @@ public struct Mesh : IResourceReleasable
/// <summary> /// <summary>
/// Gets the handle to the mesh data buffer on the GPU. /// Gets the handle to the mesh data buffer on the GPU.
/// </summary> /// </summary>
public Handle<RHI.GPUBuffer> ObjectDataBuffer public Handle<GPUBuffer> ObjectDataBuffer
{ {
get; internal set; 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); Vertices = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
Indices = new UnsafeList<uint>(indices.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 Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
namespace Ghost.Graphics.Core; 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 public readonly unsafe ref struct RenderingContext
{ {
private readonly IGraphicsEngine _engine; private readonly IGraphicsEngine _engine;
@@ -82,6 +83,49 @@ public readonly unsafe ref struct RenderingContext
ResourceDatabase.SetResourceBarrierData(resource, new ResourceBarrierData(newLayout, newAccess, newSync)); 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) public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
{ {
var mesh = _resourceManager.CreateMesh(vertices, indices); 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(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan()); UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan()); UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading); TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput); 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(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan()); UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
_directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan()); UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading); TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput); 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.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletTrianglesBuffer.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()); UploadBuffer(meshRef.MeshLetBuffer, meshletData.meshlets.AsSpan());
_directCmd.UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan()); UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan());
_directCmd.UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan()); UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan());
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading); 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); 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(); var bufferHandle = meshData.ObjectDataBuffer.AsResource();
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); 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); 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) public void UploadTexture<T>(Handle<GPUTexture> texture, ReadOnlySpan<T> data)
where T : unmanaged where T : unmanaged
{ {
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
//var size = ResourceAllocator.GetSizeInfo(desc).Size; //var size = ResourceAllocator.GetSizeInfo(desc).Size;
//if ((ulong)(data.Length * sizeof(T)) != ResourceAllocator.GetSizeInfo(desc).Size) //if ((ulong)(data.Length * sizeof(T)) != ResourceAllocator.GetSizeInfo(desc).Size)
//{ //{
// throw new ArgumentException("Data size does not match texture 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 _); 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); TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy);
fixed (T* pData = data) fixed (T* pData = data)
@@ -273,7 +330,7 @@ public readonly unsafe ref struct RenderingContext
slicePitch = slicePitch 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; namespace Ghost.Graphics.RenderGraphModule;
/// <summary> /// <summary>
/// Main render graph class that manages resource allocation and pass execution. /// Main render graph class that manages heap allocation and pass execution.
/// </summary> /// </summary>
public sealed class RenderGraph : IDisposable public sealed class RenderGraph : IDisposable
{ {
@@ -125,7 +125,7 @@ public sealed class RenderGraph : IDisposable
/// </summary> /// </summary>
/// <param name="buffer">The external buffer handle.</param> /// <param name="buffer">The external buffer handle.</param>
/// <returns>The identifier of the imported render graph buffer. Invalid if import fails.</returns> /// <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()); var r = _resourceDatabase.GetResourceDescription(buffer.AsResource());
if (r.IsFailure) if (r.IsFailure)
@@ -178,7 +178,7 @@ public sealed class RenderGraph : IDisposable
} }
/// <summary> /// <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> /// </summary>
public Error Compile(ViewState viewState) public Error Compile(ViewState viewState)
{ {

View File

@@ -12,7 +12,7 @@ internal struct MemoryBlock
public bool isFree; public bool isFree;
public int firstUsePass; public int firstUsePass;
public int lastUsePass; 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) public MemoryBlock(ulong offset, ulong size)
{ {
@@ -267,7 +267,7 @@ internal sealed class ResourceAliasingManager
private readonly ResourceHeap _heap; private readonly ResourceHeap _heap;
private readonly List<PlacedResource> _placedResources; 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 readonly Dictionary<int, int> _logicalToPlaced;
private const ulong _DEFAULT_TEXTURE_ALIGNMENT = 65536; // 64KB private const ulong _DEFAULT_TEXTURE_ALIGNMENT = 65536; // 64KB
@@ -373,7 +373,7 @@ internal sealed class ResourceAliasingManager
_heap.size = peakMemoryUsage; _heap.size = peakMemoryUsage;
_heap.Reset(); _heap.Reset();
// Allocate each logical resource in the heap // Allocate each logical heap in the heap
foreach (var (logicalIndex, logicalResource) in logicalResources) foreach (var (logicalIndex, logicalResource) in logicalResources)
{ {
// TODO: Currently we are recalculating the aliasing candidates in the real allocation pass. // 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 // 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++) for (var i = 0; i < _placedResources.Count; i++)
{ {
var placed = _placedResources[i]; var placed = _placedResources[i];
@@ -432,7 +432,7 @@ internal sealed class ResourceAliasingManager
// Check if they're at the same offset // Check if they're at the same offset
if (other.heapOffset == placed.heapOffset) 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 var otherLogicalIndex = other.aliasedLogicalResources[0]; // Each has exactly one at this point
if (!placed.aliasedLogicalResources.Contains(otherLogicalIndex)) if (!placed.aliasedLogicalResources.Contains(otherLogicalIndex))
{ {

View File

@@ -12,7 +12,7 @@ internal enum BarrierFlags
} }
/// <summary> /// <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> /// </summary>
internal struct ResourceBarrier internal struct ResourceBarrier
{ {
@@ -55,7 +55,7 @@ internal struct ResourceBarrier
} }
/// <summary> /// <summary>
/// Tracks the current state of a resource across passes during compilation. /// Tracks the current state of a heap across passes during compilation.
/// </summary> /// </summary>
internal sealed class ResourceStateTracker internal sealed class ResourceStateTracker
{ {
@@ -124,7 +124,7 @@ internal static class RenderGraphBarriers
} }
/// <summary> /// <summary>
/// Inserts aliasing barriers when a placed resource is reused. /// Inserts aliasing barriers when a placed heap is reused.
/// </summary> /// </summary>
private static void InsertAliasingBarriers( private static void InsertAliasingBarriers(
RenderGraphPassBase pass, RenderGraphPassBase pass,
@@ -148,20 +148,20 @@ internal static class RenderGraphBarriers
continue; 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) if (resource.firstUsePass == pass.index)
{ {
// Get the placed resource // Get the placed heap
var placedIndex = aliasingManager.GetPlacedResourceIndex(id.Value); var placedIndex = aliasingManager.GetPlacedResourceIndex(id.Value);
if (placedIndex >= 0) if (placedIndex >= 0)
{ {
var placed = aliasingManager.GetPlacedResource(placedIndex); 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 // we need an aliasing barrier when switching between them
if (placed != null && placed.aliasedLogicalResources.Count > 1) 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; Identifier<RGResource> resourceBefore = default;
var mostRecentLastUse = -1; var mostRecentLastUse = -1;
@@ -169,10 +169,10 @@ internal static class RenderGraphBarriers
{ {
if (otherLogicalIndex != id.Value) if (otherLogicalIndex != id.Value)
{ {
// Get resource by global index // Get heap by global index
var otherResource = resources.GetResourceByIndex(otherLogicalIndex); 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 && if (otherResource.lastUsePass < pass.index &&
otherResource.lastUsePass > mostRecentLastUse) 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) if (mostRecentLastUse >= 0)
{ {
// Aliasing Requirement: Transition to Undefined, Sync with Predecessor // Aliasing Requirement: Transition to Undefined, Sync with Predecessor
@@ -215,7 +215,7 @@ internal static class RenderGraphBarriers
List<CompiledBarrier> compiledBarriers, List<CompiledBarrier> compiledBarriers,
RenderGraphResourceRegistry resources) 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) void AddTransition(Identifier<RGResource> id, ResourceBarrierData targetState)
{ {
var resource = resources.GetResource(id); var resource = resources.GetResource(id);

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderGraphModule; namespace Ghost.Graphics.RenderGraphModule;
/// <summary> /// <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. /// barrier compilation, and cache management.
/// </summary> /// </summary>
internal sealed class RenderGraphCompiler 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 Size = _aliasingManager.Heap.size + 64 * 1024, // Add 64KB padding to avoid potential overflows
Alignment = ResourceHeap.DEFAULT_ALIGNMENT, Alignment = ResourceHeap.DEFAULT_ALIGNMENT,
HeapFlags = HeapFlags.AlowBufferAndTexture, HeapFlags = HeapFlags.AllowAllBufferAndTexture,
HeapType = HeapType.Default HeapType = HeapType.Default
}; };

View File

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

View File

@@ -78,7 +78,7 @@ internal static class RenderGraphHasher
} }
/// <summary> /// <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 imported textures, hashes the backing handle.
/// For transient textures, hashes the descriptor (respecting size mode). /// For transient textures, hashes the descriptor (respecting size mode).
/// </summary> /// </summary>
@@ -94,7 +94,7 @@ internal static class RenderGraphHasher
// Hash imported flag // Hash imported flag
writer.Write(resource.isImported); writer.Write(resource.isImported);
// For imported textures, hash the backing resource handle // For imported textures, hash the backing heap handle
if (resource.isImported) if (resource.isImported)
{ {
writer.Write(resource.backingResource.GetHashCode()); writer.Write(resource.backingResource.GetHashCode());

View File

@@ -204,7 +204,7 @@ internal sealed class RenderGraphNativePassBuilder
{ {
var laterPass = compiledPasses[passB]; 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>>(); var renderTargets = new HashSet<Identifier<RGResource>>();
for (var i = 0; i <= laterPass.maxColorIndex; i++) for (var i = 0; i <= laterPass.maxColorIndex; i++)
{ {
@@ -241,7 +241,7 @@ internal sealed class RenderGraphNativePassBuilder
/// <summary> /// <summary>
/// Infers optimal load/store operations for all attachments in a native render pass. /// 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> /// </summary>
private void InferLoadStoreOps(NativeRenderPass nativePass) private void InferLoadStoreOps(NativeRenderPass nativePass)
{ {

View File

@@ -211,7 +211,7 @@ internal sealed class RenderGraphResourceRegistry
return new Identifier<RGTexture>(resource.index); 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>(); var resource = _pool.Rent<RenderGraphResource>();
resource.name = name; resource.name = name;
@@ -256,7 +256,7 @@ internal sealed class RenderGraphResourceRegistry
} }
/// <summary> /// <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> /// </summary>
public RenderGraphResource GetResourceByIndex(int index) 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."); 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); _swapChainManager = new SwapChainManager(_graphicsEngine);
// Create frame resources for synchronization // Create frame resources for synchronization
@@ -246,7 +246,7 @@ public class RenderSystem : IDisposable
var flushFence = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue); var flushFence = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue);
_graphicsEngine.Device.GraphicsQueue.WaitForValue(flushFence); _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; frameResource.FenceValue = flushFence;
foreach (var resource in _frameResources) foreach (var resource in _frameResources)
@@ -322,7 +322,7 @@ public class RenderSystem : IDisposable
} }
// End the frame and present // End the frame and present
_resourceManager.EndFrame(_cpuFenceValue); _resourceManager.EndFrame(_gpuFenceValue);
r = _graphicsEngine.EndFrame(_gpuFenceValue); r = _graphicsEngine.EndFrame(_gpuFenceValue);
if (r.IsFailure) if (r.IsFailure)
@@ -426,9 +426,11 @@ public class RenderSystem : IDisposable
_renderPipeline.Dispose(); _renderPipeline.Dispose();
_swapChainManager.Dispose();
_resourceManager.Dispose(); _resourceManager.Dispose();
_graphicsEngine.Dispose(); _graphicsEngine.Dispose();
_swapChainManager.Dispose();
_shutdownEvent.Dispose(); _shutdownEvent.Dispose();
_disposed = true; _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; namespace Ghost.Graphics;
public sealed class ResourceManager : IDisposable public sealed partial class ResourceManager : IDisposable
{ {
private readonly struct ResourceReturnEntry private readonly struct ResourceReturnEntry
{ {
@@ -22,6 +22,7 @@ public sealed class ResourceManager : IDisposable
} }
} }
private readonly IRenderDevice _renderDevice;
private readonly IResourceAllocator _resourceAllocator; private readonly IResourceAllocator _resourceAllocator;
private readonly IResourceDatabase _resourceDatabase; private readonly IResourceDatabase _resourceDatabase;
@@ -31,15 +32,13 @@ public sealed class ResourceManager : IDisposable
private readonly MaterialPaletteStore _materialPalettes; private readonly MaterialPaletteStore _materialPalettes;
private ulong _currentFrame; private ulong _cpuFrame;
private UnsafeHashMap<ResourceDesc, UnsafeQueue<Handle<GPUResource>>> _resourceCache;
private UnsafeQueue<ResourceReturnEntry> _resourceReturnQueue;
private bool _disposed; private bool _disposed;
public ResourceManager(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase) public ResourceManager(IRenderDevice renderDevice, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
{ {
_renderDevice = renderDevice;
_resourceAllocator = resourceAllocator; _resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase; _resourceDatabase = resourceDatabase;
@@ -49,8 +48,7 @@ public sealed class ResourceManager : IDisposable
_materialPalettes = new MaterialPaletteStore(); _materialPalettes = new MaterialPaletteStore();
_resourceCache = new UnsafeHashMap<ResourceDesc, UnsafeQueue<Handle<GPUResource>>>(32, Allocator.Persistent); InitializePool();
_resourceReturnQueue = new UnsafeQueue<ResourceReturnEntry>(32, Allocator.Persistent);
} }
~ResourceManager() ~ResourceManager()
@@ -58,30 +56,16 @@ public sealed class ResourceManager : IDisposable
Dispose(); Dispose();
} }
internal void BeginFrame(ulong currentFrame) internal void BeginFrame(ulong cpuFrame)
{ {
Debug.Assert(!_disposed); Debug.Assert(!_disposed);
_currentFrame = currentFrame; _cpuFrame = cpuFrame;
} }
internal void EndFrame(ulong completedFrame) internal void EndFrame(ulong gpuFrame)
{ {
Debug.Assert(!_disposed); Debug.Assert(!_disposed);
EndFramePool(gpuFrame);
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);
}
} }
/// <summary> /// <summary>
@@ -198,7 +182,7 @@ public sealed class ResourceManager : IDisposable
} }
/// <summary> /// <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> /// </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> /// <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) public void ReleaseMesh(Handle<Mesh> handle)
@@ -364,38 +348,6 @@ public sealed class ResourceManager : IDisposable
shader.ReleaseResource(_resourceDatabase); 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() public void Dispose()
{ {
if (_disposed) if (_disposed)
@@ -423,19 +375,7 @@ public sealed class ResourceManager : IDisposable
_shaders.Dispose(); _shaders.Dispose();
_materialPalettes.Dispose(); _materialPalettes.Dispose();
foreach (var kvp in _resourceCache) DisposePool();
{
var queue = kvp.Value;
while (queue.TryDequeue(out var handle))
{
_resourceDatabase.ReleaseResource(handle);
}
queue.Dispose();
}
_resourceCache.Dispose();
_resourceReturnQueue.Dispose();
_disposed = true; _disposed = true;
GC.SuppressFinalize(this); 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 Ghost.MeshOptimizer;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; 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++) for (var j = 0; j < group.Count; j++)
{ {
boundsList[j] = (clusters[group[j]].bounds); 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); var maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles);
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, Allocator.FreeList); using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, handle);
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, Allocator.FreeList); using var meshletVertices = new UnsafeArray<uint>((int)indexCount, handle);
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, Allocator.FreeList); using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, handle);
var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr(); var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
var pMeshletVertices = (uint*)meshletVertices.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++) for (nuint i = 0; i < meshletCount; i++)
{ {
@@ -255,9 +257,9 @@ public static unsafe class MeshletUtility
var cluster = new Cluster var cluster = new Cluster
{ {
vertices = meshlet.vertex_count, vertices = meshlet.vertex_count,
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), Allocator.FreeList), indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), handle),
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, Allocator.FreeList), uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, handle),
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), Allocator.FreeList), localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), handle),
group = -1, group = -1,
refined = -1 refined = -1
}; };
@@ -324,12 +326,12 @@ public static unsafe class MeshletUtility
} }
} }
private static UnsafeList<UnsafeList<int>> Partition(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeArray<uint> remap, 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) if (pending.Count <= (int)config.partitionSize)
{ {
var single = new UnsafeList<UnsafeList<int>>(1, allocator); var single = new UnsafeList<UnsafeList<int>>(1, handle);
var pendingcpy = new UnsafeList<int>(pending.Count, Allocator.FreeList); var pendingcpy = new UnsafeList<int>(pending.Count, handle);
pendingcpy.AddRange(pending.AsSpan()); pendingcpy.AddRange(pending.AsSpan());
single.Add(pendingcpy); single.Add(pendingcpy);
@@ -343,8 +345,8 @@ public static unsafe class MeshletUtility
totalIndexCount += (nuint)clusters[pending[i]].indices.Count; totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
} }
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, Allocator.FreeList); using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, handle);
using var clusterCounts = new UnsafeList<uint>(pending.Count, Allocator.FreeList); using var clusterCounts = new UnsafeList<uint>(pending.Count, handle);
nuint offset = 0; nuint offset = 0;
for (var i = 0; i < pending.Count; i++) for (var i = 0; i < pending.Count; i++)
@@ -359,7 +361,7 @@ public static unsafe class MeshletUtility
offset += (nuint)cluster.indices.Count; offset += (nuint)cluster.indices.Count;
} }
using var clusterPart = new UnsafeArray<uint>(pending.Count, Allocator.FreeList); using var clusterPart = new UnsafeArray<uint>(pending.Count, handle);
var partitionCount = MeshOptApi.PartitionClusters( var partitionCount = MeshOptApi.PartitionClusters(
(uint*)clusterPart.GetUnsafePtr(), (uint*)clusterPart.GetUnsafePtr(),
@@ -373,10 +375,10 @@ public static unsafe class MeshletUtility
config.partitionSize 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++) 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++) for (var i = 0; i < pending.Count; i++)
@@ -387,9 +389,9 @@ public static unsafe class MeshletUtility
return partitions; return partitions;
} }
private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback) 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++) for (var i = 0; i < group.Count; i++)
{ {
@@ -423,10 +425,10 @@ public static unsafe class MeshletUtility
public uint id; public uint id;
} }
private static void SimplifyFallback(ref UnsafeArray<uint> lod, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint target_count, float* error) 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 = new UnsafeArray<SloppyVertex>(indices.Count, handle);
using var subset_locks = new UnsafeArray<byte>(indices.Count, Allocator.FreeList); using var subset_locks = new UnsafeArray<byte>(indices.Count, handle);
lod.Resize(indices.Count); lod.Resize(indices.Count);
@@ -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) if (targetCount >= (nuint)indices.Count)
{ {
@@ -527,7 +529,7 @@ public static unsafe class MeshletUtility
if ((nuint)lod.Length > targetCount && config.simplifyFallbackSloppy) if ((nuint)lod.Length > targetCount && config.simplifyFallbackSloppy)
{ {
SimplifyFallback(ref lod, in mesh, indices, locks, targetCount, error); SimplifyFallback(ref lod, in mesh, indices, locks, targetCount, error, handle);
*error *= config.simplifyErrorFactorSloppy; *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)"); 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 pool = new MemoryPool<VirtualArena, VirtualArena.CreationOptions>(new VirtualArena.CreationOptions
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, Allocator.FreeList); {
reserveCapacity = 256 * 1024 * 1024
});
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, pool.AllocationHandle, AllocationOption.Clear); ;
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, pool.AllocationHandle);
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
@@ -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++) for (var i = 0; i < clusters.Count; i++)
{ {
clusters[i].bounds = ComputeBounds(in mesh, clusters[i].indices, 0.0f); clusters[i].bounds = ComputeBounds(in mesh, clusters[i].indices, 0.0f);
} }
using var pending = new UnsafeList<int>(clusters.Count, Allocator.FreeList); using var pending = new UnsafeList<int>(clusters.Count, pool.AllocationHandle);
for (var i = 0; i < clusters.Count; i++) for (var i = 0; i < clusters.Count; i++)
{ {
pending.Add(i); pending.Add(i);
@@ -616,14 +623,14 @@ public static unsafe class MeshletUtility
while (pending.Count > 1) while (pending.Count > 1)
{ {
using var groups = Partition(in config, in mesh, clusters, pending, remap, Allocator.FreeList); using var groups = Partition(in config, in mesh, clusters, pending, remap, pool.AllocationHandle);
pending.Clear(); pending.Clear();
LockBoundary(locks, groups, clusters, remap, mesh.vertexLock); LockBoundary(locks, groups, clusters, remap, mesh.vertexLock);
for (var i = 0; i < groups.Count; i++) for (var i = 0; i < groups.Count; i++)
{ {
using var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, 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++) for (var j = 0; j < groups[i].Count; j++)
{ {
var clusterIndices = clusters[groups[i][j]].indices; 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 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; 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)) if ((nuint)simplified.Length > (nuint)(merged.Count * config.simplifyThreshold))
{ {
bounds.error = float.MaxValue; bounds.error = float.MaxValue;
OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
continue; continue;
} }
bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive; bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive;
var refined = OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); 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++) for (var j = 0; j < groups[i].Count; j++)
{ {
clusters[groups[i][j]].Dispose(); clusters[groups[i][j]].Dispose();
} }
using var split = Clusterize(in config, in mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Length, 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++) for (var j = 0; j < split.Count; j++)
{ {
split[j].refined = refined; split[j].refined = refined;
@@ -674,7 +681,7 @@ public static unsafe class MeshletUtility
{ {
var bounds = clusters[pending[0]].bounds; var bounds = clusters[pending[0]].bounds;
bounds.error = float.MaxValue; bounds.error = float.MaxValue;
OutputGroup(in config, in mesh, clusters, pending, bounds, depth, outputContext, outputCallback); OutputGroup(in config, in mesh, clusters, pending, bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
} }
var finalClusterCount = (nuint)clusters.Count; var finalClusterCount = (nuint)clusters.Count;

View File

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

View File

@@ -141,9 +141,9 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
continue; continue;
} }
Handle<GPUResource> instanceBufferHandle = default; Handle<GPUBuffer> instanceBufferHandle = default;
Handle<GPUResource> viewBufferHandle = default; Handle<GPUBuffer> viewBufferHandle = default;
Handle<GPUResource> frameBufferHandle = default; Handle<GPUBuffer> frameBufferHandle = default;
try try
{ {
@@ -220,17 +220,16 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
//var frustum = CreateFrustum(request.view.nearClipPlane, request.view.farClipPlane, vp, viewDir, viewPos); //var frustum = CreateFrustum(request.view.nearClipPlane, request.view.farClipPlane, vp, viewDir, viewPos);
var instanceDataSize = (uint)(instanceCount * sizeof(InstanceData)); var instanceDataSize = (uint)(instanceCount * sizeof(InstanceData));
var instanceBufferDesc = ResourceDesc.Buffer(new BufferDesc var instanceBufferDesc = new BufferDesc
{ {
Size = instanceDataSize, Size = instanceDataSize,
Stride = (uint)sizeof(InstanceData), Stride = (uint)sizeof(InstanceData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource, Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
MemoryType = ResourceMemoryType.Upload, // Upload directly for simplicity in testing MemoryType = ResourceMemoryType.Upload, // Upload directly for simplicity in testing
}); };
// TODO: Optimize by suballocation. instanceBufferHandle = resourceManager.CreateTransientBuffer(in instanceBufferDesc, "Instance Buffer");
instanceBufferHandle = resourceManager.GetPooledResource(instanceBufferDesc); var instanceBufferResource = instanceBufferHandle.AsResource();
var instanceBufferResource = instanceBufferHandle.AsGraphicsBuffer();
var instanceDataArray = new InstanceData[instanceCount]; var instanceDataArray = new InstanceData[instanceCount];
var instanceIdx = 0; var instanceIdx = 0;
@@ -256,21 +255,21 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
}; };
} }
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(instanceBufferHandle, null, BarrierSync.Copy, null, BarrierAccess.CopyDest)); ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(instanceBufferResource, null, BarrierSync.Copy, null, BarrierAccess.CopyDest));
ctx.CommandBuffer.UploadBuffer(instanceBufferResource, instanceDataArray.AsSpan()); ctx.CommandBuffer.UploadBuffer(instanceBufferHandle, instanceDataArray.AsSpan());
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(instanceBufferHandle, BarrierSync.Copy, BarrierSync.AllShading, BarrierAccess.CopyDest, BarrierAccess.ShaderResource)); ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(instanceBufferResource, BarrierSync.Copy, BarrierSync.AllShading, BarrierAccess.CopyDest, BarrierAccess.ShaderResource));
// 2. Allocate and populate View Data buffer // 2. Allocate and populate View Data buffer
var viewDataSize = (uint)sizeof(ViewData); var viewBufferDesc = new BufferDesc
var viewBufferDesc = ResourceDesc.Buffer(new BufferDesc
{ {
Size = viewDataSize, Size = (uint)sizeof(ViewData),
Stride = viewDataSize, Stride = (uint)sizeof(ViewData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource, Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
MemoryType = ResourceMemoryType.Upload, MemoryType = ResourceMemoryType.Upload,
}); };
viewBufferHandle = resourceManager.GetPooledResource(viewBufferDesc); viewBufferHandle = resourceManager.CreateTransientBuffer(in viewBufferDesc, "View Buffer");
var viewBufferResource = viewBufferHandle.AsResource();
var viewData = new ViewData var viewData = new ViewData
{ {
@@ -283,31 +282,32 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
screenSize = new float4(request.view.sensorSize.x, request.view.sensorSize.y, 1.0f / request.view.sensorSize.x, 1.0f / request.view.sensorSize.y) screenSize = new float4(request.view.sensorSize.x, request.view.sensorSize.y, 1.0f / request.view.sensorSize.x, 1.0f / request.view.sensorSize.y)
}; };
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(viewBufferHandle, null, BarrierSync.Copy, null, BarrierAccess.CopyDest)); ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(viewBufferResource, null, BarrierSync.Copy, null, BarrierAccess.CopyDest));
ctx.CommandBuffer.UploadBuffer(viewBufferHandle.AsGraphicsBuffer(), new ReadOnlySpan<ViewData>(in viewData)); ctx.CommandBuffer.UploadBuffer(viewBufferHandle, new ReadOnlySpan<ViewData>(in viewData));
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(viewBufferHandle, BarrierSync.Copy, BarrierSync.AllShading, BarrierAccess.CopyDest, BarrierAccess.ShaderResource)); ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(viewBufferResource, BarrierSync.Copy, BarrierSync.AllShading, BarrierAccess.CopyDest, BarrierAccess.ShaderResource));
// 3. Allocate and populate Global Frame Data buffer // 3. Allocate and populate Global Frame Data buffer
var frameDataSize = (uint)sizeof(FrameData); var frameDataSize = (uint)sizeof(FrameData);
var frameBufferDesc = ResourceDesc.Buffer(new BufferDesc var frameBufferDesc = new BufferDesc
{ {
Size = frameDataSize, Size = frameDataSize,
Stride = frameDataSize, Stride = frameDataSize,
Usage = BufferUsage.Raw | BufferUsage.ShaderResource, Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
MemoryType = ResourceMemoryType.Upload, MemoryType = ResourceMemoryType.Upload,
}); };
frameBufferHandle = resourceManager.GetPooledResource(frameBufferDesc); frameBufferHandle = resourceManager.CreateTransientBuffer(in frameBufferDesc, "Frame Buffer");
var frameBufferResource = frameBufferHandle.AsResource();
var frameData = new FrameData var frameData = new FrameData
{ {
viewBufferIndex = resourceDatabase.GetBindlessIndex(viewBufferHandle), viewBufferIndex = resourceDatabase.GetBindlessIndex(viewBufferResource),
instanceBufferIndex = resourceDatabase.GetBindlessIndex(instanceBufferResource.AsResource()), instanceBufferIndex = resourceDatabase.GetBindlessIndex(instanceBufferResource),
}; };
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(frameBufferHandle, null, BarrierSync.Copy, null, BarrierAccess.CopyDest)); ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(frameBufferResource, null, BarrierSync.Copy, null, BarrierAccess.CopyDest));
ctx.CommandBuffer.UploadBuffer(frameBufferHandle.AsGraphicsBuffer(), new ReadOnlySpan<FrameData>(in frameData)); ctx.CommandBuffer.UploadBuffer(frameBufferHandle, new ReadOnlySpan<FrameData>(in frameData));
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(frameBufferHandle, BarrierSync.Copy, BarrierSync.AllShading, BarrierAccess.CopyDest, BarrierAccess.ShaderResource)); ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(frameBufferResource, BarrierSync.Copy, BarrierSync.AllShading, BarrierAccess.CopyDest, BarrierAccess.ShaderResource));
if (request.renderFunc != null) if (request.renderFunc != null)
{ {
@@ -320,7 +320,9 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
var backBuffer = _renderGraph.ImportTexture(rt, "BackBuffer"); var backBuffer = _renderGraph.ImportTexture(rt, "BackBuffer");
MeshletDebugPass(backBuffer, request.opaqueRenderList, MeshletDebugPass(backBuffer, request.opaqueRenderList,
resourceDatabase.GetBindlessIndex(frameBufferHandle), resourceDatabase.GetBindlessIndex(viewBufferHandle), resourceDatabase.GetBindlessIndex(instanceBufferHandle)); resourceDatabase.GetBindlessIndex(frameBufferResource),
resourceDatabase.GetBindlessIndex(viewBufferResource),
resourceDatabase.GetBindlessIndex(instanceBufferResource));
var viewState = new ViewState(rtSize.x, rtSize.y, rtSize.x, rtSize.y); var viewState = new ViewState(rtSize.x, rtSize.y, rtSize.x, rtSize.y);
_renderGraph.Compile(viewState); _renderGraph.Compile(viewState);
@@ -329,10 +331,9 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
} }
finally finally
{ {
// We must enqueue a return for the pooled resources so they are freed next frame. _renderSystem.GraphicsEngine.ResourceDatabase.ReleaseResource(instanceBufferHandle.AsResource());
resourceManager.ReturnPooledResource(instanceBufferHandle); _renderSystem.GraphicsEngine.ResourceDatabase.ReleaseResource(viewBufferHandle.AsResource());
resourceManager.ReturnPooledResource(viewBufferHandle); _renderSystem.GraphicsEngine.ResourceDatabase.ReleaseResource(frameBufferHandle.AsResource());
resourceManager.ReturnPooledResource(frameBufferHandle);
if (request.swapChainIndex >= 0) if (request.swapChainIndex >= 0)
{ {

View File

@@ -67,7 +67,6 @@ internal static class MeshUtility
} }
using var flatVertices = new UnsafeList<Vertex>(1024, scope0.AllocationHandle); using var flatVertices = new UnsafeList<Vertex>(1024, scope0.AllocationHandle);
//using var flatIndices = new UnsafeList<uint>(1024, scope0.AllocationHandle);
var needComputeNormals = false; var needComputeNormals = false;
@@ -91,7 +90,7 @@ internal static class MeshUtility
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u); var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
using var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, scope1.AllocationHandle); var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, scope1.AllocationHandle);
for (var j = 0u; j < pMesh->num_faces; j++) for (var j = 0u; j < pMesh->num_faces; j++)
{ {

View File

@@ -5,7 +5,6 @@ using Ghost.Engine.Utilities;
using Ghost.Entities; using Ghost.Entities;
using Ghost.Graphics.Core; using Ghost.Graphics.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;