Refactor asset streaming & resource management system

- Introduce Ghost.Engine.Streaming namespace and split asset entry logic into type-specific classes (TextureAssetEntry, MeshAssetEntry, SceneAssetEntry)
- Make AssetEntry abstract; add AssetEntryFactory for type dispatch
- Update AssetManager and ResourceStreamingProcessor for new entry model, supporting uploadable and processable assets
- Redesign scene/mesh asset loading, serialization, and binary formats with versioning (SceneContentHeader, MeshContentHeader)
- Move SceneLoadingType to Ghost.Engine and make public
- Inline performance-critical APIs with MethodImplOptions.AggressiveInlining
- Add deep cloning and improved resource management for Mesh and meshlet data
- Allow nullable log messages in Logger
- Update Misaki.HighPerformance package references
- Remove obsolete files (Asset.cs, ActivationHandler.cs, old mesh logic)
- Improve resource release logic in ResourceManager
- Update RenderContext and ResourceStreamingContext for new streaming model
- Add UnsafeArray/UnsafeList clone utilities
- Update scene serialization/deserialization for new format
- Update tests for new APIs, asset states, and formats
This commit is contained in:
2026-05-12 22:51:51 +09:00
parent 314b0111f0
commit bb07644580
43 changed files with 1202 additions and 1114 deletions

View File

@@ -1,5 +1,6 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Engine.Streaming;
namespace Ghost.Editor.Core.Assets; namespace Ghost.Editor.Core.Assets;

View File

@@ -1,4 +1,5 @@
using Ghost.Engine; using Ghost.Engine;
using Ghost.Engine.Streaming;
using System.Collections.Concurrent; using System.Collections.Concurrent;
namespace Ghost.Editor.Core.Assets; namespace Ghost.Editor.Core.Assets;

View File

@@ -1,6 +1,6 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Editor.Core.Services; using Ghost.Editor.Core.Services;
using Ghost.Engine; using Ghost.Engine.Streaming;
namespace Ghost.Editor.Core.Assets; namespace Ghost.Editor.Core.Assets;

View File

@@ -1,7 +1,7 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Core.Graphics; using Ghost.Core.Graphics;
using Ghost.DSL.ShaderCompiler; using Ghost.DSL.ShaderCompiler;
using Ghost.Engine; using Ghost.Engine.Streaming;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ghost.Editor.Core.Assets; namespace Ghost.Editor.Core.Assets;

View File

@@ -1,7 +0,0 @@
namespace Ghost.Editor.Core.SceneGraph;
public enum SceneLoadingType
{
Single = 0,
Additive = 1,
}

View File

@@ -1,7 +1,7 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Editor.Core.Assets; using Ghost.Editor.Core.Assets;
using Ghost.Editor.Core.Contracts; using Ghost.Editor.Core.Contracts;
using Ghost.Engine; using Ghost.Engine.Streaming;
namespace Ghost.Editor.Core.Services; namespace Ghost.Editor.Core.Services;

View File

