Refactor scene loading/materialization & asset streaming

- Overhauled scene loading with async, incremental, and cancellable operations using new types (SceneLoadStatus, SceneLoadOptions, etc.)
- Added thread-safe management of pending/loaded scenes and per-frame materialization budgeting
- Changed mesh header offsets from ulong to long; updated all related code to use stream positions directly
- Improved resource release logic for mesh/texture asset entries
- Refactored asset loading jobs and made reimport flag atomic
- Stream utility now always aligns memory blocks to 16 bytes
- Misc: fixed mock content headers, cleaned up code, and improved interface visibility
This commit is contained in:
2026-05-22 18:21:16 +09:00
parent 6b501efda0
commit a1c5ccf937
12 changed files with 583 additions and 72 deletions

View File

@@ -402,28 +402,28 @@ internal class MeshAssetHandler : IImportableAssetHandler, IPackableAssetHandler
using var stream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None); using var stream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None);
stream.Write(header); stream.Write(header);
header.vertexOffset = (ulong)stream.Position; header.vertexOffset = stream.Position;
await stream.WriteAsync<Vertex, UnsafeList<Vertex>>(geometry.Vertices, token); await stream.WriteAsync<Vertex, UnsafeList<Vertex>>(geometry.Vertices, token);
header.indexOffset = (ulong)stream.Position; header.indexOffset = stream.Position;
await stream.WriteAsync<uint, UnsafeList<uint>>(geometry.Indices, token); await stream.WriteAsync<uint, UnsafeList<uint>>(geometry.Indices, token);
header.materialPartOffset = (ulong)stream.Position; header.materialPartOffset = stream.Position;
WriteMaterialParts(stream, geometry.MaterialParts.AsSpan()); WriteMaterialParts(stream, geometry.MaterialParts.AsSpan());
header.meshletOffset = (ulong)stream.Position; header.meshletOffset = stream.Position;
await stream.WriteAsync<Meshlet, UnsafeList<Meshlet>>(meshletData.GetRef().meshlets, token); await stream.WriteAsync<Meshlet, UnsafeList<Meshlet>>(meshletData.GetRef().meshlets, token);
header.meshletGroupOffset = (ulong)stream.Position; header.meshletGroupOffset = stream.Position;
await stream.WriteAsync<MeshletGroup, UnsafeList<MeshletGroup>>(meshletData.GetRef().groups, token); await stream.WriteAsync<MeshletGroup, UnsafeList<MeshletGroup>>(meshletData.GetRef().groups, token);
header.meshletHierarchyNodeOffset = (ulong)stream.Position; header.meshletHierarchyNodeOffset = stream.Position;
await stream.WriteAsync<MeshletHierarchyNode, UnsafeList<MeshletHierarchyNode>>(meshletData.GetRef().hierarchyNodes, token); await stream.WriteAsync<MeshletHierarchyNode, UnsafeList<MeshletHierarchyNode>>(meshletData.GetRef().hierarchyNodes, token);
header.meshletVertexOffset = (ulong)stream.Position; header.meshletVertexOffset = stream.Position;
await stream.WriteAsync<uint, UnsafeList<uint>>(meshletData.GetRef().meshletVertices, token); await stream.WriteAsync<uint, UnsafeList<uint>>(meshletData.GetRef().meshletVertices, token);
header.meshletTriangleOffset = (ulong)stream.Position; header.meshletTriangleOffset = stream.Position;
await stream.WriteAsync<uint, UnsafeList<uint>>(meshletData.GetRef().meshletTriangles, token); await stream.WriteAsync<uint, UnsafeList<uint>>(meshletData.GetRef().meshletTriangles, token);
stream.Position = 0; stream.Position = 0;

View File

@@ -12,10 +12,6 @@ internal class SceneAssetHandler : IImportableAssetHandler, IPackableAssetHandle
return new SceneAssetSettings(); return new SceneAssetSettings();
} }
public ValueTask OpenAsync(string assetPath)
{
}
public async ValueTask<Result<IAsset>> LoadAssetAsync(string assetPath, Guid id, IAssetSettings? settings, CancellationToken token = default) public async ValueTask<Result<IAsset>> LoadAssetAsync(string assetPath, Guid id, IAssetSettings? settings, CancellationToken token = default)
{ {
try try

View File

@@ -172,7 +172,7 @@ internal partial class ContentBrowserViewModel : ObservableObject
} }
else else
{ {
_assetRegistry.OpenAsset(SelectedItem.FullName); // _assetRegistry.OpenAsset(SelectedItem.FullName);
return (null, 1); return (null, 1);
} }
} }

