Refactor asset handler system and catalog for safety
- Introduced AssetHandlerInfo struct for handler registration and lookup, enabling handler caching and decoupling instantiation from extension/type. - Changed CustomAssetHandlerAttribute to use required named properties; updated source generator. - Replaced HandlerTypeId with AssetTypeId throughout metadata, catalog, and sub-asset records for clarity. - Refactored asset catalog to use connection pooling and local command creation for thread safety. - Updated asset handler interfaces and implementations to align with new registration system and removed redundant properties. - Migrated mesh import and meshlet building to async JobScheduler jobs; switched to TLSF allocator and improved safety checks. - Made meshlet/LOD hierarchy building async and job-based with better memory management. - Updated usages and tests for new APIs; refreshed project references and package versions. - Improved documentation and code comments for clarity.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_MIMALLOC;MHP_FASTMATH</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_MIMALLOC;MHP_FASTMATH</DefineConstants>
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
@@ -22,12 +22,12 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.9" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.6" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.20">
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.24">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.7" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.8" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.7" />
|
||||
<PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
|
||||
|
||||
25
src/Runtime/Ghost.Core/Graphics/ShaderPropertyRegistry.cs
Normal file
25
src/Runtime/Ghost.Core/Graphics/ShaderPropertyRegistry.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Ghost.Core.Graphics;
|
||||
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
public struct ShaderPropertyInfo
|
||||
{
|
||||
public string shaderName;
|
||||
public string code;
|
||||
public uint size;
|
||||
}
|
||||
|
||||
public static class ShaderPropertiesRegistry
|
||||
{
|
||||
private static readonly Dictionary<string, ShaderPropertyInfo> s_nameToCode = new Dictionary<string, ShaderPropertyInfo>(StringComparer.Ordinal);
|
||||
|
||||
public static void Register(string name, string code, uint size)
|
||||
{
|
||||
s_nameToCode[name] = new ShaderPropertyInfo { shaderName = name, code = code, size = size };
|
||||
}
|
||||
|
||||
public static bool TryGetInfo(string name, out ShaderPropertyInfo info)
|
||||
{
|
||||
return s_nameToCode.TryGetValue(name, out info);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,9 +1,20 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Core;
|
||||
|
||||
public static class TempJobAllocatorHandle
|
||||
{
|
||||
extension(AllocationHandle)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the allocation handle for the TempJobAllocator, which is designed for temporary allocations within jobs. This allocator provides a simple interface for allocating and freeing memory that is automatically reset after a certain number of frames, making it ideal for use in job systems where temporary data is needed.
|
||||
/// </summary>
|
||||
public static AllocationHandle TempJob => TempJobAllocator.AllocationHandle;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial struct TempJobAllocator
|
||||
{
|
||||
private static TempJobAllocator* _pAllocator;
|
||||
@@ -13,7 +24,8 @@ public unsafe partial struct TempJobAllocator
|
||||
internal static void Initialize(nuint capacity)
|
||||
{
|
||||
Logger.DebugAssert(_pAllocator == null, "TempJobAllocator is already initialized.");
|
||||
_pAllocator = (TempJobAllocator*)Malloc((nuint)sizeof(TempJobAllocator));
|
||||
_pAllocator = (TempJobAllocator*)NativeMemory.Alloc((nuint)sizeof(TempJobAllocator));
|
||||
*_pAllocator = new TempJobAllocator(_pAllocator, capacity);
|
||||
}
|
||||
|
||||
internal static void Dispose()
|
||||
@@ -28,8 +40,8 @@ public unsafe partial struct TempJobAllocator
|
||||
_pAllocator->_pArena[i].Dispose();
|
||||
}
|
||||
|
||||
MemoryUtility.Free(_pAllocator->_pArena);
|
||||
MemoryUtility.Free(_pAllocator);
|
||||
NativeMemory.Free(_pAllocator->_pArena);
|
||||
NativeMemory.Free(_pAllocator);
|
||||
|
||||
_pAllocator = null;
|
||||
}
|
||||
@@ -38,40 +50,33 @@ public unsafe partial struct TempJobAllocator
|
||||
public unsafe partial struct TempJobAllocator : IAllocator
|
||||
{
|
||||
private const int _FRAME_LATENCY = 4;
|
||||
private const int _MAGIC_ID = -559038737;
|
||||
|
||||
private VirtualArena* _pArena;
|
||||
private int _currentFrameCount;
|
||||
private int _currentFrameIndex;
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private fixed int _allocationsPerFrame[_FRAME_LATENCY];
|
||||
#endif
|
||||
|
||||
private MemoryHandle _memoryHandle;
|
||||
private AllocationHandle _handle;
|
||||
private readonly AllocationHandle _handle;
|
||||
|
||||
public readonly AllocationHandle Handle => _handle;
|
||||
|
||||
internal TempJobAllocator(void* pSelf, nuint capacity)
|
||||
{
|
||||
var memoryHandle = default(MemoryHandle);
|
||||
|
||||
_pArena = (VirtualArena*)Malloc((nuint)(sizeof(VirtualArena) * _FRAME_LATENCY));
|
||||
_pArena = (VirtualArena*)NativeMemory.Alloc((nuint)(sizeof(VirtualArena) * _FRAME_LATENCY));
|
||||
_currentFrameCount = 0;
|
||||
_currentFrameIndex = 0;
|
||||
_memoryHandle = memoryHandle;
|
||||
|
||||
for (var i = 0; i < _FRAME_LATENCY; i++)
|
||||
{
|
||||
_pArena[i] = new VirtualArena(capacity);
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_allocationsPerFrame[i] = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
_handle = new AllocationHandle
|
||||
{
|
||||
State = Unsafe.AsPointer(ref this),
|
||||
Alloc = &Allocate,
|
||||
Realloc = &Reallocate,
|
||||
Free = &Free
|
||||
};
|
||||
_handle = new AllocationHandle(pSelf, &Allocate, &Reallocate, &Free);
|
||||
}
|
||||
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
@@ -84,7 +89,9 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
||||
return null;
|
||||
}
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
Interlocked.Increment(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
||||
#endif
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -103,7 +110,7 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
||||
return null;
|
||||
}
|
||||
|
||||
MemCpy(ptr, newPtr, Math.Min(oldSize, newSize));
|
||||
MemoryUtility.MemCpy(ptr, newPtr, Math.Min(oldSize, newSize));
|
||||
|
||||
return newPtr;
|
||||
}
|
||||
@@ -111,7 +118,9 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
||||
private static void Free(void* instance, void* ptr)
|
||||
{
|
||||
var pSelf = (TempJobAllocator*)instance;
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
Interlocked.Decrement(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
|
||||
#endif
|
||||
}
|
||||
|
||||
public int AdvanceFrame()
|
||||
@@ -123,6 +132,13 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
||||
|
||||
(_pArena + _currentFrameIndex)->Reset();
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
if (_allocationsPerFrame[_currentFrameIndex] != 0)
|
||||
{
|
||||
Logger.Error($"TempJobAllocator: Detected {_allocationsPerFrame[_currentFrameIndex]} leaked allocations from frame {_currentFrameCount - _FRAME_LATENCY}.");
|
||||
}
|
||||
#endif
|
||||
|
||||
return allocations;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -22,6 +23,29 @@ internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
|
||||
context.RegisterSourceOutput(handerCandidates, GenerateRegistrationCode);
|
||||
}
|
||||
|
||||
private static T GetValueOrDefault<T>(IDictionary<string, TypedConstant> dictionary, string key, T defaultValue = default)
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value.Value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static ImmutableArray<TypedConstant> GetValuesOrDefault(IDictionary<string, TypedConstant> dictionary, string key)
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out var value))
|
||||
{
|
||||
return value.Values;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private void GenerateRegistrationCode(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> array)
|
||||
{
|
||||
if (array.IsDefaultOrEmpty)
|
||||
@@ -39,12 +63,24 @@ internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
|
||||
continue;
|
||||
}
|
||||
|
||||
var id = attribute.ConstructorArguments[0].Value as string;
|
||||
var extensionsTypesConstants = attribute.ConstructorArguments[1].Values;
|
||||
var extensions = $"new string[] {{ {string.Join(", ", extensionsTypesConstants.Select(v => v.ToCSharpString()))} }}";
|
||||
var version = (int)attribute.ConstructorArguments[2].Value;
|
||||
var properties = attribute.NamedArguments.ToDictionary(kv => kv.Key, kv => kv.Value);
|
||||
|
||||
sb.AppendLine($" global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterHandler(new {symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}(), Guid.Parse(\"{id}\"), {extensions}, {version});");
|
||||
var id = GetValueOrDefault(properties, "AssetTypeId", string.Empty);
|
||||
var runtimeType = GetValueOrDefault(properties, "RuntimeAssetType", 0);
|
||||
var version = GetValueOrDefault(properties, "Version", 1);
|
||||
var allowCaching = GetValueOrDefault(properties, "AllowCaching", false) ? "true" : "false";
|
||||
|
||||
var extensionsTypesConstants = GetValuesOrDefault(properties, "Extensions");
|
||||
var extensions = string.Join(", ", extensionsTypesConstants.Select(v => v.ToCSharpString()));
|
||||
|
||||
sb.AppendLine(" global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterHandler(");
|
||||
sb.AppendLine($" typeof({symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}),");
|
||||
sb.AppendLine($" System.Guid.Parse(\"{id}\"),");
|
||||
sb.AppendLine($" (Ghost.Engine.AssetType){runtimeType},");
|
||||
sb.AppendLine($" {version},");
|
||||
sb.AppendLine($" {allowCaching},");
|
||||
sb.AppendLine($" new string[] {{ {extensions} }});");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
var registerTypeName = "g_assethandler_registeration";
|
||||
|
||||
@@ -176,6 +176,7 @@ namespace {info.TypeSymbol.ContainingNamespace.ToDisplayString()}
|
||||
[global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 4)]
|
||||
{info.TypeSymbol.DeclaredAccessibility.ToString().ToLower()} partial struct {info.TypeSymbol.Name}
|
||||
{{
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
public const string HLSL_SOURCE = @""
|
||||
# ifndef {definedSymbol}
|
||||
# define {definedSymbol}
|
||||
@@ -185,13 +186,14 @@ struct {info.Name}
|
||||
}};
|
||||
# endif // {definedSymbol}"";
|
||||
}}
|
||||
#endif
|
||||
}}";
|
||||
|
||||
context.AddSource($"{info.TypeSymbol.Name}_HLSL.gen.cs", code);
|
||||
codeBuilder.Clear();
|
||||
|
||||
var typeFullName = info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
registerBuilder.AppendLine($@" global::Ghost.DSL.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}));");
|
||||
registerBuilder.AppendLine($@" global::Ghost.Core.Graphics.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}));");
|
||||
}
|
||||
|
||||
var registerTypeName = "g_shaderproperty_registeration";
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Runtime\Ghost.Core\Ghost.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Editor\Ghost.DSL\Ghost.DSL.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Graphics.D3D12\Ghost.Graphics.D3D12.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Graphics.RHI\Ghost.Graphics.RHI.csproj" />
|
||||
<ProjectReference Include="..\..\ThridParty\Ghost.MeshOptimizer\Ghost.MeshOptimizer.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user