feat(graphics): refactor pipeline keying and allocators
Major refactor of graphics pipeline keying, shader cache, and resource allocation. Replaced most Allocator usage with AllocationHandle, modernized logger usage, and unified pipeline state keys. Updated MeshUtility to use AllocationHandle.FreeList. Added new shader pipeline architecture docs and improved error handling throughout. BREAKING CHANGE: Pipeline keying and resource allocation APIs have changed.
This commit is contained in:
@@ -140,13 +140,8 @@ internal static class DSLShaderCompiler
|
||||
|
||||
var pixelShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.pixelShader.entry };
|
||||
|
||||
var asHash = Hash.Combine64(GetUniqueId(amplificationShaderCode.code), GetUniqueId(amplificationShaderCode.entryPoint));
|
||||
var msHash = Hash.Combine64(GetUniqueId(meshShaderCode.code), GetUniqueId(meshShaderCode.entryPoint));
|
||||
var psHash = Hash.Combine64(GetUniqueId(pixelShaderCode.code), GetUniqueId(pixelShaderCode.entryPoint));
|
||||
|
||||
passes[i] = new PassDescriptor
|
||||
{
|
||||
identifier = Hash.Combine64(GetUniqueId(semantics.name + pass.name), asHash, msHash, psHash),
|
||||
name = pass.name,
|
||||
|
||||
amplificationShaderCode = amplificationShaderCode,
|
||||
|
||||
@@ -166,9 +166,9 @@ public partial class App : Application
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.LogError(e.Exception);
|
||||
Logger.Error(e.Exception);
|
||||
#if DEBUG
|
||||
Debugger.BreakForUserUnhandledException(e.Exception);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.7" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.8" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.11">
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.9" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.13">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -72,7 +72,6 @@ public struct PipelineState
|
||||
get; set;
|
||||
}
|
||||
|
||||
|
||||
public static PipelineState Default => new PipelineState
|
||||
{
|
||||
ZTest = ZTest.LessEqual,
|
||||
@@ -107,4 +106,4 @@ public struct PipelineState
|
||||
var code64 = GetHashCode64();
|
||||
return ((int)code64) ^ (int)(code64 >> 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ public struct PassDescriptor
|
||||
{
|
||||
public GraphicsShaderDescriptor shader;
|
||||
|
||||
public ulong identifier;
|
||||
public string name;
|
||||
|
||||
public ShaderCode amplificationShaderCode;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Core;
|
||||
|
||||
@@ -151,90 +153,105 @@ public static class Logger
|
||||
public static LogCollection Logs => s_logger.Logs;
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Log(LogLevel level, object? message)
|
||||
{
|
||||
s_logger.Log(message?.ToString() ?? "null", level);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Log(LogLevel level, string message)
|
||||
{
|
||||
s_logger.Log(message, level);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Log(LogLevel level, string format, params object?[] args)
|
||||
{
|
||||
s_logger.Log(string.Format(format, args), level);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogInfo(object? message)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Info(object? message)
|
||||
{
|
||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Info);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogInfo(string message)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Info(string message)
|
||||
{
|
||||
s_logger.Log(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogInfo(string format, params object?[] args)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Info(string format, params object?[] args)
|
||||
{
|
||||
s_logger.Log(string.Format(format, args), LogLevel.Info);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogWarning(object? message)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Warning(object? message)
|
||||
{
|
||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Warning);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogWarning(string message)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Warning(string message)
|
||||
{
|
||||
s_logger.Log(message, LogLevel.Warning);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogWarning(string format, params object?[] args)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Warning(string format, params object?[] args)
|
||||
{
|
||||
s_logger.Log(string.Format(format, args), LogLevel.Warning);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogError(object? message)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Error(object? message)
|
||||
{
|
||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Error);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogError(string message)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Error(string message)
|
||||
{
|
||||
s_logger.Log(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogError(string format, params object?[] args)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Error(string format, params object?[] args)
|
||||
{
|
||||
s_logger.Log(string.Format(format, args), LogLevel.Error);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void LogError(Exception ex)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Error(Exception ex)
|
||||
{
|
||||
s_logger.Log(ex);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
public static void Assert(bool condition, string message)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Assert(bool condition, [CallerArgumentExpression(nameof(condition))] string? message = null)
|
||||
{
|
||||
s_logger.Assert(condition, message);
|
||||
s_logger.Assert(condition, message ?? "null");
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Conditional("DEBUG")]
|
||||
[Conditional("GHOST_EDITOR")]
|
||||
public static void Debug(object? message)
|
||||
@@ -243,6 +260,7 @@ public static class Logger
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Conditional("DEBUG")]
|
||||
[Conditional("GHOST_EDITOR")]
|
||||
public static void Debug(string message)
|
||||
@@ -251,10 +269,26 @@ public static class Logger
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Conditional("DEBUG")]
|
||||
[Conditional("GHOST_EDITOR")]
|
||||
public static void Debug(string format, params object?[] args)
|
||||
{
|
||||
s_logger.Log(string.Format(format, args), LogLevel.Debug);
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Conditional("DEBUG")]
|
||||
[Conditional("GHOST_EDITOR")]
|
||||
public static void DebugAssert([DoesNotReturnIf(false)] bool condition, [CallerArgumentExpression(nameof(condition))] string? message = null)
|
||||
{
|
||||
s_logger.Assert(condition, message?.ToString() ?? "null");
|
||||
#if DEBUG
|
||||
if (!condition)
|
||||
{
|
||||
System.Diagnostics.Debug.Fail(message ?? "Assertion failed.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,8 +353,8 @@ public static class ResultExtensions
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
public static T GetValueOrThrow<T, S>(this Result<T, S> result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
||||
where S : struct, Enum
|
||||
public static T GetValueOrThrow<T, E>(this Result<T, E> result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
||||
where E : struct, Enum
|
||||
{
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
@@ -364,17 +364,34 @@ public static class ResultExtensions
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
public static ref T GetValueOrThrow<T, E>(this RefResult<T, E> result, [CallerArgumentExpression(nameof(result))] string? op = null)
|
||||
where E : struct, Enum
|
||||
{
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
throw new InvalidOperationException($"{op} failed: status {result.Error}");
|
||||
}
|
||||
|
||||
return ref result.Value;
|
||||
}
|
||||
|
||||
public static T? GetValueOrDefault<T>(this Result<T> result, T? defaultValue = default)
|
||||
{
|
||||
return result.IsSuccess ? result.Value : defaultValue;
|
||||
}
|
||||
|
||||
public static T? GetValueOrDefault<T, S>(this Result<T, S> result, T? defaultValue = default)
|
||||
where S : struct, Enum
|
||||
public static T? GetValueOrDefault<T, E>(this Result<T, E> result, T? defaultValue = default)
|
||||
where E : struct, Enum
|
||||
{
|
||||
return result.IsSuccess ? result.Value : defaultValue;
|
||||
}
|
||||
|
||||
public static ref T GetValueOrDefault<T, E>(this RefResult<T, E> result)
|
||||
where E : struct, Enum
|
||||
{
|
||||
return ref result.IsSuccess ? ref result.Value : ref Unsafe.NullRef<T>();
|
||||
}
|
||||
|
||||
public static bool TryGetValue<T>(this Result<T> result, out T value)
|
||||
{
|
||||
if (result.IsSuccess)
|
||||
@@ -529,4 +546,4 @@ public static class ResultExtensions
|
||||
return onFailure(result.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Core;
|
||||
@@ -13,7 +12,7 @@ public unsafe partial struct TempJobAllocator
|
||||
|
||||
internal static void Initialize(nuint capacity)
|
||||
{
|
||||
Debug.Assert(_pAllocator == null, "TempJobAllocator is already initialized.");
|
||||
Logger.DebugAssert(_pAllocator == null, "TempJobAllocator is already initialized.");
|
||||
_pAllocator = (TempJobAllocator*)Malloc((nuint)sizeof(TempJobAllocator));
|
||||
}
|
||||
|
||||
@@ -71,52 +70,29 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
||||
State = Unsafe.AsPointer(ref this),
|
||||
Alloc = &Allocate,
|
||||
Realloc = &Reallocate,
|
||||
Free = &Free,
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
IsValid = &IsValid,
|
||||
#else
|
||||
IsValid = null,
|
||||
#endif
|
||||
Free = &Free
|
||||
};
|
||||
}
|
||||
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
var pSelf = (TempJobAllocator*)instance;
|
||||
var pCurrentArena = pSelf->_pArena + pSelf->_currentFrameIndex;
|
||||
var ptr = pCurrentArena->Allocate(size, alignment, allocationOption);
|
||||
if (ptr == null)
|
||||
{
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = MemoryHandle.Invalid;
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = new MemoryHandle(_MAGIC_ID, pSelf->_currentFrameCount);
|
||||
#endif
|
||||
return ptr;
|
||||
}
|
||||
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
if (ptr == null)
|
||||
{
|
||||
return Allocate(instance, newSize, alignment, allocationOption
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, pHandle
|
||||
#endif
|
||||
);
|
||||
return Allocate(instance, newSize, alignment, allocationOption);
|
||||
}
|
||||
|
||||
var pSelf = (TempJobAllocator*)instance;
|
||||
@@ -132,24 +108,12 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
private static void Free(void* instance, void* ptr
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle handle
|
||||
#endif
|
||||
)
|
||||
private static void Free(void* instance, void* ptr)
|
||||
{
|
||||
var pSelf = (TempJobAllocator*)instance;
|
||||
Interlocked.Decrement(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
||||
}
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private static bool IsValid(void* instance, MemoryHandle handle)
|
||||
{
|
||||
var pSelf = (TempJobAllocator*)instance;
|
||||
return handle.ID == _MAGIC_ID && handle.Generation > pSelf->_currentFrameCount - _FRAME_LATENCY;
|
||||
}
|
||||
#endif
|
||||
|
||||
public int AdvanceFrame()
|
||||
{
|
||||
var allocations = Interlocked.Exchange(ref _allocationsPerFrame[_currentFrameIndex], 0);
|
||||
@@ -161,4 +125,4 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
||||
|
||||
return allocations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Core.Utilities;
|
||||
|
||||
public ref struct BinaryReader
|
||||
{
|
||||
private readonly Span<byte> _buffer;
|
||||
private int _position;
|
||||
|
||||
public int Position
|
||||
{
|
||||
readonly get => _position;
|
||||
set => _position = value;
|
||||
}
|
||||
|
||||
public BinaryReader(Span<byte> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public T Read<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
var value = Unsafe.ReadUnaligned<T>(ref _buffer[_position]);
|
||||
_position += Unsafe.SizeOf<T>();
|
||||
return value;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> ReadBytes(int length)
|
||||
{
|
||||
var span = _buffer.Slice(_position, length);
|
||||
_position += length;
|
||||
return span;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Core.Utilities;
|
||||
|
||||
public struct BufferWriter : IDisposable
|
||||
public unsafe struct BufferWriter : IDisposable
|
||||
{
|
||||
private UnsafeList<byte> _buffer;
|
||||
private int _position;
|
||||
@@ -21,17 +22,21 @@ public struct BufferWriter : IDisposable
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public unsafe void Write<T>(T value)
|
||||
public void Write<T>(T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
||||
_position += sizeof(T);
|
||||
}
|
||||
|
||||
public void WriteBytes(ReadOnlySpan<byte> data)
|
||||
public void WriteSpan<T>(ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
data.CopyTo(_buffer.AsSpan().Slice(_position, data.Length));
|
||||
_position += data.Length;
|
||||
var size = sizeof(T) * data.Length;
|
||||
var byteSpan = MemoryMarshal.AsBytes(data);
|
||||
|
||||
byteSpan.CopyTo(_buffer.AsSpan().Slice(_position, size));
|
||||
_position += size;
|
||||
}
|
||||
|
||||
public Span<byte> ReserveSpan(int length)
|
||||
@@ -51,3 +56,88 @@ public struct BufferWriter : IDisposable
|
||||
_buffer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe ref struct SpanWriter
|
||||
{
|
||||
private Span<byte> _buffer;
|
||||
private int _position;
|
||||
|
||||
public int Position
|
||||
{
|
||||
readonly get => _position;
|
||||
set => _position = value;
|
||||
}
|
||||
|
||||
public SpanWriter(Span<byte> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public void Write<T>(T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
||||
_position += sizeof(T);
|
||||
}
|
||||
|
||||
public void WriteSpan<T>(ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var size = sizeof(T) * data.Length;
|
||||
var byteSpan = MemoryMarshal.AsBytes(data);
|
||||
|
||||
byteSpan.CopyTo(_buffer.Slice(_position, size));
|
||||
_position += size;
|
||||
|
||||
}
|
||||
|
||||
public readonly Span<byte> AsSpan()
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe ref struct SpanReader
|
||||
{
|
||||
private readonly Span<byte> _buffer;
|
||||
private int _position;
|
||||
|
||||
public int Position
|
||||
{
|
||||
readonly get => _position;
|
||||
set => _position = value;
|
||||
}
|
||||
|
||||
public SpanReader(Span<byte> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public T Read<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
var value = Unsafe.ReadUnaligned<T>(ref _buffer[_position]);
|
||||
_position += Unsafe.SizeOf<T>();
|
||||
return value;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> ReadSpan<T>(int length)
|
||||
where T : unmanaged
|
||||
{
|
||||
var size = sizeof(T) * length;
|
||||
var span = MemoryMarshal.Cast<byte, T>(_buffer.Slice(_position, size));
|
||||
|
||||
_position += size;
|
||||
return span;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> ReadToEnd<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
var span = MemoryMarshal.Cast<byte, T>(_buffer.Slice(_position));
|
||||
_position += span.Length;
|
||||
return span;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderPipeline;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
@@ -16,6 +15,8 @@ internal unsafe class GPUScene : IDisposable
|
||||
private uint _requiredResize;
|
||||
private bool _disposed;
|
||||
|
||||
public Handle<GPUBuffer> SceneBuffer => _sceneBuffer;
|
||||
|
||||
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, uint initialCount)
|
||||
{
|
||||
_resourceAllocator = resourceAllocator;
|
||||
@@ -30,7 +31,7 @@ internal unsafe class GPUScene : IDisposable
|
||||
};
|
||||
|
||||
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
|
||||
Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||
Logger.DebugAssert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||
|
||||
_capacity = initialCount;
|
||||
}
|
||||
@@ -48,22 +49,21 @@ internal unsafe class GPUScene : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
var newCapacity = _capacity * 2;
|
||||
newCapacity = Math.Max(newCapacity, _capacity + _requiredResize);
|
||||
var newCapacity = Math.Max(_capacity * 2, _capacity + _requiredResize);
|
||||
|
||||
var newBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (ulong)newCapacity * (ulong)sizeof(InstanceData),
|
||||
Size = newCapacity * (ulong)sizeof(InstanceData),
|
||||
Stride = (uint)sizeof(InstanceData),
|
||||
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
var newBuffer = _resourceAllocator.CreateBuffer(in newBufferDesc, "SceneBuffer_Resized");
|
||||
Debug.Assert(newBuffer.IsValid);
|
||||
Logger.DebugAssert(newBuffer.IsValid);
|
||||
|
||||
// Copy existing data to the new buffer
|
||||
cmd.CopyBuffer(newBuffer, _sceneBuffer, 0, 0, (ulong)_instanceCount * (ulong)sizeof(InstanceData));
|
||||
cmd.CopyBuffer(newBuffer, _sceneBuffer, 0, 0, _instanceCount * (ulong)sizeof(InstanceData));
|
||||
|
||||
// Replace old buffer with the new one
|
||||
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
[GenerateShaderProperty("Internal/UpdateGPUScene")]
|
||||
public partial struct UpdateGPUSceneShaderProperty
|
||||
{
|
||||
public uint gpuSceneBuffer;
|
||||
public uint addBuffer;
|
||||
public uint addCount;
|
||||
public uint removeBuffer;
|
||||
public uint removeCount;
|
||||
}
|
||||
|
||||
internal partial class GhostRenderPipeline
|
||||
{
|
||||
public void UpdateGPUScene(RenderContext ctx, Handle<GPUBuffer> addBuffer, int addCount, Handle<GPUBuffer> removeBuffer, int removeCount)
|
||||
{
|
||||
if (addCount <= 0 && removeCount <= 0)
|
||||
{
|
||||
Logger.DebugAssert(addBuffer.IsInvalid && removeBuffer.IsInvalid, "Buffers should be invalid when there are no updates.");
|
||||
return; // No updates needed
|
||||
}
|
||||
|
||||
// NOTE: We dispatch it here instead of in render graph is because the update does not perform every frame.
|
||||
// The topology change of the graph will trigger the recompilation of the render graph, which is expensive.
|
||||
// Currently the render graph does not support import invalid resources, which means we can not handle the early return in the render func.
|
||||
// Furthermore, updating the GPU scene does not rely on other resources and passes, it's isolated and always run before the actual rendering.
|
||||
// So it's fine to dispatch it here directly.
|
||||
|
||||
var property = new UpdateGPUSceneShaderProperty
|
||||
{
|
||||
gpuSceneBuffer = ctx.ResourceDatabase.GetBindlessIndex(_gpuScene.SceneBuffer.AsResource(), BindlessAccess.UnorderedAccess),
|
||||
addBuffer = ctx.ResourceDatabase.GetBindlessIndex(addBuffer.AsResource()),
|
||||
addCount = (uint)addCount,
|
||||
removeBuffer = ctx.ResourceDatabase.GetBindlessIndex(removeBuffer.AsResource()),
|
||||
removeCount = (uint)removeCount
|
||||
};
|
||||
|
||||
// TODO: Write and load the shader. This is just a placeholder for now.
|
||||
var shader = default(Handle<ComputeShader>);
|
||||
var keywords = new LocalKeywordSet();
|
||||
|
||||
ctx.DispatchCompute(shader, 0, in keywords, in property, new uint3());
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
internal class GhostRenderPipeline : IRenderPipeline
|
||||
internal partial class GhostRenderPipeline : IRenderPipeline
|
||||
{
|
||||
private struct AddInstanceData
|
||||
{
|
||||
@@ -39,11 +39,11 @@ internal class GhostRenderPipeline : IRenderPipeline
|
||||
{
|
||||
_renderSystem = renderSystem;
|
||||
|
||||
_renderGraph = new RenderGraph(renderSystem.ResourceManager, renderSystem.GraphicsEngine);
|
||||
_renderGraph = new RenderGraph(renderSystem);
|
||||
_gpuScene = new GPUScene(renderSystem.GraphicsEngine.ResourceAllocator, renderSystem.GraphicsEngine.ResourceDatabase, 102_400u); // 102.4k objects should be enough for now
|
||||
}
|
||||
|
||||
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase)
|
||||
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
{
|
||||
if (!ghostPayload.AddRequest.IsEmpty)
|
||||
{
|
||||
@@ -82,13 +82,16 @@ internal class GhostRenderPipeline : IRenderPipeline
|
||||
}
|
||||
|
||||
resourceDatabase.UnmapResource(addBuffer.AsResource(), 0, null);
|
||||
|
||||
count = i;
|
||||
return addBuffer;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
return default;
|
||||
}
|
||||
|
||||
private static unsafe Handle<GPUBuffer> CreateRemoveInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase)
|
||||
private static unsafe Handle<GPUBuffer> CreateRemoveInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
{
|
||||
if (!ghostPayload.RemoveRequest.IsEmpty)
|
||||
{
|
||||
@@ -116,9 +119,12 @@ internal class GhostRenderPipeline : IRenderPipeline
|
||||
}
|
||||
|
||||
resourceDatabase.UnmapResource(removeBuffer.AsResource(), 0, null);
|
||||
|
||||
count = i;
|
||||
return removeBuffer;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
return default;
|
||||
}
|
||||
|
||||
@@ -131,15 +137,20 @@ internal class GhostRenderPipeline : IRenderPipeline
|
||||
|
||||
foreach (ref readonly var request in ghostPayload.RenderRequests)
|
||||
{
|
||||
if (!RenderPipelineUtility.GetVPMatrices(_renderSystem, in request, out var view, out var projection, out var screenSize))
|
||||
try
|
||||
{
|
||||
continue;
|
||||
using var viewData = new RenderViewData(_renderSystem.SwapChainManager, resourceDatabase, in request);
|
||||
RenderPipelineUtility.GetVPMatrices(in request, viewData.ScreenSize, out var view, out var projection);
|
||||
|
||||
var addBuffer = CreateAddInstanceBuffer(ghostPayload, resourceManager, resourceDatabase, out var addCount);
|
||||
var removeBuffer = CreateRemoveInstanceBuffer(ghostPayload, resourceManager, resourceDatabase, out var removeCount);
|
||||
|
||||
UpdateGPUScene(ctx, addBuffer, addCount, removeBuffer, removeCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
}
|
||||
|
||||
var addBuffer = CreateAddInstanceBuffer(ghostPayload, resourceManager, resourceDatabase);
|
||||
var removeBuffer = CreateRemoveInstanceBuffer(ghostPayload, resourceManager, resourceDatabase);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
|
||||
{
|
||||
_renderPipeline = renderPipeline;
|
||||
|
||||
_renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
_renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent);
|
||||
_addRequest = new ConcurrentQueue<AddInstanceRequest>();
|
||||
_removeRequest = new ConcurrentQueue<RemoveInstanceRequest>();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class RenderPipelineSystemAttribute<T> : RenderPipelineSystemAttribute
|
||||
public override Type SettingsType => typeof(T);
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public static class RenderPipelineSystemRegistry
|
||||
{
|
||||
private static readonly Dictionary<nint, List<Func<ISystem>>> s_renderPipelineSystems = new();
|
||||
|
||||
@@ -124,8 +124,8 @@ internal unsafe struct Chunk : IDisposable
|
||||
|
||||
public Chunk(int bufferSize, int capacity, int componentCount, uint globalVersion)
|
||||
{
|
||||
_data = new UnsafeArray<byte>(bufferSize, Allocator.Persistent, AllocationOption.Clear);
|
||||
_versions = new UnsafeArray<uint>(componentCount, Allocator.Persistent);
|
||||
_data = new UnsafeArray<byte>(bufferSize, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||
_versions = new UnsafeArray<uint>(componentCount, AllocationHandle.Persistent);
|
||||
_capacity = capacity;
|
||||
_count = 0;
|
||||
|
||||
@@ -200,13 +200,13 @@ internal unsafe struct Archetype : IDisposable
|
||||
_id = id;
|
||||
_worldID = worldID;
|
||||
|
||||
_chunks = new UnsafeList<Chunk>(4, Allocator.Persistent);
|
||||
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||
_chunks = new UnsafeList<Chunk>(4, AllocationHandle.Persistent);
|
||||
_edgesAdd = new UnsafeList<Edge>(4, AllocationHandle.Persistent);
|
||||
_edgesRemove = new UnsafeList<Edge>(4, AllocationHandle.Persistent);
|
||||
|
||||
if (componentIds.IsEmpty)
|
||||
{
|
||||
_signature = new UnsafeBitSet(1, Allocator.Persistent, AllocationOption.Clear);
|
||||
_signature = new UnsafeBitSet(1, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||
_hash = 0;
|
||||
|
||||
_signature.ClearAll();
|
||||
@@ -224,7 +224,7 @@ internal unsafe struct Archetype : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
_signature = new UnsafeBitSet(highestComponentID + 1, Allocator.Persistent, AllocationOption.Clear);
|
||||
_signature = new UnsafeBitSet(highestComponentID + 1, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||
_hash = _signature.GetHashCode();
|
||||
|
||||
CalculateLayout(componentIds);
|
||||
@@ -268,8 +268,8 @@ internal unsafe struct Archetype : IDisposable
|
||||
|
||||
_maxComponentID = maxComponentID;
|
||||
_entityCapacity = Chunk.CHUNK_BUFFER_SIZE / bytesPerEntity;
|
||||
_layouts = new UnsafeArray<ComponentMemoryLayout>(components.Length, Allocator.Persistent);
|
||||
_componentIDToLayoutIndex = new UnsafeArray<int>(_maxComponentID + 1, Allocator.Persistent);
|
||||
_layouts = new UnsafeArray<ComponentMemoryLayout>(components.Length, AllocationHandle.Persistent);
|
||||
_componentIDToLayoutIndex = new UnsafeArray<int>(_maxComponentID + 1, AllocationHandle.Persistent);
|
||||
|
||||
_componentIDToLayoutIndex.AsSpan().Fill(-1);
|
||||
|
||||
|
||||
@@ -168,11 +168,11 @@ public partial class ComponentManager : IDisposable
|
||||
{
|
||||
_world = world;
|
||||
|
||||
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
|
||||
_entityQueries = new UnsafeList<EntityQuery>(16, Allocator.Persistent);
|
||||
_archetypes = new UnsafeList<Archetype>(16, AllocationHandle.Persistent);
|
||||
_entityQueries = new UnsafeList<EntityQuery>(16, AllocationHandle.Persistent);
|
||||
|
||||
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
|
||||
_querieLookup = new UnsafeHashMap<int, Identifier<EntityQuery>>(16, Allocator.Persistent);
|
||||
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, AllocationHandle.Persistent);
|
||||
_querieLookup = new UnsafeHashMap<int, Identifier<EntityQuery>>(16, AllocationHandle.Persistent);
|
||||
|
||||
// Create the empty archetype
|
||||
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
|
||||
@@ -297,11 +297,6 @@ public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
|
||||
_hashCode = -1;
|
||||
}
|
||||
|
||||
public ComponentSet(Allocator allocator, params ReadOnlySpan<Identifier<IComponent>> components)
|
||||
: this(AllocationManager.GetAllocationHandle(allocator), components)
|
||||
{
|
||||
}
|
||||
|
||||
public readonly bool Equals(ComponentSet other)
|
||||
{
|
||||
return _hashCode == other._hashCode;
|
||||
|
||||
@@ -25,7 +25,7 @@ public unsafe class EntityCommandBuffer : IDisposable
|
||||
public EntityCommandBuffer(EntityManager entityManager)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_buffer = new UnsafeList<byte>(4096, Allocator.Persistent);
|
||||
_buffer = new UnsafeList<byte>(4096, AllocationHandle.Persistent);
|
||||
}
|
||||
|
||||
~EntityCommandBuffer()
|
||||
|
||||
@@ -50,7 +50,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
internal EntityManager(World world, int initialCapacity)
|
||||
{
|
||||
_world = world;
|
||||
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, Allocator.Persistent, AllocationOption.Clear);
|
||||
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||
_scriptComponents = new SlotMap<List<ScriptComponent>>(initialCapacity / 2);
|
||||
// _storages = new IManagedComponentStorage[16];
|
||||
}
|
||||
@@ -638,7 +638,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
newArchetype.SetComponentData(newChunkIndex, newRowIndex, componentID, pComponent);
|
||||
|
||||
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
||||
Debug.Assert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
|
||||
Logger.DebugAssert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
|
||||
if (r != Error.None)
|
||||
{
|
||||
return r;
|
||||
@@ -742,7 +742,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
|
||||
|
||||
var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
||||
Debug.Assert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
|
||||
Logger.DebugAssert(r == Error.None); // We assert it because the entity should exist if the whole system is consistent.
|
||||
if (r != Error.None)
|
||||
{
|
||||
return r;
|
||||
|
||||
@@ -345,7 +345,7 @@ public unsafe partial struct EntityQuery : IDisposable
|
||||
_id = id;
|
||||
_worldID = worldID;
|
||||
_mask = mask;
|
||||
_matchingArchetypes = new UnsafeList<Identifier<Archetype>>(8, Allocator.Persistent);
|
||||
_matchingArchetypes = new UnsafeList<Identifier<Archetype>>(8, AllocationHandle.Persistent);
|
||||
}
|
||||
|
||||
// TODO: Fetching layout every time is not optimal. Cache them?
|
||||
@@ -642,7 +642,7 @@ public ref partial struct QueryBuilder : IDisposable
|
||||
|
||||
public Identifier<EntityQuery> Build(World world, bool dispose = true)
|
||||
{
|
||||
BuildQueryMask(AllocationManager.GetAllocationHandle(Allocator.Persistent), out var mask);
|
||||
BuildQueryMask(AllocationHandle.Persistent, out var mask);
|
||||
|
||||
var maskHash = mask.GetHashCode();
|
||||
var queryID = world.ComponentManager.GetEntityQueryIDByMaskHash(maskHash);
|
||||
|
||||
@@ -56,7 +56,7 @@ internal sealed unsafe class SharedComponentStore : IDisposable
|
||||
|
||||
public SharedComponentStore(int initialCapacity = 16)
|
||||
{
|
||||
_perType = new UnsafeHashMap<int, TypeStore>(initialCapacity, Allocator.Persistent);
|
||||
_perType = new UnsafeHashMap<int, TypeStore>(initialCapacity, AllocationHandle.Persistent);
|
||||
}
|
||||
|
||||
~SharedComponentStore()
|
||||
@@ -203,9 +203,9 @@ internal sealed unsafe class SharedComponentStore : IDisposable
|
||||
var store = new TypeStore
|
||||
{
|
||||
typeSize = typeSize,
|
||||
data = new UnsafeList<byte>(typeSize * 16, Allocator.Persistent),
|
||||
infos = new UnsafeList<EntryInfo>(16, Allocator.Persistent),
|
||||
hashLookup = new UnsafeHashMap<long, int>(16, Allocator.Persistent),
|
||||
data = new UnsafeList<byte>(typeSize * 16, AllocationHandle.Persistent),
|
||||
infos = new UnsafeList<EntryInfo>(16, AllocationHandle.Persistent),
|
||||
hashLookup = new UnsafeHashMap<long, int>(16, AllocationHandle.Persistent),
|
||||
freeListHead = 0,
|
||||
versionCounter = 0
|
||||
};
|
||||
@@ -217,7 +217,7 @@ internal sealed unsafe class SharedComponentStore : IDisposable
|
||||
_perType.Add(componentTypeId, store);
|
||||
|
||||
existing = ref _perType.GetValueRef(componentTypeId, out exist);
|
||||
Debug.Assert(exist);
|
||||
Logger.DebugAssert(exist);
|
||||
|
||||
return ref existing;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public abstract class SystemBase : ISystem
|
||||
{
|
||||
if (!_requiredQueries.IsCreated)
|
||||
{
|
||||
_requiredQueries = new UnsafeList<int>(4, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
_requiredQueries = new UnsafeList<int>(4, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent);
|
||||
}
|
||||
|
||||
_requiredQueries.Add(queryID.Value);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
@@ -473,7 +474,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
|
||||
ref var record = ref recordResult.Value;
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
|
||||
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
|
||||
var format = record.desc.TextureDescriptor.Format.ToDXGIFormat();
|
||||
var clearColor = rtDesc.ClearColor;
|
||||
|
||||
// Map load operation
|
||||
@@ -527,7 +528,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
|
||||
ref var record = ref recordResult.Value;
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv);
|
||||
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
|
||||
var format = record.desc.TextureDescriptor.Format.ToDXGIFormat();
|
||||
|
||||
// Map depth load operation
|
||||
var depthLoadAccessType = depthDesc.DepthLoadOp switch
|
||||
@@ -638,7 +639,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
pNativeObject->RSSetViewports(1, &d3d12Viewport);
|
||||
}
|
||||
|
||||
public void SetPipelineState(Key128<GraphicsPipeline> pipelineKey)
|
||||
public void SetPipelineState(Key128<PipelineState> pipelineKey)
|
||||
{
|
||||
AssertNotDisposed();
|
||||
ThrowIfNotRecording();
|
||||
@@ -701,7 +702,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
{
|
||||
BufferLocation = record.ResourcePtr.Get()->GetGPUVirtualAddress() + offset,
|
||||
SizeInBytes = (uint)(record.ResourcePtr.Get()->GetDesc().Width - offset),
|
||||
StrideInBytes = record.desc.BufferDescription.Stride
|
||||
StrideInBytes = record.desc.BufferDescriptor.Stride
|
||||
};
|
||||
|
||||
pNativeObject->IASetVertexBuffers(slot, 1, &vbView);
|
||||
@@ -859,7 +860,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
|
||||
IncrementCommandCount();
|
||||
|
||||
Debug.Assert(commandSignature is D3D12CommandSignature);
|
||||
Logger.DebugAssert(commandSignature is D3D12CommandSignature);
|
||||
|
||||
var resource = _resourceDatabase.GetResource(argumentBuffer.AsResource());
|
||||
var countResource = _resourceDatabase.GetResource(countBuffer.AsResource());
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
@@ -88,7 +89,7 @@ internal unsafe class D3D12CommandSignature : D3D12Object<ID3D12CommandSignature
|
||||
|
||||
public IntPtr NativePointer => (IntPtr)NativeObject.Get();
|
||||
|
||||
public D3D12CommandSignature(D3D12RenderDevice device, D3D12PipelineLibrary pipelineLibrary, ref readonly CommandSignatureDesc desc, Key128<GraphicsPipeline> pipelineKey)
|
||||
public D3D12CommandSignature(D3D12RenderDevice device, D3D12PipelineLibrary pipelineLibrary, ref readonly CommandSignatureDesc desc, Key128<PipelineState> pipelineKey)
|
||||
: base(CreateCommandSignature(device, pipelineLibrary, in desc))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
@@ -67,7 +68,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable
|
||||
Stride = device.NativeObject.Get()->GetDescriptorHandleIncrementSize(type);
|
||||
|
||||
var success = AllocateResources(numDescriptors);
|
||||
Debug.Assert(success);
|
||||
Logger.DebugAssert(success);
|
||||
|
||||
_heap.Get()->SetName(name);
|
||||
if (ShaderVisible)
|
||||
@@ -240,7 +241,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable
|
||||
|
||||
if (!_allocatedDescriptors.IsCreated)
|
||||
{
|
||||
_allocatedDescriptors = new UnsafeBitSet(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear);
|
||||
_allocatedDescriptors = new UnsafeBitSet(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -299,7 +300,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Debug.Assert(NumAllocatedDescriptors == 0);
|
||||
Logger.DebugAssert(NumAllocatedDescriptors == 0);
|
||||
|
||||
_heap.Dispose();
|
||||
_shaderVisibleHeap.Dispose();
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
#endif
|
||||
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
@@ -80,13 +80,13 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
|
||||
public ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return new D3D12CommandAllocator(_device, type);
|
||||
}
|
||||
|
||||
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
return new D3D12CommandBuffer(
|
||||
_device,
|
||||
@@ -99,7 +99,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
|
||||
public ICommandBuffer GetPooledCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
for (var i = 0; i < _commandBufferPool.Count; i++)
|
||||
{
|
||||
@@ -116,25 +116,25 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
|
||||
public void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
_commandBufferReturnQueue.Enqueue(new CommandBufferReturnEntry(commandBuffer, _cpuFrame));
|
||||
}
|
||||
|
||||
public ISwapChain CreateSwapChain(SwapChainDesc desc)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return new DXGISwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount);
|
||||
}
|
||||
|
||||
public ICommandSignature CreateCommandSignature(ref readonly CommandSignatureDesc desc, Key128<GraphicsPipeline> pipelineKey)
|
||||
public ICommandSignature CreateCommandSignature(ref readonly CommandSignatureDesc desc, Key128<PipelineState> pipelineKey)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return new D3D12CommandSignature(_device, _pipelineLibrary, in desc, pipelineKey);
|
||||
}
|
||||
|
||||
public void BeginFrame(ulong cpuFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
_cpuFrame = cpuFrame;
|
||||
_resourceDatabase.BeginFrame(cpuFrame);
|
||||
@@ -142,7 +142,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
|
||||
public void EndFrame(ulong gpuFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
_resourceDatabase.EndFrame(gpuFrame);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
@@ -51,7 +52,7 @@ public unsafe abstract class D3D12Object<T>: IRHIObject
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void AssertNotDisposed()
|
||||
{
|
||||
Debug.Assert(_nativeObject.Get() != null, "Object has been disposed.");
|
||||
Logger.DebugAssert(_nativeObject.Get() != null, "Object has been disposed.");
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
||||
@@ -60,7 +60,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
||||
{
|
||||
_device = device;
|
||||
|
||||
_pipelineCache = new UnsafeHashMap<UInt128, D3D12PipelineState>(32, Allocator.Persistent);
|
||||
_pipelineCache = new UnsafeHashMap<UInt128, D3D12PipelineState>(32, AllocationHandle.Persistent);
|
||||
|
||||
CreateDefaultRootSignature().ThrowIfFailed();
|
||||
}
|
||||
@@ -129,7 +129,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
||||
}
|
||||
|
||||
var size = pNativeObject->GetSerializedSize();
|
||||
using var buffer = new UnsafeArray<byte>((int)size, Allocator.Persistent); // We use persistent Heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries.
|
||||
using var buffer = new UnsafeArray<byte>((int)size, AllocationHandle.Persistent); // We use persistent Heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries.
|
||||
|
||||
ThrowIfFailed(pNativeObject->Serialize(buffer.GetUnsafePtr(), size));
|
||||
|
||||
@@ -157,7 +157,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
||||
}
|
||||
|
||||
var hr = pNativeObject->LoadPipeline(pKeyStr, pStreamDesc, __uuidof(pPipelineState), (void**)&pPipelineState);
|
||||
|
||||
|
||||
if (hr == E.E_INVALIDARG)
|
||||
{
|
||||
// Pipeline not found in the library, create a new one.
|
||||
@@ -177,37 +177,37 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public Result<Key128<GraphicsPipeline>> CreateGraphicsPipeline(ref readonly GraphicsPSODescriptor descriptor, ReadOnlySpan<byte> asByteCode, ReadOnlySpan<byte> msByteCode, ReadOnlySpan<byte> psByteCode)
|
||||
public Result<Key128<PipelineState>> CreateGraphicsPipeline(ref readonly GraphicsPSODesc desc)
|
||||
{
|
||||
AssertNotDisposed();
|
||||
|
||||
if (descriptor.RtvFormats.Length > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
|
||||
if (desc.RtvFormats.Length > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
|
||||
{
|
||||
return Result.Failure($"RTV format count exceeds the maximum supported render target count of {D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT}.");
|
||||
}
|
||||
|
||||
var passPipelineKey = new PassPipelineHash(descriptor.RtvFormats, descriptor.DsvFormat);
|
||||
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(descriptor.VariantKey, descriptor.PipelineOption, passPipelineKey);
|
||||
var passAttachmentKey = new PassAttachmentHash(desc.RtvFormats, desc.DsvFormat);
|
||||
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(desc.VariantKey, desc.PipelineOption, passAttachmentKey);
|
||||
|
||||
if (!_pipelineCache.ContainsKey(pipelineKey))
|
||||
{
|
||||
fixed (byte* pASByteCode = asByteCode, pMSByteCode = msByteCode, pPSByteCode = psByteCode)
|
||||
fixed (byte* pASByteCode = desc.AsCode, pMSByteCode = desc.MsCode, pPSByteCode = desc.PsCode)
|
||||
{
|
||||
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
|
||||
var msPipelinedesc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
|
||||
{
|
||||
pRootSignature = _defaultRootSignature.Get(),
|
||||
MS = new D3D12_SHADER_BYTECODE(pMSByteCode, (nuint)msByteCode.Length),
|
||||
PS = new D3D12_SHADER_BYTECODE(pPSByteCode, (nuint)psByteCode.Length),
|
||||
MS = new D3D12_SHADER_BYTECODE(pMSByteCode, (nuint)desc.MsCode.Length),
|
||||
PS = new D3D12_SHADER_BYTECODE(pPSByteCode, (nuint)desc.PsCode.Length),
|
||||
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
|
||||
SampleMask = UINT32_MAX,
|
||||
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
|
||||
NumRenderTargets = (uint)descriptor.RtvFormats.Length,
|
||||
DSVFormat = descriptor.DsvFormat.ToDXGIFormat(),
|
||||
DepthStencilState = BuildDepthStencil(descriptor.PipelineOption.ZTest, descriptor.PipelineOption.ZWrite),
|
||||
NumRenderTargets = (uint)desc.RtvFormats.Length,
|
||||
DSVFormat = desc.DsvFormat.ToDXGIFormat(),
|
||||
DepthStencilState = BuildDepthStencil(desc.PipelineOption.ZTest, desc.PipelineOption.ZWrite),
|
||||
NodeMask = 0,
|
||||
Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
|
||||
|
||||
BlendState = descriptor.PipelineOption.Blend switch
|
||||
BlendState = desc.PipelineOption.Blend switch
|
||||
{
|
||||
Blend.Opaque => D3D12Utility.D3D12_BLEND_DESC_OPAQUE,
|
||||
Blend.Alpha => D3D12Utility.D3D12_BLEND_DESC_ALPHA_BLEND,
|
||||
@@ -216,7 +216,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
||||
Blend.PremultipliedAlpha => D3D12Utility.D3D12_BLEND_DESC_PREMULTIPLIED,
|
||||
_ => D3D12Utility.D3D12_BLEND_DESC_OPAQUE
|
||||
},
|
||||
RasterizerState = descriptor.PipelineOption.Cull switch
|
||||
RasterizerState = desc.PipelineOption.Cull switch
|
||||
{
|
||||
Cull.Off => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE,
|
||||
Cull.Front => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_CLOCKWISE,
|
||||
@@ -225,25 +225,25 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
||||
},
|
||||
};
|
||||
|
||||
if (asByteCode.Length != 0)
|
||||
if (desc.AsCode.Length != 0)
|
||||
{
|
||||
desc.AS = new D3D12_SHADER_BYTECODE(pASByteCode, (nuint)asByteCode.Length);
|
||||
msPipelinedesc.AS = new D3D12_SHADER_BYTECODE(pASByteCode, (nuint)desc.AsCode.Length);
|
||||
}
|
||||
|
||||
for (var i = 0; i < descriptor.RtvFormats.Length; i++)
|
||||
for (var i = 0; i < desc.RtvFormats.Length; i++)
|
||||
{
|
||||
desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat();
|
||||
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)((int)descriptor.PipelineOption.ColorMask & 0x0F);
|
||||
msPipelinedesc.RTVFormats[i] = desc.RtvFormats[i].ToDXGIFormat();
|
||||
msPipelinedesc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)((int)desc.PipelineOption.ColorMask & 0x0F);
|
||||
}
|
||||
|
||||
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc);
|
||||
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in msPipelinedesc);
|
||||
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
|
||||
{
|
||||
pPipelineStateSubobjectStream = &meshStream,
|
||||
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_MESH_STATE_STREAM)
|
||||
};
|
||||
|
||||
var result = CreatePSO(descriptor.VariantKey, pipelineKey, &streamDesc);
|
||||
var result = CreatePSO(desc.VariantKey, pipelineKey, &streamDesc);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
@@ -254,25 +254,25 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
||||
return pipelineKey;
|
||||
}
|
||||
|
||||
public Result<Key128<ComputePipeline>> CreateComputePipeline(ref readonly ComputePSODescriptor descriptor, ReadOnlySpan<byte> csBytecode)
|
||||
public Result<Key128<PipelineState>> CreateComputePipeline(ref readonly ComputePSODesc desc)
|
||||
{
|
||||
AssertNotDisposed();
|
||||
|
||||
var pipelineKey = RHIUtility.CreateComputePipelineKey(descriptor.VariantKey);
|
||||
var pipelineKey = RHIUtility.CreateComputePipelineKey(desc.VariantKey);
|
||||
if (!_pipelineCache.ContainsKey(pipelineKey))
|
||||
{
|
||||
fixed (byte* pCSByteCode = csBytecode)
|
||||
fixed (byte* pCSByteCode = desc.CsCode)
|
||||
{
|
||||
var byteCode = new D3D12_SHADER_BYTECODE(pCSByteCode, (nuint)csBytecode.Length);
|
||||
var desc = new CD3DX12_PIPELINE_STATE_STREAM_CS(in byteCode);
|
||||
var byteCode = new D3D12_SHADER_BYTECODE(pCSByteCode, (nuint)desc.CsCode.Length);
|
||||
var csPipelineDesc = new CD3DX12_PIPELINE_STATE_STREAM_CS(in byteCode);
|
||||
|
||||
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
|
||||
{
|
||||
pPipelineStateSubobjectStream = &desc,
|
||||
pPipelineStateSubobjectStream = &csPipelineDesc,
|
||||
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_STATE_STREAM_CS)
|
||||
};
|
||||
|
||||
var result = CreatePSO(descriptor.VariantKey, pipelineKey, &streamDesc);
|
||||
var result = CreatePSO(desc.VariantKey, pipelineKey, &streamDesc);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
|
||||
@@ -2,7 +2,6 @@ using Ghost.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
@@ -510,11 +509,11 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
D3D12_RESOURCE_DESC d3d12Desc;
|
||||
if (desc.Type == ResourceType.Texture)
|
||||
{
|
||||
d3d12Desc = desc.TextureDescription.ToD3D12ResourceDesc();
|
||||
d3d12Desc = desc.TextureDescriptor.ToD3D12ResourceDesc();
|
||||
}
|
||||
else
|
||||
{
|
||||
d3d12Desc = desc.BufferDescription.ToD3D12ResourceDesc();
|
||||
d3d12Desc = desc.BufferDescriptor.ToD3D12ResourceDesc();
|
||||
}
|
||||
|
||||
var info = _device.NativeObject.Get()->GetResourceAllocationInfo(0, 1, &d3d12Desc);
|
||||
@@ -559,7 +558,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
|
||||
public Handle<GPUTexture> CreateTexture(ref readonly TextureDesc desc, string? name = null, CreationOptions options = default)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
CheckTexture2DSize(desc.Width, desc.Height);
|
||||
|
||||
@@ -659,7 +658,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
|
||||
public Handle<GPUTexture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string? name = null, CreationOptions options = default)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
var textureDesc = desc.ToTextureDescription();
|
||||
return CreateTexture(in textureDesc, name, options);
|
||||
@@ -667,7 +666,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
|
||||
public Handle<GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string? name = null, CreationOptions options = default)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
CheckBufferSize(desc.Size);
|
||||
|
||||
var resourceDesc = desc.ToD3D12ResourceDesc();
|
||||
@@ -771,7 +770,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
|
||||
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
if (_resourceDatabase.TryGetSampler(in desc, out var id))
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@ using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.DirectX;
|
||||
@@ -16,19 +15,19 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
internal unsafe record struct ResourceRecord
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct ResourceUnion
|
||||
public struct __resource_union
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public UniquePtr<D3D12MA_Allocation> allocation;
|
||||
[FieldOffset(0)]
|
||||
public UniquePtr<ID3D12Resource> resource;
|
||||
|
||||
public ResourceUnion(D3D12MA_Allocation* allocation)
|
||||
public __resource_union(D3D12MA_Allocation* allocation)
|
||||
{
|
||||
this.allocation = allocation;
|
||||
}
|
||||
|
||||
public ResourceUnion(ID3D12Resource* resource)
|
||||
public __resource_union(ID3D12Resource* resource)
|
||||
{
|
||||
this.resource = resource;
|
||||
}
|
||||
@@ -36,7 +35,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public ResourceDesc desc;
|
||||
public ResourceViewGroup viewGroup;
|
||||
public ResourceUnion resource;
|
||||
public __resource_union resource;
|
||||
|
||||
public ResourceBarrierData barrierData;
|
||||
|
||||
@@ -47,7 +46,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public ResourceRecord(D3D12MA_Allocation* allocation, ResourceBarrierData barrierData, ResourceViewGroup viewGroup, ResourceDesc desc)
|
||||
{
|
||||
this.resource = new ResourceUnion(allocation);
|
||||
this.resource = new __resource_union(allocation);
|
||||
this.isExternal = false;
|
||||
|
||||
this.viewGroup = viewGroup;
|
||||
@@ -57,7 +56,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public ResourceRecord(ID3D12Resource* resource, ResourceBarrierData barrierData, ResourceViewGroup viewGroup, ResourceDesc desc)
|
||||
{
|
||||
this.resource = new ResourceUnion(resource);
|
||||
this.resource = new __resource_union(resource);
|
||||
this.isExternal = true;
|
||||
|
||||
this.viewGroup = viewGroup;
|
||||
@@ -118,13 +117,13 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
{
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
|
||||
_resources = new UnsafeSlotMap<ResourceRecord>(64, Allocator.Persistent, AllocationOption.Clear);
|
||||
_samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, Allocator.Persistent);
|
||||
_resources = new UnsafeSlotMap<ResourceRecord>(64, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||
_samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, AllocationHandle.Persistent);
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
_resourceName = new Dictionary<Handle<GPUResource>, string>(64);
|
||||
#endif
|
||||
|
||||
_releaseQueue = new UnsafeQueue<ReleaseEntry>(32, Allocator.Persistent);
|
||||
_releaseQueue = new UnsafeQueue<ReleaseEntry>(32, AllocationHandle.Persistent);
|
||||
}
|
||||
|
||||
~D3D12ResourceDatabase()
|
||||
@@ -134,7 +133,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
internal Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceBarrierData initialBarrierData, ResourceViewGroup viewGroup, ResourceDesc desc, string? name = null)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
if (pResource == null)
|
||||
{
|
||||
@@ -173,7 +172,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
internal Handle<GPUResource> AddAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
if (allocation == null)
|
||||
{
|
||||
@@ -217,25 +216,25 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public void EnterParallelRead()
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
Interlocked.Exchange(ref _writeLock, 1);
|
||||
}
|
||||
|
||||
public void ExitParallelRead()
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
Interlocked.Exchange(ref _writeLock, 0);
|
||||
}
|
||||
|
||||
public bool HasResource(Handle<GPUResource> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _resources.Contains(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
public RefResult<ResourceRecord, Error> GetResourceRecord(Handle<GPUResource> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
@@ -296,7 +295,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
var r = GetResourceRecord(handle);
|
||||
if (r.IsFailure || !r.Value.Allocated)
|
||||
{
|
||||
return ~0u;
|
||||
return uint.MaxValue;
|
||||
}
|
||||
|
||||
return access switch
|
||||
@@ -310,7 +309,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public string? GetResourceName(Handle<GPUResource> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (_resourceName.TryGetValue(handle, out var name))
|
||||
@@ -323,7 +322,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public void ReleaseResource(Handle<GPUResource> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
if (!_resources.TryGetElementAt(handle.ID, handle.Generation, out var record))
|
||||
{
|
||||
@@ -342,7 +341,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public void ReleaseResourceImmediately(Handle<GPUResource> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist || !info.Allocated)
|
||||
@@ -356,7 +355,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public Identifier<Sampler> AddSampler(ref readonly SamplerDesc desc, int id)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
if (_samplers.ContainsKey(desc))
|
||||
{
|
||||
@@ -371,13 +370,13 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public bool TryGetSampler(ref readonly SamplerDesc desc, out Identifier<Sampler> id)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _samplers.TryGetValue(desc, out id);
|
||||
}
|
||||
|
||||
public void ReleaseSampler(Identifier<Sampler> id)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
// NOTE: We almost never release samplers individually, because they are cheap and can be reused.
|
||||
// Ideally we would release all samplers at once when disposing the ResourceDatabase.
|
||||
@@ -447,13 +446,13 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
internal void BeginFrame(ulong cpuFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
_cpuFrame = cpuFrame;
|
||||
}
|
||||
|
||||
internal void EndFrame(ulong gpuFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
while (_releaseQueue.TryPeek(out var toRelease) && toRelease.fenceValue < gpuFrame)
|
||||
{
|
||||
@@ -464,7 +463,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
internal void ReleaseAllResourcesImmediately()
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
foreach (ref var entry in _releaseQueue)
|
||||
{
|
||||
|
||||
@@ -108,7 +108,7 @@ internal unsafe class DXGISwapChain : ISwapChain
|
||||
|
||||
public DXGISwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount)
|
||||
{
|
||||
Debug.Assert(bufferCount >= 2);
|
||||
Logger.DebugAssert(bufferCount >= 2);
|
||||
|
||||
_resourceDatabase = resourceDatabase;
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
@@ -117,7 +117,7 @@ internal unsafe class DXGISwapChain : ISwapChain
|
||||
var pSfwapChain = CreateSwapChain(device, desc, bufferCount);
|
||||
_swapChain.Attach(pSfwapChain);
|
||||
|
||||
_backBuffers = new UnsafeArray<Handle<GPUTexture>>((int)bufferCount, Allocator.Persistent);
|
||||
_backBuffers = new UnsafeArray<Handle<GPUTexture>>((int)bufferCount, AllocationHandle.Persistent);
|
||||
|
||||
Width = desc.Width;
|
||||
Height = desc.Height;
|
||||
@@ -168,20 +168,20 @@ internal unsafe class DXGISwapChain : ISwapChain
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Handle<GPUTexture> GetCurrentBackBuffer()
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlySpan<Handle<GPUTexture>> GetBackBuffers()
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _backBuffers.AsSpan();
|
||||
}
|
||||
|
||||
public void Present(bool vsync = true)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
var presentFlags = 0u;
|
||||
var syncInterval = vsync ? 1u : 0u;
|
||||
@@ -192,7 +192,7 @@ internal unsafe class DXGISwapChain : ISwapChain
|
||||
|
||||
public void Resize(uint width, uint height)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
if (Width == width && Height == height)
|
||||
{
|
||||
@@ -215,7 +215,7 @@ internal unsafe class DXGISwapChain : ISwapChain
|
||||
|
||||
public void SetScale(float scaleX, float scaleY)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
var inverseScaleX = 1.0f / scaleX;
|
||||
var inverseScaleY = 1.0f / scaleY;
|
||||
|
||||
@@ -53,9 +53,9 @@ internal static unsafe class D3D12Utility
|
||||
var ptr = uPtr.Get();
|
||||
if (ptr != null)
|
||||
{
|
||||
Debug.Assert(ptr != other);
|
||||
Logger.DebugAssert(ptr != other);
|
||||
var refCount = ptr->Release();
|
||||
Debug.Assert(refCount == 0);
|
||||
Logger.DebugAssert(refCount == 0);
|
||||
}
|
||||
|
||||
uPtr = new UniquePtr<T>(other);
|
||||
@@ -69,7 +69,7 @@ internal static unsafe class D3D12Utility
|
||||
if (ptr != null)
|
||||
{
|
||||
var refCount = ptr->Release();
|
||||
//Debug.Assert(refCount == 0);
|
||||
//Logger.DebugAssert(refCount == 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,8 +166,6 @@ public struct ResourceRange
|
||||
}
|
||||
|
||||
public readonly struct ShaderVariant;
|
||||
public readonly struct GraphicsPipeline;
|
||||
public readonly struct ComputePipeline;
|
||||
|
||||
public readonly struct ShaderPass
|
||||
{
|
||||
@@ -187,11 +185,11 @@ public readonly struct ShaderPass
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct PassPipelineHash : IEquatable<PassPipelineHash>
|
||||
public readonly struct PassAttachmentHash : IEquatable<PassAttachmentHash>
|
||||
{
|
||||
public readonly UInt128 value;
|
||||
|
||||
public PassPipelineHash(ReadOnlySpan<TextureFormat> rtvFormats, TextureFormat dsvFormat)
|
||||
public PassAttachmentHash(ReadOnlySpan<TextureFormat> rtvFormats, TextureFormat dsvFormat)
|
||||
{
|
||||
if (rtvFormats.Length > 8)
|
||||
{
|
||||
@@ -211,16 +209,21 @@ public readonly struct PassPipelineHash : IEquatable<PassPipelineHash>
|
||||
value = new UInt128(rtvPart, (ulong)dsvFormat);
|
||||
}
|
||||
|
||||
public bool Equals(PassPipelineHash other) => value == other.value;
|
||||
public override bool Equals(object? obj) => obj is PassPipelineHash other && Equals(other);
|
||||
public bool Equals(PassAttachmentHash other) => value == other.value;
|
||||
public override bool Equals(object? obj) => obj is PassAttachmentHash other && Equals(other);
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
|
||||
public static bool operator ==(PassPipelineHash left, PassPipelineHash right) => left.Equals(right);
|
||||
public static bool operator !=(PassPipelineHash left, PassPipelineHash right) => !(left == right);
|
||||
public static bool operator ==(PassAttachmentHash left, PassAttachmentHash right) => left.Equals(right);
|
||||
public static bool operator !=(PassAttachmentHash left, PassAttachmentHash right) => !(left == right);
|
||||
}
|
||||
|
||||
public ref struct GraphicsPSODescriptor
|
||||
public ref struct GraphicsPSODesc
|
||||
{
|
||||
public UInt128 CompiledHash
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Key64<ShaderVariant> VariantKey
|
||||
{
|
||||
get; set;
|
||||
@@ -240,14 +243,39 @@ public ref struct GraphicsPSODescriptor
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> AsCode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> MsCode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> PsCode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct ComputePSODescriptor
|
||||
public ref struct ComputePSODesc
|
||||
{
|
||||
public UInt128 CompiledHash
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Key64<ShaderVariant> VariantKey
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> CsCode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct CBufferPropertyInfo
|
||||
@@ -640,7 +668,7 @@ public struct BarrierDesc
|
||||
public record struct ResourceDesc
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal struct resource_union
|
||||
internal struct __union
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public TextureDesc textureDescription;
|
||||
@@ -648,7 +676,7 @@ public record struct ResourceDesc
|
||||
public BufferDesc bufferDescription;
|
||||
}
|
||||
|
||||
private resource_union _desc;
|
||||
private __union _desc;
|
||||
|
||||
public ResourceType Type
|
||||
{
|
||||
@@ -656,21 +684,21 @@ public record struct ResourceDesc
|
||||
}
|
||||
|
||||
[UnscopedRef]
|
||||
public ref TextureDesc TextureDescription
|
||||
public ref TextureDesc TextureDescriptor
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(Type == ResourceType.Texture);
|
||||
Logger.DebugAssert(Type == ResourceType.Texture);
|
||||
return ref _desc.textureDescription;
|
||||
}
|
||||
}
|
||||
|
||||
[UnscopedRef]
|
||||
public ref BufferDesc BufferDescription
|
||||
public ref BufferDesc BufferDescriptor
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(Type == ResourceType.Buffer);
|
||||
Logger.DebugAssert(Type == ResourceType.Buffer);
|
||||
return ref _desc.bufferDescription;
|
||||
}
|
||||
}
|
||||
@@ -680,7 +708,7 @@ public record struct ResourceDesc
|
||||
return new ResourceDesc
|
||||
{
|
||||
Type = ResourceType.Buffer,
|
||||
BufferDescription = desc
|
||||
BufferDescriptor = desc
|
||||
};
|
||||
}
|
||||
|
||||
@@ -689,7 +717,7 @@ public record struct ResourceDesc
|
||||
return new ResourceDesc
|
||||
{
|
||||
Type = ResourceType.Texture,
|
||||
TextureDescription = desc
|
||||
TextureDescriptor = desc
|
||||
};
|
||||
}
|
||||
|
||||
@@ -702,8 +730,8 @@ public record struct ResourceDesc
|
||||
|
||||
return Type switch
|
||||
{
|
||||
ResourceType.Texture => TextureDescription.Equals(other.TextureDescription),
|
||||
ResourceType.Buffer => BufferDescription.Equals(other.BufferDescription),
|
||||
ResourceType.Texture => TextureDescriptor.Equals(other.TextureDescriptor),
|
||||
ResourceType.Buffer => BufferDescriptor.Equals(other.BufferDescriptor),
|
||||
_ => throw new InvalidOperationException($"Unknown resource type: {Type}")
|
||||
};
|
||||
}
|
||||
@@ -712,8 +740,8 @@ public record struct ResourceDesc
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
ResourceType.Texture => HashCode.Combine(Type, TextureDescription),
|
||||
ResourceType.Buffer => HashCode.Combine(Type, BufferDescription),
|
||||
ResourceType.Texture => HashCode.Combine(Type, TextureDescriptor),
|
||||
ResourceType.Buffer => HashCode.Combine(Type, BufferDescriptor),
|
||||
_ => throw new InvalidOperationException($"Unknown resource type: {Type}")
|
||||
};
|
||||
}
|
||||
@@ -722,8 +750,8 @@ public record struct ResourceDesc
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
ResourceType.Texture => $"Texture: {TextureDescription}",
|
||||
ResourceType.Buffer => $"Buffer: {BufferDescription}",
|
||||
ResourceType.Texture => $"Texture: {TextureDescriptor}",
|
||||
ResourceType.Buffer => $"Buffer: {BufferDescriptor}",
|
||||
_ => $"Unknown resource type: {Type}"
|
||||
};
|
||||
}
|
||||
@@ -1649,4 +1677,4 @@ public enum SetWorkGraphFlags
|
||||
{
|
||||
None,
|
||||
Initialize
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
@@ -97,7 +98,7 @@ public interface ICommandBuffer : IRHIObject
|
||||
/// Sets the pipeline state object
|
||||
/// </summary>
|
||||
/// <param name="pipelineKey">Pipeline state to set</param>
|
||||
void SetPipelineState(Key128<GraphicsPipeline> pipelineKey);
|
||||
void SetPipelineState(Key128<PipelineState> pipelineKey);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the constant buffer view for the specified slot in the graphics pipeline.
|
||||
@@ -216,4 +217,4 @@ public interface ICommandBuffer : IRHIObject
|
||||
/// <param name="intermediate">A handle to an intermediate GPU resource used to stage the subresource data before copying to the destination resource.</param>
|
||||
/// <param name="subResources">A span containing the data for each subresource to update. Each element represents a subresource and its associated data.</param>
|
||||
void UpdateSubResources(Handle<GPUResource> resource, Handle<GPUResource> intermediate, params ReadOnlySpan<SubResourceData> subResources);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
@@ -6,6 +7,6 @@ public interface IPipelineLibrary : IDisposable
|
||||
{
|
||||
void SaveLibraryToDisk(string filePath);
|
||||
bool HasPipelineStateObject(UInt128 key);
|
||||
Result<Key128<GraphicsPipeline>> CreateGraphicsPipeline(ref readonly GraphicsPSODescriptor descriptor, ReadOnlySpan<byte> asByteCode, ReadOnlySpan<byte> msByteCode, ReadOnlySpan<byte> psByteCode);
|
||||
Result<Key128<ComputePipeline>> CreateComputePipeline(ref readonly ComputePSODescriptor descriptor, ReadOnlySpan<byte> csByteCode);
|
||||
Result<Key128<PipelineState>> CreateGraphicsPipeline(ref readonly GraphicsPSODesc desc);
|
||||
Result<Key128<PipelineState>> CreateComputePipeline(ref readonly ComputePSODesc desc);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public unsafe struct LocalKeywordSet
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetHash64()
|
||||
public ulong GetHashCode64()
|
||||
{
|
||||
var hash = 14695981039346656037ul; // FNV Offset basis
|
||||
|
||||
@@ -124,4 +124,4 @@ public unsafe struct LocalKeywordSet
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ namespace Ghost.Graphics.RHI;
|
||||
public static class RHIUtility
|
||||
{
|
||||
public const int MAX_RENDER_TARGETS = 8;
|
||||
public const ulong SHADER_ID_MASK = 0xFFFFFFFFFFFFFFF0ul;
|
||||
public const ulong PIPELINE_KEY_MASK = 0xFFFFFFFFFFFFFFF0ul;
|
||||
public const ulong GRAPHICS_PIPELINE_KEY_FLAG = 0x1ul;
|
||||
public const ulong COMPUTE_PIPELINE_KEY_FLAG = 0x2ul;
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint GetBytesPerPixel(this TextureFormat format)
|
||||
{
|
||||
@@ -149,6 +149,20 @@ public static class RHIUtility
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong GetShaderID(string shaderName)
|
||||
{
|
||||
var hash = XxHash64.HashToUInt64(MemoryMarshal.AsBytes(shaderName.AsSpan()));
|
||||
return hash & SHADER_ID_MASK;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong GetPassID(ulong shaderID, int passIndex)
|
||||
{
|
||||
Logger.DebugAssert(passIndex >= 0 && passIndex < 16, "Pass index must be between 0 and 15 to fit within the shader ID mask.");
|
||||
return shaderID | ((ulong)passIndex & 0xFul);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Key64<ShaderPass> CreateShaderPassKey(ulong passID, ulong compiledHash)
|
||||
{
|
||||
@@ -158,11 +172,11 @@ public static class RHIUtility
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Key64<ShaderVariant> CreateShaderVariantKey(ulong passKey, ref readonly LocalKeywordSet keywords)
|
||||
{
|
||||
var keywordHash = keywords.GetHash64();
|
||||
var keywordHash = keywords.GetHashCode64();
|
||||
return new Key64<ShaderVariant>(Hash.Combine64(passKey, keywordHash));
|
||||
}
|
||||
|
||||
public static unsafe Key128<GraphicsPipeline> CreateGraphicsPipelineKey(Key64<ShaderVariant> shaderVariantKey, PipelineState pipelineState, PassPipelineHash passKey)
|
||||
public static unsafe Key128<PipelineState> CreateGraphicsPipelineKey(ulong compiledHash, PipelineState pipelineState, PassAttachmentHash passAttachmentHash)
|
||||
{
|
||||
// Order-sensitive 128-bit mix. Cheap and stable, avoids span hashing.
|
||||
static ulong Mix64(ulong x)
|
||||
@@ -175,10 +189,10 @@ public static class RHIUtility
|
||||
return x;
|
||||
}
|
||||
|
||||
var mLo = shaderVariantKey.Value;
|
||||
var mLo = compiledHash;
|
||||
var mHi = pipelineState.GetHashCode64();
|
||||
|
||||
var pPasskey = (ulong*)&passKey.value;
|
||||
var pPasskey = (ulong*)&passAttachmentHash.value;
|
||||
var pLo = pPasskey[0];
|
||||
var pHi = pPasskey[1];
|
||||
|
||||
@@ -188,19 +202,25 @@ public static class RHIUtility
|
||||
|
||||
lo = lo & PIPELINE_KEY_MASK | GRAPHICS_PIPELINE_KEY_FLAG; // Ensure graphics pipeline keys are distinguishable from compute pipeline keys.
|
||||
|
||||
return new Key128<GraphicsPipeline>(new UInt128(hi, lo));
|
||||
return new Key128<PipelineState>(new UInt128(hi, lo));
|
||||
}
|
||||
|
||||
public static Key128<ComputePipeline> CreateComputePipelineKey(Key64<ShaderVariant> shaderVariantKey)
|
||||
public static Key128<PipelineState> CreateComputePipelineKey(ulong compiledHash)
|
||||
{
|
||||
var shaderHash = shaderVariantKey.Value;
|
||||
var stateHash = ~shaderVariantKey.Value;
|
||||
// Since compute shader don't have blend state or attachment configurations, we can afford a simpler key generation.
|
||||
// Just use the compiled hash with a distinct flag to avoid collisions with graphics pipeline keys.
|
||||
#if true
|
||||
return new Key128<PipelineState>(new UInt128(compiledHash, compiledHash ^ COMPUTE_PIPELINE_KEY_FLAG));
|
||||
#else
|
||||
var shaderHash = compiledHash;
|
||||
var stateHash = ~compiledHash;
|
||||
// Simple XOR mix. Not as robust as the graphics pipeline key, but sufficient for compute shaders which have fewer variants.
|
||||
var hi = shaderHash ^ (stateHash + 0x9E3779B97F4A7C15ul) ^ (shaderHash * 0xD6E8FEB86659FD93ul);
|
||||
var lo = stateHash ^ (shaderHash + 0xC2B2AE3D27D4EB4Ful) ^ (stateHash * 0x165667B19E3779F9ul);
|
||||
lo = lo & PIPELINE_KEY_MASK | COMPUTE_PIPELINE_KEY_FLAG; // Ensure compute pipeline keys are distinguishable from graphics pipeline keys.
|
||||
|
||||
return new Key128<ComputePipeline>(new UInt128(hi, lo));
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -208,4 +228,4 @@ public static class RHIUtility
|
||||
{
|
||||
return key.TryFormat(destination, out var _, "X16");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public static class RootSignatureLayout
|
||||
public struct PushConstantsData
|
||||
{
|
||||
public const uint NUM_32BITS_VALUE = 12u / sizeof(uint);
|
||||
|
||||
|
||||
[FieldOffset(0)]
|
||||
public uint frameBuffer;
|
||||
[FieldOffset(4)]
|
||||
@@ -23,6 +23,11 @@ public struct PushConstantsData
|
||||
public uint instanceIndex;
|
||||
[FieldOffset(8)]
|
||||
public uint propertyBuffer;
|
||||
|
||||
public ReadOnlySpan<uint> AsUInts()
|
||||
{
|
||||
return MemoryMarshal.CreateReadOnlySpan(ref frameBuffer, (int)NUM_32BITS_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
||||
@@ -24,7 +24,7 @@ internal struct CBufferCache : IResourceReleasable
|
||||
public CBufferCache(Handle<GPUBuffer> buffer, uint bufferSize)
|
||||
{
|
||||
_size = bufferSize;
|
||||
_cpuData = new UnsafeArray<byte>((int)bufferSize, Allocator.Persistent);
|
||||
_cpuData = new UnsafeArray<byte>((int)bufferSize, AllocationHandle.Persistent);
|
||||
_gpuResource = buffer;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public struct Material : IResourceReleasable
|
||||
{
|
||||
if (!_passPipelineOverride.IsCreated)
|
||||
{
|
||||
_passPipelineOverride = new UnsafeArray<PipelineOverride>(shader.PassCount, Allocator.Persistent);
|
||||
_passPipelineOverride = new UnsafeArray<PipelineOverride>(shader.PassCount, AllocationHandle.Persistent);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -104,7 +104,7 @@ public struct Material : IResourceReleasable
|
||||
_keywordMask.Clear();
|
||||
for (var i = 0; i < shader.PassCount; i++)
|
||||
{
|
||||
ref var pass = ref shader.GetPassReference(i);
|
||||
ref readonly var pass = ref shader.GetPassReference(i);
|
||||
_passPipelineOverride[i] = new PipelineOverride
|
||||
{
|
||||
shaderPass = pass.Key,
|
||||
|
||||
@@ -50,8 +50,8 @@ internal sealed class MaterialPaletteStore : IDisposable
|
||||
initialCapacity = 16;
|
||||
}
|
||||
|
||||
_entries = new UnsafeList<Entry>(initialCapacity + 1, Allocator.Persistent);
|
||||
_lookup = new UnsafeHashMap<ulong, int>(initialCapacity * 2, Allocator.Persistent);
|
||||
_entries = new UnsafeList<Entry>(initialCapacity + 1, AllocationHandle.Persistent);
|
||||
_lookup = new UnsafeHashMap<ulong, int>(initialCapacity * 2, AllocationHandle.Persistent);
|
||||
_freeListHead = 0;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ internal sealed class MaterialPaletteStore : IDisposable
|
||||
|
||||
if (!newEntry.materials.IsCreated)
|
||||
{
|
||||
newEntry.materials = new UnsafeList<Handle<Material>>(materials.Length, Allocator.Persistent);
|
||||
newEntry.materials = new UnsafeList<Handle<Material>>(materials.Length, AllocationHandle.Persistent);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -186,8 +186,8 @@ public struct Mesh : IResourceReleasable
|
||||
|
||||
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);
|
||||
Vertices = new UnsafeList<Vertex>(vertices.Length, AllocationHandle.Persistent);
|
||||
Indices = new UnsafeList<uint>(indices.Length, AllocationHandle.Persistent);
|
||||
Vertices.CopyFrom(vertices);
|
||||
Indices.CopyFrom(indices);
|
||||
VertexBuffer = vertexBuffer;
|
||||
@@ -298,10 +298,10 @@ public struct Mesh : IResourceReleasable
|
||||
ref var data = ref mesh->_meshletData;
|
||||
|
||||
// Ensure lists are initialized
|
||||
if (!data.groups.IsCreated) data.groups = new UnsafeList<MeshletGroup>(16, Allocator.Persistent);
|
||||
if (!data.meshlets.IsCreated) data.meshlets = new UnsafeList<Meshlet>(64, Allocator.Persistent);
|
||||
if (!data.meshletVertices.IsCreated) data.meshletVertices = new UnsafeList<uint>(128, Allocator.Persistent);
|
||||
if (!data.meshletTriangles.IsCreated) data.meshletTriangles = new UnsafeList<uint>(128, Allocator.Persistent);
|
||||
if (!data.groups.IsCreated) data.groups = new UnsafeList<MeshletGroup>(16, AllocationHandle.Persistent);
|
||||
if (!data.meshlets.IsCreated) data.meshlets = new UnsafeList<Meshlet>(64, AllocationHandle.Persistent);
|
||||
if (!data.meshletVertices.IsCreated) data.meshletVertices = new UnsafeList<uint>(128, AllocationHandle.Persistent);
|
||||
if (!data.meshletTriangles.IsCreated) data.meshletTriangles = new UnsafeList<uint>(128, AllocationHandle.Persistent);
|
||||
|
||||
var meshletGroup = new MeshletGroup
|
||||
{
|
||||
|
||||
@@ -4,7 +4,8 @@ using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Diagnostics;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
@@ -12,21 +13,23 @@ namespace Ghost.Graphics.Core;
|
||||
public readonly unsafe ref struct RenderContext
|
||||
{
|
||||
private readonly ResourceManager _resourceManager;
|
||||
private readonly ShaderLibrary _shaderLibrary;
|
||||
private readonly IGraphicsEngine _engine;
|
||||
private readonly ICommandBuffer _cmd;
|
||||
private readonly ICommandBuffer _commandBuffer;
|
||||
|
||||
public ICommandBuffer CommandBuffer => _cmd;
|
||||
public ICommandBuffer CommandBuffer => _commandBuffer;
|
||||
|
||||
public ResourceManager ResourceManager => _resourceManager;
|
||||
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
|
||||
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
|
||||
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
|
||||
|
||||
internal RenderContext(ResourceManager resourceManager, IGraphicsEngine engine, ICommandBuffer cmd)
|
||||
internal RenderContext(ResourceManager resourceManager, ShaderLibrary shaderLibrary, IGraphicsEngine engine, ICommandBuffer cmd)
|
||||
{
|
||||
_resourceManager = resourceManager;
|
||||
_shaderLibrary = shaderLibrary;
|
||||
_engine = engine;
|
||||
_cmd = cmd;
|
||||
_commandBuffer = cmd;
|
||||
}
|
||||
|
||||
private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync)
|
||||
@@ -41,7 +44,7 @@ public readonly unsafe ref struct RenderContext
|
||||
desc = BarrierDesc.Buffer(resource, newSync, newAccess);
|
||||
}
|
||||
|
||||
_cmd.Barrier(desc);
|
||||
_commandBuffer.Barrier(desc);
|
||||
}
|
||||
|
||||
public void UploadBuffer<T>(Handle<GPUBuffer> buffer, params ReadOnlySpan<T> data)
|
||||
@@ -53,10 +56,10 @@ public readonly unsafe ref struct RenderContext
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(r.Value.Type == ResourceType.Buffer);
|
||||
Logger.DebugAssert(r.Value.Type == ResourceType.Buffer);
|
||||
|
||||
var sizeInBytes = (nuint)(data.Length * sizeof(T));
|
||||
var memoryType = r.Value.BufferDescription.HeapType;
|
||||
var memoryType = r.Value.BufferDescriptor.HeapType;
|
||||
|
||||
if (memoryType == HeapType.Upload)
|
||||
{
|
||||
@@ -89,7 +92,7 @@ public readonly unsafe ref struct RenderContext
|
||||
_engine.ResourceDatabase.UnmapResource(uploadHandle.AsResource(), 0, null);
|
||||
}
|
||||
|
||||
_cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||
_commandBuffer.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +130,8 @@ public readonly unsafe ref struct RenderContext
|
||||
|
||||
public Handle<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, bool staticMesh)
|
||||
{
|
||||
var vertexList = new UnsafeList<Vertex>(vertices.Length, Allocator.Persistent);
|
||||
var indexList = new UnsafeList<uint>(indices.Length, Allocator.Persistent);
|
||||
var vertexList = new UnsafeList<Vertex>(vertices.Length, AllocationHandle.Persistent);
|
||||
var indexList = new UnsafeList<uint>(indices.Length, AllocationHandle.Persistent);
|
||||
|
||||
vertexList.CopyFrom(vertices);
|
||||
indexList.CopyFrom(indices);
|
||||
@@ -262,7 +265,7 @@ public readonly unsafe ref struct RenderContext
|
||||
where T : unmanaged
|
||||
{
|
||||
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.TextureDescriptor.Format.GetSurfaceInfo(desc.TextureDescriptor.Width, desc.TextureDescriptor.Height, out var rowPitch, out var slicePitch, out _);
|
||||
|
||||
var requiredSize = ResourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
|
||||
var uploadDesc = new BufferDesc
|
||||
@@ -289,7 +292,89 @@ public readonly unsafe ref struct RenderContext
|
||||
slicePitch = slicePitch
|
||||
};
|
||||
|
||||
_cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
||||
_commandBuffer.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
||||
}
|
||||
}
|
||||
|
||||
public void DispatchCompute<T>(Handle<ComputeShader> compute, int entryIndex, ref readonly LocalKeywordSet keywordSet, ref readonly T property, uint3 threadGroupCount)
|
||||
where T : unmanaged
|
||||
{
|
||||
ref var shader = ref ResourceManager.GetComputeShaderReference(compute).GetValueOrThrow();
|
||||
|
||||
var entryHash = shader.GetEntryID(entryIndex);
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
||||
|
||||
// TODO: Refactor this into a helper method.
|
||||
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(variantKey);
|
||||
if (error.IsFailure)
|
||||
{
|
||||
// TODO: Fallback to an error material.
|
||||
Logger.Debug($"No compiled shader found for compute shader {shader.UniqueID} with entry point {entryIndex} and keywords {keywordSet}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var pipelineKey = RHIUtility.CreateComputePipelineKey(compiledHash);
|
||||
|
||||
if (!PipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||
{
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex, scope.AllocationHandle);
|
||||
if (compiledCacheResult.IsFailure)
|
||||
{
|
||||
// TODO: Fallback to a checkerboard shader.
|
||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
||||
}
|
||||
|
||||
var cache = compiledCacheResult.Value;
|
||||
Logger.DebugAssert(cache.compiledHash == compiledHash);
|
||||
|
||||
ShaderLibrary.ParseCacheData(cache.byteCode, out _, out var byteCodeOffsets, out var byteCodes);
|
||||
Logger.DebugAssert(byteCodeOffsets.Length == 1);
|
||||
|
||||
var psoDes = new ComputePSODesc
|
||||
{
|
||||
CompiledHash = compiledHash,
|
||||
VariantKey = variantKey,
|
||||
CsCode = byteCodes.Slice((int)byteCodeOffsets[0]),
|
||||
};
|
||||
|
||||
PipelineLibrary.CreateComputePipeline(in psoDes).GetValueOrThrow();
|
||||
}
|
||||
|
||||
_commandBuffer.SetPipelineState(pipelineKey);
|
||||
|
||||
|
||||
var propertySpan = MemoryMarshal.AsBytes(new ReadOnlySpan<T>(in property));
|
||||
// TODO: Placed resource has 64k alignment requirement, which can waste lots of memory. We can allocate a large buffer and slice it for each dispatch to avoid this issue.
|
||||
var propertyBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (uint)propertySpan.Length,
|
||||
Stride = (uint)sizeof(T),
|
||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Upload,
|
||||
};
|
||||
var properyBuffer = ResourceManager.CreateTransientBuffer(in propertyBufferDesc);
|
||||
|
||||
var mappedData = ResourceDatabase.MapResource(properyBuffer.AsResource(), 0, null);
|
||||
Logger.DebugAssert(mappedData != null, "Failed to map property buffer.");
|
||||
|
||||
fixed (byte* pData = propertySpan)
|
||||
{
|
||||
MemoryUtility.MemCpy(mappedData, pData, (nuint)propertySpan.Length);
|
||||
}
|
||||
|
||||
error = ResourceDatabase.UnmapResource(properyBuffer.AsResource(), 0, null);
|
||||
Logger.DebugAssert(error.IsSuccess, $"Failed to unmap property buffer: {error}.");
|
||||
|
||||
var pushConstant = new PushConstantsData
|
||||
{
|
||||
// TODO: Support frame and view buffer.
|
||||
frameBuffer = 0,
|
||||
viewBuffer = 0,
|
||||
propertyBuffer = ResourceDatabase.GetBindlessIndex(properyBuffer.AsResource()),
|
||||
};
|
||||
|
||||
_commandBuffer.SetGraphicsRoot32Constants(0, pushConstant.AsUInts());
|
||||
_commandBuffer.DispatchCompute(threadGroupCount.x, threadGroupCount.y, threadGroupCount.z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,21 +18,21 @@ public enum GateFit : uint
|
||||
public struct Frustum
|
||||
{
|
||||
[InlineArray(6)]
|
||||
public struct plane_array
|
||||
public struct __plane_array
|
||||
{
|
||||
private float4 plane;
|
||||
}
|
||||
|
||||
[InlineArray(8)]
|
||||
public struct corner_array
|
||||
public struct __corner_array
|
||||
{
|
||||
private float3 corner;
|
||||
}
|
||||
|
||||
public plane_array planes;
|
||||
public corner_array corners;
|
||||
public __plane_array planes;
|
||||
public __corner_array corners;
|
||||
|
||||
public static void CalculateFrustumPlanes(float4x4 finalMatrix, ref plane_array outPlanes)
|
||||
public static void CalculateFrustumPlanes(float4x4 finalMatrix, ref __plane_array outPlanes)
|
||||
{
|
||||
const int planeFrustumLeft = 0;
|
||||
const int planeFrustumRight = 1;
|
||||
|
||||
@@ -3,7 +3,6 @@ using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -16,9 +15,6 @@ public partial struct Shader
|
||||
private static readonly Dictionary<string, int> s_passNameToID = new Dictionary<string, int>();
|
||||
private static int s_nextPassID = 0;
|
||||
|
||||
private static readonly Dictionary<string, int> s_propertyNameToID = new Dictionary<string, int>();
|
||||
private static int s_nextPropertyID = 0;
|
||||
|
||||
private static readonly Dictionary<string, int> s_keywordNameToID = new Dictionary<string, int>();
|
||||
private static readonly Dictionary<int, string> s_keywordIDToName = new Dictionary<int, string>();
|
||||
private static int s_nextKeywordID = 0;
|
||||
@@ -34,17 +30,6 @@ public partial struct Shader
|
||||
return id;
|
||||
}
|
||||
|
||||
public static Identifier<ShaderProperty> GetPropertyID(string propertyName)
|
||||
{
|
||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_propertyNameToID, propertyName, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
id = s_nextPropertyID++;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public static int GetKeywordID(string keywordName)
|
||||
{
|
||||
ref var id = ref CollectionsMarshal.GetValueRefOrAddDefault(s_keywordNameToID, keywordName, out var exists);
|
||||
@@ -75,6 +60,7 @@ public partial struct Shader
|
||||
/// </summary>
|
||||
public partial struct Shader : IResourceReleasable
|
||||
{
|
||||
private readonly ulong _nameHash;
|
||||
private readonly uint _propertyBufferSize;
|
||||
private UnsafeArray<ShaderPass> _shaderPasses;
|
||||
private UnsafeHashMap<int, int> _passIDToLocal;
|
||||
@@ -83,15 +69,17 @@ public partial struct Shader : IResourceReleasable
|
||||
// TODO: Tag to pass index for fast lookup.
|
||||
// We can use a int array since the number and index of tags are fixed at compile time.
|
||||
|
||||
public readonly ulong UniqueID => _nameHash;
|
||||
public readonly int PassCount => _shaderPasses.Count;
|
||||
public readonly uint PropertyBufferSize => _propertyBufferSize;
|
||||
|
||||
internal Shader(GraphicsShaderDescriptor descriptor)
|
||||
{
|
||||
_nameHash = RHIUtility.GetShaderID(descriptor.name);
|
||||
_propertyBufferSize = descriptor.propertyBufferSize;
|
||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, AllocationHandle.Persistent);
|
||||
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, AllocationHandle.Persistent);
|
||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, AllocationHandle.Persistent);
|
||||
|
||||
for (var i = 0; i < descriptor.passes.Length; i++)
|
||||
{
|
||||
@@ -129,7 +117,7 @@ public partial struct Shader : IResourceReleasable
|
||||
|
||||
_shaderPasses[i] = new ShaderPass
|
||||
{
|
||||
Key = pass.identifier,
|
||||
Key = RHIUtility.GetPassID(_nameHash, i),
|
||||
DefaultState = pass.localPipeline,
|
||||
KeywordIDs = keywords,
|
||||
};
|
||||
@@ -172,7 +160,7 @@ public partial struct Shader : IResourceReleasable
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref ShaderPass GetPassReference(int index)
|
||||
public ref readonly ShaderPass GetPassReference(int index)
|
||||
{
|
||||
return ref _shaderPasses[index];
|
||||
}
|
||||
@@ -200,25 +188,26 @@ public partial struct Shader : IResourceReleasable
|
||||
|
||||
public unsafe partial struct ComputeShader : IResourceReleasable
|
||||
{
|
||||
private fixed ulong _entryHash[8];
|
||||
private readonly int _entryPointCount;
|
||||
private readonly ulong _nameHash;
|
||||
private fixed ulong entryHashes[8]; // Support up to 8 entry points for now, can be extended if needed.
|
||||
private readonly uint _propertyBufferSize;
|
||||
|
||||
private LocalKeywordSet _localKeywordSet;
|
||||
private UnsafeHashMap<int, int> _keywordIDToLocal;
|
||||
|
||||
public readonly ulong UniqueID => _nameHash;
|
||||
public readonly uint PropertyBufferSize => _propertyBufferSize;
|
||||
|
||||
internal ComputeShader(ComputeShaderDescriptor descriptor)
|
||||
{
|
||||
_nameHash = RHIUtility.GetShaderID(descriptor.name);
|
||||
_propertyBufferSize = descriptor.propertyBufferSize;
|
||||
_entryPointCount = descriptor.shaderCodes.Length;
|
||||
|
||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, AllocationHandle.Persistent);
|
||||
|
||||
for (var i = 0; i < descriptor.shaderCodes.Length; i++)
|
||||
{
|
||||
_entryHash[i] = descriptor.shaderCodes[i].GetHashCode64();
|
||||
entryHashes[i] = RHIUtility.GetPassID(_nameHash, i);
|
||||
}
|
||||
|
||||
var localKeywordIndex = 0;
|
||||
@@ -244,10 +233,10 @@ public unsafe partial struct ComputeShader : IResourceReleasable
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetEntryHash(int entryPointIndex)
|
||||
public ulong GetEntryID(int entryIndex)
|
||||
{
|
||||
Debug.Assert(entryPointIndex >= 0 && entryPointIndex < _entryPointCount);
|
||||
return _entryHash[entryPointIndex];
|
||||
Logger.DebugAssert(entryIndex >= 0 && entryIndex < 8, "Entry index out of bounds.");
|
||||
return entryHashes[entryIndex];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="TestCompute.gcomp" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Misaki.HighPerformance.Analyzer" Version="1.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Diagnostics;
|
||||
using Ghost.Graphics.Services;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
@@ -114,7 +113,7 @@ public sealed class RenderGraph : IDisposable
|
||||
}
|
||||
|
||||
var desc = r.Value;
|
||||
return _resources.ImportTexture(in desc.TextureDescription, texture, name, clearColor, clearDepth, clearStencil, clearAtFirstUse, discardAtLastUse);
|
||||
return _resources.ImportTexture(in desc.TextureDescriptor, texture, name, clearColor, clearDepth, clearStencil, clearAtFirstUse, discardAtLastUse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -132,7 +131,7 @@ public sealed class RenderGraph : IDisposable
|
||||
}
|
||||
|
||||
var desc = r.Value;
|
||||
return _resources.ImportBuffer(in desc.BufferDescription, buffer, name);
|
||||
return _resources.ImportBuffer(in desc.BufferDescriptor, buffer, name);
|
||||
}
|
||||
|
||||
public IRasterRenderGraphBuilder AddRasterRenderPass<TPassData>(string name, out TPassData passData)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Diagnostics;
|
||||
@@ -359,7 +360,7 @@ internal sealed class ResourceAliasingManager
|
||||
logicalIndex,
|
||||
alignment);
|
||||
|
||||
Debug.Assert(success, "Simulation allocation failed - heap should be unlimited in size");
|
||||
Logger.DebugAssert(success, "Simulation allocation failed - heap should be unlimited in size");
|
||||
}
|
||||
|
||||
// Get peak usage from simulation
|
||||
|
||||
@@ -279,7 +279,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
Debug.Assert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range.");
|
||||
Logger.DebugAssert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range.");
|
||||
|
||||
var id = UseTexture(texture, flags);
|
||||
if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid)
|
||||
|
||||
@@ -2,6 +2,7 @@ using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
@@ -30,14 +31,16 @@ public interface IRasterRenderContext : IRenderGraphContext
|
||||
|
||||
void SetActiveMaterial(Handle<Material> material);
|
||||
void SetActiveMaterial(ref readonly Material material);
|
||||
[Obsolete("No point to set active mesh anymore in gpu driven pipeline.")]
|
||||
void SetActiveMesh(Handle<Mesh> mesh);
|
||||
[Obsolete("No point to set active mesh anymore in gpu driven pipeline.")]
|
||||
void SetActiveMesh(ref readonly Mesh mesh);
|
||||
void DispatchMesh(uint3 threadGroupCount);
|
||||
}
|
||||
|
||||
public interface IComputeRenderContext : IRenderGraphContext
|
||||
{
|
||||
void SetActiveCompute(Handle<ComputeShader> computeShader, uint entryIndex);
|
||||
void SetActiveCompute(Handle<ComputeShader> computeShader, int entryIndex);
|
||||
void DispatchCompute(uint3 threadGroupCount);
|
||||
}
|
||||
|
||||
@@ -160,35 +163,58 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
}
|
||||
|
||||
ref var shader = ref shaderResult.Value;
|
||||
ref var pass = ref shader.GetPassReference(material.ActivePassIndex);
|
||||
ref readonly var pass = ref shader.GetPassReference(material.ActivePassIndex);
|
||||
|
||||
var passPipelineHash = new PassPipelineHash(_rtvFormats, _dsvFormat);
|
||||
var passPipelineHash = new PassAttachmentHash(_rtvFormats, _dsvFormat);
|
||||
var materialPipeline = material.GetPassPipelineOverride(material.ActivePassIndex);
|
||||
|
||||
// Mask out the keywords that are not used in this pass.
|
||||
var variantMask = material._keywordMask & pass.KeywordIDs;
|
||||
var shaderVariantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask);
|
||||
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(shaderVariantKey, materialPipeline, passPipelineHash);
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask);
|
||||
|
||||
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(variantKey);
|
||||
if (error.IsFailure)
|
||||
{
|
||||
// TODO: Fallback to a default shader or show an error material.
|
||||
return;
|
||||
}
|
||||
|
||||
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(compiledHash, materialPipeline, passPipelineHash);
|
||||
|
||||
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||
{
|
||||
var compiledCacheResult = _shaderLibrary.GetCache(shaderVariantKey);
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, material.ActivePassIndex, scope.AllocationHandle);
|
||||
if (compiledCacheResult.IsFailure)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
||||
}
|
||||
|
||||
var psoDes = new GraphicsPSODescriptor
|
||||
var cache = compiledCacheResult.Value;
|
||||
Logger.DebugAssert(cache.compiledHash == compiledHash);
|
||||
|
||||
ShaderLibrary.ParseCacheData(cache.byteCode, out _, out var byteCodeOffsets, out var byteCodes);
|
||||
Logger.DebugAssert(byteCodeOffsets.Length == 3); // as, ms, ps
|
||||
|
||||
var asByteCode = byteCodes.Slice((int)byteCodeOffsets[0], (int)(byteCodeOffsets[1] - byteCodeOffsets[0]));
|
||||
var msByteCode = byteCodes.Slice((int)byteCodeOffsets[1], (int)(byteCodeOffsets[2] - byteCodeOffsets[1]));
|
||||
var psByteCode = byteCodes.Slice((int)byteCodeOffsets[2]);
|
||||
|
||||
var psoDes = new GraphicsPSODesc
|
||||
{
|
||||
VariantKey = shaderVariantKey,
|
||||
CompiledHash = compiledHash,
|
||||
VariantKey = variantKey,
|
||||
PipelineOption = materialPipeline,
|
||||
|
||||
RtvFormats = _rtvFormats.AsSpan(0, _rtvCount),
|
||||
DsvFormat = _dsvFormat,
|
||||
|
||||
AsCode = asByteCode,
|
||||
MsCode = msByteCode,
|
||||
PsCode = psByteCode,
|
||||
};
|
||||
|
||||
var compiled = compiledCacheResult.Value;
|
||||
_pipelineLibrary.CreateGraphicsPipeline(in psoDes, in compiled).GetValueOrThrow();
|
||||
_pipelineLibrary.CreateGraphicsPipeline(in psoDes).GetValueOrThrow();
|
||||
}
|
||||
|
||||
_activePerMaterialData = material._cBufferCache.GpuResource;
|
||||
@@ -238,7 +264,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
_commandBuffer.DispatchMesh(threadGroupCount.x, threadGroupCount.y, threadGroupCount.z);
|
||||
}
|
||||
|
||||
public void SetActiveCompute(Handle<ComputeShader> computeShader, uint entryIndex)
|
||||
public void SetActiveCompute(Handle<ComputeShader> computeShader, int entryIndex)
|
||||
{
|
||||
var r = _resourceManager.GetComputeShaderReference(computeShader);
|
||||
if (r.IsFailure)
|
||||
@@ -247,30 +273,50 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
}
|
||||
|
||||
ref var shader = ref r.Value;
|
||||
var entryHash = shader.GetEntryHash((int)entryIndex);
|
||||
var entryHash = shader.GetEntryID(entryIndex);
|
||||
var keywordSet = new LocalKeywordSet(); // TODO: Support keywords in compute shader.
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
||||
var pipelineKey = RHIUtility.CreateComputePipelineKey(variantKey);
|
||||
|
||||
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(variantKey);
|
||||
if (error.IsFailure)
|
||||
{
|
||||
// TODO: Fallback to a default shader or show an error material.
|
||||
return;
|
||||
}
|
||||
|
||||
var pipelineKey = RHIUtility.CreateComputePipelineKey(compiledHash);
|
||||
|
||||
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||
{
|
||||
var compiledCacheResult = _shaderCompiler.GetCompiledCache(variantKey);
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex, scope.AllocationHandle);
|
||||
if (compiledCacheResult.IsFailure)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
||||
}
|
||||
var psoDes = new ComputePSODescriptor
|
||||
|
||||
var cache = compiledCacheResult.Value;
|
||||
Logger.DebugAssert(cache.compiledHash == compiledHash);
|
||||
|
||||
ShaderLibrary.ParseCacheData(cache.byteCode, out _, out var byteCodeOffsets, out var byteCodes);
|
||||
Logger.DebugAssert(byteCodeOffsets.Length == 1);
|
||||
|
||||
var psoDes = new ComputePSODesc
|
||||
{
|
||||
CompiledHash = compiledHash,
|
||||
VariantKey = variantKey,
|
||||
CsCode = byteCodes.Slice((int)byteCodeOffsets[0]),
|
||||
};
|
||||
var compiled = compiledCacheResult.Value;
|
||||
_pipelineLibrary.CreateComputePipeline(in psoDes, in compiled).GetValueOrThrow();
|
||||
|
||||
_pipelineLibrary.CreateComputePipeline(in psoDes).GetValueOrThrow();
|
||||
}
|
||||
|
||||
_commandBuffer.SetPipelineState(pipelineKey);
|
||||
}
|
||||
|
||||
public void DispatchCompute(uint3 threadGroupCount)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
}
|
||||
|
||||
public ICommandBuffer GetCommandBufferUnsafe()
|
||||
|
||||
@@ -26,8 +26,8 @@ internal sealed class RenderGraphExecutor
|
||||
|
||||
private void SetViewport(ReadOnlySpan<RenderTargetInfo> color, DepthStencilInfo depthStencil)
|
||||
{
|
||||
// This should not happened since the compiler should have rejected any render pass with an invalid render target configuration, but just in case, we use Debug.Assert to validate our assumptions.
|
||||
Debug.Assert(color.Length > 0 || depthStencil.texture.IsValid);
|
||||
// This should not happened since the compiler should have rejected any render pass with an invalid render target configuration, but just in case, we use Logger.DebugAssert to validate our assumptions.
|
||||
Logger.DebugAssert(color.Length > 0 || depthStencil.texture.IsValid);
|
||||
|
||||
ViewportDesc viewportDesc = default;
|
||||
ScissorRectDesc scissorDesc = default;
|
||||
@@ -178,7 +178,7 @@ internal sealed class RenderGraphExecutor
|
||||
else
|
||||
{
|
||||
// All the reaster pass should be merged into native render pass, so if we encounter a raster pass here, it means something went wrong during compilation.
|
||||
Debug.Assert(pass.type != RenderPassType.Raster);
|
||||
Logger.DebugAssert(pass.type != RenderPassType.Raster);
|
||||
|
||||
// Compute pass or Unsafe pass
|
||||
var e = ExecuteBarriersForPass(commandBuffer, logicalPassIndex, ref barrierIndex, compiledBarriers);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
@@ -24,107 +25,115 @@ public interface IRenderPipeline : IDisposable
|
||||
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
|
||||
}
|
||||
|
||||
public static class RenderPipelineUtility
|
||||
public readonly ref struct RenderViewData : IDisposable
|
||||
{
|
||||
public static bool GetVPMatrices(RenderSystem renderSystem, ref readonly RenderRequest request, out float4x4 view, out float4x4 projection, out uint2 screenSize)
|
||||
private readonly ref readonly RenderRequest _request;
|
||||
private readonly SwapChainManager _swapChainManager;
|
||||
|
||||
private readonly Handle<GPUTexture> _colorTexture;
|
||||
private readonly uint2 _screenSize;
|
||||
|
||||
public readonly ref readonly RenderRequest Request => ref _request;
|
||||
public readonly Handle<GPUTexture> ColorTexture => _colorTexture;
|
||||
public readonly uint2 ScreenSize => _screenSize;
|
||||
|
||||
public RenderViewData(SwapChainManager swapChainManager, IResourceDatabase resourceDatabase, ref readonly RenderRequest request)
|
||||
{
|
||||
Handle<GPUTexture> rtHandle;
|
||||
_request = ref request;
|
||||
_swapChainManager = swapChainManager;
|
||||
|
||||
if (request.swapChainIndex < 0)
|
||||
{
|
||||
rtHandle = request.colorTarget;
|
||||
_colorTexture = request.colorTarget;
|
||||
Logger.DebugAssert(_colorTexture.IsValid, "Invalid color target texture.");
|
||||
}
|
||||
else if (renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
|
||||
else if (swapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
|
||||
{
|
||||
rtHandle = swapChain.GetCurrentBackBuffer();
|
||||
_colorTexture = swapChain.GetCurrentBackBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
view = default;
|
||||
projection = default;
|
||||
screenSize = default;
|
||||
|
||||
return false;
|
||||
throw new InvalidOperationException($"Invalid swap chain index: {request.swapChainIndex}");
|
||||
}
|
||||
|
||||
try
|
||||
var (desc, error) = resourceDatabase.GetResourceDescription(_colorTexture.AsResource());
|
||||
if (error.IsFailure)
|
||||
{
|
||||
var rtResult = renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rtHandle.AsResource());
|
||||
if (rtResult.IsFailure)
|
||||
{
|
||||
view = default;
|
||||
projection = default;
|
||||
screenSize = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
screenSize = new uint2(rtResult.Value.TextureDescription.Width, rtResult.Value.TextureDescription.Height);
|
||||
var aspectScreen = (float)screenSize.x / screenSize.y;
|
||||
|
||||
view = math.inverse(request.view.localToWorld);
|
||||
|
||||
var vfov = 2.0f * math.atan(request.view.sensorSize.y / (2.0f * request.view.focalLength));
|
||||
var hfov = 2.0f * math.atan(request.view.sensorSize.x / (2.0f * request.view.focalLength));
|
||||
var aspectSensor = request.view.sensorSize.x / request.view.sensorSize.y;
|
||||
|
||||
float vfovF;
|
||||
switch (request.view.gateFit)
|
||||
{
|
||||
case GateFit.Vertical:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
|
||||
case GateFit.Horizontal:
|
||||
// Adjust VFOV so that the sensor width fits the screen width
|
||||
var horizontalAspectBuffer = math.tan(hfov * 0.5f);
|
||||
vfovF = 2.0f * math.atan(horizontalAspectBuffer / aspectScreen);
|
||||
break;
|
||||
|
||||
case GateFit.Fill:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
|
||||
case GateFit.Overscan:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
default:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
}
|
||||
|
||||
var m_11 = 1.0f / math.tan(vfovF * 0.5f);
|
||||
var m_00 = m_11 / aspectScreen;
|
||||
var m_22 = request.view.farClipPlane / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||
var m_23 = -(request.view.farClipPlane * request.view.nearClipPlane) / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||
|
||||
projection = new float4x4
|
||||
(
|
||||
m_00, 0, 0, 0,
|
||||
0, m_11, 0, 0,
|
||||
0, 0, m_22, m_23,
|
||||
0, 0, 1, 0
|
||||
);
|
||||
|
||||
return true;
|
||||
throw new InvalidOperationException($"Failed to get resource description for color target texture. Error: {error}");
|
||||
}
|
||||
finally
|
||||
|
||||
_screenSize = new uint2(desc.TextureDescriptor.Width, desc.TextureDescriptor.Height);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_request.swapChainIndex >= 0)
|
||||
{
|
||||
if (request.swapChainIndex >= 0)
|
||||
{
|
||||
renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
|
||||
}
|
||||
_swapChainManager.ReleaseSwapChain(_request.swapChainIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class RenderPipelineUtility
|
||||
{
|
||||
public static void GetVPMatrices(ref readonly RenderRequest request, uint2 screenSize, out float4x4 view, out float4x4 projection)
|
||||
{
|
||||
var aspectScreen = (float)screenSize.x / screenSize.y;
|
||||
|
||||
view = math.inverse(request.view.localToWorld);
|
||||
|
||||
var vfov = 2.0f * math.atan(request.view.sensorSize.y / (2.0f * request.view.focalLength));
|
||||
var hfov = 2.0f * math.atan(request.view.sensorSize.x / (2.0f * request.view.focalLength));
|
||||
var aspectSensor = request.view.sensorSize.x / request.view.sensorSize.y;
|
||||
|
||||
float vfovF;
|
||||
switch (request.view.gateFit)
|
||||
{
|
||||
case GateFit.Vertical:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
|
||||
case GateFit.Horizontal:
|
||||
// Adjust VFOV so that the sensor width fits the screen width
|
||||
var horizontalAspectBuffer = math.tan(hfov * 0.5f);
|
||||
vfovF = 2.0f * math.atan(horizontalAspectBuffer / aspectScreen);
|
||||
break;
|
||||
|
||||
case GateFit.Fill:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
|
||||
case GateFit.Overscan:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
default:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
}
|
||||
|
||||
var m_11 = 1.0f / math.tan(vfovF * 0.5f);
|
||||
var m_00 = m_11 / aspectScreen;
|
||||
var m_22 = request.view.farClipPlane / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||
var m_23 = -(request.view.farClipPlane * request.view.nearClipPlane) / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||
|
||||
projection = new float4x4
|
||||
(
|
||||
m_00, 0, 0, 0,
|
||||
0, m_11, 0, 0,
|
||||
0, 0, m_22, m_23,
|
||||
0, 0, 1, 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +124,8 @@ public class RenderSystem : IDisposable
|
||||
get => _renderPipelineSettings;
|
||||
set
|
||||
{
|
||||
Debug.Assert(value != null, "RenderPipelineSettings cannot be set to null.");
|
||||
Debug.Assert(!_disposed, "Cannot set RenderPipelineSettings on a disposed RenderSystem.");
|
||||
Logger.DebugAssert(value != null, "RenderPipelineSettings cannot be set to null.");
|
||||
Logger.DebugAssert(!_disposed, "Cannot set RenderPipelineSettings on a disposed RenderSystem.");
|
||||
|
||||
if (value == _renderPipelineSettings)
|
||||
{
|
||||
@@ -229,7 +229,7 @@ public class RenderSystem : IDisposable
|
||||
#if DEBUG
|
||||
Debugger.Break();
|
||||
#endif
|
||||
Logger.LogError($"Render failed: {result.Message}");
|
||||
Logger.Error($"Render failed: {result.Message}");
|
||||
}
|
||||
|
||||
var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
|
||||
@@ -295,7 +295,7 @@ public class RenderSystem : IDisposable
|
||||
{
|
||||
cmd.Begin(frameResource.CommandAllocator);
|
||||
|
||||
var ctx = new RenderContext(_resourceManager, _graphicsEngine, cmd);
|
||||
var ctx = new RenderContext(_resourceManager, _shaderLibrary, _graphicsEngine, cmd);
|
||||
|
||||
_renderPipeline.Render(ctx, frameIndex, frameResource.RenderPayload);
|
||||
_swapChainManager.TransitionToPresent(cmd);
|
||||
@@ -337,7 +337,7 @@ public class RenderSystem : IDisposable
|
||||
|
||||
internal void Start()
|
||||
{
|
||||
Debug.Assert(!_disposed, "Cannot start a disposed RenderSystem.");
|
||||
Logger.DebugAssert(!_disposed, "Cannot start a disposed RenderSystem.");
|
||||
|
||||
if (_isRunning)
|
||||
{
|
||||
@@ -350,7 +350,7 @@ public class RenderSystem : IDisposable
|
||||
|
||||
internal void Stop()
|
||||
{
|
||||
Debug.Assert(!_disposed, "Cannot stop a disposed RenderSystem.");
|
||||
Logger.DebugAssert(!_disposed, "Cannot stop a disposed RenderSystem.");
|
||||
|
||||
if (!_isRunning)
|
||||
{
|
||||
@@ -364,7 +364,7 @@ public class RenderSystem : IDisposable
|
||||
|
||||
internal void SignalCPUReady()
|
||||
{
|
||||
Debug.Assert(!_disposed, "Cannot signal CPU ready on a disposed RenderSystem.");
|
||||
Logger.DebugAssert(!_disposed, "Cannot signal CPU ready on a disposed RenderSystem.");
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
ref var frameResource = ref _frameResources[eventIndex];
|
||||
@@ -375,13 +375,13 @@ public class RenderSystem : IDisposable
|
||||
|
||||
internal void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize)
|
||||
{
|
||||
Debug.Assert(!_disposed, "Cannot request swap chain resize on a disposed RenderSystem.");
|
||||
Logger.DebugAssert(!_disposed, "Cannot request swap chain resize on a disposed RenderSystem.");
|
||||
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
|
||||
}
|
||||
|
||||
internal bool TryAcquireCPUFrame()
|
||||
{
|
||||
Debug.Assert(!_disposed, "Cannot acquire CPU frame on a disposed RenderSystem.");
|
||||
Logger.DebugAssert(!_disposed, "Cannot acquire CPU frame on a disposed RenderSystem.");
|
||||
|
||||
var requiredGpuFence = _cpuFenceValue < _config.FrameBufferCount ? 0 : _cpuFenceValue - _config.FrameBufferCount + 1;
|
||||
|
||||
@@ -398,7 +398,7 @@ public class RenderSystem : IDisposable
|
||||
|
||||
public bool WaitForGPUReady(int timeOut = -1)
|
||||
{
|
||||
Debug.Assert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
||||
Logger.DebugAssert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
||||
|
||||
var submittedFenceValue = Volatile.Read(ref _submittedFenceValue);
|
||||
if (submittedFenceValue == 0)
|
||||
@@ -412,7 +412,7 @@ public class RenderSystem : IDisposable
|
||||
|
||||
public void WaitIdle()
|
||||
{
|
||||
Debug.Assert(!_disposed, "Cannot wait idle on a disposed RenderSystem.");
|
||||
Logger.DebugAssert(!_disposed, "Cannot wait idle on a disposed RenderSystem.");
|
||||
foreach (var frameResource in _frameResources)
|
||||
{
|
||||
if (frameResource.FenceValue > 0)
|
||||
@@ -424,7 +424,7 @@ public class RenderSystem : IDisposable
|
||||
|
||||
public IRenderPayload GetCurrentFramePayload()
|
||||
{
|
||||
Debug.Assert(!_disposed, "Cannot get current frame payload from a disposed RenderSystem.");
|
||||
Logger.DebugAssert(!_disposed, "Cannot get current frame payload from a disposed RenderSystem.");
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
ref var frameResource = ref _frameResources[eventIndex];
|
||||
|
||||
@@ -27,11 +27,11 @@ public partial class ResourceManager
|
||||
public ulong retireFrame;
|
||||
}
|
||||
|
||||
private UnsafeList<Page> _activePages = new UnsafeList<Page>(4, Allocator.Persistent);
|
||||
private UnsafeQueue<Page> _freePages = new UnsafeQueue<Page>(4, Allocator.Persistent);
|
||||
private UnsafeQueue<RetiringPage> _retiringPages = new UnsafeQueue<RetiringPage>(4, Allocator.Persistent);
|
||||
private UnsafeList<Page> _activePages = new UnsafeList<Page>(4, AllocationHandle.Persistent);
|
||||
private UnsafeQueue<Page> _freePages = new UnsafeQueue<Page>(4, AllocationHandle.Persistent);
|
||||
private UnsafeQueue<RetiringPage> _retiringPages = new UnsafeQueue<RetiringPage>(4, AllocationHandle.Persistent);
|
||||
|
||||
private UnsafeList<Handle<GPUResource>> _frameTransientResources = new UnsafeList<Handle<GPUResource>>(4, Allocator.Persistent);
|
||||
private UnsafeList<Handle<GPUResource>> _frameTransientResources = new UnsafeList<Handle<GPUResource>>(4, AllocationHandle.Persistent);
|
||||
|
||||
private static bool IsHeapFlagsCompatible(HeapFlags pageHeapFlags, HeapFlags requiredHeapFlags)
|
||||
{
|
||||
|
||||
@@ -44,10 +44,10 @@ public sealed partial class ResourceManager : IDisposable
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent);
|
||||
_materials = new UnsafeSlotMap<Material>(64, Allocator.Persistent);
|
||||
_shaders = new UnsafeSlotMap<Shader>(16, Allocator.Persistent);
|
||||
_computeShaders = new UnsafeSlotMap<ComputeShader>(16, Allocator.Persistent);
|
||||
_meshes = new UnsafeSlotMap<Mesh>(64, AllocationHandle.Persistent);
|
||||
_materials = new UnsafeSlotMap<Material>(64, AllocationHandle.Persistent);
|
||||
_shaders = new UnsafeSlotMap<Shader>(16, AllocationHandle.Persistent);
|
||||
_computeShaders = new UnsafeSlotMap<ComputeShader>(16, AllocationHandle.Persistent);
|
||||
|
||||
_materialPalettes = new MaterialPaletteStore();
|
||||
}
|
||||
@@ -59,25 +59,25 @@ public sealed partial class ResourceManager : IDisposable
|
||||
|
||||
internal void BeginFrame(ulong submittedFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
_submittedFrame = submittedFrame;
|
||||
}
|
||||
|
||||
internal void EndFrame(ulong completedFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
EndFramePool(completedFrame);
|
||||
}
|
||||
|
||||
public void EnterParallelRead()
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
Volatile.Write(ref _writeLock, 1);
|
||||
}
|
||||
|
||||
public void ExitParallelRead()
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
Volatile.Write(ref _writeLock, 0);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
|
||||
public unsafe Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
var spinner = new SpinWait();
|
||||
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
||||
@@ -152,7 +152,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <returns>An <see cref="Handle{Material}"/> representing the newly created material.</returns>
|
||||
public Handle<Material> CreateMaterial(Handle<Shader> shader)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
var spinner = new SpinWait();
|
||||
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
||||
@@ -184,7 +184,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
|
||||
public Handle<Shader> CreateGraphicsShader(GraphicsShaderDescriptor descriptor)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
var spinner = new SpinWait();
|
||||
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
||||
@@ -207,7 +207,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
|
||||
public Handle<ComputeShader> CreateComputeShader(ComputeShaderDescriptor descriptor)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
var spinner = new SpinWait();
|
||||
while (Interlocked.CompareExchange(ref _writeLock, 1, 0) != 0)
|
||||
{
|
||||
@@ -233,7 +233,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
|
||||
public bool HasMesh(Handle<Mesh> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _meshes.Contains(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <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)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
@@ -278,7 +278,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
|
||||
public bool HasMaterial(Handle<Material> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _materials.Contains(handle.ID, handle.Generation);
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
|
||||
public void ReleaseMaterial(Handle<Material> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
@@ -323,7 +323,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <returns>The palette index. Index 0 represents an empty palette.</returns>
|
||||
public int GetOrCreateMaterialPalette(ReadOnlySpan<Handle<Material>> materials)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
foreach (var material in materials)
|
||||
{
|
||||
@@ -342,7 +342,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <param name="paletteID">The palette index to validate.</param>
|
||||
public bool HasMaterialPalette(Identifier<MaterialPalette> paletteID)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _materialPalettes.IsValid(paletteID);
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <param name="paletteID">The palette index to query.</param>
|
||||
public MaterialPalette GetMaterialPaletteInfo(Identifier<MaterialPalette> paletteID)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _materialPalettes.GetInfo(paletteID);
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <param name="localMaterialIndex">The material slot inside the palette.</param>
|
||||
public Handle<Material> GetMaterialPaletteMaterial(Identifier<MaterialPalette> paletteID, int localMaterialIndex)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _materialPalettes.GetMaterial(paletteID, localMaterialIndex);
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <param name="paletteID">The palette index to release.</param>
|
||||
public void ReleaseMaterialPalette(Identifier<MaterialPalette> paletteID)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
_materialPalettes.Release(paletteID);
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
|
||||
public bool HasShader(Handle<Shader> id)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _shaders.Contains(id.ID, id.Generation);
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <param name="handle">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
|
||||
public void ReleaseShader(Handle<Shader> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
ref var shader = ref _shaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
@@ -429,7 +429,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <returns>true if a compute shader with the specified identifier exists; otherwise, false.</returns>
|
||||
public bool HasComputeShader(Handle<ComputeShader> id)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
return _computeShaders.Contains(id.ID, id.Generation);
|
||||
}
|
||||
|
||||
@@ -455,7 +455,7 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// <param name="handle">The identifier of the compute shader to release. Must refer to a valid, previously created ComputeShader.</param>
|
||||
public void ReleaseComputeShader(Handle<ComputeShader> handle)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
Logger.DebugAssert(!_disposed);
|
||||
|
||||
ref var computeShader = ref _computeShaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
|
||||
if (!exist)
|
||||
|
||||
@@ -1,52 +1,82 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.IO.Hashing;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Graphics.Services;
|
||||
|
||||
internal class ShaderLibrary : IDisposable
|
||||
internal unsafe struct ShaderByteCode
|
||||
{
|
||||
public byte* pCode;
|
||||
public ulong size;
|
||||
}
|
||||
|
||||
internal struct ShaderCache : IDisposable
|
||||
{
|
||||
public MemoryBlock byteCode;
|
||||
public ulong compiledHash;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
byteCode.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe class ShaderLibrary : IDisposable
|
||||
{
|
||||
public struct CacheHeader
|
||||
{
|
||||
public ulong id;
|
||||
public int index;
|
||||
public int byteCodeOffsetCount;
|
||||
}
|
||||
|
||||
private struct CacheEntry: IDisposable
|
||||
{
|
||||
public UnsafeArray<UnsafeArray<byte>> byteCode;
|
||||
public UnsafeArray<ShaderCache> cache;
|
||||
|
||||
public void Insert(int index, ReadOnlySpan<byte> data)
|
||||
public void UpdateCache(int index, ref MemoryBlock data, ulong hash)
|
||||
{
|
||||
if (index >= byteCode.Length)
|
||||
if (index >= cache.Length)
|
||||
{
|
||||
var newByteCode = new UnsafeArray<UnsafeArray<byte>>(index + 1, Allocator.Persistent);
|
||||
for (int i = 0; i < byteCode.Length; i++)
|
||||
var newByteCode = new UnsafeArray<ShaderCache>(index + 1, AllocationHandle.Persistent);
|
||||
for (int i = 0; i < cache.Length; i++)
|
||||
{
|
||||
newByteCode[i] = byteCode[i];
|
||||
newByteCode[i] = cache[i];
|
||||
}
|
||||
|
||||
byteCode.Dispose();
|
||||
byteCode = newByteCode;
|
||||
cache.Dispose();
|
||||
cache = newByteCode;
|
||||
}
|
||||
|
||||
var byteData = new UnsafeArray<byte>(data.Length, Allocator.Persistent);
|
||||
byteData.CopyFrom(data);
|
||||
byteCode[index] = byteData;
|
||||
cache[index].byteCode = data;
|
||||
cache[index].compiledHash = hash;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
for (int i = 0; i < byteCode.Length; i++)
|
||||
for (int i = 0; i < cache.Length; i++)
|
||||
{
|
||||
byteCode[i].Dispose();
|
||||
cache[i].Dispose();
|
||||
}
|
||||
|
||||
cache.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private UnsafeHashMap<ulong, CacheEntry> _inMemoryCache;
|
||||
private UnsafeHashMap<ulong, ulong> _variantToCompiledHash;
|
||||
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly IShaderCompilationBridge? _shaderCompilationBridge;
|
||||
|
||||
internal ShaderLibrary(IShaderCompilationBridge? shaderCompilationBridge, string cacheDirectory)
|
||||
{
|
||||
_inMemoryCache = new UnsafeHashMap<ulong, CacheEntry>(16, Allocator.Persistent);
|
||||
_inMemoryCache = new UnsafeHashMap<ulong, CacheEntry>(16, AllocationHandle.Persistent);
|
||||
_variantToCompiledHash = new UnsafeHashMap<ulong, ulong>(16, AllocationHandle.Persistent);
|
||||
|
||||
_cacheDirectory = cacheDirectory;
|
||||
_shaderCompilationBridge = shaderCompilationBridge;
|
||||
@@ -66,33 +96,91 @@ internal class ShaderLibrary : IDisposable
|
||||
return Path.Combine(folderPath, $"shader_cache_{hashString}.bin");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void CacheCompiledResult(ulong id, int index, ReadOnlySpan<byte> byteCode)
|
||||
public static void ParseCacheData(MemoryBlock data, out CacheHeader header, out ReadOnlySpan<ulong> offsets, out ReadOnlySpan<byte> byteCodes)
|
||||
{
|
||||
var data = new UnsafeArray<byte>(byteCode.Length, Allocator.Persistent);
|
||||
data.CopyFrom(byteCode);
|
||||
Logger.DebugAssert(data.IsCreated);
|
||||
|
||||
ref var entry = ref _inMemoryCache.GetValueRefOrAddDefault(id, out var exists);
|
||||
entry.Insert(index, byteCode);
|
||||
var reader = new SpanReader(data.AsSpan<byte>());
|
||||
header = reader.Read<CacheHeader>();
|
||||
offsets = reader.ReadSpan<ulong>(header.byteCodeOffsetCount);
|
||||
byteCodes = reader.ReadToEnd<byte>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Result<UnsafeArray<byte>, Error> GetCache(ulong id, int index, AllocationHandle allocationHandle)
|
||||
public void CacheCompiledResult(ulong id, int index, Key64<ShaderVariant> variantKey, ReadOnlySpan<ShaderByteCode> byteCodes)
|
||||
{
|
||||
var header = new CacheHeader
|
||||
{
|
||||
id = id,
|
||||
index = index,
|
||||
byteCodeOffsetCount = byteCodes.Length,
|
||||
};
|
||||
|
||||
var offsets = stackalloc ulong[byteCodes.Length];
|
||||
var offset = (ulong)(sizeof(CacheHeader) + (sizeof(ulong) * byteCodes.Length));
|
||||
for (var i = 0; i < byteCodes.Length; i++)
|
||||
{
|
||||
offsets[i] = offset;
|
||||
offset += byteCodes[i].size;
|
||||
}
|
||||
|
||||
var data = new MemoryBlock((nuint)offset, 8, AllocationHandle.Persistent);
|
||||
var writer = new SpanWriter(data.AsSpan<byte>());
|
||||
|
||||
writer.Write(header);
|
||||
|
||||
for (var i = 0; i < byteCodes.Length; i++)
|
||||
{
|
||||
writer.Write(offsets[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < byteCodes.Length; i++)
|
||||
{
|
||||
var byteCode = byteCodes[i];
|
||||
var src = new ReadOnlySpan<byte>(byteCode.pCode, (int)byteCode.size);
|
||||
writer.WriteSpan(src);
|
||||
}
|
||||
|
||||
var codeHash = XxHash64.HashToUInt64(data.AsSpan<byte>());
|
||||
_variantToCompiledHash[variantKey] = codeHash;
|
||||
|
||||
ref var entry = ref _inMemoryCache.GetValueRefOrAddDefault(id, out var exists);
|
||||
entry.UpdateCache(index, ref data, codeHash);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Result<ShaderCache, Error> GetCompiledCache(ulong id, int index, AllocationHandle allocationHandle)
|
||||
{
|
||||
if (_inMemoryCache.TryGetValue(id, out var entry))
|
||||
{
|
||||
if (index < entry.byteCode.Length)
|
||||
if (index < entry.cache.Length)
|
||||
{
|
||||
var byteCode = entry.byteCode[index];
|
||||
var result = new UnsafeArray<byte>(byteCode.Length, allocationHandle);
|
||||
result.CopyFrom(byteCode);
|
||||
return result;
|
||||
var shaderCache = entry.cache[index];
|
||||
var result = new MemoryBlock(shaderCache.byteCode.Size, shaderCache.byteCode.Alignment, allocationHandle);
|
||||
|
||||
result.CopyFrom(shaderCache.byteCode.AsSpan<byte>());
|
||||
return new ShaderCache
|
||||
{
|
||||
byteCode = result,
|
||||
compiledHash = shaderCache.compiledHash,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Result<ulong, Error> GetCompiledHash(Key64<ShaderVariant> variantKey)
|
||||
{
|
||||
if (_variantToCompiledHash.TryGetValue(variantKey, out var compiledHash))
|
||||
{
|
||||
return compiledHash;
|
||||
}
|
||||
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var kvp in _inMemoryCache)
|
||||
|
||||
@@ -32,19 +32,19 @@ internal sealed class SwapChainRecord
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReleaseRef()
|
||||
public int ReleaseRef()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var current = Volatile.Read(ref _refCount);
|
||||
if (current == 0)
|
||||
{
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref _refCount, current - 1, current) == current)
|
||||
{
|
||||
return (current - 1) == 0;
|
||||
return current - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,13 +109,13 @@ public class SwapChainManager : IDisposable
|
||||
{
|
||||
var record = Volatile.Read(ref _swapChains[index]);
|
||||
|
||||
if (record != null && record.ReleaseRef())
|
||||
if (record != null && record.ReleaseRef() == 0)
|
||||
{
|
||||
record.SwapChain.Dispose();
|
||||
Interlocked.CompareExchange(ref _swapChains[index], null, record);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void TransitionToPresent(ICommandBuffer commandBuffer)
|
||||
{
|
||||
for (int i = 0; i < MAX_SWAP_CHAINS; i++)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
// TODO: This file should be moved to editor project since there is no reason we need to build meshlets and LOD at runtime.
|
||||
|
||||
using Ghost.Core;
|
||||
using Ghost.MeshOptimizer;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
@@ -178,7 +179,7 @@ public static unsafe class MeshletUtility
|
||||
|
||||
private static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
|
||||
{
|
||||
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, Allocator.FreeList);
|
||||
using var boundsList = new UnsafeArray<ClodBounds>(group.Count, AllocationHandle.FreeList);
|
||||
for (var j = 0; j < group.Count; j++)
|
||||
{
|
||||
boundsList[j] = (clusters[group[j]].bounds);
|
||||
@@ -210,9 +211,9 @@ public static unsafe class MeshletUtility
|
||||
{
|
||||
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, AllocationHandle.FreeList);
|
||||
using var meshletVertices = new UnsafeArray<uint>((int)indexCount, AllocationHandle.FreeList);
|
||||
using var meshletTriangles = new UnsafeArray<byte>((int)indexCount, AllocationHandle.FreeList);
|
||||
|
||||
var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr();
|
||||
var pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr();
|
||||
@@ -240,7 +241,7 @@ public static unsafe class MeshletUtility
|
||||
);
|
||||
}
|
||||
|
||||
var clusters = new UnsafeList<Cluster>((int)meshletCount, Allocator.FreeList);
|
||||
var clusters = new UnsafeList<Cluster>((int)meshletCount, AllocationHandle.FreeList);
|
||||
|
||||
for (nuint i = 0; i < meshletCount; i++)
|
||||
{
|
||||
@@ -259,9 +260,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), AllocationHandle.FreeList),
|
||||
uniqueVertices = new UnsafeList<uint>((int)meshlet.vertex_count, AllocationHandle.FreeList),
|
||||
localIndices = new UnsafeList<byte>((int)(meshlet.triangle_count * 3), AllocationHandle.FreeList),
|
||||
group = -1,
|
||||
refined = -1
|
||||
};
|
||||
@@ -332,8 +333,8 @@ public static unsafe class MeshletUtility
|
||||
{
|
||||
if (pending.Count <= (int)config.partitionSize)
|
||||
{
|
||||
var single = new UnsafeList<UnsafeList<int>>(1, Allocator.FreeList);
|
||||
var pendingcpy = new UnsafeList<int>(pending.Count, Allocator.FreeList);
|
||||
var single = new UnsafeList<UnsafeList<int>>(1, AllocationHandle.FreeList);
|
||||
var pendingcpy = new UnsafeList<int>(pending.Count, AllocationHandle.FreeList);
|
||||
|
||||
pendingcpy.AddRange(pending.AsSpan());
|
||||
single.Add(pendingcpy);
|
||||
@@ -347,8 +348,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, AllocationHandle.FreeList);
|
||||
using var clusterCounts = new UnsafeList<uint>(pending.Count, AllocationHandle.FreeList);
|
||||
|
||||
nuint offset = 0;
|
||||
for (var i = 0; i < pending.Count; i++)
|
||||
@@ -363,7 +364,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, AllocationHandle.FreeList);
|
||||
|
||||
var partitionCount = MeshOptApi.PartitionClusters(
|
||||
(uint*)clusterPart.GetUnsafePtr(),
|
||||
@@ -377,10 +378,10 @@ public static unsafe class MeshletUtility
|
||||
config.partitionSize
|
||||
);
|
||||
|
||||
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, Allocator.FreeList);
|
||||
var partitions = new UnsafeList<UnsafeList<int>>((int)partitionCount, AllocationHandle.FreeList);
|
||||
for (nuint i = 0; i < partitionCount; i++)
|
||||
{
|
||||
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), Allocator.FreeList));
|
||||
partitions.Add(new UnsafeList<int>((int)(config.partitionSize + config.partitionSize / 3), AllocationHandle.FreeList));
|
||||
}
|
||||
|
||||
for (var i = 0; i < pending.Count; i++)
|
||||
@@ -393,7 +394,7 @@ public static unsafe class MeshletUtility
|
||||
|
||||
private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback)
|
||||
{
|
||||
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, Allocator.FreeList);
|
||||
using var groupClusters = new UnsafeList<ClodCluster>(group.Count, AllocationHandle.FreeList);
|
||||
|
||||
for (var i = 0; i < group.Count; i++)
|
||||
{
|
||||
@@ -429,8 +430,8 @@ public static unsafe class MeshletUtility
|
||||
|
||||
private static void SimplifyFallback(ref UnsafeArray<uint> lod, ref readonly ClodMesh mesh, ReadOnlyUnsafeCollection<uint> indices, ReadOnlyUnsafeCollection<byte> locks, nuint target_count, float* error)
|
||||
{
|
||||
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, Allocator.FreeList);
|
||||
using var subset_locks = new UnsafeArray<byte>(indices.Count, Allocator.FreeList);
|
||||
using var subset = new UnsafeArray<SloppyVertex>(indices.Count, AllocationHandle.FreeList);
|
||||
using var subset_locks = new UnsafeArray<byte>(indices.Count, AllocationHandle.FreeList);
|
||||
|
||||
lod.Resize(indices.Count);
|
||||
|
||||
@@ -440,7 +441,7 @@ public static unsafe class MeshletUtility
|
||||
for (var i = 0; i < indices.Count; ++i)
|
||||
{
|
||||
var v = indices[i];
|
||||
Debug.Assert(v < mesh.vertexCount);
|
||||
Logger.DebugAssert(v < mesh.vertexCount);
|
||||
|
||||
subset[i].x = mesh.vertexPositions[v * positions_stride + 0];
|
||||
subset[i].y = mesh.vertexPositions[v * positions_stride + 1];
|
||||
@@ -466,7 +467,7 @@ 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)
|
||||
{
|
||||
var lod = new UnsafeArray<uint>(indices.Count, Allocator.FreeList);
|
||||
var lod = new UnsafeArray<uint>(indices.Count, AllocationHandle.FreeList);
|
||||
|
||||
if (targetCount >= (nuint)indices.Count)
|
||||
{
|
||||
@@ -577,10 +578,10 @@ public static unsafe class MeshletUtility
|
||||
/// <returns>The total count of generated clusters.</returns>
|
||||
public static nuint Build(ref readonly ClodConfig config, ref readonly ClodMesh mesh, void* outputContext, ClodOutputDelegate? outputCallback)
|
||||
{
|
||||
Debug.Assert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
|
||||
Logger.DebugAssert(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 locks = new UnsafeArray<byte>((int)mesh.vertexCount, AllocationHandle.FreeList, AllocationOption.Clear); ;
|
||||
using var remap = new UnsafeArray<uint>((int)mesh.vertexCount, AllocationHandle.FreeList);
|
||||
|
||||
MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
|
||||
|
||||
@@ -610,7 +611,7 @@ public static unsafe class MeshletUtility
|
||||
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, AllocationHandle.FreeList);
|
||||
for (var i = 0; i < clusters.Count; i++)
|
||||
{
|
||||
pending.Add(i);
|
||||
@@ -627,7 +628,7 @@ public static unsafe class MeshletUtility
|
||||
|
||||
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, AllocationHandle.FreeList);
|
||||
for (var j = 0; j < groups[i].Count; j++)
|
||||
{
|
||||
var clusterIndices = clusters[groups[i][j]].indices;
|
||||
@@ -690,4 +691,4 @@ public static unsafe class MeshletUtility
|
||||
|
||||
return finalClusterCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Diagnostics;
|
||||
using Ghost.Graphics.Services;
|
||||
|
||||
namespace Ghost.Graphics.Utilities;
|
||||
@@ -17,10 +16,10 @@ public static unsafe class RenderingUtility
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(r.Value.Type == ResourceType.Buffer);
|
||||
Logger.DebugAssert(r.Value.Type == ResourceType.Buffer);
|
||||
|
||||
var sizeInBytes = (nuint)(data.Length * sizeof(T));
|
||||
var memoryType = r.Value.BufferDescription.HeapType;
|
||||
var memoryType = r.Value.BufferDescriptor.HeapType;
|
||||
|
||||
if (memoryType == HeapType.Upload)
|
||||
{
|
||||
@@ -61,7 +60,7 @@ public static unsafe class RenderingUtility
|
||||
where T : unmanaged
|
||||
{
|
||||
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.TextureDescriptor.Format.GetSurfaceInfo(desc.TextureDescriptor.Width, desc.TextureDescriptor.Height, out var rowPitch, out var slicePitch, out _);
|
||||
|
||||
var requiredSize = resourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
|
||||
var uploadDesc = new BufferDesc
|
||||
|
||||
@@ -139,7 +139,7 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
|
||||
continue;
|
||||
}
|
||||
|
||||
var rtSize = new uint2(rtResult.Value.TextureDescription.Width, rtResult.Value.TextureDescription.Height);
|
||||
var rtSize = new uint2(rtResult.Value.TextureDescriptor.Width, rtResult.Value.TextureDescriptor.Height);
|
||||
var aspectScreen = (float)rtSize.x / rtSize.y;
|
||||
|
||||
|
||||
|
||||
@@ -52,9 +52,9 @@ public class RenderExtractionSystem : ISystem
|
||||
ref readonly var camLtwRef = ref camLtw.Get();
|
||||
|
||||
// TODO: Classify transparent objects into a separate render list and render via oit.
|
||||
var renderList = new RenderList(1, 64, Allocator.FreeList);
|
||||
var transparentRenderList = new RenderList(1, 64, Allocator.FreeList);
|
||||
var shadowCasterRenderList = new RenderList(1, 64, Allocator.FreeList);
|
||||
var renderList = new RenderList(1, 64, AllocationHandle.FreeList);
|
||||
var transparentRenderList = new RenderList(1, 64, AllocationHandle.FreeList);
|
||||
var shadowCasterRenderList = new RenderList(1, 64, AllocationHandle.FreeList);
|
||||
|
||||
// TODO: This chould be done in earallel jobs.
|
||||
foreach (var chunk in meshQuery.GetChunkIterator())
|
||||
|
||||
@@ -36,11 +36,11 @@ public partial class UnitTestApp : Application
|
||||
|
||||
UnhandledException += (sender, e) =>
|
||||
{
|
||||
Logger.LogError(e.Exception);
|
||||
Logger.Error(e.Exception);
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debugger.Break();
|
||||
#endif
|
||||
Environment.FailFast("Unhandled exception", e.Exception);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ internal static class MeshUtility
|
||||
space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY,
|
||||
};
|
||||
|
||||
using var str = new UnsafeArray<byte>(Encoding.UTF8.GetByteCount(filePath) + 1, Allocator.FreeList);
|
||||
using var str = new UnsafeArray<byte>(Encoding.UTF8.GetByteCount(filePath) + 1, AllocationHandle.FreeList);
|
||||
var count = Encoding.UTF8.GetBytes(filePath, str.AsSpan());
|
||||
str[count] = 0;
|
||||
|
||||
@@ -60,7 +60,7 @@ internal static class MeshUtility
|
||||
return Result.Failure(error.description.ToString());
|
||||
}
|
||||
|
||||
using var flatVertices = new UnsafeList<Vertex>(1024, Allocator.FreeList);
|
||||
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
|
||||
|
||||
var needComputeNormals = false;
|
||||
|
||||
@@ -83,7 +83,7 @@ internal static class MeshUtility
|
||||
|
||||
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
|
||||
|
||||
using var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, Allocator.FreeList);
|
||||
using var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, AllocationHandle.FreeList);
|
||||
|
||||
for (var j = 0u; j < pMesh->num_faces; j++)
|
||||
{
|
||||
@@ -142,8 +142,8 @@ internal static class MeshUtility
|
||||
|
||||
var numIndices = (uint)flatVertices.Count;
|
||||
|
||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, Allocator.FreeList);
|
||||
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, Allocator.FreeList);
|
||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
||||
|
||||
var stream = new ufbx_vertex_stream
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user