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:
@@ -11,16 +11,16 @@ namespace Ghost.Graphics.Core;
|
||||
internal struct CBufferCache : IResourceReleasable
|
||||
{
|
||||
private UnsafeArray<byte> _cpuData;
|
||||
private Handle<RHI.GPUBuffer> _gpuResource;
|
||||
private Handle<GPUBuffer> _gpuResource;
|
||||
private uint _size;
|
||||
|
||||
public readonly UnsafeArray<byte> CpuData => _cpuData;
|
||||
public readonly Handle<RHI.GPUBuffer> GpuResource => _gpuResource;
|
||||
public readonly Handle<GPUBuffer> GpuResource => _gpuResource;
|
||||
public readonly uint Size => _size;
|
||||
|
||||
public readonly bool IsCreated => _size != 0 && _gpuResource.IsValid && _cpuData.IsCreated;
|
||||
|
||||
public CBufferCache(Handle<RHI.GPUBuffer> buffer, uint bufferSize)
|
||||
public CBufferCache(Handle<GPUBuffer> buffer, uint bufferSize)
|
||||
{
|
||||
_size = bufferSize;
|
||||
_cpuData = new UnsafeArray<byte>((int)bufferSize, Allocator.Persistent);
|
||||
@@ -37,7 +37,7 @@ internal struct CBufferCache : IResourceReleasable
|
||||
_cpuData.Dispose();
|
||||
database.ReleaseResource(_gpuResource.AsResource());
|
||||
|
||||
_gpuResource = Handle<RHI.GPUBuffer>.Invalid;
|
||||
_gpuResource = Handle<GPUBuffer>.Invalid;
|
||||
_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
@@ -138,7 +137,7 @@ public struct Mesh : IResourceReleasable
|
||||
/// <summary>
|
||||
/// Gets the handle to the vertex buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<RHI.GPUBuffer> VertexBuffer
|
||||
public Handle<GPUBuffer> VertexBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
@@ -146,7 +145,7 @@ public struct Mesh : IResourceReleasable
|
||||
/// <summary>
|
||||
/// Gets the handle to the index buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<RHI.GPUBuffer> IndexBuffer
|
||||
public Handle<GPUBuffer> IndexBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
@@ -154,7 +153,7 @@ public struct Mesh : IResourceReleasable
|
||||
/// <summary>
|
||||
/// Gets the handle to the meshlet buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<RHI.GPUBuffer> MeshLetBuffer
|
||||
public Handle<GPUBuffer> MeshLetBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
@@ -162,7 +161,7 @@ public struct Mesh : IResourceReleasable
|
||||
/// <summary>
|
||||
/// Gets the handle to the meshlet vertices buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<RHI.GPUBuffer> MeshletVerticesBuffer
|
||||
public Handle<GPUBuffer> MeshletVerticesBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
@@ -170,7 +169,7 @@ public struct Mesh : IResourceReleasable
|
||||
/// <summary>
|
||||
/// Gets the handle to the meshlet triangles buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<RHI.GPUBuffer> MeshletTrianglesBuffer
|
||||
public Handle<GPUBuffer> MeshletTrianglesBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
@@ -178,12 +177,12 @@ public struct Mesh : IResourceReleasable
|
||||
/// <summary>
|
||||
/// Gets the handle to the mesh data buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<RHI.GPUBuffer> ObjectDataBuffer
|
||||
public Handle<GPUBuffer> ObjectDataBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<RHI.GPUBuffer> vertexBuffer, Handle<RHI.GPUBuffer> indexBuffer)
|
||||
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GPUBuffer> vertexBuffer, Handle<GPUBuffer> indexBuffer)
|
||||
{
|
||||
Vertices = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
||||
Indices = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
|
||||
|
||||
@@ -2,10 +2,11 @@ using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
// TODO: Temporary rendering context for resource creation and data upload. We will refactor it later when we have a better understanding of the engine architecture.
|
||||
// TODO: Temporary rendering context for heap creation and data upload. We will refactor it later when we have a better understanding of the engine architecture.
|
||||
public readonly unsafe ref struct RenderingContext
|
||||
{
|
||||
private readonly IGraphicsEngine _engine;
|
||||
@@ -82,6 +83,49 @@ public readonly unsafe ref struct RenderingContext
|
||||
ResourceDatabase.SetResourceBarrierData(resource, new ResourceBarrierData(newLayout, newAccess, newSync));
|
||||
}
|
||||
|
||||
private void UploadBuffer<T>(Handle<GPUBuffer> buffer, params ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var r = _engine.ResourceDatabase.GetResourceDescription(buffer.AsResource());
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(r.Value.Type == ResourceType.Buffer);
|
||||
|
||||
var sizeInBytes = (nuint)(data.Length * sizeof(T));
|
||||
var memoryType = r.Value.BufferDescription.MemoryType;
|
||||
|
||||
if (memoryType == ResourceMemoryType.Upload)
|
||||
{
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
ResourceDatabase.Map(buffer.AsResource(), 0, null, null, pData, sizeInBytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//var uploadHandle = _resourceAllocator.CreateTempUploadBuffer(sizeInBytes, out var offset);
|
||||
//var uploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
|
||||
var uploadDesc = new BufferDesc
|
||||
{
|
||||
Size = sizeInBytes,
|
||||
Usage = BufferUsage.Upload,
|
||||
MemoryType = ResourceMemoryType.Upload,
|
||||
};
|
||||
|
||||
var uploadHandle = _resourceManager.CreateTransientBuffer(in uploadDesc);
|
||||
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
ResourceDatabase.Map(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
|
||||
}
|
||||
|
||||
_directCmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
|
||||
{
|
||||
var mesh = _resourceManager.CreateMesh(vertices, indices);
|
||||
@@ -98,8 +142,8 @@ public readonly unsafe ref struct RenderingContext
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
_directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
|
||||
_directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
|
||||
UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan());
|
||||
UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan());
|
||||
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
|
||||
@@ -147,8 +191,8 @@ public readonly unsafe ref struct RenderingContext
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
_directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
|
||||
_directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
|
||||
UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan());
|
||||
UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan());
|
||||
|
||||
TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading);
|
||||
TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput);
|
||||
@@ -204,9 +248,9 @@ public readonly unsafe ref struct RenderingContext
|
||||
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
_directCmd.UploadBuffer(meshRef.MeshLetBuffer, meshletData.meshlets.AsSpan());
|
||||
_directCmd.UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan());
|
||||
_directCmd.UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan());
|
||||
UploadBuffer(meshRef.MeshLetBuffer, meshletData.meshlets.AsSpan());
|
||||
UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan());
|
||||
UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan());
|
||||
|
||||
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
|
||||
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
|
||||
@@ -236,7 +280,7 @@ public readonly unsafe ref struct RenderingContext
|
||||
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
||||
|
||||
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
_directCmd.UploadBuffer(meshData.ObjectDataBuffer, data);
|
||||
UploadBuffer(meshData.ObjectDataBuffer, data);
|
||||
TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading);
|
||||
}
|
||||
|
||||
@@ -252,16 +296,29 @@ public readonly unsafe ref struct RenderingContext
|
||||
public void UploadTexture<T>(Handle<GPUTexture> texture, ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
|
||||
|
||||
//var size = ResourceAllocator.GetSizeInfo(desc).Size;
|
||||
//if ((ulong)(data.Length * sizeof(T)) != ResourceAllocator.GetSizeInfo(desc).Size)
|
||||
//{
|
||||
// throw new ArgumentException("Data size does not match texture size.");
|
||||
//}
|
||||
|
||||
var desc = ResourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
|
||||
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
|
||||
|
||||
var requiredSize = ResourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
|
||||
var uploadDesc = new BufferDesc
|
||||
{
|
||||
Size = requiredSize,
|
||||
Usage = BufferUsage.Upload,
|
||||
MemoryType = ResourceMemoryType.Upload,
|
||||
};
|
||||
|
||||
var uploadHandle = _resourceManager.CreateTransientBuffer(in uploadDesc);
|
||||
if (uploadHandle.IsInvalid)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to create upload buffer for texture data.");
|
||||
}
|
||||
|
||||
TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
|
||||
fixed (T* pData = data)
|
||||
@@ -273,7 +330,7 @@ public readonly unsafe ref struct RenderingContext
|
||||
slicePitch = slicePitch
|
||||
};
|
||||
|
||||
_directCmd.UploadTexture(texture, subresourceData);
|
||||
_directCmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Diagnostics;
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Main render graph class that manages resource allocation and pass execution.
|
||||
/// Main render graph class that manages heap allocation and pass execution.
|
||||
/// </summary>
|
||||
public sealed class RenderGraph : IDisposable
|
||||
{
|
||||
@@ -125,7 +125,7 @@ public sealed class RenderGraph : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="buffer">The external buffer handle.</param>
|
||||
/// <returns>The identifier of the imported render graph buffer. Invalid if import fails.</returns>
|
||||
public Identifier<RGBuffer> ImportBuffer(Handle<RHI.GPUBuffer> buffer, string name)
|
||||
public Identifier<RGBuffer> ImportBuffer(Handle<GPUBuffer> buffer, string name)
|
||||
{
|
||||
var r = _resourceDatabase.GetResourceDescription(buffer.AsResource());
|
||||
if (r.IsFailure)
|
||||
@@ -178,7 +178,7 @@ public sealed class RenderGraph : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles the render graph by culling unused passes and determining resource lifetimes.
|
||||
/// Compiles the render graph by culling unused passes and determining heap lifetimes.
|
||||
/// </summary>
|
||||
public Error Compile(ViewState viewState)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ internal struct MemoryBlock
|
||||
public bool isFree;
|
||||
public int firstUsePass;
|
||||
public int lastUsePass;
|
||||
public int logicalResourceIndex; // Which logical resource is currently using this block
|
||||
public int logicalResourceIndex; // Which logical heap is currently using this block
|
||||
|
||||
public MemoryBlock(ulong offset, ulong size)
|
||||
{
|
||||
@@ -267,7 +267,7 @@ internal sealed class ResourceAliasingManager
|
||||
|
||||
private readonly ResourceHeap _heap;
|
||||
private readonly List<PlacedResource> _placedResources;
|
||||
// Mapping from logical resource index to placed resource index
|
||||
// Mapping from logical heap index to placed heap index
|
||||
private readonly Dictionary<int, int> _logicalToPlaced;
|
||||
|
||||
private const ulong _DEFAULT_TEXTURE_ALIGNMENT = 65536; // 64KB
|
||||
@@ -373,7 +373,7 @@ internal sealed class ResourceAliasingManager
|
||||
_heap.size = peakMemoryUsage;
|
||||
_heap.Reset();
|
||||
|
||||
// Allocate each logical resource in the heap
|
||||
// Allocate each logical heap in the heap
|
||||
foreach (var (logicalIndex, logicalResource) in logicalResources)
|
||||
{
|
||||
// TODO: Currently we are recalculating the aliasing candidates in the real allocation pass.
|
||||
@@ -414,7 +414,7 @@ internal sealed class ResourceAliasingManager
|
||||
}
|
||||
|
||||
// Second pass: Populate aliasedLogicalResources lists
|
||||
// For each placed resource, find all OTHER placed resources at the same offset
|
||||
// For each placed heap, find all OTHER placed resources at the same offset
|
||||
for (var i = 0; i < _placedResources.Count; i++)
|
||||
{
|
||||
var placed = _placedResources[i];
|
||||
@@ -432,7 +432,7 @@ internal sealed class ResourceAliasingManager
|
||||
// Check if they're at the same offset
|
||||
if (other.heapOffset == placed.heapOffset)
|
||||
{
|
||||
// Add the other's logical resource to this one's aliased list
|
||||
// Add the other's logical heap to this one's aliased list
|
||||
var otherLogicalIndex = other.aliasedLogicalResources[0]; // Each has exactly one at this point
|
||||
if (!placed.aliasedLogicalResources.Contains(otherLogicalIndex))
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ internal enum BarrierFlags
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a resource barrier requirement that needs to be resolved at runtime.
|
||||
/// Represents a heap barrier requirement that needs to be resolved at runtime.
|
||||
/// </summary>
|
||||
internal struct ResourceBarrier
|
||||
{
|
||||
@@ -55,7 +55,7 @@ internal struct ResourceBarrier
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the current state of a resource across passes during compilation.
|
||||
/// Tracks the current state of a heap across passes during compilation.
|
||||
/// </summary>
|
||||
internal sealed class ResourceStateTracker
|
||||
{
|
||||
@@ -124,7 +124,7 @@ internal static class RenderGraphBarriers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts aliasing barriers when a placed resource is reused.
|
||||
/// Inserts aliasing barriers when a placed heap is reused.
|
||||
/// </summary>
|
||||
private static void InsertAliasingBarriers(
|
||||
RenderGraphPassBase pass,
|
||||
@@ -148,20 +148,20 @@ internal static class RenderGraphBarriers
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is the first use of this logical resource
|
||||
// Check if this is the first use of this logical heap
|
||||
if (resource.firstUsePass == pass.index)
|
||||
{
|
||||
// Get the placed resource
|
||||
// Get the placed heap
|
||||
var placedIndex = aliasingManager.GetPlacedResourceIndex(id.Value);
|
||||
if (placedIndex >= 0)
|
||||
{
|
||||
var placed = aliasingManager.GetPlacedResource(placedIndex);
|
||||
|
||||
// If this placed resource has multiple aliased resources,
|
||||
// If this placed heap has multiple aliased resources,
|
||||
// we need an aliasing barrier when switching between them
|
||||
if (placed != null && placed.aliasedLogicalResources.Count > 1)
|
||||
{
|
||||
// Find the resource that used this placed memory most recently before this pass
|
||||
// Find the heap that used this placed memory most recently before this pass
|
||||
Identifier<RGResource> resourceBefore = default;
|
||||
var mostRecentLastUse = -1;
|
||||
|
||||
@@ -169,10 +169,10 @@ internal static class RenderGraphBarriers
|
||||
{
|
||||
if (otherLogicalIndex != id.Value)
|
||||
{
|
||||
// Get resource by global index
|
||||
// Get heap by global index
|
||||
var otherResource = resources.GetResourceByIndex(otherLogicalIndex);
|
||||
|
||||
// Check if this resource finished before our resource starts
|
||||
// Check if this heap finished before our heap starts
|
||||
if (otherResource.lastUsePass < pass.index &&
|
||||
otherResource.lastUsePass > mostRecentLastUse)
|
||||
{
|
||||
@@ -182,7 +182,7 @@ internal static class RenderGraphBarriers
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a previous resource, insert aliasing barrier
|
||||
// If we found a previous heap, insert aliasing barrier
|
||||
if (mostRecentLastUse >= 0)
|
||||
{
|
||||
// Aliasing Requirement: Transition to Undefined, Sync with Predecessor
|
||||
@@ -215,7 +215,7 @@ internal static class RenderGraphBarriers
|
||||
List<CompiledBarrier> compiledBarriers,
|
||||
RenderGraphResourceRegistry resources)
|
||||
{
|
||||
// Helper to add a compiled barrier for a resource transition
|
||||
// Helper to add a compiled barrier for a heap transition
|
||||
void AddTransition(Identifier<RGResource> id, ResourceBarrierData targetState)
|
||||
{
|
||||
var resource = resources.GetResource(id);
|
||||
|
||||
@@ -22,7 +22,7 @@ public enum ResourceExtractionFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// Releases the old resource after extraction.
|
||||
/// Releases the old heap after extraction.
|
||||
/// </summary>
|
||||
ReleaseAfterExtract = 1 << 0,
|
||||
}
|
||||
@@ -36,19 +36,19 @@ public interface IRenderGraphBuilder : IDisposable
|
||||
void AllowPassCulling(bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new texture resource based on the specified desc.
|
||||
/// Creates a new texture heap based on the specified desc.
|
||||
/// </summary>
|
||||
/// <param name="desc">A structure that defines the properties and configuration of the texture to create.</param>
|
||||
/// <param name="name">The name of the texture resource.</param>
|
||||
/// <returns>An identifier for the newly created texture resource.</returns>
|
||||
/// <param name="name">The name of the texture heap.</param>
|
||||
/// <returns>An identifier for the newly created texture heap.</returns>
|
||||
Identifier<RGTexture> CreateTexture(in RGTextureDesc desc, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new buffer resource based on the specified desc.
|
||||
/// Creates a new buffer heap based on the specified desc.
|
||||
/// </summary>
|
||||
/// <param name="desc">A structure that defines the properties and configuration of the buffer to create.</param>
|
||||
/// <param name="name">The name of the buffer resource.</param>
|
||||
/// <returns>An identifier for the newly created buffer resource.</returns>
|
||||
/// <param name="name">The name of the buffer heap.</param>
|
||||
/// <returns>An identifier for the newly created buffer heap.</returns>
|
||||
Identifier<RGBuffer> CreateBuffer(in BufferDesc desc, string name);
|
||||
|
||||
/// <summary>
|
||||
@@ -69,17 +69,17 @@ public interface IRenderGraphBuilder : IDisposable
|
||||
Identifier<RGBuffer> UseBuffer(Identifier<RGBuffer> buffer, AccessFlags accessMode);
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the actual texture resource associated with the given identifier for use in outside of the render graph execution context.
|
||||
/// Extracts the actual texture heap associated with the given identifier for use in outside of the render graph execution context.
|
||||
/// </summary>
|
||||
/// <param name="src">The identifier of the texture to be extracted.</param>
|
||||
/// <param name="dst">A handle to receive the actual GPU texture resource.</param>
|
||||
/// <param name="dst">A handle to receive the actual GPU texture heap.</param>
|
||||
void QueryTextureExtraction(Identifier<RGTexture> src, Handle<GPUTexture> dst, ResourceExtractionFlags flags = ResourceExtractionFlags.ReleaseAfterExtract);
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the actual buffer resource associated with the given identifier for use in outside of the render graph execution context.
|
||||
/// Extracts the actual buffer heap associated with the given identifier for use in outside of the render graph execution context.
|
||||
/// </summary>
|
||||
/// <param name="src">The identifier of the buffer to be extracted.</param>
|
||||
/// <param name="dst">A handle to receive the actual GPU buffer resource.</param>
|
||||
/// <param name="dst">A handle to receive the actual GPU buffer heap.</param>
|
||||
void QueryBufferExtraction(Identifier<RGBuffer> src, Handle<GPUBuffer> dst, ResourceExtractionFlags flags = ResourceExtractionFlags.ReleaseAfterExtract);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public interface IRasterRenderGraphBuilder : IRenderGraphBuilder
|
||||
/// <summary>
|
||||
/// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer resource.</param>
|
||||
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer heap.</param>
|
||||
/// <returns>An identifier for the buffer.</returns>
|
||||
Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer);
|
||||
|
||||
@@ -150,7 +150,7 @@ public interface IUnsafeRenderGraphBuilder : IRenderGraphBuilder
|
||||
/// <summary>
|
||||
/// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer resource.</param>
|
||||
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer heap.</param>
|
||||
/// <returns>An identifier for the buffer.</returns>
|
||||
Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer);
|
||||
|
||||
|
||||
@@ -16,16 +16,16 @@ internal sealed class CachedCompilation
|
||||
// Culling decisions for each pass
|
||||
public readonly List<bool> passCulledFlags = new(64);
|
||||
|
||||
// Physical resource aliasing mappings (logical index -> physical index)
|
||||
// Physical heap aliasing mappings (logical index -> physical index)
|
||||
public readonly Dictionary<int, int> logicalToPhysical = new(128);
|
||||
|
||||
// Placed resource metadata
|
||||
// Placed heap metadata
|
||||
public readonly List<PlacedResourceData> placedResources = new(32);
|
||||
|
||||
// Compiled barriers (stores only target states, queries before state from ResourceManager)
|
||||
public readonly List<CompiledBarrier> compiledBarriers = new(128);
|
||||
|
||||
// Real gpu resource
|
||||
// Real gpu heap
|
||||
public readonly List<Handle<GPUResource>> backingResources = new(32);
|
||||
|
||||
// View state used for this compilation
|
||||
@@ -44,7 +44,7 @@ internal sealed class CachedCompilation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Placed resource data for caching.
|
||||
/// Placed heap data for caching.
|
||||
/// </summary>
|
||||
internal struct PlacedResourceData
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using Ghost.Graphics.RHI;
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// Handles compilation of the render graph including pass culling, resource allocation,
|
||||
/// Handles compilation of the render graph including pass culling, heap allocation,
|
||||
/// barrier compilation, and cache management.
|
||||
/// </summary>
|
||||
internal sealed class RenderGraphCompiler
|
||||
@@ -229,7 +229,7 @@ internal sealed class RenderGraphCompiler
|
||||
{
|
||||
Size = _aliasingManager.Heap.size + 64 * 1024, // Add 64KB padding to avoid potential overflows
|
||||
Alignment = ResourceHeap.DEFAULT_ALIGNMENT,
|
||||
HeapFlags = HeapFlags.AlowBufferAndTexture,
|
||||
HeapFlags = HeapFlags.AllowAllBufferAndTexture,
|
||||
HeapType = HeapType.Default
|
||||
};
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ public interface IRenderGraphContext
|
||||
|
||||
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
|
||||
Handle<GPUTexture> GetActualTexture(Identifier<RGTexture> texture);
|
||||
Handle<RHI.GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer);
|
||||
Handle<GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer);
|
||||
|
||||
Handle<GPUTexture> GetHistoryTexture(ReadOnlySpan<Identifier<RGTexture>> texture, int historyOffset);
|
||||
Handle<RHI.GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffer, int historyOffset);
|
||||
Handle<GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffer, int historyOffset);
|
||||
|
||||
ICommandBuffer GetCommandBufferUnsafe();
|
||||
}
|
||||
@@ -60,8 +60,8 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
private TextureFormat _dsvFormat;
|
||||
private int _rtvCount;
|
||||
|
||||
private Handle<RHI.GPUBuffer> _activePerMaterialData;
|
||||
private Handle<RHI.GPUBuffer> _activePerMeshData;
|
||||
private Handle<GPUBuffer> _activePerMaterialData;
|
||||
private Handle<GPUBuffer> _activePerMeshData;
|
||||
private int _activeMeshIndexCount;
|
||||
|
||||
private uint _activeFrameBuffer;
|
||||
@@ -120,9 +120,9 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
return _resources.GetResource(texture.AsResource()).backingResource.AsTexture();
|
||||
}
|
||||
|
||||
public Handle<RHI.GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer)
|
||||
public Handle<GPUBuffer> GetActualBuffer(Identifier<RGBuffer> buffer)
|
||||
{
|
||||
return _resources.GetResource(buffer.AsResource()).backingResource.AsGraphicsBuffer();
|
||||
return _resources.GetResource(buffer.AsResource()).backingResource.AsBuffer();
|
||||
}
|
||||
|
||||
public Handle<GPUTexture> GetHistoryTexture(ReadOnlySpan<Identifier<RGTexture>> textures, int historyOffset)
|
||||
@@ -141,11 +141,11 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
return GetActualTexture(textures[index]);
|
||||
}
|
||||
|
||||
public Handle<RHI.GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffers, int historyOffset)
|
||||
public Handle<GPUBuffer> GetHistoryBuffer(ReadOnlySpan<Identifier<RGBuffer>> buffers, int historyOffset)
|
||||
{
|
||||
if (historyOffset < 0 || historyOffset >= buffers.Length)
|
||||
{
|
||||
return Handle<RHI.GPUBuffer>.Invalid;
|
||||
return Handle<GPUBuffer>.Invalid;
|
||||
}
|
||||
|
||||
var index = (int)(_frameIndex % buffers.Length) - historyOffset;
|
||||
@@ -172,7 +172,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
var r = _resourceManager.GetMaterialReference(material);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
_activePerMaterialData = Handle<RHI.GPUBuffer>.Invalid;
|
||||
_activePerMaterialData = Handle<GPUBuffer>.Invalid;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
var shaderResult = _resourceManager.GetShaderReference(material.Shader);
|
||||
if (shaderResult.IsFailure)
|
||||
{
|
||||
_activePerMaterialData = Handle<RHI.GPUBuffer>.Invalid;
|
||||
_activePerMaterialData = Handle<GPUBuffer>.Invalid;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
var r = _resourceManager.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
_activePerMeshData = Handle<RHI.GPUBuffer>.Invalid;
|
||||
_activePerMeshData = Handle<GPUBuffer>.Invalid;
|
||||
_activeMeshIndexCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ internal static class RenderGraphHasher
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a hash of a texture resource's structural properties.
|
||||
/// Computes a hash of a texture heap's structural properties.
|
||||
/// For imported textures, hashes the backing handle.
|
||||
/// For transient textures, hashes the descriptor (respecting size mode).
|
||||
/// </summary>
|
||||
@@ -94,7 +94,7 @@ internal static class RenderGraphHasher
|
||||
// Hash imported flag
|
||||
writer.Write(resource.isImported);
|
||||
|
||||
// For imported textures, hash the backing resource handle
|
||||
// For imported textures, hash the backing heap handle
|
||||
if (resource.isImported)
|
||||
{
|
||||
writer.Write(resource.backingResource.GetHashCode());
|
||||
|
||||
@@ -204,7 +204,7 @@ internal sealed class RenderGraphNativePassBuilder
|
||||
{
|
||||
var laterPass = compiledPasses[passB];
|
||||
|
||||
// Build a set of render target resource IDs (color + depth)
|
||||
// Build a set of render target heap IDs (color + depth)
|
||||
var renderTargets = new HashSet<Identifier<RGResource>>();
|
||||
for (var i = 0; i <= laterPass.maxColorIndex; i++)
|
||||
{
|
||||
@@ -241,7 +241,7 @@ internal sealed class RenderGraphNativePassBuilder
|
||||
|
||||
/// <summary>
|
||||
/// Infers optimal load/store operations for all attachments in a native render pass.
|
||||
/// Uses resource lifetime information to minimize memory bandwidth (critical for TBDR GPUs).
|
||||
/// Uses heap lifetime information to minimize memory bandwidth (critical for TBDR GPUs).
|
||||
/// </summary>
|
||||
private void InferLoadStoreOps(NativeRenderPass nativePass)
|
||||
{
|
||||
|
||||
@@ -211,7 +211,7 @@ internal sealed class RenderGraphResourceRegistry
|
||||
return new Identifier<RGTexture>(resource.index);
|
||||
}
|
||||
|
||||
public Identifier<RGBuffer> ImportBuffer(ref readonly BufferDesc desc, Handle<RHI.GPUBuffer> buffer, string name)
|
||||
public Identifier<RGBuffer> ImportBuffer(ref readonly BufferDesc desc, Handle<GPUBuffer> buffer, string name)
|
||||
{
|
||||
var resource = _pool.Rent<RenderGraphResource>();
|
||||
resource.name = name;
|
||||
@@ -256,7 +256,7 @@ internal sealed class RenderGraphResourceRegistry
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets resource by global index. Use this when iterating over all resources.
|
||||
/// Gets heap by global index. Use this when iterating over all resources.
|
||||
/// </summary>
|
||||
public RenderGraphResource GetResourceByIndex(int index)
|
||||
{
|
||||
|
||||
@@ -163,7 +163,7 @@ public class RenderSystem : IDisposable
|
||||
throw new NotSupportedException($"The specified graphics API '{desc.GraphicsAPI}' is not supported.");
|
||||
}
|
||||
|
||||
_resourceManager = new ResourceManager(_graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
|
||||
_resourceManager = new ResourceManager(_graphicsEngine.Device, _graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
|
||||
_swapChainManager = new SwapChainManager(_graphicsEngine);
|
||||
|
||||
// Create frame resources for synchronization
|
||||
@@ -246,7 +246,7 @@ public class RenderSystem : IDisposable
|
||||
var flushFence = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue);
|
||||
_graphicsEngine.Device.GraphicsQueue.WaitForValue(flushFence);
|
||||
|
||||
// Sync the current frame resource to this new fence to keep state consistent
|
||||
// Sync the current frame heap to this new fence to keep state consistent
|
||||
frameResource.FenceValue = flushFence;
|
||||
|
||||
foreach (var resource in _frameResources)
|
||||
@@ -322,7 +322,7 @@ public class RenderSystem : IDisposable
|
||||
}
|
||||
|
||||
// End the frame and present
|
||||
_resourceManager.EndFrame(_cpuFenceValue);
|
||||
_resourceManager.EndFrame(_gpuFenceValue);
|
||||
r = _graphicsEngine.EndFrame(_gpuFenceValue);
|
||||
|
||||
if (r.IsFailure)
|
||||
@@ -426,9 +426,11 @@ public class RenderSystem : IDisposable
|
||||
|
||||
_renderPipeline.Dispose();
|
||||
|
||||
_swapChainManager.Dispose();
|
||||
_resourceManager.Dispose();
|
||||
_graphicsEngine.Dispose();
|
||||
|
||||
_swapChainManager.Dispose();
|
||||
|
||||
_shutdownEvent.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
|
||||
271
src/Runtime/Ghost.Graphics/ResourceManager.Pool.cs
Normal file
271
src/Runtime/Ghost.Graphics/ResourceManager.Pool.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
public sealed class ResourceManager : IDisposable
|
||||
public sealed partial class ResourceManager : IDisposable
|
||||
{
|
||||
private readonly struct ResourceReturnEntry
|
||||
{
|
||||
@@ -22,6 +22,7 @@ public sealed class ResourceManager : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private readonly IRenderDevice _renderDevice;
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
|
||||
@@ -31,15 +32,13 @@ public sealed class ResourceManager : IDisposable
|
||||
|
||||
private readonly MaterialPaletteStore _materialPalettes;
|
||||
|
||||
private ulong _currentFrame;
|
||||
|
||||
private UnsafeHashMap<ResourceDesc, UnsafeQueue<Handle<GPUResource>>> _resourceCache;
|
||||
private UnsafeQueue<ResourceReturnEntry> _resourceReturnQueue;
|
||||
private ulong _cpuFrame;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public ResourceManager(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
|
||||
public ResourceManager(IRenderDevice renderDevice, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
|
||||
{
|
||||
_renderDevice = renderDevice;
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
@@ -48,9 +47,8 @@ public sealed class ResourceManager : IDisposable
|
||||
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
|
||||
|
||||
_materialPalettes = new MaterialPaletteStore();
|
||||
|
||||
_resourceCache = new UnsafeHashMap<ResourceDesc, UnsafeQueue<Handle<GPUResource>>>(32, Allocator.Persistent);
|
||||
_resourceReturnQueue = new UnsafeQueue<ResourceReturnEntry>(32, Allocator.Persistent);
|
||||
|
||||
InitializePool();
|
||||
}
|
||||
|
||||
~ResourceManager()
|
||||
@@ -58,30 +56,16 @@ public sealed class ResourceManager : IDisposable
|
||||
Dispose();
|
||||
}
|
||||
|
||||
internal void BeginFrame(ulong currentFrame)
|
||||
internal void BeginFrame(ulong cpuFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
_currentFrame = currentFrame;
|
||||
_cpuFrame = cpuFrame;
|
||||
}
|
||||
|
||||
internal void EndFrame(ulong completedFrame)
|
||||
internal void EndFrame(ulong gpuFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
|
||||
while (_resourceReturnQueue.TryPeek(out var entry) && entry.returnFrame <= completedFrame)
|
||||
{
|
||||
_resourceReturnQueue.Dequeue();
|
||||
var result = _resourceDatabase.GetResourceDescription(entry.handle);
|
||||
Debug.Assert(result.IsSuccess);
|
||||
|
||||
ref var queue = ref _resourceCache.GetValueRefOrAddDefault(result.Value, out var exist);
|
||||
if (!exist)
|
||||
{
|
||||
queue = new UnsafeQueue<Handle<GPUResource>>(4, Allocator.Persistent);
|
||||
}
|
||||
|
||||
queue.Enqueue(entry.handle);
|
||||
}
|
||||
EndFramePool(gpuFrame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -198,7 +182,7 @@ public sealed class ResourceManager : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
|
||||
/// Releases the mesh heap associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
|
||||
public void ReleaseMesh(Handle<Mesh> handle)
|
||||
@@ -364,38 +348,6 @@ public sealed class ResourceManager : IDisposable
|
||||
shader.ReleaseResource(_resourceDatabase);
|
||||
}
|
||||
|
||||
public Handle<GPUResource> GetPooledResource(in ResourceDesc desc)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
|
||||
ref var queue = ref _resourceCache.GetValueRef(desc, out var exist);
|
||||
if (exist && queue.TryDequeue(out Handle<GPUResource> handle))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
|
||||
handle = desc.Type switch
|
||||
{
|
||||
ResourceType.Buffer => _resourceAllocator.CreateBuffer(in desc.BufferDescription, "PooledBuffer").AsResource(),
|
||||
ResourceType.Texture => _resourceAllocator.CreateTexture(in desc.TextureDescription, "PooledTexture").AsResource(),
|
||||
_ => throw new ArgumentException("Invalid resource type.", nameof(desc)),
|
||||
};
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public void ReturnPooledResource(Handle<GPUResource> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
|
||||
if (handle.IsInvalid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_resourceReturnQueue.Enqueue(new ResourceReturnEntry(handle, _currentFrame));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
@@ -423,19 +375,7 @@ public sealed class ResourceManager : IDisposable
|
||||
_shaders.Dispose();
|
||||
_materialPalettes.Dispose();
|
||||
|
||||
foreach (var kvp in _resourceCache)
|
||||
{
|
||||
var queue = kvp.Value;
|
||||
while (queue.TryDequeue(out var handle))
|
||||
{
|
||||
_resourceDatabase.ReleaseResource(handle);
|
||||
}
|
||||
|
||||
queue.Dispose();
|
||||
}
|
||||
|
||||
_resourceCache.Dispose();
|
||||
_resourceReturnQueue.Dispose();
|
||||
DisposePool();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
1228
src/Runtime/Ghost.Graphics/Shaders/Includes/S2H/s2h.hlsl
Normal file
1228
src/Runtime/Ghost.Graphics/Shaders/Includes/S2H/s2h.hlsl
Normal file
File diff suppressed because it is too large
Load Diff
354
src/Runtime/Ghost.Graphics/Shaders/Includes/S2H/s2h_3d.hlsl
Normal file
354
src/Runtime/Ghost.Graphics/Shaders/Includes/S2H/s2h_3d.hlsl
Normal 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
|
||||
245
src/Runtime/Ghost.Graphics/Shaders/Includes/S2H/s2h_scatter.hlsl
Normal file
245
src/Runtime/Ghost.Graphics/Shaders/Includes/S2H/s2h_scatter.hlsl
Normal 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
|
||||
@@ -1,4 +1,6 @@
|
||||
using Ghost.Graphics.Core;
|
||||
// Source: https://github.com/zeux/meshoptimizer/blob/master/demo/clusterlod.h
|
||||
// Translated from C++ to C#.
|
||||
|
||||
using Ghost.MeshOptimizer;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
@@ -172,9 +174,9 @@ public static unsafe class MeshletUtility
|
||||
};
|
||||
}
|
||||
|
||||
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
|
||||
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group, AllocationHandle handle)
|
||||
{
|
||||
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, Allocator.FreeList);
|
||||
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, handle);
|
||||
for (var j = 0; j < group.Count; j++)
|
||||
{
|
||||
boundsList[j] = (clusters[group[j]].bounds);
|
||||
@@ -202,13 +204,13 @@ public static unsafe class MeshletUtility
|
||||
};
|
||||
}
|
||||
|
||||
private static UnsafeList<Cluster> Clusterize(ref readonly ClodConfig config, ref readonly ClodMesh mesh, uint* indices, nuint indexCount, Allocator allocator)
|
||||
private static UnsafeList<Cluster> Clusterize(ref readonly ClodConfig config, ref readonly ClodMesh mesh, uint* indices, nuint indexCount, AllocationHandle handle)
|
||||
{
|
||||
var maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles);
|
||||
|
||||
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, Allocator.FreeList);
|
||||
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, Allocator.FreeList);
|
||||
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, Allocator.FreeList);
|
||||
using var meshlets = new UnsafeArray<meshopt_Meshlet>((int)maxMeshlets, handle);
|
||||
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, handle);
|
||||
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, handle);
|
||||
|
||||
var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
|
||||
var pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr();
|
||||
@@ -236,7 +238,7 @@ public static unsafe class MeshletUtility
|
||||
);
|
||||
}
|
||||
|
||||
var clusters = new UnsafeList<Cluster>((int)meshletCount, allocator);
|
||||
var clusters = new UnsafeList<Cluster>((int)meshletCount, handle);
|
||||
|
||||
for (nuint i = 0; i < meshletCount; i++)
|
||||
{
|
||||
@@ -255,9 +257,9 @@ public static unsafe class MeshletUtility
|
||||
var cluster = new Cluster
|
||||
{
|
||||
vertices = meshlet.vertex_count,
|
||||
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), Allocator.FreeList),
|
||||
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, Allocator.FreeList),
|
||||
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), Allocator.FreeList),
|
||||
indices = new UnsafeList<uint>((int)(meshlet.triangle_count * 3), handle),
|
||||
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, handle),
|
||||
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), handle),
|
||||
group = -1,
|
||||
refined = -1
|
||||
};
|
||||
@@ -324,16 +326,16 @@ public static unsafe class MeshletUtility
|
||||
}
|
||||
}
|
||||
|
||||
private static UnsafeList<UnsafeList<int>> Partition(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeArray<uint> remap, Allocator allocator)
|
||||
private static UnsafeList<UnsafeList<int>> Partition(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeArray<uint> remap, AllocationHandle handle)
|
||||
{
|
||||
if (pending.Count <= (int)config.partitionSize)
|
||||
{
|
||||
var single = new UnsafeList<UnsafeList<int>>(1, allocator);
|
||||
var pendingcpy = new UnsafeList<int>(pending.Count, Allocator.FreeList);
|
||||
|
||||
var single = new UnsafeList<UnsafeList<int>>(1, handle);
|
||||
var pendingcpy = new UnsafeList<int>(pending.Count, handle);
|
||||
|
||||
pendingcpy.AddRange(pending.AsSpan());
|
||||
single.Add(pendingcpy);
|
||||
|
||||
|
||||
return single;
|
||||
}
|
||||
|
||||
@@ -343,8 +345,8 @@ public static unsafe class MeshletUtility
|
||||
totalIndexCount += (nuint)clusters[pending[i]].indices.Count;
|
||||
}
|
||||
|
||||
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, Allocator.FreeList);
|
||||
using var clusterCounts = new UnsafeList<uint>(pending.Count, Allocator.FreeList);
|
||||
using var clusterIndices = new UnsafeList<uint>((int)totalIndexCount, handle);
|
||||
using var clusterCounts = new UnsafeList<uint>(pending.Count, handle);
|
||||
|
||||
nuint offset = 0;
|
||||
for (var i = 0; i < pending.Count; i++)
|
||||
@@ -359,7 +361,7 @@ public static unsafe class MeshletUtility
|
||||
offset += (nuint)cluster.indices.Count;
|
||||
}
|
||||
|
||||
using var clusterPart = new UnsafeArray<uint>(pending.Count, Allocator.FreeList);
|
||||
using var clusterPart = new UnsafeArray<uint>(pending.Count, handle);
|
||||
|
||||
var partitionCount = MeshOptApi.PartitionClusters(
|
||||
(uint*)clusterPart.GetUnsafePtr(),
|
||||
@@ -373,10 +375,10 @@ public static unsafe class MeshletUtility
|
||||
config.partitionSize
|
||||
);
|
||||
|
||||
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, allocator);
|
||||
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, handle);
|
||||
for (nuint i = 0; i < partitionCount; i++)
|
||||
{
|
||||
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), allocator));
|
||||
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), handle));
|
||||
}
|
||||
|
||||
for (var i = 0; i < pending.Count; i++)
|
||||
@@ -387,9 +389,9 @@ public static unsafe class MeshletUtility
|
||||
return partitions;
|
||||
}
|
||||
|
||||
private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback)
|
||||
private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback, AllocationHandle handle)
|
||||
{
|
||||
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, Allocator.FreeList);
|
||||
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, handle);
|
||||
|
||||
for (var i = 0; i < group.Count; i++)
|
||||
{
|
||||
@@ -423,35 +425,35 @@ public static unsafe class MeshletUtility
|
||||
public uint id;
|
||||
}
|
||||
|
||||
private static void SimplifyFallback(ref UnsafeArray<uint> lod, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint target_count, float* error)
|
||||
private static void SimplifyFallback(ref UnsafeArray<uint> lod, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint target_count, float* error, AllocationHandle handle)
|
||||
{
|
||||
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, Allocator.FreeList);
|
||||
using var subset_locks = new UnsafeArray<byte>(indices.Count, Allocator.FreeList);
|
||||
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, handle);
|
||||
using var subset_locks = new UnsafeArray<byte>(indices.Count, handle);
|
||||
|
||||
lod.Resize(indices.Count);
|
||||
|
||||
var positions_stride = mesh.vertexPositionsStride / sizeof(float);
|
||||
var positions_stride = mesh.vertexPositionsStride / sizeof(float);
|
||||
|
||||
// deindex the mesh subset to avoid calling simplifySloppy on the entire vertex buffer (which is prohibitively expensive without sparsity)
|
||||
for (var i = 0; i<indices.Count; ++i)
|
||||
{
|
||||
var v = indices[i];
|
||||
Debug.Assert(v<mesh.vertexCount);
|
||||
// deindex the mesh subset to avoid calling simplifySloppy on the entire vertex buffer (which is prohibitively expensive without sparsity)
|
||||
for (var i = 0; i < indices.Count; ++i)
|
||||
{
|
||||
var v = indices[i];
|
||||
Debug.Assert(v < mesh.vertexCount);
|
||||
|
||||
subset[i].x = mesh.vertexPositions[v * positions_stride + 0];
|
||||
subset[i].y = mesh.vertexPositions[v * positions_stride + 1];
|
||||
subset[i].z = mesh.vertexPositions[v * positions_stride + 2];
|
||||
subset[i].id = v;
|
||||
subset[i].y = mesh.vertexPositions[v * positions_stride + 1];
|
||||
subset[i].z = mesh.vertexPositions[v * positions_stride + 2];
|
||||
subset[i].id = v;
|
||||
|
||||
subset_locks[i] = locks[v];
|
||||
lod[i] = (uint)i;
|
||||
subset_locks[i] = locks[v];
|
||||
lod[i] = (uint)i;
|
||||
}
|
||||
|
||||
var newSize = MeshOptApi.SimplifySloppy((uint*)lod.GetUnsafePtr(), (uint*)lod.GetUnsafePtr(), (nuint)lod.Count, (float*)subset.GetUnsafePtr(), (nuint)subset.Count, (nuint)sizeof(SloppyVertex), (byte*)subset_locks.GetUnsafePtr(), target_count, float.MaxValue, error);
|
||||
lod.Resize((int)newSize);
|
||||
|
||||
// convert error to absolute
|
||||
* error *= MeshOptApi.SimplifyScale((float*)subset.GetUnsafePtr(), (nuint)subset.Count, (nuint)sizeof(SloppyVertex));
|
||||
*error *= MeshOptApi.SimplifyScale((float*)subset.GetUnsafePtr(), (nuint)subset.Count, (nuint)sizeof(SloppyVertex));
|
||||
|
||||
// restore original vertex indices
|
||||
for (var i = 0; i < lod.Count; ++i)
|
||||
@@ -460,9 +462,9 @@ public static unsafe class MeshletUtility
|
||||
}
|
||||
}
|
||||
|
||||
public static UnsafeArray<uint> Simplify(ref readonly ClodConfig config, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint targetCount, float* error, Allocator allocator)
|
||||
public static UnsafeArray<uint> Simplify(ref readonly ClodConfig config, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint targetCount, float* error, AllocationHandle handle)
|
||||
{
|
||||
var lod = new UnsafeArray<uint>(indices.Count, allocator);
|
||||
var lod = new UnsafeArray<uint>(indices.Count, handle);
|
||||
|
||||
if (targetCount >= (nuint)indices.Count)
|
||||
{
|
||||
@@ -527,7 +529,7 @@ public static unsafe class MeshletUtility
|
||||
|
||||
if ((nuint)lod.Length > targetCount && config.simplifyFallbackSloppy)
|
||||
{
|
||||
SimplifyFallback(ref lod, in mesh, indices, locks, targetCount, error);
|
||||
SimplifyFallback(ref lod, in mesh, indices, locks, targetCount, error, handle);
|
||||
*error *= config.simplifyErrorFactorSloppy;
|
||||
}
|
||||
|
||||
@@ -575,8 +577,13 @@ public static unsafe class MeshletUtility
|
||||
{
|
||||
Debug.Assert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
|
||||
|
||||
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, Allocator.FreeList, AllocationOption.Clear);
|
||||
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, Allocator.FreeList);
|
||||
using var pool = new MemoryPool<VirtualArena, VirtualArena.CreationOptions>(new VirtualArena.CreationOptions
|
||||
{
|
||||
reserveCapacity = 256 * 1024 * 1024
|
||||
});
|
||||
|
||||
using var locks = new UnsafeArray<byte>((int)mesh.vertexCount, pool.AllocationHandle, AllocationOption.Clear); ;
|
||||
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, pool.AllocationHandle);
|
||||
|
||||
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
||||
|
||||
@@ -599,14 +606,14 @@ public static unsafe class MeshletUtility
|
||||
}
|
||||
}
|
||||
|
||||
using var clusters = Clusterize(in config, in mesh, mesh.indices, mesh.indexCount, Allocator.FreeList);
|
||||
using var clusters = Clusterize(in config, in mesh, mesh.indices, mesh.indexCount, pool.AllocationHandle);
|
||||
|
||||
for (var i = 0; i < clusters.Count; i++)
|
||||
{
|
||||
clusters[i].bounds = ComputeBounds(in mesh, clusters[i].indices, 0.0f);
|
||||
}
|
||||
|
||||
using var pending = new UnsafeList<int>(clusters.Count, Allocator.FreeList);
|
||||
using var pending = new UnsafeList<int>(clusters.Count, pool.AllocationHandle);
|
||||
for (var i = 0; i < clusters.Count; i++)
|
||||
{
|
||||
pending.Add(i);
|
||||
@@ -616,14 +623,14 @@ public static unsafe class MeshletUtility
|
||||
|
||||
while (pending.Count > 1)
|
||||
{
|
||||
using var groups = Partition(in config, in mesh, clusters, pending, remap, Allocator.FreeList);
|
||||
using var groups = Partition(in config, in mesh, clusters, pending, remap, pool.AllocationHandle);
|
||||
pending.Clear();
|
||||
|
||||
LockBoundary(locks, groups, clusters, remap, mesh.vertexLock);
|
||||
|
||||
for (var i = 0; i < groups.Count; i++)
|
||||
{
|
||||
using var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, Allocator.FreeList);
|
||||
using var merged = new UnsafeList<uint>(groups[i].Count * (int)config.maxTriangles * 3, pool.AllocationHandle);
|
||||
for (var j = 0; j < groups[i].Count; j++)
|
||||
{
|
||||
var clusterIndices = clusters[groups[i][j]].indices;
|
||||
@@ -631,28 +638,28 @@ public static unsafe class MeshletUtility
|
||||
}
|
||||
|
||||
var targetSize = (nuint)(merged.Count / 3 * config.simplifyRatio * 3.0f);
|
||||
var bounds = MergeBounds(clusters, groups[i]);
|
||||
var bounds = MergeBounds(clusters, groups[i], pool.AllocationHandle);
|
||||
|
||||
var error = 0.0f;
|
||||
using var simplified = Simplify(in config, in mesh, merged.AsReadOnly(), locks.AsReadOnly(), targetSize, &error, Allocator.FreeList);
|
||||
using var simplified = Simplify(in config, in mesh, merged.AsReadOnly(), locks.AsReadOnly(), targetSize, &error, pool.AllocationHandle);
|
||||
|
||||
if ((nuint)simplified.Length > (nuint)(merged.Count * config.simplifyThreshold))
|
||||
{
|
||||
bounds.error = float.MaxValue;
|
||||
OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
|
||||
OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
|
||||
continue;
|
||||
}
|
||||
|
||||
bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive;
|
||||
|
||||
var refined = OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
|
||||
|
||||
var refined = OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
|
||||
|
||||
for (var j = 0; j < groups[i].Count; j++)
|
||||
{
|
||||
clusters[groups[i][j]].Dispose();
|
||||
}
|
||||
|
||||
using var split = Clusterize(in config, in mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Length, Allocator.FreeList);
|
||||
using var split = Clusterize(in config, in mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Length, pool.AllocationHandle);
|
||||
for (var j = 0; j < split.Count; j++)
|
||||
{
|
||||
split[j].refined = refined;
|
||||
@@ -674,7 +681,7 @@ public static unsafe class MeshletUtility
|
||||
{
|
||||
var bounds = clusters[pending[0]].bounds;
|
||||
bounds.error = float.MaxValue;
|
||||
OutputGroup(in config, in mesh, clusters, pending, bounds, depth, outputContext, outputCallback);
|
||||
OutputGroup(in config, in mesh, clusters, pending, bounds, depth, outputContext, outputCallback, pool.AllocationHandle);
|
||||
}
|
||||
|
||||
var finalClusterCount = (nuint)clusters.Count;
|
||||
|
||||
@@ -53,11 +53,11 @@ shader "MyShader/Standard"
|
||||
MeshData meshData = LoadData<MeshData>(instanceData.meshBuffer, 0);
|
||||
|
||||
ByteAddressBuffer meshletBuffer = GET_BUFFER(meshData.meshletBuffer);
|
||||
Meshlet m = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
|
||||
Meshlet meshlet = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
|
||||
|
||||
s_Payload.meshletIndex = groupID.x;
|
||||
|
||||
uint lodLevel = (m.packedCounts >> 24) & 0xFFu;
|
||||
uint lodLevel = (meshlet.packedCounts >> 24) & 0xFFu;
|
||||
uint emitMeshlet = lodLevel == 0u ? 1u : 0u;
|
||||
DispatchMesh(emitMeshlet, 1u, 1u, s_Payload);
|
||||
}
|
||||
@@ -74,10 +74,10 @@ shader "MyShader/Standard"
|
||||
MeshData meshData = LoadData<MeshData>(instanceData.meshBuffer, 0);
|
||||
|
||||
ByteAddressBuffer meshletBuffer = GET_BUFFER(meshData.meshletBuffer);
|
||||
Meshlet m = meshletBuffer.Load<Meshlet>(meshPayload.meshletIndex * sizeof(Meshlet));
|
||||
Meshlet meshlet = meshletBuffer.Load<Meshlet>(meshPayload.meshletIndex * sizeof(Meshlet));
|
||||
|
||||
uint vertexCount = m.packedCounts & 0xFFu;
|
||||
uint triangleCount = (m.packedCounts >> 8) & 0xFFu;
|
||||
uint vertexCount = meshlet.packedCounts & 0xFFu;
|
||||
uint triangleCount = (meshlet.packedCounts >> 8) & 0xFFu;
|
||||
SetMeshOutputCounts(vertexCount, triangleCount);
|
||||
|
||||
ByteAddressBuffer meshletVerticesBuffer = GET_BUFFER(meshData.meshletVerticesBuffer);
|
||||
@@ -86,7 +86,7 @@ shader "MyShader/Standard"
|
||||
// Write vertex output
|
||||
if (groupThreadID.x < vertexCount)
|
||||
{
|
||||
uint vertexIndex = meshletVerticesBuffer.Load((m.vertexOffset + groupThreadID.x) * 4);
|
||||
uint vertexIndex = meshletVerticesBuffer.Load((meshlet.vertexOffset + groupThreadID.x) * 4);
|
||||
ByteAddressBuffer vertices = GET_BUFFER(meshData.vertexBuffer);
|
||||
Vertex v = vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
|
||||
|
||||
@@ -112,7 +112,7 @@ shader "MyShader/Standard"
|
||||
uint triangleIndex = groupThreadID.x;
|
||||
|
||||
// Load the packed 32-bit integer containing the 3 local indices
|
||||
uint packedIndices = meshletTrianglesBuffer.Load((m.triangleOffset + triangleIndex) * 4);
|
||||
uint packedIndices = meshletTrianglesBuffer.Load((meshlet.triangleOffset + triangleIndex) * 4);
|
||||
|
||||
uint i0 = packedIndices & 0xFF;
|
||||
uint i1 = (packedIndices >> 8) & 0xFF;
|
||||
|
||||
Reference in New Issue
Block a user