View File

@@ -1,5 +1,6 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -66,7 +67,8 @@ public static class StreamUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryBlock ReadMemory(this Stream stream, long length, AllocationHandle allocationHandle) public static MemoryBlock ReadMemory(this Stream stream, long length, AllocationHandle allocationHandle)
{ {
var memory = new MemoryBlock((nuint)length, 16, allocationHandle); var alignedLength = MemoryUtility.AlignUp((nuint)length, 16);
var memory = new MemoryBlock(alignedLength, 16, allocationHandle);
// C# built-in collections use int for indexing, so we need to ensure that the buffer size does not exceed int.MaxValue // 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(0x7fffffffL, length); var maxChunkSize = (int)Math.Min(0x7fffffffL, length);

View File

@@ -5,6 +5,7 @@ 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;
using System.Collections.Concurrent;
using System.Text; using System.Text;
namespace Ghost.Engine.Core; namespace Ghost.Engine.Core;
@@ -106,15 +107,418 @@ public class LoadedSceneData : IDisposable
} }
} }
public enum SceneLoadStatus
{
Queued = 0,
WaitingForDependencies = 1,
Parsing = 2,
WaitingForMaterialization = 3,
Materializing = 4,
Completed = 5,
Failed = 6,
Canceled = 7,
}
public struct SceneLoadOptions
{
public bool DeferMaterialization;
public int MaxEntitiesPerFrame;
public int Priority;
public readonly bool AutoMaterialize => !DeferMaterialization;
}
public readonly struct SceneMaterializeBudget
{
public readonly int MaxScenes;
public readonly int MaxEntities;
public SceneMaterializeBudget(int maxEntities, int maxScenes = 0)
{
MaxEntities = maxEntities;
MaxScenes = maxScenes;
}
public static SceneMaterializeBudget Unlimited => new(int.MaxValue, int.MaxValue);
}
public sealed class SceneLoadOperation
{
private readonly PendingSceneLoad _pendingLoad;
internal PendingSceneLoad PendingLoad => _pendingLoad;
public SceneLoadStatus Status => _pendingLoad.Status;
public float Progress => _pendingLoad.Progress;
public Scene Scene => _pendingLoad.Scene;
public string? ErrorMessage => _pendingLoad.ErrorMessage;
public bool IsParsed => _pendingLoad.Status >= SceneLoadStatus.WaitingForMaterialization && _pendingLoad.Status < SceneLoadStatus.Failed;
public bool IsMaterialized => _pendingLoad.Status == SceneLoadStatus.Completed;
public bool IsCompleted => _pendingLoad.Status is SceneLoadStatus.Completed or SceneLoadStatus.Failed or SceneLoadStatus.Canceled;
internal SceneLoadOperation(PendingSceneLoad pendingLoad)
{
_pendingLoad = pendingLoad;
}
public void Cancel()
{
_pendingLoad.Cancel();
}
}
internal sealed class PendingSceneLoad : IDisposable
{
private enum MaterializePhase
{
NotStarted = 0,
CreateEntities = 1,
SetComponents = 2,
RemapEntityReferences = 3,
Completed = 4,
}
private readonly AssetEntry _sceneEntry;
private readonly SceneLoadOptions _options;
private LoadedSceneData? _loadedData;
private UnsafeArray<Entity> _fileLocalToRuntimeEntity;
private MaterializePhase _phase;
private int _nextCreateIndex;
private int _nextSetComponentIndex;
private int _nextRemapIndex;
private int _status;
private int _releasedSceneEntry;
private bool _singleResetApplied;
private bool _disposed;
public World World { get; }
public AssetRef<Scene> SceneAsset { get; }
public SceneLoadingType LoadingType { get; }
public Scene Scene { get; private set; }
public SceneLoadOptions Options => _options;
public SceneLoadStatus Status => (SceneLoadStatus)Volatile.Read(ref _status);
public string? ErrorMessage { get; private set; }
public bool IsTerminal => Status is SceneLoadStatus.Completed or SceneLoadStatus.Failed or SceneLoadStatus.Canceled;
public float Progress
{
get
{
var status = Status;
if (status == SceneLoadStatus.Completed)
{
return 1.0f;
}
var loadedData = _loadedData;
if (loadedData == null || !loadedData.entities.IsCreated || loadedData.entities.Length == 0)
{
return status switch
{
SceneLoadStatus.Queued => 0.0f,
SceneLoadStatus.WaitingForDependencies => 0.1f,
SceneLoadStatus.Parsing => 0.25f,
SceneLoadStatus.WaitingForMaterialization => 0.5f,
SceneLoadStatus.Materializing => 0.75f,
_ => 0.0f,
};
}
var entityCount = loadedData.entities.Length;
var completedEntities = Math.Min(entityCount * 3, _nextCreateIndex + _nextSetComponentIndex + _nextRemapIndex);
return 0.5f + (0.5f * completedEntities / (entityCount * 3));
}
}
public PendingSceneLoad(World world, AssetRef<Scene> sceneAsset, SceneLoadingType loadingType, SceneLoadOptions options, AssetEntry sceneEntry)
{
World = world;
SceneAsset = sceneAsset;
LoadingType = loadingType;
_options = options;
_sceneEntry = sceneEntry;
Scene = Scene.Invalid;
_status = (int)SceneLoadStatus.Queued;
}
public void SetStatus(SceneLoadStatus status)
{
Volatile.Write(ref _status, (int)status);
}
public void CompleteParsing(LoadedSceneData loadedData)
{
_loadedData = loadedData;
SetStatus(SceneLoadStatus.WaitingForMaterialization);
}
public void Fail(string message)
{
ErrorMessage = message;
SetStatus(SceneLoadStatus.Failed);
Dispose();
}
public void Cancel()
{
var status = Status;
if (status is SceneLoadStatus.Completed or SceneLoadStatus.Failed or SceneLoadStatus.Canceled)
{
return;
}
SetStatus(SceneLoadStatus.Canceled);
if (status >= SceneLoadStatus.WaitingForMaterialization)
{
Dispose();
}
}
public int Materialize(int maxEntities)
{
if (maxEntities <= 0 || IsTerminal)
{
return 0;
}
var loadedData = _loadedData;
if (loadedData == null)
{
return 0;
}
if (!_singleResetApplied && LoadingType == SceneLoadingType.Single)
{
SceneManager.ReleaseMaterializedSceneReferences(World);
World.Reset();
_singleResetApplied = true;
}
if (_phase == MaterializePhase.NotStarted)
{
Scene = SceneManager.CreateScene();
_fileLocalToRuntimeEntity = new UnsafeArray<Entity>(loadedData.entities.Length, AllocationHandle.Persistent);
_phase = MaterializePhase.CreateEntities;
SetStatus(SceneLoadStatus.Materializing);
}
var consumed = 0;
while (consumed < maxEntities && _phase != MaterializePhase.Completed)
{
switch (_phase)
{
case MaterializePhase.CreateEntities:
consumed += MaterializeCreateEntities(loadedData, maxEntities - consumed);
if (_nextCreateIndex >= loadedData.entities.Length)
{
_phase = MaterializePhase.SetComponents;
}
break;
case MaterializePhase.SetComponents:
consumed += MaterializeSetComponents(loadedData, maxEntities - consumed);
if (_nextSetComponentIndex >= loadedData.entities.Length)
{
_phase = MaterializePhase.RemapEntityReferences;
}
break;
case MaterializePhase.RemapEntityReferences:
consumed += MaterializeRemapEntityReferences(loadedData, maxEntities - consumed);
if (_nextRemapIndex >= loadedData.entities.Length)
{
CompleteMaterialization();
}
break;
}
}
return consumed;
}
private int MaterializeCreateEntities(LoadedSceneData loadedData, int maxEntities)
{
using var scope = AllocationManager.CreateStackScope();
using var sharedCom = new SharedComponentSet(256, scope.AllocationHandle);
var consumed = 0;
while (consumed < maxEntities && _nextCreateIndex < loadedData.entities.Length)
{
ref var pending = ref loadedData.entities[_nextCreateIndex];
using var typeIds = new UnsafeList<Identifier<IComponent>>(pending.componentTypeIDs.Count + 1, scope.AllocationHandle);
typeIds.Add(ComponentTypeID<SceneID>.Value);
for (var i = 0; i < pending.componentTypeIDs.Count; i++)
{
typeIds.Add(pending.componentTypeIDs[i]);
}
sharedCom.With(new SceneID { value = Scene.ID });
var set = new ComponentSetView(typeIds, sharedCom);
var entity = World.EntityManager.CreateEntity(set);
_fileLocalToRuntimeEntity[pending.fileLocalIndex] = entity;
sharedCom.Reset();
_nextCreateIndex++;
consumed++;
}
return consumed;
}
private unsafe int MaterializeSetComponents(LoadedSceneData loadedData, int maxEntities)
{
var consumed = 0;
while (consumed < maxEntities && _nextSetComponentIndex < loadedData.entities.Length)
{
ref var pending = ref loadedData.entities[_nextSetComponentIndex];
var entity = _fileLocalToRuntimeEntity[pending.fileLocalIndex];
if (entity.IsValid)
{
for (var i = 0; i < pending.componentData.Count; i++)
{
var (typeID, data) = pending.componentData[i];
World.EntityManager.SetComponent(entity, typeID, data.GetUnsafePtr());
}
}
_nextSetComponentIndex++;
consumed++;
}
return consumed;
}
private unsafe int MaterializeRemapEntityReferences(LoadedSceneData loadedData, int maxEntities)
{
var consumed = 0;
while (consumed < maxEntities && _nextRemapIndex < loadedData.entities.Length)
{
ref var pending = ref loadedData.entities[_nextRemapIndex];
var entity = _fileLocalToRuntimeEntity[pending.fileLocalIndex];
if (entity.IsValid)
{
for (var i = 0; i < pending.entityFields.Count; i++)
{
var (componentDataIndex, fieldOffsets) = pending.entityFields[i];
var compTypeID = pending.componentData[componentDataIndex].typeID;
var pComponent = World.EntityManager.GetComponent(entity, compTypeID);
if (pComponent == null)
{
continue;
}
for (var f = 0; f < fieldOffsets.Length; f++)
{
var fieldOffset = fieldOffsets[f];
var pField = (byte*)pComponent + fieldOffset;
var fileLocalIndex = *(int*)pField;
var remappedEntity = fileLocalIndex >= 0 && fileLocalIndex < _fileLocalToRuntimeEntity.Length ?
_fileLocalToRuntimeEntity[fileLocalIndex] :
Entity.Invalid;
*(Entity*)pField = remappedEntity;
}
}
}
_nextRemapIndex++;
consumed++;
}
return consumed;
}
private void CompleteMaterialization()
{
_phase = MaterializePhase.Completed;
SceneManager.RegisterMaterializedScene(this);
_loadedData?.Dispose();
_loadedData = null;
if (_fileLocalToRuntimeEntity.IsCreated)
{
_fileLocalToRuntimeEntity.Dispose();
}
SetStatus(SceneLoadStatus.Completed);
}
public void ReleaseMaterializedReference()
{
if (Interlocked.Exchange(ref _releasedSceneEntry, 1) == 0)
{
_sceneEntry.Release();
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_loadedData?.Dispose();
_loadedData = null;
if (_fileLocalToRuntimeEntity.IsCreated)
{
_fileLocalToRuntimeEntity.Dispose();
}
if (Status != SceneLoadStatus.Completed)
{
ReleaseMaterializedReference();
}
_disposed = true;
}
}
/// <summary> /// <summary>
/// Manages scenes within a world. /// Manages scenes within a world.
/// </summary> /// </summary>
public static class SceneManager public static class SceneManager
{ {
private readonly struct SceneKey : IEquatable<SceneKey>
{
private readonly Identifier<World> _worldID;
private readonly ushort _sceneID;
public SceneKey(World world, Scene scene)
{
_worldID = world.ID;
_sceneID = scene.ID;
}
public bool Equals(SceneKey other)
{
return _worldID == other._worldID && _sceneID == other._sceneID;
}
public override bool Equals(object? obj)
{
return obj is SceneKey other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_worldID, _sceneID);
}
}
private static ushort s_nextSceneID; private static ushort s_nextSceneID;
private static readonly Queue<ushort> s_recycledSceneIDs = new(); private static readonly Queue<ushort> s_recycledSceneIDs = new();
private static readonly Lock s_creationLock = new(); private static readonly Lock s_creationLock = new();
private static readonly Lock s_loadedScenesLock = new();
private static readonly ConcurrentQueue<PendingSceneLoad> s_pendingMaterialization = new();
private static readonly Dictionary<SceneKey, PendingSceneLoad> s_loadedScenes = new();
/// <summary> /// <summary>
/// Creates a new scene in the world. /// Creates a new scene in the world.
@@ -244,6 +648,77 @@ public static class SceneManager
return ParseSceneData(header, ref reader, allocationHandle); return ParseSceneData(header, ref reader, allocationHandle);
} }
internal static void EnqueuePendingScene(PendingSceneLoad pendingSceneLoad)
{
s_pendingMaterialization.Enqueue(pendingSceneLoad);
}
internal static void RegisterMaterializedScene(PendingSceneLoad pendingSceneLoad)
{
lock (s_loadedScenesLock)
{
s_loadedScenes[new SceneKey(pendingSceneLoad.World, pendingSceneLoad.Scene)] = pendingSceneLoad;
}
}
internal static void ReleaseMaterializedSceneReferences(World world)
{
lock (s_loadedScenesLock)
{
foreach (var (key, pendingLoad) in s_loadedScenes.ToArray())
{
if (pendingLoad.World != world)
{
continue;
}
pendingLoad.ReleaseMaterializedReference();
s_recycledSceneIDs.Enqueue(pendingLoad.Scene.ID);
s_loadedScenes.Remove(key);
}
}
}
public static void MaterializePendingScenes(World world, SceneMaterializeBudget budget = default)
{
var maxScenes = budget.MaxScenes > 0 ? budget.MaxScenes : int.MaxValue;
var remainingEntities = budget.MaxEntities > 0 ? budget.MaxEntities : int.MaxValue;
var pendingCount = s_pendingMaterialization.Count;
var processedScenes = 0;
for (var i = 0; i < pendingCount && processedScenes < maxScenes && remainingEntities > 0; i++)
{
if (!s_pendingMaterialization.TryDequeue(out var pendingLoad))
{
break;
}
if (pendingLoad.World != world || pendingLoad.IsTerminal)
{
if (!pendingLoad.IsTerminal)
{
s_pendingMaterialization.Enqueue(pendingLoad);
}
continue;
}
var sceneBudget = pendingLoad.Options.MaxEntitiesPerFrame > 0 ?
Math.Min(remainingEntities, pendingLoad.Options.MaxEntitiesPerFrame) :
remainingEntities;
var consumed = pendingLoad.Materialize(sceneBudget);
remainingEntities -= consumed;
if (!pendingLoad.IsTerminal)
{
s_pendingMaterialization.Enqueue(pendingLoad);
}
processedScenes++;
}
}
/// <summary> /// <summary>
/// Materializes the loaded scene data into actual entities in the world, setting their components and remapping entity references. /// Materializes the loaded scene data into actual entities in the world, setting their components and remapping entity references.
/// </summary> /// </summary>
@@ -361,6 +836,15 @@ public static class SceneManager
} }
s_recycledSceneIDs.Enqueue(scene.ID); s_recycledSceneIDs.Enqueue(scene.ID);
lock (s_loadedScenesLock)
{
var key = new SceneKey(world, scene);
if (s_loadedScenes.Remove(key, out var pendingLoad))
{
pendingLoad.ReleaseMaterializedReference();
}
}
} }
/// <summary> /// <summary>