@@ -8,7 +8,7 @@ namespace Ghost.Editor.Core.Services;
[EditorInjection(EditorInjectionAttribute.ServiceLifetime.Singleton, typeof(EditorWorldService))] [EditorInjection(EditorInjectionAttribute.ServiceLifetime.Singleton, typeof(EditorWorldService))]
public class EditorWorldService : IDisposable public class EditorWorldService : IDisposable
{ {
private const int DEFAULT_ENTITY_CAPACITY = 1024; private const int _DEFAULT_ENTITY_CAPACITY = 1024;
public World EditorWorld public World EditorWorld
{ {
@@ -22,9 +22,7 @@ public class EditorWorldService : IDisposable
public EditorWorldService() public EditorWorldService()
{ {
EditorWorld = World.Create(entityCapacity: DEFAULT_ENTITY_CAPACITY); EditorWorld = World.Create(entityCapacity: _DEFAULT_ENTITY_CAPACITY);
// CreateDefaultScene();
// RebuildSceneGraph();
} }
public void CreateDefaultScene() public void CreateDefaultScene()

View File

@@ -2,8 +2,10 @@ using Ghost.Core;
using Ghost.Editor.Core.Contracts; using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Core.SceneGraph; using Ghost.Editor.Core.SceneGraph;
using Ghost.Editor.Core.Utilities; using Ghost.Editor.Core.Utilities;
using Ghost.Engine;
using Ghost.Engine.Components; using Ghost.Engine.Components;
using Ghost.Engine.Core; using Ghost.Engine.Core;
using Ghost.Engine.Streaming;
using Ghost.Entities; using Ghost.Entities;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
@@ -16,11 +18,30 @@ using System.Text.Json.Serialization;
namespace Ghost.Editor.Core.Services; namespace Ghost.Editor.Core.Services;
[EditorInjection(EditorInjectionAttribute.ServiceLifetime.Singleton, typeof(SceneSerializationService))] internal sealed class SceneSaveData
public class SceneSerializationService : IDisposable
{ {
private const int SCENE_FORMAT_VERSION = 1; public uint FormatVersion
{
get; set;
} = 1;
public List<EntitySaveData> Entities
{
get; set;
} = new();
}
internal sealed class EntitySaveData
{
public Dictionary<string, JsonElement> Components
{
get; set;
} = new();
}
[EditorInjection(EditorInjectionAttribute.ServiceLifetime.Singleton, typeof(SceneSerializationService))]
internal class SceneSerializationService : IDisposable
{
private static readonly Dictionary<Type, FieldInfo[]> s_entityFieldsCache = new(); private static readonly Dictionary<Type, FieldInfo[]> s_entityFieldsCache = new();
private static readonly JsonSerializerOptions s_jsonOptions = new() private static readonly JsonSerializerOptions s_jsonOptions = new()
{ {
@@ -35,7 +56,7 @@ public class SceneSerializationService : IDisposable
public override Entity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override Entity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
var localId = reader.GetInt32(); var localId = reader.GetInt32();
return new Entity(localId, localId >= 0 ? 0 : -1); return new Entity(localId, localId >= 0 ? 1 : 0);
} }
public override void Write(Utf8JsonWriter writer, Entity value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, Entity value, JsonSerializerOptions options)
@@ -122,13 +143,12 @@ public class SceneSerializationService : IDisposable
#region Binary Serialization #region Binary Serialization
private static readonly byte[] SCENE_MAGIC = Encoding.UTF8.GetBytes("GSCN");
private static uint GetTypeNameHash(string typeName) private static uint GetTypeNameHash(string typeName)
{ {
var hash = 2166136261u; var hash = 2166136261u;
foreach (var c in typeName) for (var i = 0; i < typeName.Length; i++)
{ {
var c = typeName[i];
hash ^= c; hash ^= c;
hash *= 16777619u; hash *= 16777619u;
} }
@@ -140,9 +160,14 @@ public class SceneSerializationService : IDisposable
{ {
using var writer = new BinaryWriter(targetStream, Encoding.UTF8, true); using var writer = new BinaryWriter(targetStream, Encoding.UTF8, true);
writer.Write(SCENE_MAGIC); var header = new SceneContentHeader
writer.Write(SCENE_FORMAT_VERSION); {
writer.Write(data.Entities?.Count ?? 0); magic = SceneContentHeader.MAGIC,
version = SceneContentHeader.VERSION,
entityCount = data.Entities.Count,
};
writer.Write(MemoryMarshal.AsBytes(new ReadOnlySpan<SceneContentHeader>(ref header)));
if (data.Entities == null) if (data.Entities == null)
{ {
@@ -171,9 +196,11 @@ public class SceneSerializationService : IDisposable
if (componentType == null) if (componentType == null)
{ {
writer.Write(typeHash); writer.Write(typeHash);
var nameBytes = Encoding.UTF8.GetBytes(typeName); var nameBytes = Encoding.UTF8.GetBytes(typeName);
writer.Write(nameBytes.Length); writer.Write(nameBytes.Length);
writer.Write(nameBytes); writer.Write(nameBytes);
var jsonBytes = Encoding.UTF8.GetBytes(componentElement.GetRawText()); var jsonBytes = Encoding.UTF8.GetBytes(componentElement.GetRawText());
writer.Write(jsonBytes.Length); writer.Write(jsonBytes.Length);
writer.Write(jsonBytes); writer.Write(jsonBytes);
@@ -189,11 +216,10 @@ public class SceneSerializationService : IDisposable
var compInfo = ComponentRegistry.GetComponentInfo(componentType); var compInfo = ComponentRegistry.GetComponentInfo(componentType);
var rawBytes = new byte[compInfo.size]; using var scope = AllocationManager.CreateStackScope();
fixed (byte* pDest = rawBytes) using var buffer = new MemoryBlock((nuint)compInfo.size, (nuint)compInfo.alignment, scope.AllocationHandle);
{
Marshal.StructureToPtr(boxed, (nint)pDest, false); Marshal.StructureToPtr(boxed, (nint)buffer.GetUnsafePtr(), false);
}
var entityFieldOffsets = GetEntityFields(componentType); var entityFieldOffsets = GetEntityFields(componentType);
var offsets = new int[entityFieldOffsets.Length]; var offsets = new int[entityFieldOffsets.Length];
@@ -203,12 +229,15 @@ public class SceneSerializationService : IDisposable
} }
writer.Write(typeHash); writer.Write(typeHash);
var nameBytes2 = Encoding.UTF8.GetBytes(typeName); var nameBytes2 = Encoding.UTF8.GetBytes(typeName);
writer.Write(nameBytes2.Length); writer.Write(nameBytes2.Length);
writer.Write(nameBytes2); writer.Write(nameBytes2);
writer.Write(rawBytes.Length);
writer.Write(rawBytes); writer.Write((int)buffer.Size);
writer.Write(buffer.AsSpan<byte>());
writer.Write(offsets.Length); writer.Write(offsets.Length);
foreach (var off in offsets) foreach (var off in offsets)
{ {
writer.Write(off); writer.Write(off);
@@ -229,7 +258,7 @@ public class SceneSerializationService : IDisposable
var root = document.RootElement; var root = document.RootElement;
var data = new SceneSaveData var data = new SceneSaveData
{ {
FormatVersion = root.TryGetProperty("formatVersion", out var v) ? v.GetInt32() : 1, FormatVersion = root.TryGetProperty("formatVersion", out var v) ? v.GetUInt32() : 1,
}; };
if (root.TryGetProperty("entities", out var entitiesElement)) if (root.TryGetProperty("entities", out var entitiesElement))
@@ -261,7 +290,8 @@ public class SceneSerializationService : IDisposable
{ {
if (loadingType == SceneLoadingType.Single) if (loadingType == SceneLoadingType.Single)
{ {
ClearEditorWorld(); // TODO: Support TimeData
_worldService.EditorWorld.Clear(default);
} }
var world = _worldService.EditorWorld; var world = _worldService.EditorWorld;
@@ -388,29 +418,6 @@ public class SceneSerializationService : IDisposable
var genericMethod = getOrRegisterMethod.MakeGenericMethod(type); var genericMethod = getOrRegisterMethod.MakeGenericMethod(type);
return (Identifier<IComponent>)genericMethod.Invoke(null, null)!; return (Identifier<IComponent>)genericMethod.Invoke(null, null)!;
} }
private unsafe void ClearEditorWorld()
{
var world = _worldService.EditorWorld;
using var scope = AllocationManager.CreateStackScope();
using var entitiesToDestroy = new UnsafeList<Entity>(128, scope.AllocationHandle);
for (var archIdx = 0; archIdx < world.ComponentManager.ArchetypeCount; archIdx++)
{
ref var archetype = ref world.ComponentManager.GetArchetypeReference(archIdx);
for (var chunkIdx = 0; chunkIdx < archetype.ChunkCount; chunkIdx++)
{
ref var chunk = ref archetype.GetChunkReference(chunkIdx);
var entitySpan = new Span<Entity>((byte*)chunk.GetUnsafePtr() + archetype.EntityIDsOffset, chunk._count);
entitiesToDestroy.AddRange(entitySpan);
}
}
world.EntityManager.DestroyEntities(entitiesToDestroy.AsSpan());
}
#endregion #endregion
#region Save Scene from Editor World #region Save Scene from Editor World
@@ -443,14 +450,14 @@ public class SceneSerializationService : IDisposable
var data = new SceneSaveData var data = new SceneSaveData
{ {
FormatVersion = SCENE_FORMAT_VERSION, FormatVersion = SceneContentHeader.VERSION,
}; };
using var stream = new MemoryStream(); using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }); using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
writer.WriteStartObject(); writer.WriteStartObject();
writer.WriteNumber("formatVersion", SCENE_FORMAT_VERSION); writer.WriteNumber("formatVersion", SceneContentHeader.VERSION);
writer.WriteStartArray("entities"); writer.WriteStartArray("entities");
foreach (var entity in sorted) foreach (var entity in sorted)
@@ -567,28 +574,3 @@ public class SceneSerializationService : IDisposable
{ {
} }
} }
#region Data Model
public sealed class SceneSaveData
{
public int FormatVersion
{
get; set;
} = 1;
public List<EntitySaveData> Entities
{
get; set;
} = new();
}
public sealed class EntitySaveData
{
public Dictionary<string, JsonElement> Components
{
get; set;
} = new();
}
#endregion

View File

@@ -7,6 +7,7 @@ using Ghost.Editor.ViewModels.Controls;
using Ghost.Editor.ViewModels.Windows; using Ghost.Editor.ViewModels.Windows;
using Ghost.Editor.Views.Windows; using Ghost.Editor.Views.Windows;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Engine.Streaming;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;

View File

@@ -1,6 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Ghost.Core.Utilities; using Ghost.Engine.Streaming;
using Ghost.Engine;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
namespace Ghost.Editor.Models; namespace Ghost.Editor.Models;

View File

@@ -1,5 +1,17 @@
namespace Ghost.Core; namespace Ghost.Core;
public interface ICloneable<T>
where T : ICloneable<T>
{
/// <summary>
/// Deep copy the object to create a new instance that contains the same value.
/// </summary>
/// <remarks>
/// This often does not clone any gpu resources if the object holds any.
/// </remarks>
T Clone();
}
public class Wrapper<T> public class Wrapper<T>
{ {
public T? Value public T? Value

View File

@@ -20,13 +20,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.9" /> <PackageReference Include="Misaki.HighPerformance" Version="1.0.10" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.6" /> <PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.7" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.26"> <PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.7.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" /> <PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.4" />
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.8" /> <PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.8" />
<PackageReference Include="System.IO.Hashing" Version="10.0.7" /> <PackageReference Include="System.IO.Hashing" Version="10.0.7" />
<PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" /> <PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" />

View File

@@ -6,18 +6,18 @@ public readonly struct Handle<T> : IEquatable<Handle<T>>
{ {
public int ID public int ID
{ {
get => field - 1; get;
} }
public int Generation public int Generation
{ {
get => field - 1; get;
} }
public Handle(int id, int generation) public Handle(int id, int generation)
{ {
ID = id + 1; ID = id;
Generation = generation + 1; Generation = generation;
} }
public static Handle<T> Invalid => default; public static Handle<T> Invalid => default;

View File

@@ -96,12 +96,12 @@ public static class Logger
[StackTraceHidden] [StackTraceHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log(string message, LogLevel level) public void Log(string? message, LogLevel level)
{ {
lock (_lock) lock (_lock)
{ {
var stackTrace = CaptureStackTrace ? new StackTrace(true).ToString() : null; var stackTrace = CaptureStackTrace ? new StackTrace(true).ToString() : null;
var logMessage = new LogMessage(level, message, stackTrace); var logMessage = new LogMessage(level, message ?? string.Empty, stackTrace);
_logs.Add(logMessage); _logs.Add(logMessage);
OnLogAdded?.Invoke(logMessage); OnLogAdded?.Invoke(logMessage);
@@ -156,7 +156,7 @@ public static class Logger
[StackTraceHidden] [StackTraceHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Log(LogLevel level, string message) public static void Log(LogLevel level, string? message)
{ {
s_logger.Log(message, level); s_logger.Log(message, level);
} }
@@ -177,7 +177,7 @@ public static class Logger
[StackTraceHidden] [StackTraceHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Info(string message) public static void Info(string? message)
{ {
s_logger.Log(message, LogLevel.Info); s_logger.Log(message, LogLevel.Info);
} }
@@ -198,7 +198,7 @@ public static class Logger
[StackTraceHidden] [StackTraceHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Warning(string message) public static void Warning(string? message)
{ {
s_logger.Log(message, LogLevel.Warning); s_logger.Log(message, LogLevel.Warning);
} }
@@ -223,7 +223,7 @@ public static class Logger
[StackTraceHidden] [StackTraceHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Error(string message) public static void Error(string? message)
{ {
s_logger.Log(message, LogLevel.Error); s_logger.Log(message, LogLevel.Error);
#if DEBUG #if DEBUG
@@ -274,7 +274,7 @@ public static class Logger
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("DEBUG")] [Conditional("DEBUG")]
[Conditional("GHOST_EDITOR")] [Conditional("GHOST_EDITOR")]
public static void Debug(string message) public static void Debug(string? message)
{ {
s_logger.Log(message, LogLevel.Debug); s_logger.Log(message, LogLevel.Debug);
} }

View File

@@ -0,0 +1,35 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Core.Utilities;
public static class CollectionUtility
{
public static UnsafeArray<T> Clone<T>(this UnsafeArray<T> src, AllocationHandle allocationHandle)
where T : unmanaged
{
if (!src.IsCreated || src.Count == 0)
{
return default;
}
var dst = new UnsafeArray<T>(src.Count, allocationHandle);
src.CopyTo(dst);
return dst;
}
public static UnsafeList<T> Clone<T>(this UnsafeList<T> src, AllocationHandle allocationHandle)
where T : unmanaged
{
if (!src.IsCreated || src.Count == 0)
{
return default;
}
var dst = new UnsafeList<T>(src.Count, allocationHandle);
src.CopyTo(dst);
return dst;
}
}

View File

@@ -1,11 +0,0 @@
using Ghost.Engine.Models;
namespace Ghost.Engine;
internal static class ActivationHandler
{
public static void Handle(LaunchArgument args)
{
}
}

View File

@@ -1,56 +0,0 @@
using System.Runtime.InteropServices;
namespace Ghost.Engine.AssetLoader;
public readonly struct AssetReference : IEquatable<AssetReference>
{
private readonly int _value;
/// <summary>
/// The index of the asset in the dependency list.
/// </summary>
public int Index
{
get => Math.Abs(_value) - 1;
}
public static AssetReference Null => default;
public readonly bool IsInternal => _value >= 0;
public readonly bool IsExternal => _value < 0;
public AssetReference(int index, bool isInternal)
{
if (index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index must be non-negative");
}
_value = isInternal ? index + 1 : -(index + 1);
}
public bool Equals(AssetReference other)
{
return _value == other._value;
}
public override int GetHashCode()
{
return _value.GetHashCode();
}
public override bool Equals(object? obj)
{
return obj is AssetReference reference && Equals(reference);
}
public static bool operator ==(AssetReference left, AssetReference right)
{
return left.Equals(right);
}
public static bool operator !=(AssetReference left, AssetReference right)
{
return !(left == right);
}
}

View File

@@ -1,332 +0,0 @@
using Ghost.Core;
using Ghost.Graphics;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.Geometry;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Engine;
[StructLayout(LayoutKind.Sequential)]
internal struct MeshContentHeader
{
public const uint MAGIC = 0x48534D47; // GMSH
public const uint VERSION = 1;
public uint magic;
public uint version;
public uint vertexCount;
public uint indexCount;
public uint materialPartCount;
public uint meshletCount;
public uint meshletGroupCount;
public uint meshletHierarchyNodeCount;
public uint meshletVertexCount;
public uint meshletTriangleCount;
public uint materialSlotCount;
public uint lodLevelCount;
public float3 boundsMin;
public float3 boundsMax;
public ulong vertexOffset;
public ulong indexOffset;
public ulong materialPartOffset;
public ulong meshletOffset;
public ulong meshletGroupOffset;
public ulong meshletHierarchyNodeOffset;
public ulong meshletVertexOffset;
public ulong meshletTriangleOffset;
}
[StructLayout(LayoutKind.Sequential)]
internal struct MeshContentMaterialPart
{
public int materialIndex;
public int indexStart;
public int indexCount;
public int vertexStart;
public int vertexCount;
}
internal unsafe partial class AssetEntry
{
private sealed unsafe class MeshParsedData
{
public MeshContentHeader header;
public byte* pVertices;
public byte* pIndices;
public byte* pMeshlets;
public byte* pMeshletGroups;
public byte* pMeshletHierarchyNodes;
public byte* pMeshletVertices;
public byte* pMeshletTriangles;
}
private static void RegisterMeshCallback()
{
s_onCreation[(int)AssetType.Mesh] = static (e) =>
{
var handle = e._resourceManager.CreateEmptyMesh();
e.SetStorage(handle);
};
s_onParseRawData[(int)AssetType.Mesh] = static (e) => e.ParseMeshData();
s_onRecordUpload[(int)AssetType.Mesh] = static (e, ctx) => e.RecordMeshUpload(ctx);
s_onUploadComplete[(int)AssetType.Mesh] = static (e, ctx) => e.OnMeshUploadComplete(ctx);
s_onReleaseResource[(int)AssetType.Mesh] = static (e) =>
{
var handle = e.GetStorage<Handle<Mesh>>();
if (handle.IsValid)
{
e._resourceManager.ReleaseMesh(handle);
}
};
}
private Result ParseMeshData()
{
var pData = (byte*)_rawData.GetUnsafePtr();
Logger.DebugAssert(pData != null);
if (_rawData.Size < (nuint)sizeof(MeshContentHeader))
{
return Result.Failure("Mesh content is smaller than the header.");
}
var header = Unsafe.ReadUnaligned<MeshContentHeader>(pData);
if (header.magic != MeshContentHeader.MAGIC || header.version != MeshContentHeader.VERSION)
{
return Result.Failure("Unsupported mesh content format.");
}
if (header.vertexCount == 0 || header.indexCount == 0 ||
header.meshletCount == 0 || header.meshletGroupCount == 0 ||
header.meshletHierarchyNodeCount == 0 || header.meshletVertexCount == 0 ||
header.meshletTriangleCount == 0)
{
return Result.Failure("Mesh content is missing required geometry or meshlet data.");
}
if (!ValidateRange(header.vertexOffset, header.vertexCount, (uint)sizeof(Vertex)) ||
!ValidateRange(header.indexOffset, header.indexCount, sizeof(uint)) ||
!ValidateRange(header.meshletOffset, header.meshletCount, (uint)sizeof(Meshlet)) ||
!ValidateRange(header.meshletGroupOffset, header.meshletGroupCount, (uint)sizeof(MeshletGroup)) ||
!ValidateRange(header.meshletHierarchyNodeOffset, header.meshletHierarchyNodeCount, (uint)sizeof(MeshletHierarchyNode)) ||
!ValidateRange(header.meshletVertexOffset, header.meshletVertexCount, sizeof(uint)) ||
!ValidateRange(header.meshletTriangleOffset, header.meshletTriangleCount, sizeof(uint)))
{
return Result.Failure("Mesh content contains an invalid data range.");
}
if (header.materialPartCount > 0 && !ValidateRange(header.materialPartOffset, header.materialPartCount, (uint)sizeof(MeshContentMaterialPart)))
{
return Result.Failure("Mesh content contains an invalid material part range.");
}
_parsedObject = new MeshParsedData
{
header = header,
pVertices = pData + header.vertexOffset,
pIndices = pData + header.indexOffset,
pMeshlets = pData + header.meshletOffset,
pMeshletGroups = pData + header.meshletGroupOffset,
pMeshletHierarchyNodes = pData + header.meshletHierarchyNodeOffset,
pMeshletVertices = pData + header.meshletVertexOffset,
pMeshletTriangles = pData + header.meshletTriangleOffset,
};
return Result.Success();
bool ValidateRange(ulong offset, uint count, uint stride)
{
var size = (ulong)count * stride;
return offset <= _rawData.Size && size <= _rawData.Size - (nuint)offset;
}
}
private Result RecordMeshUpload(ResourceStreamingContext context)
{
if (_parsedObject is not MeshParsedData data)
{
return Result.Failure("Mesh parse data is missing.");
}
ref readonly var header = ref data.header;
var vertexBuffer = CreateBuffer(context, data.pVertices, header.vertexCount, (uint)sizeof(Vertex),
BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw, "Mesh_VertexBuffer");
var indexBuffer = CreateBuffer(context, data.pIndices, header.indexCount, sizeof(uint),
BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw, "Mesh_IndexBuffer");
var meshletBuffer = CreateBuffer(context, data.pMeshlets, header.meshletCount, (uint)sizeof(Meshlet),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_Meshlets");
var meshletVerticesBuffer = CreateBuffer(context, data.pMeshletVertices, header.meshletVertexCount, sizeof(uint),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletVertices");
var meshletTrianglesBuffer = CreateBuffer(context, data.pMeshletTriangles, header.meshletTriangleCount, sizeof(uint),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletTriangles");
var meshletGroupBuffer = CreateBuffer(context, data.pMeshletGroups, header.meshletGroupCount, (uint)sizeof(MeshletGroup),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletGroups");
var meshletHierarchyBuffer = CreateBuffer(context, data.pMeshletHierarchyNodes, header.meshletHierarchyNodeCount, (uint)sizeof(MeshletHierarchyNode),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletHierarchy");
if (vertexBuffer.IsInvalid || indexBuffer.IsInvalid || meshletBuffer.IsInvalid ||
meshletVerticesBuffer.IsInvalid || meshletTrianglesBuffer.IsInvalid ||
meshletGroupBuffer.IsInvalid || meshletHierarchyBuffer.IsInvalid)
{
return Result.Failure("Failed to create one or more mesh GPU buffers.");
}
var meshData = new MeshData
{
worldBoundsMin = header.boundsMin,
worldBoundsMax = header.boundsMax,
vertexBuffer = context.ResourceDatabase.GetBindlessIndex(vertexBuffer.AsResource()),
indexBuffer = context.ResourceDatabase.GetBindlessIndex(indexBuffer.AsResource()),
meshletBuffer = context.ResourceDatabase.GetBindlessIndex(meshletBuffer.AsResource()),
meshletVerticesBuffer = context.ResourceDatabase.GetBindlessIndex(meshletVerticesBuffer.AsResource()),
meshletTrianglesBuffer = context.ResourceDatabase.GetBindlessIndex(meshletTrianglesBuffer.AsResource()),
meshletGroupBuffer = context.ResourceDatabase.GetBindlessIndex(meshletGroupBuffer.AsResource()),
meshletHierarchyBuffer = context.ResourceDatabase.GetBindlessIndex(meshletHierarchyBuffer.AsResource()),
meshletCount = header.meshletCount,
lodLevelCount = header.lodLevelCount,
materialSlotCount = header.materialSlotCount,
};
var meshDataBufferDesc = new BufferDesc
{
Size = (ulong)sizeof(MeshData),
Stride = (uint)sizeof(MeshData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
var meshDataBuffer = RenderingUtility.CreateBuffer(
context.ResourceManager,
context.ResourceDatabase,
context.ResourceAllocator,
context.CopyPipeline.GetCommandBuffer(),
&meshData,
(nuint)sizeof(MeshData),
in meshDataBufferDesc,
"Mesh_MeshDataBuffer");
if (meshDataBuffer.IsInvalid)
{
return Result.Failure("Failed to create mesh data buffer.");
}
var newHandle = context.ResourceManager.CreateUploadedMesh(
vertexBuffer,
indexBuffer,
meshletBuffer,
meshletVerticesBuffer,
meshletTrianglesBuffer,
meshletGroupBuffer,
meshletHierarchyBuffer,
meshDataBuffer,
(int)header.vertexCount,
(int)header.indexCount,
(int)header.meshletCount,
(int)header.lodLevelCount,
(int)header.materialSlotCount,
new AABB(header.boundsMin, header.boundsMax));
if (newHandle.IsInvalid)
{
return Result.Failure("Failed to register uploaded mesh.");
}
var oldHandle = GetStorage<Handle<Mesh>>();
SetStorage((oldHandle, newHandle));
return Result.Success();
}
private static Handle<GPUBuffer> CreateBuffer(ResourceStreamingContext context, void* pData, uint count, uint stride, BufferUsage usage, string name)
{
var desc = new BufferDesc
{
Size = (ulong)count * stride,
Stride = stride,
Usage = usage,
HeapType = HeapType.Default,
};
return RenderingUtility.CreateBuffer(
context.ResourceManager,
context.ResourceDatabase,
context.ResourceAllocator,
context.CopyPipeline.GetCommandBuffer(),
pData,
(nuint)desc.Size,
in desc,
name);
}
private void OnMeshUploadComplete(ResourceStreamingContext context)
{
var (oldHandle, newHandle) = GetStorage<(Handle<Mesh>, Handle<Mesh>)>();
// FIX: Do not reaplce the mesh, replace the underlying buffers and data instead because we are using persistent gpu scene. Replacing the mesh does not update the gpu scene at all.
var actualHandle = context.ResourceManager.ReplaceMesh(oldHandle, newHandle);
if (actualHandle.IsInvalid)
{
SetStorage(oldHandle);
return;
}
var result = context.ResourceManager.GetMeshReference(actualHandle);
if (result.IsSuccess)
{
ref readonly var mesh = ref result.Value;
context.GraphicsCommandBuffer.Barrier(
BarrierDesc.Buffer(mesh.VertexBuffer.AsResource(), BarrierSync.VertexShading, BarrierAccess.VertexBuffer | BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.IndexBuffer.AsResource(), BarrierSync.IndexInput, BarrierAccess.IndexBuffer | BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshLetBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshletVerticesBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshletTrianglesBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshletGroupBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshletHierarchyBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshDataBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource));
}
SetStorage(actualHandle);
_rawData.Dispose();
_parsedObject = null;
}
}
internal partial class AssetManager
{
public Handle<Mesh> ResolveMesh(Guid assetID)
{
if (assetID == Guid.Empty)
{
return Handle<Mesh>.Invalid;
}
var entry = GetOrCreateEntry(assetID);
Logger.DebugAssert(entry.AssetType == AssetType.Mesh);
return entry.GetStorage<Handle<Mesh>>();
}
public int ReleaseMesh(Guid assetID)
{
if (assetID == Guid.Empty)
{
return 0;
}
if (!_entries.TryGetValue(assetID, out var entry) || entry.AssetType != AssetType.Mesh)
{
return 0;
}
return entry.Release();
}
}

View File

@@ -1,5 +1,11 @@
namespace Ghost.Engine; namespace Ghost.Engine;
public enum SceneLoadingType
{
Single = 0,
Additive = 1,
}
public enum ShadowCastingMode : uint public enum ShadowCastingMode : uint
{ {
Off, Off,

View File

@@ -1,5 +1,6 @@
using Ghost.Core.Graphics; using Ghost.Core.Graphics;
using Ghost.Engine.RenderPipeline; using Ghost.Engine.RenderPipeline;
using Ghost.Engine.Streaming;
using Ghost.Graphics; using Ghost.Graphics;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.Jobs;

View File

@@ -0,0 +1,144 @@
using Ghost.Core;
using Ghost.Graphics;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using System.Runtime.CompilerServices;
namespace Ghost.Engine.Streaming;
internal static class AssetEntryFactory
{
public static AssetEntry CreateNewEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
{
return assetType switch
{
AssetType.Texture => new TextureAssetEntry(manager, resourceDatabase, resourceManager, assetId, dependencies),
AssetType.Mesh => new MeshAssetEntry(manager, resourceDatabase, resourceManager, assetId, dependencies),
AssetType.Material => throw new NotImplementedException(),
AssetType.Shader => throw new NotImplementedException(),
AssetType.Scene => new SceneAssetEntry(manager, resourceDatabase, resourceManager, assetId, dependencies),
AssetType.Audio => throw new NotImplementedException(),
AssetType.Video => throw new NotImplementedException(),
AssetType.Json => throw new NotImplementedException(),
AssetType.Unknown => throw new NotImplementedException(),
_ => throw new NotSupportedException($"Unsupported asset type {assetType}.")
};
}
}
// TODO: Progress report
internal abstract class AssetEntry
{
private readonly AssetManager _assetManager;
private readonly ResourceManager _resourceManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly Guid _assetId;
private readonly AssetType _assetType;
private readonly Guid[] _dependencies;
private JobHandle _loadJobHandle;
private int _refCount;
private int _state;
private bool _pendingReimport;
protected ResourceManager ResourceManager => _resourceManager;
protected IResourceDatabase ResourceDatabase => _resourceDatabase;
public AssetManager AssetManager => _assetManager;
public Guid AssetId => _assetId;
public JobHandle LoadJobHandle => _loadJobHandle;
public AssetType AssetType => _assetType;
public ReadOnlySpan<Guid> Dependencies => _dependencies;
public int RefCount => Volatile.Read(ref _refCount);
public ref bool PendingReimport => ref _pendingReimport;
public ref int StateValue => ref _state;
public AssetState State
{
get => (AssetState)Volatile.Read(ref _state);
set => Volatile.Write(ref _state, (int)value);
}
protected AssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
{
_assetManager = manager;
_resourceDatabase = resourceDatabase;
_resourceManager = resourceManager;
_assetId = assetId;
_assetType = assetType;
_dependencies = dependencies;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetLoadJobHandle(JobHandle handle)
{
_loadJobHandle = handle;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetPendingReimport()
{
Volatile.Write(ref _pendingReimport, true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddRef()
{
Interlocked.Increment(ref _refCount);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Release()
{
Logger.DebugAssert(State == AssetState.Ready);
var newRefCount = Interlocked.Decrement(ref _refCount);
Logger.DebugAssert(newRefCount >= 0, "Reference count should not be negative");
if (newRefCount == 0)
{
_assetManager.RemoveEntry(_assetId);
OnReleaseResource();
foreach (var dep in _dependencies)
{
if (_assetManager.TryGetEntry(dep, out var entry))
{
entry.Release();
}
}
}
return newRefCount;
}
public abstract Result OnLoadRawData([OwnershipTransfer] ref MemoryBlock memory);
public abstract void OnReleaseResource();
}
internal abstract class ProcessableAssetEntry : AssetEntry
{
protected ProcessableAssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
: base(manager, resourceDatabase, resourceManager, assetId, assetType, dependencies)
{
}
public abstract Result<JobHandle> OnProcessing(object? context);
}
internal abstract class UploadableAssetEntry : AssetEntry
{
protected UploadableAssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
: base(manager, resourceDatabase, resourceManager, assetId, assetType, dependencies)
{
}
public abstract Result OnRecordUploadCommands(ResourceStreamingContext context);
public abstract void OnUploadComplete(ResourceStreamingContext context);
}

View File

@@ -1,10 +1,8 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Core.Utilities; using Ghost.Core.Utilities;
using Ghost.Graphics;
using Ghost.Graphics.Services; using Ghost.Graphics.Services;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
@@ -12,7 +10,7 @@ using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Ghost.Engine; namespace Ghost.Engine.Streaming;
public enum AssetType public enum AssetType
{ {
@@ -25,7 +23,7 @@ public enum AssetType
Video = 6, Video = 6,
Json = 7, Json = 7,
Unknown = 32, // We are unlikely to have more than 32 asset types. Unknown = 64,
} }
public enum AssetState public enum AssetState
@@ -34,7 +32,7 @@ public enum AssetState
Scheduled = 1, Scheduled = 1,
Loading = 2, Loading = 2,
Loaded = 3, Loaded = 3,
Uploading = 4, Processing = 4,
Ready = 5, Ready = 5,
Failed = 6, Failed = 6,
} }
@@ -50,207 +48,12 @@ public interface IContentProvider
AssetType GetAssetType(Guid guid); AssetType GetAssetType(Guid guid);
} }
internal partial class AssetEntry
{
private static readonly Action<AssetEntry>?[] s_onCreation = new Action<AssetEntry>[(int)AssetType.Unknown + 1];
private static readonly Func<AssetEntry, Result>?[] s_onParseRawData = new Func<AssetEntry, Result>[(int)AssetType.Unknown + 1];
private static readonly Func<AssetEntry, ResourceStreamingContext, Result>?[] s_onRecordUpload = new Func<AssetEntry, ResourceStreamingContext, Result>[(int)AssetType.Unknown + 1];
private static readonly Action<AssetEntry, ResourceStreamingContext>?[] s_onUploadComplete = new Action<AssetEntry, ResourceStreamingContext>[(int)AssetType.Unknown + 1];
private static readonly Action<AssetEntry>?[] s_onReleaseResource = new Action<AssetEntry>[(int)AssetType.Unknown + 1];
static AssetEntry()
{
RegisterTextureCallback();
RegisterMeshCallback();
}
}
internal unsafe partial class AssetEntry
{
private struct Storage
{
public fixed byte data[64];
}
private readonly AssetManager _assetManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly ResourceManager _resourceManager;
private readonly Guid _assetId;
private readonly AssetType _assetType;
private readonly Guid[] _dependencies;
private Storage _storage;
private MemoryBlock _rawData;
private object? _parsedObject;
private JobHandle _loadJobHandle;
private int _refCount;
private int _state;
private bool _pendingReimport;
public Guid AssetId => _assetId;
public MemoryBlock RawData => _rawData;
public JobHandle LoadJobHandle => _loadJobHandle;
public AssetType AssetType => _assetType;
public ReadOnlySpan<Guid> Dependencies => _dependencies;
public int RefCount => Volatile.Read(ref _refCount);
public ref int StateValue => ref _state;
public AssetState State
{
get => (AssetState)Volatile.Read(ref _state);
set => Volatile.Write(ref _state, (int)value);
}
public AssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
{
_assetManager = manager;
_resourceDatabase = resourceDatabase;
_resourceManager = resourceManager;
_assetId = assetId;
_assetType = assetType;
_dependencies = dependencies;
s_onCreation[(int)_assetType]?.Invoke(this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetStorage<T>(T asset)
where T : unmanaged
{
Unsafe.WriteUnaligned(ref _storage.data[0], asset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetStorage<T>()
where T : unmanaged
{
return Unsafe.ReadUnaligned<T>(ref _storage.data[0]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetRawData([OwnershipTransfer] ref MemoryBlock data)
{
_rawData = data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetLoadJobHandle(JobHandle handle)
{
_loadJobHandle = handle;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetPendingReimport()
{
Volatile.Write(ref _pendingReimport, true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddRef()
{
Interlocked.Increment(ref _refCount);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Release()
{
Logger.DebugAssert(State == AssetState.Ready);
var newRefCount = Interlocked.Decrement(ref _refCount);
Logger.DebugAssert(newRefCount >= 0, "Reference count should not be negative");
if (newRefCount == 0)
{
_assetManager.RemoveEntry(_assetId);
OnReleaseResource();
foreach (var dep in _dependencies)
{
if (_assetManager.TryGetEntry(dep, out var entry))
{
entry.Release();
}
}
}
return newRefCount;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Result OnParseRawData()
{
return s_onParseRawData[(int)_assetType]?.Invoke(this) ?? Result.Failure("Unsupported asset type.");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Result OnRecordUploadCommands(ResourceStreamingContext context)
{
return s_onRecordUpload[(int)_assetType]?.Invoke(this, context) ?? Result.Failure("Unsupported asset type.");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void OnUploadComplete(ResourceStreamingContext context)
{
s_onUploadComplete[(int)_assetType]?.Invoke(this, context);
Volatile.Write(ref _state, (int)AssetState.Ready);
if (Interlocked.CompareExchange(ref _pendingReimport, false, true))
{
_assetManager.ReimportAsset(_assetId); // re-queue
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void OnReleaseResource()
{
s_onReleaseResource[(int)_assetType]?.Invoke(this);
if (_rawData.IsCreated)
{
_rawData.Dispose();
}
}
}
internal struct LoadAssetJob : IJob internal struct LoadAssetJob : IJob
{ {
public Guid assetID; public Guid assetID;
public AssetType assetType; public AssetType assetType;
public AssetManager assetManager; public AssetManager assetManager;
private static Result LoadRawData(IContentProvider contentProvider, AssetEntry entry)
{
try
{
using var stream = contentProvider.OpenRead(entry.AssetId).GetValueOrThrow();
var data = new MemoryBlock((nuint)stream.Length, MemoryUtility.AlignOf<IntPtr>(), AllocationHandle.Persistent);
// C# built-in collections use int for indexing, so we need to ensure that the buffer size does not exceed int.MaxValue
var maxChunkSize = (int)Math.Min(0x7fffffffu, data.Size);
var offset = 0u;
while (offset < data.Size)
{
using var mem = NativeMemoryManager<byte>.FromMemoryBlock(data, (int)offset, maxChunkSize);
stream.ReadExactly(mem.Memory.Span);
offset += (uint)mem.Memory.Length;
}
entry.SetRawData(ref data);
return Result.Success();
}
catch (Exception ex)
{
return Result.Failure(ex.Message);
}
}
public readonly void Execute(ref readonly JobExecutionContext ctx) public readonly void Execute(ref readonly JobExecutionContext ctx)
{ {
Logger.DebugAssert(assetManager is not null); Logger.DebugAssert(assetManager is not null);
@@ -266,25 +69,44 @@ internal struct LoadAssetJob : IJob
entry.State = AssetState.Loading; entry.State = AssetState.Loading;
var result = LoadRawData(assetManager.ContentProvider, entry); try
{
using var stream = assetManager.ContentProvider.OpenRead(entry.AssetId).GetValueOrThrow();
var data = new MemoryBlock((nuint)stream.Length, MemoryUtility.AlignOf<IntPtr>(), AllocationHandle.Persistent);
// C# built-in collections use int for indexing, so we need to ensure that the buffer size does not exceed int.MaxValue
var maxChunkSize = (int)Math.Min(0x7fffffffu, data.Size);
var offset = 0u;
while (offset < data.Size)
{
using var mem = NativeMemoryManager<byte>.FromMemoryBlock(data, (int)offset, maxChunkSize);
stream.ReadExactly(mem.Memory.Span);
offset += (uint)mem.Memory.Length;
}
var result = entry.OnLoadRawData(ref data);
if (result.IsFailure) if (result.IsFailure)
{ {
entry.State = AssetState.Failed; entry.State = AssetState.Failed;
Logger.Error($"Failed to load asset {assetID}: {result.Message}"); Logger.Error($"Failed to load asset {assetID}: {result.Message}");
return; return;
} }
}
result = entry.OnParseRawData(); catch (Exception ex)
if (result.IsFailure)
{ {
entry.State = AssetState.Failed; entry.State = AssetState.Failed;
Logger.Error($"Failed to parse asset {assetID}: {result.Message}"); Logger.Error($"Failed to load asset {assetID}: {ex.Message}");
return; return;
} }
entry.State = AssetState.Loaded; entry.State = AssetState.Loaded;
if (!assetManager.StreamingProcessor.EnqueueForProcess(entry))
assetManager.StreamingProcessor.EnqueueForUpload(entry); {
// This mean the asset don't need any further processing anymore.
entry.State = AssetState.Ready;
}
} }
} }
@@ -292,8 +114,8 @@ internal struct LoadAssetJob : IJob
internal partial class AssetManager : IDisposable internal partial class AssetManager : IDisposable
{ {
private readonly IResourceDatabase _resourceDatabase; private readonly IResourceDatabase _resourceDatabase;
private readonly ResourceManager _resourceManager;
private readonly IContentProvider _contentProvider; private readonly IContentProvider _contentProvider;
private readonly ResourceManager _resourceManager;
private readonly ResourceStreamingProcessor _streamingProcessor; private readonly ResourceStreamingProcessor _streamingProcessor;
private readonly JobScheduler _jobScheduler; private readonly JobScheduler _jobScheduler;
@@ -407,7 +229,7 @@ internal partial class AssetManager : IDisposable
var type = self._contentProvider.GetAssetType(id); var type = self._contentProvider.GetAssetType(id);
var deps = self._contentProvider.GetDependencies(id); var deps = self._contentProvider.GetDependencies(id);
var entry = new AssetEntry(self, self._resourceDatabase, self._resourceManager, id, type, deps); var entry = AssetEntryFactory.CreateNewEntry(self, self._resourceDatabase, self._resourceManager, id, type, deps);
self.EnsureScheduled(entry); self.EnsureScheduled(entry);
return entry; return entry;

View File

@@ -0,0 +1,361 @@
using Ghost.Core;
using Ghost.Graphics;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Ghost.Graphics.Utilities;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.Geometry;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Engine.Streaming;
[StructLayout(LayoutKind.Sequential)]
internal struct MeshContentHeader
{
public const uint MAGIC = 0x48534D47; // GMSH
public const uint VERSION = 1;
public uint magic;
public uint version;
public int vertexCount;
public int indexCount;
public int materialPartCount;
public int meshletCount;
public int meshletGroupCount;
public int meshletHierarchyNodeCount;
public int meshletVertexCount;
public int meshletTriangleCount;
public int materialSlotCount;
public int lodLevelCount;
public float3 boundsMin;
public float3 boundsMax;
public ulong vertexOffset;
public ulong indexOffset;
public ulong materialPartOffset;
public ulong meshletOffset;
public ulong meshletGroupOffset;
public ulong meshletHierarchyNodeOffset;
public ulong meshletVertexOffset;
public ulong meshletTriangleOffset;
}
[StructLayout(LayoutKind.Sequential)]
internal struct MeshContentMaterialPart
{
public int materialIndex;
public int indexStart;
public int indexCount;
public int vertexStart;
public int vertexCount;
}
internal partial class AssetManager
{
public Handle<Mesh> ResolveMesh(Guid assetID)
{
if (assetID == Guid.Empty)
{
return Handle<Mesh>.Invalid;
}
var entry = GetOrCreateEntry(assetID);
Logger.DebugAssert(entry.AssetType == AssetType.Mesh);
return ((MeshAssetEntry)entry).MeshHandle;
}
public int ReleaseMesh(Guid assetID)
{
if (assetID == Guid.Empty)
{
return 0;
}
if (!_entries.TryGetValue(assetID, out var entry) || entry.AssetType != AssetType.Mesh)
{
return 0;
}
return entry.Release();
}
}
internal unsafe class MeshAssetEntry : UploadableAssetEntry
{
private MemoryBlock _rawData;
private MeshContentHeader _header;
private byte* _pVertices;
private byte* _pIndices;
private byte* _pMeshlets;
private byte* _pMeshletGroups;
private byte* _pMeshletHierarchyNodes;
private byte* _pMeshletVertices;
private byte* _pMeshletTriangles;
private Handle<Mesh> _actualHandle;
private Handle<Mesh> _tempHandle;
public Handle<Mesh> MeshHandle => _actualHandle;
public MeshAssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, Guid[] dependencies)
: base(manager, resourceDatabase, resourceManager, assetId, AssetType.Mesh, dependencies)
{
var mesh = default(Mesh);
mesh.VertexBuffer = resourceDatabase.CreateEmpty().AsBuffer();
mesh.IndexBuffer = resourceDatabase.CreateEmpty().AsBuffer();
mesh.MeshDataBuffer = resourceDatabase.CreateEmpty().AsBuffer();
mesh.MeshletBuffer = resourceDatabase.CreateEmpty().AsBuffer();
mesh.MeshletGroupBuffer = resourceDatabase.CreateEmpty().AsBuffer();
mesh.MeshletHierarchyBuffer = resourceDatabase.CreateEmpty().AsBuffer();
mesh.MeshletVerticesBuffer = resourceDatabase.CreateEmpty().AsBuffer();
mesh.MeshletTrianglesBuffer = resourceDatabase.CreateEmpty().AsBuffer();
_actualHandle = resourceManager.RegisterMesh(ref mesh);
}
public override Result OnLoadRawData([OwnershipTransfer] ref MemoryBlock memory)
{
bool ValidateRange(ulong offset, int count, uint stride)
{
var size = (ulong)count * stride;
return offset <= _rawData.Size && size <= _rawData.Size - (nuint)offset;
}
_rawData = memory;
var pData = (byte*)_rawData.GetUnsafePtr();
Logger.DebugAssert(pData != null);
if (_rawData.Size < (nuint)sizeof(MeshContentHeader))
{
return Result.Failure("Mesh content is smaller than the header.");
}
var header = Unsafe.ReadUnaligned<MeshContentHeader>(pData);
if (header.magic != MeshContentHeader.MAGIC || header.version != MeshContentHeader.VERSION)
{
return Result.Failure("Unsupported mesh content format.");
}
if (header.vertexCount == 0 || header.indexCount == 0 ||
header.meshletCount == 0 || header.meshletGroupCount == 0 ||
header.meshletHierarchyNodeCount == 0 || header.meshletVertexCount == 0 ||
header.meshletTriangleCount == 0)
{
return Result.Failure("Mesh content is missing required geometry or meshlet data.");
}
if (!ValidateRange(header.vertexOffset, header.vertexCount, (uint)sizeof(Vertex)) ||
!ValidateRange(header.indexOffset, header.indexCount, sizeof(uint)) ||
!ValidateRange(header.meshletOffset, header.meshletCount, (uint)sizeof(Meshlet)) ||
!ValidateRange(header.meshletGroupOffset, header.meshletGroupCount, (uint)sizeof(MeshletGroup)) ||
!ValidateRange(header.meshletHierarchyNodeOffset, header.meshletHierarchyNodeCount, (uint)sizeof(MeshletHierarchyNode)) ||
!ValidateRange(header.meshletVertexOffset, header.meshletVertexCount, sizeof(uint)) ||
!ValidateRange(header.meshletTriangleOffset, header.meshletTriangleCount, sizeof(uint)))
{
return Result.Failure("Mesh content contains an invalid data range.");
}
if (header.materialPartCount > 0 && !ValidateRange(header.materialPartOffset, header.materialPartCount, (uint)sizeof(MeshContentMaterialPart)))
{
return Result.Failure("Mesh content contains an invalid material part range.");
}
_header = header;
_pVertices = pData + header.vertexOffset;
_pIndices = pData + header.indexOffset;
_pMeshlets = pData + header.meshletOffset;
_pMeshletGroups = pData + header.meshletGroupOffset;
_pMeshletHierarchyNodes = pData + header.meshletHierarchyNodeOffset;
_pMeshletVertices = pData + header.meshletVertexOffset;
_pMeshletTriangles = pData + header.meshletTriangleOffset;
return Result.Success();
}
public override void OnReleaseResource()
{
ResourceManager.ReleaseMesh(_tempHandle);
}
private static Handle<GPUBuffer> CreateBuffer(ResourceStreamingContext context, void* pData, int count, uint stride, BufferUsage usage, string name)
{
var desc = new BufferDesc
{
Size = (ulong)count * stride,
Stride = stride,
Usage = usage,
HeapType = HeapType.Default,
};
return RenderingUtility.CreateBuffer(
context.ResourceManager,
context.ResourceDatabase,
context.ResourceAllocator,
context.CopyPipeline.GetCommandBuffer(),
pData,
(nuint)desc.Size,
in desc,
name);
}
public override Result OnRecordUploadCommands(ResourceStreamingContext context)
{
var vertexBuffer = CreateBuffer(context, _pVertices, _header.vertexCount, (uint)sizeof(Vertex),
BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw, "Mesh_VertexBuffer");
var indexBuffer = CreateBuffer(context, _pIndices, _header.indexCount, sizeof(uint),
BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw, "Mesh_IndexBuffer");
var meshletBuffer = CreateBuffer(context, _pMeshlets, _header.meshletCount, (uint)sizeof(Meshlet),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_Meshlets");
var meshletVerticesBuffer = CreateBuffer(context, _pMeshletVertices, _header.meshletVertexCount, sizeof(uint),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletVertices");
var meshletTrianglesBuffer = CreateBuffer(context, _pMeshletTriangles, _header.meshletTriangleCount, sizeof(uint),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletTriangles");
var meshletGroupBuffer = CreateBuffer(context, _pMeshletGroups, _header.meshletGroupCount, (uint)sizeof(MeshletGroup),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletGroups");
var meshletHierarchyBuffer = CreateBuffer(context, _pMeshletHierarchyNodes, _header.meshletHierarchyNodeCount, (uint)sizeof(MeshletHierarchyNode),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletHierarchy");
if (vertexBuffer.IsInvalid || indexBuffer.IsInvalid || meshletBuffer.IsInvalid ||
meshletVerticesBuffer.IsInvalid || meshletTrianglesBuffer.IsInvalid ||
meshletGroupBuffer.IsInvalid || meshletHierarchyBuffer.IsInvalid)
{
return Result.Failure("Failed to create one or more mesh GPU buffers.");
}
var meshData = new MeshData
{
worldBoundsMin = _header.boundsMin,
worldBoundsMax = _header.boundsMax,
vertexBuffer = context.ResourceDatabase.GetBindlessIndex(vertexBuffer.AsResource()),
indexBuffer = context.ResourceDatabase.GetBindlessIndex(indexBuffer.AsResource()),
meshletBuffer = context.ResourceDatabase.GetBindlessIndex(meshletBuffer.AsResource()),
meshletVerticesBuffer = context.ResourceDatabase.GetBindlessIndex(meshletVerticesBuffer.AsResource()),
meshletTrianglesBuffer = context.ResourceDatabase.GetBindlessIndex(meshletTrianglesBuffer.AsResource()),
meshletGroupBuffer = context.ResourceDatabase.GetBindlessIndex(meshletGroupBuffer.AsResource()),
meshletHierarchyBuffer = context.ResourceDatabase.GetBindlessIndex(meshletHierarchyBuffer.AsResource()),
meshletCount = (uint)_header.meshletCount,
lodLevelCount = (uint)_header.lodLevelCount,
materialSlotCount = (uint)_header.materialSlotCount,
};
var meshDataBufferDesc = new BufferDesc
{
Size = (ulong)sizeof(MeshData),
Stride = (uint)sizeof(MeshData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
var meshDataBuffer = RenderingUtility.CreateBuffer(
context.ResourceManager,
context.ResourceDatabase,
context.ResourceAllocator,
context.CopyPipeline.GetCommandBuffer(),
&meshData,
(nuint)sizeof(MeshData),
in meshDataBufferDesc,
"Mesh_MeshDataBuffer");
if (meshDataBuffer.IsInvalid)
{
return Result.Failure("Failed to create mesh data buffer.");
}
var mesh = new Mesh
{
IsMeshDataDirty = true,
VertexCount = _header.vertexCount,
IndexCount = _header.indexCount,
VertexBuffer = vertexBuffer,
IndexBuffer = indexBuffer,
MeshletBuffer = meshletBuffer,
MeshletVerticesBuffer = meshletVerticesBuffer,
MeshletTrianglesBuffer = meshletTrianglesBuffer,
MeshletGroupBuffer = meshletGroupBuffer,
MeshletHierarchyBuffer = meshletHierarchyBuffer,
MeshDataBuffer = meshDataBuffer,
BoundingBox = new AABB(_header.boundsMin, _header.boundsMax),
MeshletData = new MeshletMeshData
{
meshletCount = _header.meshletCount,
lodLevelCount = _header.lodLevelCount,
materialSlotCount = _header.materialSlotCount,
}
};
var newHandle = context.ResourceManager.RegisterMesh(ref mesh);
if (newHandle.IsInvalid)
{
return Result.Failure("Failed to register uploaded mesh.");
}
_tempHandle = newHandle;
return Result.Success();
}
public override void OnUploadComplete(ResourceStreamingContext context)
{
var (dstMeshRef, dstError) = context.ResourceManager.GetMeshReference(_actualHandle);
var (srcMeshRef, srcError) = context.ResourceManager.GetMeshReference(_tempHandle);
if (dstError.IsFailure || srcError.IsFailure)
{
return;
}
ref var dstMesh = ref dstMeshRef.Get();
ref var srcMesh = ref srcMeshRef.Get();
var temp = dstMesh;
Logger.DebugAssert(!dstMesh.Vertices.IsCreated);
Logger.DebugAssert(!dstMesh.Indices.IsCreated);
dstMesh = srcMesh.Clone();
dstMesh.IsMeshDataDirty = false;
dstMesh.VertexBuffer = context.ResourceDatabase.Replace(temp.VertexBuffer.AsResource(), srcMesh.VertexBuffer.AsResource()).AsBuffer();
dstMesh.IndexBuffer = context.ResourceDatabase.Replace(temp.IndexBuffer.AsResource(), srcMesh.IndexBuffer.AsResource()).AsBuffer();
dstMesh.MeshDataBuffer = context.ResourceDatabase.Replace(temp.MeshDataBuffer.AsResource(), srcMesh.MeshDataBuffer.AsResource()).AsBuffer();
dstMesh.MeshletBuffer = context.ResourceDatabase.Replace(temp.MeshletBuffer.AsResource(), srcMesh.MeshletBuffer.AsResource()).AsBuffer();
dstMesh.MeshletGroupBuffer = context.ResourceDatabase.Replace(temp.MeshletGroupBuffer.AsResource(), srcMesh.MeshletGroupBuffer.AsResource()).AsBuffer();
dstMesh.MeshletHierarchyBuffer = context.ResourceDatabase.Replace(temp.MeshletHierarchyBuffer.AsResource(), srcMesh.MeshletHierarchyBuffer.AsResource()).AsBuffer();
dstMesh.MeshletVerticesBuffer = context.ResourceDatabase.Replace(temp.MeshletVerticesBuffer.AsResource(), srcMesh.MeshletVerticesBuffer.AsResource()).AsBuffer();
dstMesh.MeshletTrianglesBuffer = context.ResourceDatabase.Replace(temp.MeshletTrianglesBuffer.AsResource(), srcMesh.MeshletTrianglesBuffer.AsResource()).AsBuffer();
context.ResourceManager.ReleaseMesh(_tempHandle);
context.CommandBuffer.Barrier(
BarrierDesc.Buffer(dstMesh.VertexBuffer, BarrierSync.VertexShading, BarrierAccess.VertexBuffer | BarrierAccess.ShaderResource),
BarrierDesc.Buffer(dstMesh.IndexBuffer, BarrierSync.IndexInput, BarrierAccess.IndexBuffer | BarrierAccess.ShaderResource),
BarrierDesc.Buffer(dstMesh.MeshletBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(dstMesh.MeshletVerticesBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(dstMesh.MeshletTrianglesBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(dstMesh.MeshletGroupBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(dstMesh.MeshletHierarchyBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(dstMesh.MeshDataBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource));
_actualHandle = Handle<Mesh>.Invalid;
_rawData.Dispose();
_pVertices = null;
_pIndices = null;
_pMeshlets = null;
_pMeshletGroups = null;
_pMeshletHierarchyNodes = null;
_pMeshletVertices = null;
_pMeshletTriangles = null;
}
}

View File

@@ -1,28 +1,70 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics; using Ghost.Graphics;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
namespace Ghost.Engine; namespace Ghost.Engine.Streaming;
internal class ResourceStreamingProcessor : IResourceStreamingProcessor internal class ResourceStreamingProcessor : IResourceStreamingProcessor
{ {
private const int _MAX_UPLOADS_PER_FRAME = 8; private const int _MAX_UPLOADS_PER_FRAME = 8;
private readonly ConcurrentQueue<AssetEntry> _pendingUpload; private readonly ConcurrentQueue<ProcessableAssetEntry> _pendingProcess;
private readonly ConcurrentQueue<AssetEntry> _pendingFinalize; private readonly ConcurrentQueue<UploadableAssetEntry> _pendingUpload;
private readonly ConcurrentQueue<UploadableAssetEntry> _pendingFinalize;
private ulong _pendingCopyFenceValue; private ulong _pendingCopyFenceValue;
public ResourceStreamingProcessor() public ResourceStreamingProcessor()
{ {
_pendingUpload = new ConcurrentQueue<AssetEntry>(); _pendingProcess = new ConcurrentQueue<ProcessableAssetEntry>();
_pendingFinalize = new ConcurrentQueue<AssetEntry>(); _pendingUpload = new ConcurrentQueue<UploadableAssetEntry>();
_pendingFinalize = new ConcurrentQueue<UploadableAssetEntry>();
_pendingCopyFenceValue = 0; _pendingCopyFenceValue = 0;
} }
public void EnqueueForUpload(AssetEntry entry) public bool EnqueueForProcess(AssetEntry entry)
{ {
_pendingUpload.Enqueue(entry); if (entry is UploadableAssetEntry uploadable)
{
_pendingUpload.Enqueue(uploadable);
return true;
}
else if (entry is ProcessableAssetEntry processable)
{
_pendingProcess.Enqueue(processable);
return true;
}
return false;
}
public void ProcessPendingResource(JobScheduler jobScheduler, object? context)
{
using var scope = AllocationManager.CreateStackScope();
using var handles = new UnsafeList<JobHandle>(_pendingProcess.Count, scope.AllocationHandle);
while (_pendingProcess.TryDequeue(out var entry))
{
var result = entry.OnProcessing(context);
if (result.IsFailure)
{
Logger.Error(result.Message);
continue;
}
var handle = result.Value;
if (!handle.IsValid)
{
continue;
}
handles.Add(handle);
}
jobScheduler.WaitAll(handles);
} }
public void ProcessPendingUploads(ResourceStreamingContext context) public void ProcessPendingUploads(ResourceStreamingContext context)
@@ -32,6 +74,12 @@ internal class ResourceStreamingProcessor : IResourceStreamingProcessor
{ {
while (_pendingFinalize.TryDequeue(out var item)) while (_pendingFinalize.TryDequeue(out var item))
{ {
Volatile.Write(ref item.StateValue, (int)AssetState.Ready);
if (Interlocked.CompareExchange(ref item.PendingReimport, false, true))
{
item.AssetManager.ReimportAsset(item.AssetId); // re-queue
}
item.OnUploadComplete(context); item.OnUploadComplete(context);
} }
@@ -68,7 +116,7 @@ internal class ResourceStreamingProcessor : IResourceStreamingProcessor
continue; continue;
} }
entry.State = AssetState.Uploading; entry.State = AssetState.Processing;
_pendingFinalize.Enqueue(entry); _pendingFinalize.Enqueue(entry);
uploadCount++; uploadCount++;

View File

@@ -3,71 +3,18 @@ using Ghost.Core.Utilities;
using Ghost.Engine.Components; using Ghost.Engine.Components;
using Ghost.Engine.Core; using Ghost.Engine.Core;
using Ghost.Entities; using Ghost.Entities;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace Ghost.Engine; namespace Ghost.Engine.Streaming;
internal partial class AssetEntry internal static class SceneLoader
{
private static void RegisterSceneCallback()
{
s_onCreation[(int)AssetType.Scene] = null;
s_onParseRawData[(int)AssetType.Scene] = static (e) => e.ParseSceneData();
s_onRecordUpload[(int)AssetType.Scene] = static (e, ctx) => Result.Success();
s_onUploadComplete[(int)AssetType.Scene] = null;
s_onReleaseResource[(int)AssetType.Scene] = null;
}
private unsafe Result ParseSceneData()
{
var pData = (byte*)_rawData.GetUnsafePtr();
var dataSize = _rawData.Size;
if (dataSize < 12u)
{
return Result.Failure("Scene binary data is too small.");
}
var magic = Encoding.UTF8.GetString(pData, 4);
if (magic != "GSCN")
{
return Result.Failure("Invalid scene binary magic number.");
}
return Result.Success();
}
}
internal partial class AssetManager
{
internal unsafe void* GetSceneRawDataPtr(Guid assetID)
{
var entry = GetOrCreateEntry(assetID);
Logger.DebugAssert(entry.AssetType == AssetType.Scene);
Logger.DebugAssert(entry.State >= AssetState.Loaded);
return entry.RawData.GetUnsafePtr();
}
internal int ReleaseScene(Guid assetID)
{
if (assetID == Guid.Empty)
{
return 0;
}
if (!_entries.TryGetValue(assetID, out var entry) || entry.AssetType != AssetType.Scene)
{
return 0;
}
return entry.Release();
}
}
public static class SceneLoader
{ {
private struct BinaryEntityInfo : IDisposable private struct BinaryEntityInfo : IDisposable
{ {
@@ -118,29 +65,16 @@ public static class SceneLoader
} }
} }
public static unsafe Result<int> LoadSceneIntoWorld(World world, void* pRawData, int dataSize) public static unsafe Result<int> LoadSceneIntoWorld(World world, SceneContentHeader header, void* pRawData, nuint dataSize)
{ {
var reader = new SpanReader(new ReadOnlySpan<byte>(pRawData, dataSize)); var reader = new BufferReader((byte*)pRawData, dataSize);
var magic = Encoding.UTF8.GetString(reader.ReadSpan<byte>(4));
if (magic != "GSCN")
{
return Result.Failure("Invalid scene binary magic.");
}
var version = reader.Read<int>();
if (version != 1)
{
return Result.Failure($"Unsupported scene binary version: {version}");
}
using var scope = AllocationManager.CreateStackScope(); using var scope = AllocationManager.CreateStackScope();
var entityCount = reader.Read<int>(); using var entityInfos = new BinaryEntityInfoArray(header.entityCount, scope.AllocationHandle);
using var entityInfos = new BinaryEntityInfoArray(entityCount, scope.AllocationHandle); using var forwardMap = new UnsafeHashMap<int, Entity>(header.entityCount, scope.AllocationHandle);
using var forwardMap = new UnsafeHashMap<int, Entity>(entityCount, scope.AllocationHandle);
for (var i = 0; i < entityCount; i++) for (var i = 0; i < header.entityCount; i++)
{ {
var compCount = reader.Read<int>(); var compCount = reader.Read<int>();
if (compCount == 0) if (compCount == 0)
@@ -158,7 +92,7 @@ public static class SceneLoader
var dataSz = reader.Read<int>(); var dataSz = reader.Read<int>();
var dataOff = reader.Position; var dataOff = reader.Position;
reader.Position += dataSz; reader.Position += (nuint)dataSz;
var fieldCount = reader.Read<int>(); var fieldCount = reader.Read<int>();
var fieldOffsets = new UnsafeArray<int>(fieldCount, scope.AllocationHandle); var fieldOffsets = new UnsafeArray<int>(fieldCount, scope.AllocationHandle);
@@ -174,7 +108,7 @@ public static class SceneLoader
typeHash = typeHash, typeHash = typeHash,
typeID = typeID, typeID = typeID,
dataSize = dataSz, dataSize = dataSz,
dataOffset = dataOff, dataOffset = (int)dataOff,
entityFieldCount = fieldCount, entityFieldCount = fieldCount,
entityFieldOffsets = fieldOffsets, entityFieldOffsets = fieldOffsets,
}; };
@@ -191,7 +125,7 @@ public static class SceneLoader
using var typeIds = new UnsafeList<Identifier<IComponent>>(32, scope.AllocationHandle); using var typeIds = new UnsafeList<Identifier<IComponent>>(32, scope.AllocationHandle);
typeIds.Add(ComponentTypeID<SceneID>.Value); typeIds.Add(ComponentTypeID<SceneID>.Value);
for (var i = 0; i < entityCount; i++) for (var i = 0; i < header.entityCount; i++)
{ {
ref var info = ref entityInfos[i]; ref var info = ref entityInfos[i];
@@ -213,7 +147,7 @@ public static class SceneLoader
var activeScene = SceneManager.CreateScene(); var activeScene = SceneManager.CreateScene();
for (var i = 0; i < entityCount; i++) for (var i = 0; i < header.entityCount; i++)
{ {
if (!forwardMap.TryGetValue(i, out var entity)) if (!forwardMap.TryGetValue(i, out var entity))
{ {
@@ -238,7 +172,7 @@ public static class SceneLoader
} }
} }
for (var i = 0; i < entityCount; i++) for (var i = 0; i < header.entityCount; i++)
{ {
if (!forwardMap.TryGetValue(i, out var entity)) if (!forwardMap.TryGetValue(i, out var entity))
{ {
@@ -275,6 +209,87 @@ public static class SceneLoader
} }
} }
return Result.Success(entityCount); return Result.Success(header.entityCount);
}
}
[StructLayout(LayoutKind.Sequential, Size = 64)]
internal struct SceneContentHeader
{
public const uint MAGIC = 0x4E435347; // "GSCN" in little-endian
public const uint VERSION = 1;
public uint magic;
public uint version;
public int entityCount;
}
internal unsafe class SceneAssetEntry : ProcessableAssetEntry
{
public class AdditionalData
{
public required World world;
public SceneLoadingType loadingType;
}
private readonly World _targetWorld;
private readonly SceneLoadingType _loadingType;
private MemoryBlock _memory;
private SceneContentHeader _header;
public SceneAssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, Guid[] dependencies)
: base(manager, resourceDatabase, resourceManager, assetId, AssetType.Scene, dependencies)
{
// TODO: How can I get this? Ideally the public api will be something like SceneManager.LoadScene(World, Scene, SceneLoadingType).
// Should we handle the scene loading explicitly instead of auto loading on the first resolve?
_targetWorld = World.GetWorld(0)!;
_loadingType = SceneLoadingType.Single;
}
public override Result OnLoadRawData([OwnershipTransfer] ref MemoryBlock memory)
{
_memory = memory;
if (_memory.Size < (nuint)sizeof(SceneContentHeader))
{
return Result.Failure("The size of scene is too small.");
}
var header = _memory.GetElementAt<SceneContentHeader>(0);
if (header.magic != SceneContentHeader.MAGIC)
{
return Result.Failure($"Unexpected scene header. Expect {SceneContentHeader.MAGIC}, got {header.magic}");
}
// TODO: Support version update.
if (header.version != SceneContentHeader.VERSION)
{
return Result.Failure($"Unexpected scene version. Expect {SceneContentHeader.VERSION}, got {header.version}");
}
_header = header;
return Result.Success();
}
public override Result<JobHandle> OnProcessing(object? context)
{
var pData = (byte*)_memory.GetUnsafePtr() + sizeof(SceneContentHeader);
if (_loadingType == SceneLoadingType.Single)
{
// TODO: Support TimeData.
_targetWorld.Clear(default);
}
// TODO: Parallelize.
SceneLoader.LoadSceneIntoWorld(_targetWorld, _header, pData, _memory.Size - (nuint)sizeof(SceneContentHeader));
return JobHandle.Invalid;
}
public override void OnReleaseResource()
{
} }
} }

View File

@@ -2,14 +2,23 @@ using Ghost.Core;
using Ghost.Core.Utilities; using Ghost.Core.Utilities;
using Ghost.Graphics; using Ghost.Graphics;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Ghost.Graphics.Utilities; using Ghost.Graphics.Utilities;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ghost.Engine; namespace Ghost.Engine.Streaming;
[StructLayout(LayoutKind.Sequential, Size = 64)] // Leave extra space for future expansion without breaking compatibility [StructLayout(LayoutKind.Sequential, Size = 64)] // Leave extra space for future expansion without breaking compatibility
public struct TextureContentHeader public struct TextureContentHeader
{ {
public const uint MAGIC = 0x58455447; // GTEX
public const uint VERSION = 1;
public uint magic;
public uint version;
public uint width; public uint width;
public uint height; public uint height;
public uint bpc; public uint bpc;
@@ -18,34 +27,54 @@ public struct TextureContentHeader
public uint colorComponents; public uint colorComponents;
} }
internal partial class AssetEntry internal partial class AssetManager
{ {
private unsafe class TextureData public Handle<GPUTexture> ResolveTexture(Guid assetID)
{ {
public TextureDesc desc; if (assetID == Guid.Empty)
public TextureContentHeader header; {
public byte* pData; return Handle<GPUTexture>.Invalid;
public nuint dataSize;
} }
private static void RegisterTextureCallback() var entry = GetOrCreateEntry(assetID);
{ Logger.DebugAssert(entry.AssetType == AssetType.Texture);
s_onCreation[(int)AssetType.Texture] = static (e) =>
{
// This will create a new slot in the database, but not allocation any GPU resource.
// Everything in the slot will have the same value as the fallback texture, expect the slot will be marked as shared.
var handle = e._resourceDatabase.CreateEmpty().AsTexture();
e.SetStorage(handle);
};
s_onParseRawData[(int)AssetType.Texture] = static (e) => e.ParseTextureData(); return ((TextureAssetEntry)entry).TextureHandle;
s_onRecordUpload[(int)AssetType.Texture] = static (e, ctx) => e.RecordTextureUpload(ctx); }
s_onUploadComplete[(int)AssetType.Texture] = static (e, ctx) => e.OnTextureUploadComplete(ctx);
s_onReleaseResource[(int)AssetType.Texture] = static (e) => public int ReleaseTexture(Guid assetID)
{ {
var handle = e.GetStorage<Handle<GPUTexture>>(); if (assetID == Guid.Empty)
e._resourceDatabase.ReleaseResource(handle.AsResource()); {
}; return 0;
}
if (!_entries.TryGetValue(assetID, out var entry) || entry.AssetType != AssetType.Texture)
{
return 0;
}
return entry.Release();
}
}
internal unsafe class TextureAssetEntry : UploadableAssetEntry
{
private MemoryBlock _rawData;
private TextureDesc _desc;
private byte* _pData;
private nuint _dataSize;
private Handle<GPUTexture> _actualHandle;
private Handle<GPUTexture> _tempHandle;
public Handle<GPUTexture> TextureHandle => _actualHandle;
public TextureAssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, Guid[] dependencies)
: base(manager, resourceDatabase, resourceManager, assetId, AssetType.Texture, dependencies)
{
_actualHandle = resourceDatabase.CreateEmpty().AsTexture();
} }
private static TextureFormat GetTextureFormat(uint bpc, uint colorComponents) private static TextureFormat GetTextureFormat(uint bpc, uint colorComponents)
@@ -77,14 +106,27 @@ internal partial class AssetEntry
}; };
} }
private unsafe Result ParseTextureData() public override Result OnLoadRawData([OwnershipTransfer] ref MemoryBlock memory)
{ {
var pData = (byte*)_rawData.GetUnsafePtr(); _rawData = memory;
var pData = (byte*)memory.GetUnsafePtr();
Logger.DebugAssert(pData != null); Logger.DebugAssert(pData != null);
var reader = new BufferReader(pData, _rawData.Size); var reader = new BufferReader(pData, memory.Size);
var header = reader.Read<TextureContentHeader>(); var header = reader.Read<TextureContentHeader>();
if (header.magic != TextureContentHeader.MAGIC)
{
return Result.Failure($"Unexpected texture header {header.magic}.");
}
if (header.version != TextureContentHeader.VERSION)
{
return Result.Failure($"Unsupported header version {header.version}.");
}
var textureDesc = new TextureDesc var textureDesc = new TextureDesc
{ {
Width = header.width, Width = header.width,
@@ -96,86 +138,51 @@ internal partial class AssetEntry
Usage = TextureUsage.ShaderResource, Usage = TextureUsage.ShaderResource,
}; };
// Will the gc be fine here? _desc = textureDesc;
var textureData = new TextureData _pData = reader.CurrentAddress;
{ _dataSize = reader.RemainingBytes;
desc = textureDesc,
header = header,
pData = reader.CurrentAddress,
dataSize = reader.RemainingBytes,
};
_parsedObject = textureData;
return Result.Success(); return Result.Success();
} }
private unsafe Result RecordTextureUpload(ResourceStreamingContext context) public override Result OnRecordUploadCommands(ResourceStreamingContext context)
{ {
var textureData = _parsedObject as TextureData; Logger.DebugAssert(_pData != null);
Logger.DebugAssert(textureData != null);
var newHandle = RenderingUtility.CreateTexture( var newHandle = RenderingUtility.CreateTexture(
context.ResourceManager, context.ResourceManager,
context.ResourceDatabase, context.ResourceDatabase,
context.ResourceAllocator, context.ResourceAllocator,
context.CopyPipeline.GetCommandBuffer(), context.CopyPipeline.GetCommandBuffer(),
textureData.pData, _pData,
textureData.dataSize, _dataSize,
in textureData.desc); in _desc);
if (newHandle.IsInvalid) if (newHandle.IsInvalid)
{ {
return Result.Failure("Failed to create GPU texture."); return Result.Failure("Failed to create GPU texture.");
} }
var oldHandle = GetStorage<Handle<GPUTexture>>(); _tempHandle = newHandle;
SetStorage((oldHandle, newHandle));
return Result.Success(); return Result.Success();
} }
private void OnTextureUploadComplete(ResourceStreamingContext context) public override void OnUploadComplete(ResourceStreamingContext context)
{ {
var (oldHandle, newHandle) = GetStorage<(Handle<GPUTexture>, Handle<GPUTexture>)>(); var actualHandle = context.ResourceDatabase.Replace(_actualHandle.AsResource(), _tempHandle.AsResource());
var actualHandle = context.ResourceDatabase.Replace(oldHandle.AsResource(), newHandle.AsResource()); Logger.DebugAssert(actualHandle.IsValid);
context.GraphicsCommandBuffer.Barrier(BarrierDesc.Texture(oldHandle.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource, BarrierLayout.ShaderResource)); context.CommandBuffer.Barrier(BarrierDesc.Texture(actualHandle, BarrierSync.AllShading, BarrierAccess.ShaderResource, BarrierLayout.ShaderResource));
SetStorage((actualHandle, Handle<GPUTexture>.Invalid)); _actualHandle = Handle<GPUTexture>.Invalid;
_tempHandle = actualHandle.AsTexture();
_rawData.Dispose(); _rawData.Dispose();
_parsedObject = null; _pData = null;
} }
}
public override void OnReleaseResource()
internal partial class AssetManager {
{ ResourceDatabase.ReleaseResource(_tempHandle.AsResource());
public Handle<GPUTexture> ResolveTexture(Guid assetID)
{
if (assetID == Guid.Empty)
{
return Handle<GPUTexture>.Invalid;
}
var entry = GetOrCreateEntry(assetID);
Logger.DebugAssert(entry.AssetType == AssetType.Texture);
return entry.GetStorage<Handle<GPUTexture>>();
}
public int ReleaseTexture(Guid assetID)
{
if (assetID == Guid.Empty)
{
return 0;
}
if (!_entries.TryGetValue(assetID, out var entry) || entry.AssetType != AssetType.Texture)
{
return 0;
}
return entry.Release();
} }
} }

View File

@@ -262,6 +262,16 @@ public partial class ComponentManager : IDisposable
return Identifier<EntityQuery>.Invalid; return Identifier<EntityQuery>.Invalid;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Clear()
{
_archetypes.Clear();
_entityQueries.Clear();
_archetypeLookup.Clear();
_querieLookup.Clear();
}
/// <summary> /// <summary>
/// Gets a reference to the entity query with the specified identifier. /// Gets a reference to the entity query with the specified identifier.
/// </summary> /// </summary>

View File

@@ -29,13 +29,13 @@ public readonly record struct Entity
public bool IsValid public bool IsValid
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ID != INVALID_ID; get => Generation > 0;
} }
public static Entity Invalid public static Entity Invalid
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(INVALID_ID, INVALID_ID); get => default;
} }
internal Entity(EntityID id, GenerationID generation) internal Entity(EntityID id, GenerationID generation)

View File

@@ -33,11 +33,13 @@ public unsafe class EntityCommandBuffer : IDisposable
Dispose(); Dispose();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteHeader(ECBOpCode op) private void WriteHeader(ECBOpCode op)
{ {
_buffer.Add((byte)op); _buffer.Add((byte)op);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Write<T>(T value) private void Write<T>(T value)
where T : unmanaged where T : unmanaged
{ {
@@ -52,6 +54,7 @@ public unsafe class EntityCommandBuffer : IDisposable
MemoryUtility.MemCpy((byte*)_buffer.GetUnsafePtr() + idx, &value, (nuint)size); MemoryUtility.MemCpy((byte*)_buffer.GetUnsafePtr() + idx, &value, (nuint)size);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteSpan<T>(ReadOnlySpan<T> span) private void WriteSpan<T>(ReadOnlySpan<T> span)
where T : unmanaged where T : unmanaged
{ {
@@ -69,6 +72,7 @@ public unsafe class EntityCommandBuffer : IDisposable
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T Read<T>(ref int cursor) private T Read<T>(ref int cursor)
where T : unmanaged where T : unmanaged
{ {
@@ -81,6 +85,7 @@ public unsafe class EntityCommandBuffer : IDisposable
return value; return value;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Span<T> ReadSpan<T>(ref int cursor, int length) private Span<T> ReadSpan<T>(ref int cursor, int length)
where T : unmanaged where T : unmanaged
{ {
@@ -93,6 +98,7 @@ public unsafe class EntityCommandBuffer : IDisposable
return span; return span;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void* ReadBuffer(ref int cursor, int size) private void* ReadBuffer(ref int cursor, int size)
{ {
var ptr = (byte*)_buffer.GetUnsafePtr(); var ptr = (byte*)_buffer.GetUnsafePtr();
@@ -102,12 +108,14 @@ public unsafe class EntityCommandBuffer : IDisposable
return bufferPtr; return bufferPtr;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CreateEntity(int count = 1) public void CreateEntity(int count = 1)
{ {
WriteHeader(ECBOpCode.CreateEntity); WriteHeader(ECBOpCode.CreateEntity);
Write(count); Write(count);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CreateEntity(int count, ComponentSet set) public void CreateEntity(int count, ComponentSet set)
{ {
WriteHeader(ECBOpCode.CreateEntityWithComponents); WriteHeader(ECBOpCode.CreateEntityWithComponents);
@@ -116,12 +124,14 @@ public unsafe class EntityCommandBuffer : IDisposable
WriteSpan(set.Components); WriteSpan(set.Components);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void DestroyEntity(Entity entity) public void DestroyEntity(Entity entity)
{ {
WriteHeader(ECBOpCode.DestroyEntity); WriteHeader(ECBOpCode.DestroyEntity);
Write(entity); Write(entity);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddComponent<T>(Entity entity, T component = default) public void AddComponent<T>(Entity entity, T component = default)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
@@ -131,6 +141,7 @@ public unsafe class EntityCommandBuffer : IDisposable
Write(component); Write(component);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveComponent<T>(Entity entity) public void RemoveComponent<T>(Entity entity)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
@@ -139,6 +150,7 @@ public unsafe class EntityCommandBuffer : IDisposable
Write(ComponentTypeID<T>.Value); Write(ComponentTypeID<T>.Value);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetComponent<T>(Entity entity, T component) public void SetComponent<T>(Entity entity, T component)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {

View File

@@ -4,6 +4,7 @@ using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Ghost.Entities; namespace Ghost.Entities;
@@ -60,6 +61,7 @@ public unsafe partial class EntityManager : IDisposable
Dispose(); Dispose();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Error UpdateEntityLocation(Entity entity, Identifier<Archetype> newArchetypeID, int newChunkIndex, int newRowIndex) internal Error UpdateEntityLocation(Entity entity, Identifier<Archetype> newArchetypeID, int newChunkIndex, int newRowIndex)
{ {
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist); ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
@@ -75,6 +77,7 @@ public unsafe partial class EntityManager : IDisposable
return Error.None; return Error.None;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Result<EntityLocation, Error> GetEntityLocation(Entity entity) internal Result<EntityLocation, Error> GetEntityLocation(Entity entity)
{ {
if (_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) if (_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
@@ -85,6 +88,12 @@ public unsafe partial class EntityManager : IDisposable
return Error.NotFound; return Error.NotFound;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Clear()
{
_entityLocations.Clear();
}
private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow, private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
ref Archetype newArch, int newChunk, int newRow) ref Archetype newArch, int newChunk, int newRow)
{ {
@@ -112,6 +121,7 @@ public unsafe partial class EntityManager : IDisposable
/// Create an entity with no components. /// Create an entity with no components.
/// </summary> /// </summary>
/// <returns>The created entity.</returns> /// <returns>The created entity.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Entity CreateEntity() public Entity CreateEntity()
{ {
var entities = (Span<Entity>)stackalloc Entity[1]; var entities = (Span<Entity>)stackalloc Entity[1];
@@ -125,6 +135,7 @@ public unsafe partial class EntityManager : IDisposable
/// </summary> /// </summary>
/// <param name="set">A set of component space IDs to add to the entities.</param> /// <param name="set">A set of component space IDs to add to the entities.</param>
/// <returns>The created entity.</returns> /// <returns>The created entity.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Entity CreateEntity(ComponentSetView set) public Entity CreateEntity(ComponentSetView set)
{ {
var entities = (Span<Entity>)stackalloc Entity[1]; var entities = (Span<Entity>)stackalloc Entity[1];
@@ -162,6 +173,7 @@ public unsafe partial class EntityManager : IDisposable
/// Create multiple entities with no components. /// Create multiple entities with no components.
/// </summary> /// </summary>
/// <param name="count">The number of entities to create.</param> /// <param name="count">The number of entities to create.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CreateEntities(int count) public void CreateEntities(int count)
{ {
ref var emptyArchetype = ref _world.ComponentManager.GetArchetypeReference(World.EmptyArchetypeID); ref var emptyArchetype = ref _world.ComponentManager.GetArchetypeReference(World.EmptyArchetypeID);
@@ -462,6 +474,7 @@ public unsafe partial class EntityManager : IDisposable
/// </summary> /// </summary>
/// <param name="entity">The entity to check.</param> /// <param name="entity">The entity to check.</param>
/// <returns>True if the entity exists, false otherwise.</returns> /// <returns>True if the entity exists, false otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Exists(Entity entity) public bool Exists(Entity entity)
{ {
return _entityLocations.Contains(entity.ID, entity.Generation); return _entityLocations.Contains(entity.ID, entity.Generation);
@@ -514,6 +527,7 @@ public unsafe partial class EntityManager : IDisposable
/// <typeparam name="T">The component space.</typeparam> /// <typeparam name="T">The component space.</typeparam>
/// <param name="component">The component data.</param> /// <param name="component">The component data.</param>
/// <returns>The result status of the operation.</returns> /// <returns>The result status of the operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Error CreateSingleton<T>(T component = default) public Error CreateSingleton<T>(T component = default)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
@@ -553,6 +567,7 @@ public unsafe partial class EntityManager : IDisposable
/// </summary> /// </summary>
/// <typeparam name="T">The component space.</typeparam> /// <typeparam name="T">The component space.</typeparam>
/// <returns>Reference to the component data. null ref if not found.</returns> /// <returns>Reference to the component data. null ref if not found.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetSingleton<T>() public ref T GetSingleton<T>()
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
@@ -660,6 +675,7 @@ public unsafe partial class EntityManager : IDisposable
/// <param name="entity">The entity to add the component to.</param> /// <param name="entity">The entity to add the component to.</param>
/// <param name="component">The component data.</param> /// <param name="component">The component data.</param>
/// <returns>The result status of the operation.</returns> /// <returns>The result status of the operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Error AddComponent<T>(Entity entity, T component = default) public Error AddComponent<T>(Entity entity, T component = default)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
@@ -769,6 +785,7 @@ public unsafe partial class EntityManager : IDisposable
/// <typeparam name="T">The component space.</typeparam> /// <typeparam name="T">The component space.</typeparam>
/// <param name="entity">The entity to remove the component from.</param> /// <param name="entity">The entity to remove the component from.</param>
/// <returns>The result status of the operation.</returns> /// <returns>The result status of the operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Error RemoveComponent<T>(Entity entity) public Error RemoveComponent<T>(Entity entity)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
@@ -782,6 +799,7 @@ public unsafe partial class EntityManager : IDisposable
/// <param name="componentID">The component space ID to set.</param> /// <param name="componentID">The component space ID to set.</param>
/// <param name="pComponent">Pointer to the component data.</param> /// <param name="pComponent">Pointer to the component data.</param>
/// <returns>The result status of the operation.</returns> /// <returns>The result status of the operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Error SetComponent(Entity entity, Identifier<IComponent> componentID, void* pComponent) public Error SetComponent(Entity entity, Identifier<IComponent> componentID, void* pComponent)
{ {
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
@@ -801,6 +819,7 @@ public unsafe partial class EntityManager : IDisposable
/// <typeparam name="T">The component space.</typeparam> /// <typeparam name="T">The component space.</typeparam>
/// <param name="entity">The entity to set the component data for.</param> /// <param name="entity">The entity to set the component data for.</param>
/// <param name="component">The component data.</param> /// <param name="component">The component data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Error SetComponent<T>(Entity entity, T component) public Error SetComponent<T>(Entity entity, T component)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
@@ -813,6 +832,7 @@ public unsafe partial class EntityManager : IDisposable
/// <param name="entity">The entity to get the component data for.</param> /// <param name="entity">The entity to get the component data for.</param>
/// <param name="componentID">The component space ID to get.</param> /// <param name="componentID">The component space ID to get.</param>
/// <returns>Pointer to the component data, or null if not found.</returns> /// <returns>Pointer to the component data, or null if not found.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void* GetComponent(Entity entity, Identifier<IComponent> componentID) public void* GetComponent(Entity entity, Identifier<IComponent> componentID)
{ {
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
@@ -830,6 +850,7 @@ public unsafe partial class EntityManager : IDisposable
/// <typeparam name="T">The component space.</typeparam> /// <typeparam name="T">The component space.</typeparam>
/// <param name="entity">The entity to get the component data for.</param> /// <param name="entity">The entity to get the component data for.</param>
/// <returns>Reference to the component data. null ref if not found.</returns> /// <returns>Reference to the component data. null ref if not found.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetComponent<T>(Entity entity) public ref T GetComponent<T>(Entity entity)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
@@ -843,6 +864,7 @@ public unsafe partial class EntityManager : IDisposable
/// <param name="entity">The entity to check.</param> /// <param name="entity">The entity to check.</param>
/// <param name="componentID">The component space ID to check.</param> /// <param name="componentID">The component space ID to check.</param>
/// <returns>True if the entity has the component, false otherwise.</returns> /// <returns>True if the entity has the component, false otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent(Entity entity, Identifier<IComponent> componentID) public bool HasComponent(Entity entity, Identifier<IComponent> componentID)
{ {
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location)) if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
@@ -860,6 +882,7 @@ public unsafe partial class EntityManager : IDisposable
/// <typeparam name="T">The component space.</typeparam> /// <typeparam name="T">The component space.</typeparam>
/// <param name="entity">The entity to check.</param> /// <param name="entity">The entity to check.</param>
/// <returns>True if the entity has the component, false otherwise.</returns> /// <returns>True if the entity has the component, false otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasComponent<T>(Entity entity) public bool HasComponent<T>(Entity entity)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
@@ -918,6 +941,7 @@ public unsafe partial class EntityManager : IDisposable
/// <param name="entity">The entity to set the enabled state for.</param> /// <param name="entity">The entity to set the enabled state for.</param>
/// <param name="enabled">True to enable the component, false to disable it.</ /// <param name="enabled">True to enable the component, false to disable it.</
/// <returns>The result status of the operation.</returns> /// <returns>The result status of the operation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Error SetEnabled<T>(Entity entity, bool enabled) public Error SetEnabled<T>(Entity entity, bool enabled)
where T : unmanaged, IEnableableComponent where T : unmanaged, IEnableableComponent
{ {

View File

@@ -1,5 +1,6 @@
using Ghost.Core; using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ghost.Entities; namespace Ghost.Entities;
@@ -392,12 +393,14 @@ public sealed class SystemManager : IDisposable
AddSystem<DefaultSystemGroup>(); AddSystem<DefaultSystemGroup>();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddSystem<T>() public void AddSystem<T>()
where T : ISystem, new() where T : ISystem, new()
{ {
_systems.Add(new T()); _systems.Add(new T());
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetSystem<T>() public T GetSystem<T>()
where T : ISystem where T : ISystem
{ {
@@ -412,6 +415,7 @@ public sealed class SystemManager : IDisposable
throw new InvalidOperationException($"System of type {typeof(T).FullName} not found in SystemManager."); throw new InvalidOperationException($"System of type {typeof(T).FullName} not found in SystemManager.");
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void InitializeAll(TimeData timeData) internal void InitializeAll(TimeData timeData)
{ {
if (_systems.Count == 0) if (_systems.Count == 0)
@@ -431,6 +435,7 @@ public sealed class SystemManager : IDisposable
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void UpdateAll(TimeData timeData) internal void UpdateAll(TimeData timeData)
{ {
if (_systems.Count == 0) if (_systems.Count == 0)
@@ -469,6 +474,12 @@ public sealed class SystemManager : IDisposable
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Clear()
{
_systems.Clear();
}
public void Dispose() public void Dispose()
{ {
CleanupAll(default); CleanupAll(default);

View File

@@ -144,8 +144,8 @@ public partial class World : IDisposable, IEquatable<World>
if (jobScheduler != null) if (jobScheduler != null)
{ {
_threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount]; _threadLocalECBs = new EntityCommandBuffer[jobScheduler.ThreadLocalCount];
for (var i = 0; i < jobScheduler.WorkerCount; i++) for (var i = 0; i < _threadLocalECBs.Length; i++)
{ {
_threadLocalECBs[i] = new EntityCommandBuffer(_entityManager); _threadLocalECBs[i] = new EntityCommandBuffer(_entityManager);
} }
@@ -239,6 +239,26 @@ public partial class World : IDisposable, IEquatable<World>
return _services.ContainsKey(typeof(T)); return _services.ContainsKey(typeof(T));
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear(TimeData timeData)
{
_entityManager.Clear();
_entityCommandBuffer.Reset();
if (_threadLocalECBs != null)
{
for (var i = 0; i < _threadLocalECBs.Length; i ++)
{
_threadLocalECBs[i].Reset();
}
}
_componentManager.Clear();
_systemManager.CleanupAll(timeData);
_systemManager.InitializeAll(timeData);
}
public bool Equals(World? other) public bool Equals(World? other)
{ {
return other is not null && _id == other._id; return other is not null && _id == other._id;

View File

@@ -649,6 +649,18 @@ public struct BarrierDesc
}; };
} }
public static BarrierDesc Buffer(Handle<GPUBuffer> resource, BarrierSync syncAfter, BarrierAccess accessAfter, bool isAliasing = false)
{
return new BarrierDesc
{
Type = BarrierType.Buffer,
Resource = resource.AsResource(),
SyncAfter = syncAfter,
AccessAfter = accessAfter,
IsAliasing = isAliasing
};
}
public static BarrierDesc Texture(Handle<GPUResource> resource, BarrierSync syncAfter, BarrierAccess accessAfter, BarrierLayout layoutAfter, BarrierSubresourceRange subresources = default, bool discard = false, bool isAliasing = false) public static BarrierDesc Texture(Handle<GPUResource> resource, BarrierSync syncAfter, BarrierAccess accessAfter, BarrierLayout layoutAfter, BarrierSubresourceRange subresources = default, bool discard = false, bool isAliasing = false)
{ {
return new BarrierDesc return new BarrierDesc
@@ -663,6 +675,22 @@ public struct BarrierDesc
IsAliasing = isAliasing IsAliasing = isAliasing
}; };
} }
public static BarrierDesc Texture(Handle<GPUTexture> resource, BarrierSync syncAfter, BarrierAccess accessAfter, BarrierLayout layoutAfter, BarrierSubresourceRange subresources = default, bool discard = false, bool isAliasing = false)
{
return new BarrierDesc
{
Type = BarrierType.Texture,
Resource = resource.AsResource(),
SyncAfter = syncAfter,
AccessAfter = accessAfter,
LayoutAfter = layoutAfter,
Subresources = subresources,
Discard = discard,
IsAliasing = isAliasing
};
}
} }
public record struct ResourceDesc public record struct ResourceDesc

View File

@@ -1,4 +1,5 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities; using Ghost.Graphics.Utilities;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
@@ -69,6 +70,19 @@ public struct MeshletMeshData : IDisposable
public int lodLevelCount; public int lodLevelCount;
public int materialSlotCount; public int materialSlotCount;
public readonly MeshletMeshData Clone()
{
var newData = this;
newData.meshlets = meshlets.Clone(AllocationHandle.Persistent);
newData.groups = groups.Clone(AllocationHandle.Persistent);
newData.hierarchyNodes = hierarchyNodes.Clone(AllocationHandle.Persistent);
newData.meshletVertices = meshletVertices.Clone(AllocationHandle.Persistent);
newData.meshletTriangles = meshletTriangles.Clone(AllocationHandle.Persistent);
return newData;
}
public void Dispose() public void Dispose()
{ {
meshlets.Dispose(); meshlets.Dispose();
@@ -86,11 +100,11 @@ public struct Mesh : IResourceReleasable
private MeshletMeshData _meshletData; private MeshletMeshData _meshletData;
[UnscopedRef] [UnscopedRef]
public readonly ref readonly MeshletMeshData MeshletData => ref _meshletData; public ref MeshletMeshData MeshletData => ref _meshletData;
internal bool IsMeshDataDirty internal bool IsMeshDataDirty
{ {
get; private set; get; set;
} }
/// <summary> /// <summary>
@@ -101,6 +115,7 @@ public struct Mesh : IResourceReleasable
readonly get => _vertices; readonly get => _vertices;
set set
{ {
_vertices.Dispose();
_vertices = value; _vertices = value;
VertexCount = value.Count; VertexCount = value.Count;
IsMeshDataDirty = true; IsMeshDataDirty = true;
@@ -115,6 +130,7 @@ public struct Mesh : IResourceReleasable
readonly get => _indices; readonly get => _indices;
set set
{ {
_indices.Dispose();
_indices = value; _indices = value;
IndexCount = value.Count; IndexCount = value.Count;
IsMeshDataDirty = true; IsMeshDataDirty = true;
@@ -126,7 +142,7 @@ public struct Mesh : IResourceReleasable
/// </summary> /// </summary>
public int VertexCount public int VertexCount
{ {
get; private set; get; internal set;
} }
/// <summary> /// <summary>
@@ -134,7 +150,7 @@ public struct Mesh : IResourceReleasable
/// </summary> /// </summary>
public int IndexCount public int IndexCount
{ {
get; private set; get; internal set;
} }
/// <summary> /// <summary>
@@ -164,7 +180,7 @@ public struct Mesh : IResourceReleasable
/// <summary> /// <summary>
/// Gets the handle to the meshlet buffer on the GPU. /// Gets the handle to the meshlet buffer on the GPU.
/// </summary> /// </summary>
public Handle<GPUBuffer> MeshLetBuffer public Handle<GPUBuffer> MeshletBuffer
{ {
get; internal set; get; internal set;
} }
@@ -209,17 +225,15 @@ public struct Mesh : IResourceReleasable
get; internal set; get; internal set;
} }
internal void SetMeshletSummary(int meshletCount, int lodLevelCount, int materialSlotCount) public readonly Mesh Clone()
{ {
_meshletData.meshletCount = meshletCount; var newData = this;
_meshletData.lodLevelCount = lodLevelCount;
_meshletData.materialSlotCount = materialSlotCount;
}
internal void SetCounts(int vertexCount, int indexCount) newData._vertices = _vertices.Clone(AllocationHandle.Persistent);
{ newData._indices = _indices.Clone(AllocationHandle.Persistent);
VertexCount = vertexCount; newData._meshletData = _meshletData.Clone();
IndexCount = indexCount;
return newData;
} }
public void ReleaseCpuResources() public void ReleaseCpuResources()
@@ -235,7 +249,7 @@ public struct Mesh : IResourceReleasable
database.ReleaseResource(VertexBuffer.AsResource()); database.ReleaseResource(VertexBuffer.AsResource());
database.ReleaseResource(IndexBuffer.AsResource()); database.ReleaseResource(IndexBuffer.AsResource());
database.ReleaseResource(MeshLetBuffer.AsResource()); database.ReleaseResource(MeshletBuffer.AsResource());
database.ReleaseResource(MeshletVerticesBuffer.AsResource()); database.ReleaseResource(MeshletVerticesBuffer.AsResource());
database.ReleaseResource(MeshletTrianglesBuffer.AsResource()); database.ReleaseResource(MeshletTrianglesBuffer.AsResource());
database.ReleaseResource(MeshletGroupBuffer.AsResource()); database.ReleaseResource(MeshletGroupBuffer.AsResource());

View File

@@ -10,36 +10,45 @@ using System.Runtime.InteropServices;
namespace Ghost.Graphics.Core; namespace Ghost.Graphics.Core;
// TODO: Temporary rendering context for heap creation and data upload. We will refactor it later when we have a better understanding of the engine architecture. // TODO: Temporary rendering context for heap creation and data upload. We will refactor it later when we have a better understanding of the engine architecture.
public readonly unsafe ref struct RenderContext public unsafe class RenderContext
{ {
public required ICommandBuffer CommandBuffer public ICommandBuffer CommandBuffer
{ {
get; init; get; internal set;
} = null!;
public ResourceManager ResourceManager
{
get;
} }
public required ResourceManager ResourceManager public IResourceAllocator ResourceAllocator
{ {
get; init; get;
} }
public required IResourceAllocator ResourceAllocator public IResourceDatabase ResourceDatabase
{ {
get; init; get;
} }
public required IResourceDatabase ResourceDatabase public IPipelineLibrary PipelineLibrary
{ {
get; init; get;
}
public required IPipelineLibrary PipelineLibrary
{
get; init;
} }
internal ShaderLibrary ShaderLibrary internal ShaderLibrary ShaderLibrary
{ {
get; init; get;
}
internal RenderContext(ResourceManager resourceManager, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, IPipelineLibrary pipelineLibrary, ShaderLibrary shaderLibrary)
{
ResourceManager = resourceManager;
ResourceAllocator = resourceAllocator;
ResourceDatabase = resourceDatabase;
PipelineLibrary = pipelineLibrary;
ShaderLibrary = shaderLibrary;
} }
private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync) private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync)
@@ -269,25 +278,25 @@ public readonly unsafe ref struct RenderContext
HeapType = HeapType.Default, HeapType = HeapType.Default,
}; };
meshRef.MeshLetBuffer = ResourceAllocator.CreateBuffer(in meshletDesc, "Meshlets"); meshRef.MeshletBuffer = ResourceAllocator.CreateBuffer(in meshletDesc, "Meshlets");
meshRef.MeshletVerticesBuffer = ResourceAllocator.CreateBuffer(in verticesDesc, "MeshletVertices"); meshRef.MeshletVerticesBuffer = ResourceAllocator.CreateBuffer(in verticesDesc, "MeshletVertices");
meshRef.MeshletTrianglesBuffer = ResourceAllocator.CreateBuffer(in trianglesDesc, "MeshletTriangles"); meshRef.MeshletTrianglesBuffer = ResourceAllocator.CreateBuffer(in trianglesDesc, "MeshletTriangles");
meshRef.MeshletGroupBuffer = ResourceAllocator.CreateBuffer(in groupsDesc, "MeshletGroups"); meshRef.MeshletGroupBuffer = ResourceAllocator.CreateBuffer(in groupsDesc, "MeshletGroups");
meshRef.MeshletHierarchyBuffer = ResourceAllocator.CreateBuffer(in hierarchyDesc, "MeshletHierarchy"); meshRef.MeshletHierarchyBuffer = ResourceAllocator.CreateBuffer(in hierarchyDesc, "MeshletHierarchy");
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); TransitionBarrier(meshRef.MeshletBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletGroupBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); TransitionBarrier(meshRef.MeshletGroupBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletHierarchyBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); TransitionBarrier(meshRef.MeshletHierarchyBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
UploadBuffer(meshRef.MeshLetBuffer, meshletData.meshlets.AsSpan()); UploadBuffer(meshRef.MeshletBuffer, meshletData.meshlets.AsSpan());
UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan()); UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan());
UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan()); UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan());
UploadBuffer(meshRef.MeshletGroupBuffer, meshletData.groups.AsSpan()); UploadBuffer(meshRef.MeshletGroupBuffer, meshletData.groups.AsSpan());
UploadBuffer(meshRef.MeshletHierarchyBuffer, meshletData.hierarchyNodes.AsSpan()); UploadBuffer(meshRef.MeshletHierarchyBuffer, meshletData.hierarchyNodes.AsSpan());
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading); TransitionBarrier(meshRef.MeshletBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading); TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading); TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletGroupBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading); TransitionBarrier(meshRef.MeshletGroupBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
@@ -309,7 +318,7 @@ public readonly unsafe ref struct RenderContext
worldBoundsMax = meshData.BoundingBox.Max, worldBoundsMax = meshData.BoundingBox.Max,
vertexBuffer = ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()), vertexBuffer = ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()),
indexBuffer = ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()), indexBuffer = ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()),
meshletBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshLetBuffer.AsResource()), meshletBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletBuffer.AsResource()),
meshletVerticesBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletVerticesBuffer.AsResource()), meshletVerticesBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletVerticesBuffer.AsResource()),
meshletTrianglesBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletTrianglesBuffer.AsResource()), meshletTrianglesBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletTrianglesBuffer.AsResource()),
meshletGroupBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletGroupBuffer.AsResource()), meshletGroupBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletGroupBuffer.AsResource()),

View File

@@ -3,31 +3,39 @@ using Ghost.Graphics.Services;
namespace Ghost.Graphics; namespace Ghost.Graphics;
internal readonly struct ResourceStreamingContext internal struct ResourceStreamingContext
{ {
public required AsyncCopyPipeline CopyPipeline public AsyncCopyPipeline CopyPipeline
{ {
get; init; get;
} }
public required ResourceManager ResourceManager public ResourceManager ResourceManager
{ {
get; init; get;
} }
public required IResourceDatabase ResourceDatabase public IResourceDatabase ResourceDatabase
{ {
get; init; get;
} }
public required IResourceAllocator ResourceAllocator public IResourceAllocator ResourceAllocator
{ {
get; init; get;
} }
public required ICommandBuffer GraphicsCommandBuffer public ICommandBuffer CommandBuffer
{ {
get; init; get; set;
} = null!;
internal ResourceStreamingContext(AsyncCopyPipeline copyPipeline, ResourceManager resourceManager, IResourceDatabase resourceDatabase, IResourceAllocator resourceAllocator)
{
CopyPipeline = copyPipeline;
ResourceManager = resourceManager;
ResourceDatabase = resourceDatabase;
ResourceAllocator = resourceAllocator;
} }
} }

View File

@@ -244,6 +244,23 @@ public class RenderSystem : IDisposable
var waitHandles = new WaitHandle[] { null!, _shutdownEvent }; var waitHandles = new WaitHandle[] { null!, _shutdownEvent };
var streamingContext = new ResourceStreamingContext
(
_asyncCopyPipeline,
_resourceManager,
_graphicsEngine.ResourceDatabase,
_graphicsEngine.ResourceAllocator
);
var renderContext = new RenderContext
(
_resourceManager,
_graphicsEngine.ResourceAllocator,
_graphicsEngine.ResourceDatabase,
_graphicsEngine.PipelineLibrary,
_shaderLibrary
);
while (_isRunning) while (_isRunning)
{ {
var frameIndex = (int)(_submittedFenceValue % (ulong)_frameResources.Length); var frameIndex = (int)(_submittedFenceValue % (ulong)_frameResources.Length);
@@ -304,25 +321,11 @@ public class RenderSystem : IDisposable
{ {
cmd.Begin(frameResource.CommandAllocator); cmd.Begin(frameResource.CommandAllocator);
var streamingContext = new ResourceStreamingContext streamingContext.CommandBuffer = cmd;
{ renderContext.CommandBuffer = cmd;
CopyPipeline = _asyncCopyPipeline,
GraphicsCommandBuffer = cmd,
ResourceAllocator = _graphicsEngine.ResourceAllocator,
ResourceDatabase = _graphicsEngine.ResourceDatabase,
ResourceManager = _resourceManager
};
_streamingProcessor.ProcessPendingUploads(streamingContext); _streamingProcessor.ProcessPendingUploads(streamingContext);
var renderContext = new RenderContext
{
CommandBuffer = cmd,
PipelineLibrary = _graphicsEngine.PipelineLibrary,
ResourceAllocator = _graphicsEngine.ResourceAllocator,
ResourceDatabase = _graphicsEngine.ResourceDatabase,
ResourceManager = _resourceManager,
ShaderLibrary = _shaderLibrary,
};
_renderPipeline.Render(renderContext, frameIndex, frameResource.RenderPayload); _renderPipeline.Render(renderContext, frameIndex, frameResource.RenderPayload);
_swapChainManager.TransitionToPresent(cmd); _swapChainManager.TransitionToPresent(cmd);

View File

@@ -2,6 +2,7 @@ using Ghost.Core;
using Ghost.Core.Graphics; using Ghost.Core.Graphics;
using Ghost.Graphics.Core; using Ghost.Graphics.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics.Geometry; using Misaki.HighPerformance.Mathematics.Geometry;
@@ -158,87 +159,20 @@ public sealed partial class ResourceManager : IDisposable
MeshDataBuffer = meshDataBuffer, MeshDataBuffer = meshDataBuffer,
}; };
lock (_meshWriteLock) return RegisterMesh(ref mesh);
{
var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation);
}
} }
public Handle<Mesh> CreateEmptyMesh(string? name = null) public Handle<Mesh> RegisterMesh([OwnershipTransfer] ref Mesh mesh)
{ {
Logger.DebugAssert(!_disposed); Logger.DebugAssert(!_disposed);
lock (_meshWriteLock) lock(_meshWriteLock)
{
var id = _meshes.Add(new Mesh(), out var generation);
return new Handle<Mesh>(id, generation);
}
}
public Handle<Mesh> CreateUploadedMesh(
Handle<GPUBuffer> vertexBuffer,
Handle<GPUBuffer> indexBuffer,
Handle<GPUBuffer> meshletBuffer,
Handle<GPUBuffer> meshletVerticesBuffer,
Handle<GPUBuffer> meshletTrianglesBuffer,
Handle<GPUBuffer> meshletGroupBuffer,
Handle<GPUBuffer> meshletHierarchyBuffer,
Handle<GPUBuffer> meshDataBuffer,
int vertexCount,
int indexCount,
int meshletCount,
int lodLevelCount,
int materialSlotCount,
AABB boundingBox)
{
Logger.DebugAssert(!_disposed);
var mesh = new Mesh
{
VertexBuffer = vertexBuffer,
IndexBuffer = indexBuffer,
MeshLetBuffer = meshletBuffer,
MeshletVerticesBuffer = meshletVerticesBuffer,
MeshletTrianglesBuffer = meshletTrianglesBuffer,
MeshletGroupBuffer = meshletGroupBuffer,
MeshletHierarchyBuffer = meshletHierarchyBuffer,
MeshDataBuffer = meshDataBuffer,
BoundingBox = boundingBox,
};
mesh.SetCounts(vertexCount, indexCount);
mesh.SetMeshletSummary(meshletCount, lodLevelCount, materialSlotCount);
lock (_meshWriteLock)
{ {
var id = _meshes.Add(mesh, out var generation); var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation); return new Handle<Mesh>(id, generation);
} }
} }
public Handle<Mesh> ReplaceMesh(Handle<Mesh> dst, Handle<Mesh> src)
{
Logger.DebugAssert(!_disposed);
lock (_meshWriteLock)
{
ref var dstMesh = ref _meshes.GetElementReferenceAt(dst.ID, dst.Generation, out var dstExists);
ref var srcMesh = ref _meshes.GetElementReferenceAt(src.ID, src.Generation, out var srcExists);
if (!dstExists || !srcExists)
{
return Handle<Mesh>.Invalid;
}
var oldMesh = dstMesh;
dstMesh = srcMesh;
_meshes.Remove(src.ID, src.Generation);
oldMesh.ReleaseResource(_resourceDatabase);
return dst;
}
}
/// <summary> /// <summary>
/// Creates a new material instance using the specified shader. /// Creates a new material instance using the specified shader.
/// </summary> /// </summary>
@@ -330,16 +264,12 @@ public sealed partial class ResourceManager : IDisposable
lock (_meshWriteLock) lock (_meshWriteLock)
{ {
ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (_meshes.Remove(handle.ID, handle.Generation, out var mesh))
if (!exist)
{ {
return;
}
_meshes.Remove(handle.ID, handle.Generation);
mesh.ReleaseResource(_resourceDatabase); mesh.ReleaseResource(_resourceDatabase);
} }
} }
}
/// <summary> /// <summary>
/// Determines whether a material with the specified handle exists in the collection. /// Determines whether a material with the specified handle exists in the collection.
@@ -378,16 +308,12 @@ public sealed partial class ResourceManager : IDisposable
lock (_materialWriteLock) lock (_materialWriteLock)
{ {
ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (_materials.Remove(handle.ID, handle.Generation, out var material))
if (!exist)
{ {
return;
}
_materials.Remove(handle.ID, handle.Generation);
material.ReleaseResource(_resourceDatabase); material.ReleaseResource(_resourceDatabase);
} }
} }
}
/// <summary> /// <summary>
/// Returns an existing material palette index for the specified material sequence or creates a new one. /// Returns an existing material palette index for the specified material sequence or creates a new one.
@@ -582,16 +508,12 @@ public sealed partial class ResourceManager : IDisposable
lock (_shaderWriteLock) lock (_shaderWriteLock)
{ {
ref var shader = ref _shaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (_shaders.Remove(handle.ID, handle.Generation, out var shader))
if (!exist)
{ {
return;
}
_shaders.Remove(handle.ID, handle.Generation);
shader.ReleaseResource(_resourceDatabase); shader.ReleaseResource(_resourceDatabase);
} }
} }
}
/// <summary> /// <summary>
/// Determines whether a compute shader with the specified identifier exists in the collection. /// Determines whether a compute shader with the specified identifier exists in the collection.
@@ -630,16 +552,12 @@ public sealed partial class ResourceManager : IDisposable
lock (_computeShaderWriteLock) lock (_computeShaderWriteLock)
{ {
ref var computeShader = ref _computeShaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (_computeShaders.Remove(handle.ID, handle.Generation, out var computeShader))
if (!exist)
{ {
return;
}
_computeShaders.Remove(handle.ID, handle.Generation);
computeShader.ReleaseResource(_resourceDatabase); computeShader.ReleaseResource(_resourceDatabase);
} }
} }
}
public void Dispose() public void Dispose()
{ {

View File

@@ -1,5 +1,6 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Engine.Streaming;
using Ghost.Graphics; using Ghost.Graphics;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Services; using Ghost.Graphics.Services;
@@ -88,14 +89,14 @@ public class AssetManagerTest
ResourceDatabase = _graphicsEngine.ResourceDatabase, ResourceDatabase = _graphicsEngine.ResourceDatabase,
ResourceAllocator = _graphicsEngine.ResourceAllocator, ResourceAllocator = _graphicsEngine.ResourceAllocator,
CopyPipeline = _copyPipeline, CopyPipeline = _copyPipeline,
GraphicsCommandBuffer = _commandBuffer, CommandBuffer = _commandBuffer,
}; };
_processor.ProcessPendingUploads(ctx); _processor.ProcessPendingUploads(ctx);
await Task.Delay(1000, TestContext.CancellationToken); await Task.Delay(1000, TestContext.CancellationToken);
Assert.IsGreaterThanOrEqualTo((int)AssetState.Uploading, entry.StateValue); Assert.IsGreaterThanOrEqualTo((int)AssetState.Processing, entry.StateValue);
// Trigger the completion of the upload and the transition to shader resource state. // Trigger the completion of the upload and the transition to shader resource state.
_processor.ProcessPendingUploads(ctx); _processor.ProcessPendingUploads(ctx);
@@ -132,14 +133,14 @@ public class AssetManagerTest
ResourceDatabase = _graphicsEngine.ResourceDatabase, ResourceDatabase = _graphicsEngine.ResourceDatabase,
ResourceAllocator = _graphicsEngine.ResourceAllocator, ResourceAllocator = _graphicsEngine.ResourceAllocator,
CopyPipeline = _copyPipeline, CopyPipeline = _copyPipeline,
GraphicsCommandBuffer = _commandBuffer, CommandBuffer = _commandBuffer,
}; };
_processor.ProcessPendingUploads(ctx); _processor.ProcessPendingUploads(ctx);
await Task.Delay(1000, TestContext.CancellationToken); await Task.Delay(1000, TestContext.CancellationToken);
Assert.IsGreaterThanOrEqualTo((int)AssetState.Uploading, entry.StateValue); Assert.IsGreaterThanOrEqualTo((int)AssetState.Processing, entry.StateValue);
_processor.ProcessPendingUploads(ctx); _processor.ProcessPendingUploads(ctx);
@@ -174,7 +175,7 @@ public class AssetManagerTest
ResourceDatabase = _graphicsEngine.ResourceDatabase, ResourceDatabase = _graphicsEngine.ResourceDatabase,
ResourceAllocator = _graphicsEngine.ResourceAllocator, ResourceAllocator = _graphicsEngine.ResourceAllocator,
CopyPipeline = _copyPipeline, CopyPipeline = _copyPipeline,
GraphicsCommandBuffer = _commandBuffer, CommandBuffer = _commandBuffer,
}; };
_processor.ProcessPendingUploads(ctx); _processor.ProcessPendingUploads(ctx);

View File

@@ -3,6 +3,7 @@ using Ghost.Editor.Core;
using Ghost.Editor.Core.Assets; using Ghost.Editor.Core.Assets;
using Ghost.Editor.Core.Services; using Ghost.Editor.Core.Services;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Engine.Streaming;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@@ -93,11 +94,11 @@ public class MeshAssetHandlerTests
var header = MemoryMarshal.Read<MeshContentHeader>(headerBytes); var header = MemoryMarshal.Read<MeshContentHeader>(headerBytes);
Assert.AreEqual(MeshContentHeader.MAGIC, header.magic); Assert.AreEqual(MeshContentHeader.MAGIC, header.magic);
Assert.AreEqual(MeshContentHeader.VERSION, header.version); Assert.AreEqual(MeshContentHeader.VERSION, header.version);
Assert.IsGreaterThan(0u, header.vertexCount); Assert.IsGreaterThan(0, header.vertexCount);
Assert.IsGreaterThan(0u, header.indexCount); Assert.IsGreaterThan(0, header.indexCount);
Assert.IsGreaterThan(0u, header.meshletCount); Assert.IsGreaterThan(0, header.meshletCount);
Assert.IsGreaterThan(0u, header.meshletGroupCount); Assert.IsGreaterThan(0, header.meshletGroupCount);
Assert.IsGreaterThan(0u, header.meshletHierarchyNodeCount); Assert.IsGreaterThan(0, header.meshletHierarchyNodeCount);
} }
} }

View File

@@ -5,6 +5,7 @@ using Ghost.Editor.Core.Services;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Engine.Components; using Ghost.Engine.Components;
using Ghost.Engine.Core; using Ghost.Engine.Core;
using Ghost.Engine.Streaming;
using Ghost.Entities; using Ghost.Entities;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -136,6 +137,7 @@ public class SceneSerializationTests
{ {
var scene = SceneManager.CreateScene(); var scene = SceneManager.CreateScene();
CreateSceneEntity(scene); CreateSceneEntity(scene);
var parent = CreateEntityWithHierarchy(scene, Entity.Invalid); var parent = CreateEntityWithHierarchy(scene, Entity.Invalid);
var child = CreateEntityWithHierarchy(scene, parent); var child = CreateEntityWithHierarchy(scene, parent);
@@ -366,7 +368,7 @@ public class SceneSerializationTests
var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken); var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken);
Assert.IsNotNull(data); Assert.IsNotNull(data);
Assert.AreEqual(999, data!.FormatVersion); Assert.AreEqual(999u, data!.FormatVersion);
var loadResult = _serializationService.LoadSceneIntoEditorWorld(data); var loadResult = _serializationService.LoadSceneIntoEditorWorld(data);
Assert.IsTrue(loadResult.IsSuccess, loadResult.Message); Assert.IsTrue(loadResult.IsSuccess, loadResult.Message);
@@ -395,7 +397,7 @@ public class SceneSerializationTests
{ {
fixed (byte* pBinary = binary) fixed (byte* pBinary = binary)
{ {
var result = SceneLoader.LoadSceneIntoWorld(world, pBinary, binary.Length); var result = SceneLoader.LoadSceneIntoWorld(world, *(SceneContentHeader*)pBinary, pBinary + sizeof(SceneContentHeader), (nuint)(binary.Length + sizeof(SceneContentHeader)));
Assert.IsTrue(result.IsSuccess, result.Message); Assert.IsTrue(result.IsSuccess, result.Message);
Assert.AreEqual(3, result.Value); Assert.AreEqual(3, result.Value);
} }

Binary file not shown.