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:
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user