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:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Ghost.Editor.Core.SceneGraph;
|
|
||||||
|
|
||||||
public enum SceneLoadingType
|
|
||||||
{
|
|
||||||
Single = 0,
|
|
||||||
Additive = 1,
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/Runtime/Ghost.Core/Utilities/CollectionUtility.cs
Normal file
35
src/Runtime/Ghost.Core/Utilities/CollectionUtility.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using Ghost.Engine.Models;
|
|
||||||
|
|
||||||
namespace Ghost.Engine;
|
|
||||||
|
|
||||||
internal static class ActivationHandler
|
|
||||||
{
|
|
||||||
public static void Handle(LaunchArgument args)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
144
src/Runtime/Ghost.Engine/Streaming/AssetEntry.cs
Normal file
144
src/Runtime/Ghost.Engine/Streaming/AssetEntry.cs
Normal 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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
361
src/Runtime/Ghost.Engine/Streaming/MeshAssetEntry.cs
Normal file
361
src/Runtime/Ghost.Engine/Streaming/MeshAssetEntry.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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++;
|
||||||
@@ -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()
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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()),
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
Reference in New Issue
Block a user