View File

@@ -4,7 +4,6 @@ using Ghost.Graphics.RHI;
using Ghost.Graphics.Services; using Ghost.Graphics.Services;
using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.Jobs;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using TerraFX.Interop.Windows;
namespace Ghost.Engine.Streaming; namespace Ghost.Engine.Streaming;
@@ -68,7 +67,7 @@ internal abstract class AssetEntry : IAssetEntry
private int _refCount; private int _refCount;
private int _state; private int _state;
private bool _pendingReimport; private int _pendingReimport;
protected ResourceManager ResourceManager => _resourceManager; protected ResourceManager ResourceManager => _resourceManager;
protected IResourceDatabase ResourceDatabase => _resourceDatabase; protected IResourceDatabase ResourceDatabase => _resourceDatabase;
@@ -88,7 +87,7 @@ internal abstract class AssetEntry : IAssetEntry
Volatile.Write(ref _state, (int)value); Volatile.Write(ref _state, (int)value);
if (Volatile.Read(ref _state) == (int)AssetState.Ready) if (Volatile.Read(ref _state) == (int)AssetState.Ready)
{ {
if (Interlocked.CompareExchange(ref _pendingReimport, false, true)) if (Interlocked.Exchange(ref _pendingReimport, 0) == 1)
{ {
_assetManager.ReimportAsset(_assetId); // re-queue _assetManager.ReimportAsset(_assetId); // re-queue
} }
@@ -116,7 +115,7 @@ internal abstract class AssetEntry : IAssetEntry
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetPendingReimport() public void SetPendingReimport()
{ {
Volatile.Write(ref _pendingReimport, true); Volatile.Write(ref _pendingReimport, 1);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -155,7 +154,7 @@ internal abstract class AssetEntry : IAssetEntry
} }
} }
interface IAssetEntry internal interface IAssetEntry
{ {
Guid AssetId { get; } Guid AssetId { get; }
AssetType AssetType { get; } AssetType AssetType { get; }

View File

@@ -42,6 +42,17 @@ internal struct LoadAssetJob : IJob
entry.State = AssetState.Loading; entry.State = AssetState.Loading;
if (entry is not ILoadableAssetEntry loadable)
{
entry.State = AssetState.Loaded;
if (!assetManager.StreamingProcessor.EnqueueForProcess(entry))
{
entry.State = AssetState.Ready;
}
return;
}
try try
{ {
var openResult = assetManager.ContentProvider.OpenRead(entry.AssetId); var openResult = assetManager.ContentProvider.OpenRead(entry.AssetId);
@@ -53,17 +64,12 @@ internal struct LoadAssetJob : IJob
} }
using var stream = openResult.Value; using var stream = openResult.Value;
var result = loadable.OnLoadContent(stream);
if (entry is ILoadableAssetEntry loadable) if (result.IsFailure)
{ {
var result = loadable.OnLoadContent(stream); entry.State = AssetState.Failed;
Logger.Error($"Failed to load asset {assetID}: {result.Message}");
if (result.IsFailure) return;
{
entry.State = AssetState.Failed;
Logger.Error($"Failed to load asset {assetID}: {result.Message}");
return;
}
} }
entry.State = AssetState.Loaded; entry.State = AssetState.Loaded;

View File

@@ -35,14 +35,14 @@ internal struct MeshContentHeader
public float3 boundsMin; public float3 boundsMin;
public float3 boundsMax; public float3 boundsMax;
public ulong vertexOffset; public long vertexOffset;
public ulong indexOffset; public long indexOffset;
public ulong materialPartOffset; public long materialPartOffset;
public ulong meshletOffset; public long meshletOffset;
public ulong meshletGroupOffset; public long meshletGroupOffset;
public ulong meshletHierarchyNodeOffset; public long meshletHierarchyNodeOffset;
public ulong meshletVertexOffset; public long meshletVertexOffset;
public ulong meshletTriangleOffset; public long meshletTriangleOffset;
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@@ -123,15 +123,19 @@ internal unsafe class MeshAssetEntry : AssetEntry, ILoadableAssetEntry, IUploada
public override void OnReleaseResource() public override void OnReleaseResource()
{ {
ResourceManager.ReleaseMesh(_tempHandle); ResourceManager.ReleaseMesh(_actualHandle);
if (_tempHandle.IsValid)
{
ResourceManager.ReleaseMesh(_tempHandle);
}
} }
public Result OnLoadContent(Stream contentStream) public Result OnLoadContent(Stream contentStream)
{ {
bool ValidateRange(ulong offset, int count, uint stride) bool ValidateRange(long offset, int count, uint stride)
{ {
var size = (ulong)count * stride; var size = count * stride;
return offset <= _rawData.Size && size <= _rawData.Size - (nuint)offset; return offset <= contentStream.Length && size <= contentStream.Length - offset;
} }
var header = contentStream.Read<MeshContentHeader>(); var header = contentStream.Read<MeshContentHeader>();
@@ -330,6 +334,7 @@ internal unsafe class MeshAssetEntry : AssetEntry, ILoadableAssetEntry, IUploada
dstMesh.MeshletTrianglesBuffer = context.ResourceDatabase.Replace(temp.MeshletTrianglesBuffer.AsResource(), srcMesh.MeshletTrianglesBuffer.AsResource()).AsBuffer(); dstMesh.MeshletTrianglesBuffer = context.ResourceDatabase.Replace(temp.MeshletTrianglesBuffer.AsResource(), srcMesh.MeshletTrianglesBuffer.AsResource()).AsBuffer();
context.ResourceManager.ReleaseMesh(_tempHandle); context.ResourceManager.ReleaseMesh(_tempHandle);
_tempHandle = Handle<Mesh>.Invalid;
context.CommandBuffer.Barrier( context.CommandBuffer.Barrier(
BarrierDesc.Buffer(dstMesh.VertexBuffer, BarrierSync.VertexShading, BarrierAccess.VertexBuffer | BarrierAccess.ShaderResource), BarrierDesc.Buffer(dstMesh.VertexBuffer, BarrierSync.VertexShading, BarrierAccess.VertexBuffer | BarrierAccess.ShaderResource),
@@ -341,8 +346,6 @@ internal unsafe class MeshAssetEntry : AssetEntry, ILoadableAssetEntry, IUploada
BarrierDesc.Buffer(dstMesh.MeshletHierarchyBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource), BarrierDesc.Buffer(dstMesh.MeshletHierarchyBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(dstMesh.MeshDataBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource)); BarrierDesc.Buffer(dstMesh.MeshDataBuffer, BarrierSync.AllShading, BarrierAccess.ShaderResource));
_actualHandle = Handle<Mesh>.Invalid;
_rawData.Dispose(); _rawData.Dispose();
_pVertices = null; _pVertices = null;
_pIndices = null; _pIndices = null;

View File

@@ -74,8 +74,8 @@ internal class ResourceStreamingProcessor : IResourceStreamingProcessor
{ {
while (_pendingFinalize.TryDequeue(out var item)) while (_pendingFinalize.TryDequeue(out var item))
{ {
item.State = AssetState.Ready;
item.OnUploadComplete(context); item.OnUploadComplete(context);
item.State = AssetState.Ready;
} }
_pendingCopyFenceValue = 0; _pendingCopyFenceValue = 0;

View File

@@ -29,24 +29,43 @@ public partial class AssetManager
public SceneContentHeader header; public SceneContentHeader header;
public Stream stream; public Stream stream;
public LoadedSceneData loadedSceneData; public PendingSceneLoad pendingSceneLoad;
public readonly void Execute(ref readonly JobExecutionContext context) public readonly void Execute(ref readonly JobExecutionContext context)
{ {
try try
{ {
var loadResult = SceneManager.ParseSceneData(header, stream, AllocationHandle.Persistent); if (pendingSceneLoad.Status == SceneLoadStatus.Canceled)
if (loadResult.IsFailure)
{ {
Logger.Error($"Failed to parse scene data: {loadResult.Message}"); pendingSceneLoad.Dispose();
return; return;
} }
loadedSceneData.entities = loadResult.Value.entities; pendingSceneLoad.SetStatus(SceneLoadStatus.Parsing);
var loadResult = SceneManager.ParseSceneData(header, stream, AllocationHandle.Persistent);
if (loadResult.IsFailure)
{
pendingSceneLoad.Fail(loadResult.Message ?? "Failed to parse scene data.");
return;
}
if (pendingSceneLoad.Status == SceneLoadStatus.Canceled)
{
loadResult.Value.Dispose();
pendingSceneLoad.Dispose();
return;
}
pendingSceneLoad.CompleteParsing(loadResult.Value);
if (pendingSceneLoad.Options.AutoMaterialize)
{
SceneManager.EnqueuePendingScene(pendingSceneLoad);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error($"Exception while loading scene: {ex}"); pendingSceneLoad.Fail(ex.Message);
} }
finally finally
{ {
@@ -55,7 +74,7 @@ public partial class AssetManager
} }
} }
public unsafe Result<JobHandle> LoadScene(World world, AssetRef<Scene> sceneAsset, SceneLoadingType loadingType, ref LoadedSceneData? loadedSceneData) public unsafe Result<SceneLoadOperation> LoadScene(World world, AssetRef<Scene> sceneAsset, SceneLoadingType loadingType, SceneLoadOptions options = default)
{ {
if (!sceneAsset.IsValid) if (!sceneAsset.IsValid)
{ {
@@ -91,23 +110,19 @@ public partial class AssetManager
try try
{ {
if (loadingType == SceneLoadingType.Single)
{
world.Reset();
}
loadedSceneData ??= new LoadedSceneData();
var entry = GetOrCreateEntry(sceneAsset.ID); // Purely to get the dependencies and ensure the asset is tracked, the actual loading is done in the job. var entry = GetOrCreateEntry(sceneAsset.ID); // Purely to get the dependencies and ensure the asset is tracked, the actual loading is done in the job.
var pendingSceneLoad = new PendingSceneLoad(world, sceneAsset, loadingType, options, entry);
pendingSceneLoad.SetStatus(SceneLoadStatus.WaitingForDependencies);
var job = new LoadSceneJob var job = new LoadSceneJob
{ {
header = header, header = header,
stream = stream, stream = stream,
loadedSceneData = loadedSceneData pendingSceneLoad = pendingSceneLoad
}; };
return _jobScheduler.Schedule(in job, entry.LoadJobHandle); _jobScheduler.Schedule(in job, entry.LoadJobHandle);
return Result<SceneLoadOperation>.Success(new SceneLoadOperation(pendingSceneLoad));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -104,7 +104,11 @@ internal unsafe class TextureAssetEntry : AssetEntry, ILoadableAssetEntry, IUplo
public override void OnReleaseResource() public override void OnReleaseResource()
{ {
ResourceDatabase.ReleaseResource(_tempHandle.AsResource()); ResourceDatabase.ReleaseResource(_actualHandle.AsResource());
if (_tempHandle.IsValid)
{
ResourceDatabase.ReleaseResource(_tempHandle.AsResource());
}
} }
public Result OnLoadContent(Stream contentStream) public Result OnLoadContent(Stream contentStream)
@@ -167,8 +171,8 @@ internal unsafe class TextureAssetEntry : AssetEntry, ILoadableAssetEntry, IUplo
context.CommandBuffer.Barrier(BarrierDesc.Texture(actualHandle, BarrierSync.AllShading, BarrierAccess.ShaderResource, BarrierLayout.ShaderResource)); context.CommandBuffer.Barrier(BarrierDesc.Texture(actualHandle, BarrierSync.AllShading, BarrierAccess.ShaderResource, BarrierLayout.ShaderResource));
_actualHandle = Handle<GPUTexture>.Invalid; _actualHandle = actualHandle.AsTexture();
_tempHandle = actualHandle.AsTexture(); _tempHandle = Handle<GPUTexture>.Invalid;
_textureData.Dispose(); _textureData.Dispose();
} }
} }

View File

@@ -36,6 +36,8 @@ internal class MockingContentProvider : IContentProvider
{ {
var header = new TextureContentHeader var header = new TextureContentHeader
{ {
magic = TextureContentHeader.MAGIC,
version = TextureContentHeader.VERSION,
width = width, width = width,
height = height, height = height,
bpc = 8, bpc = 8,
@@ -144,14 +146,14 @@ internal class MockingContentProvider : IContentProvider
}; };
stream.Write(header); stream.Write(header);
header.vertexOffset = (ulong)stream.Position; stream.Write(vertices); header.vertexOffset = stream.Position; stream.Write(vertices);
header.indexOffset = (ulong)stream.Position; stream.Write(indices); header.indexOffset = stream.Position; stream.Write(indices);
header.materialPartOffset = (ulong)stream.Position; stream.Write(materialParts); header.materialPartOffset = stream.Position; stream.Write(materialParts);
header.meshletOffset = (ulong)stream.Position; stream.Write(meshlets); header.meshletOffset = stream.Position; stream.Write(meshlets);
header.meshletGroupOffset = (ulong)stream.Position; stream.Write(groups); header.meshletGroupOffset = stream.Position; stream.Write(groups);
header.meshletHierarchyNodeOffset = (ulong)stream.Position; stream.Write(hierarchy); header.meshletHierarchyNodeOffset = stream.Position; stream.Write(hierarchy);
header.meshletVertexOffset = (ulong)stream.Position; stream.Write(meshletVertices); header.meshletVertexOffset = stream.Position; stream.Write(meshletVertices);
header.meshletTriangleOffset = (ulong)stream.Position; stream.Write(meshletTriangles); header.meshletTriangleOffset = stream.Position; stream.Write(meshletTriangles);
stream.Position = 0; stream.Position = 0;
stream.Write(header); stream.Write(header);