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:
2026-04-13 23:07:52 +09:00
parent c66fda5332
commit 817b32b8d9
69 changed files with 1436 additions and 2095 deletions

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -48,7 +48,6 @@ public struct PassDescriptor
{
public GraphicsShaderDescriptor shader;
public ulong identifier;
public string name;
public ShaderCode amplificationShaderCode;

View File

@@ -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
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}