Update asset system for deferred allocation & add unit tests

Modernize Misaki.HighPerformance dependencies. Refactor texture asset creation to use deferred resource slots via CreateEmpty(). Remove fallback resource fields and update texture resolution logic. Add CreateEmpty() to resource database interfaces. Introduce comprehensive unit tests with mocks for asset management. Enable unsafe code in tests.
This commit is contained in:
2026-05-02 22:54:58 +09:00
parent e384a2f38c
commit e7fedfd35a
12 changed files with 637 additions and 19 deletions

View File

@@ -20,14 +20,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.8" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.2" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.16">
<PackageReference Include="Misaki.HighPerformance" Version="1.0.9" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.3" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.17">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.2" />
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.4" />
<PackageReference Include="System.IO.Hashing" Version="10.0.7" />
<PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />

View File

@@ -34,7 +34,7 @@ internal partial class AssetEntry
{
// 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.CreateShared(e._assetManager.FallbackTexture.AsResource()).AsTexture();
var handle = e._resourceDatabase.CreateEmpty().AsTexture();
e.SetStorage(handle);
};
@@ -155,7 +155,7 @@ internal partial class AssetManager
{
if (assetID == Guid.Empty)
{
return _fallbackTexture;
return Handle<GPUTexture>.Invalid;
}
var entry = GetOrCreateEntry(assetID);

View File

@@ -295,17 +295,9 @@ internal partial class AssetManager : IDisposable
private readonly ConcurrentDictionary<Guid, AssetEntry> _entries;
// TODO
private Handle<GPUTexture> _fallbackTexture;
private Handle<GPUTexture> _fallbackNormalMap;
private Handle<Mesh> _fallbackMesh;
private Handle<Material> _fallbackMaterial;
public IContentProvider ContentProvider => _contentProvider;
public ResourceStreamingProcessor StreamingProcessor => _streamingProcessor;
public Handle<GPUTexture> FallbackTexture => _fallbackTexture;
internal AssetManager(IResourceDatabase resourceDatabase, IContentProvider contentProvider, ResourceStreamingProcessor streamingProcessor, JobScheduler jobScheduler)
{
_resourceDatabase = resourceDatabase;
@@ -378,10 +370,10 @@ internal partial class AssetManager : IDisposable
for (var i = list.Count - 1; i >= 0; i--)
{
// This should create the entry and schedule the job on those assets does not have any dependency first.
var handle = GetOrCreateEntry(list[i]).LoadJobHandle;
Logger.DebugAssert(handle.IsValid);
var depHandle = GetOrCreateEntry(list[i]).LoadJobHandle;
Logger.DebugAssert(depHandle.IsValid);
depHandles.Add(handle);
depHandles.Add(depHandle);
}
dependency = _jobScheduler.CombineDependencies(depHandles);
@@ -394,7 +386,8 @@ internal partial class AssetManager : IDisposable
assetManager = this,
};
entry.SetLoadJobHandle(_jobScheduler.Schedule(ref job, JobPriority.Low, dependency)); // Use low priority to avoid blocking main thread critical tasks like rendering and physics.
var handle = _jobScheduler.Schedule(ref job, JobPriority.Low, dependency);
entry.SetLoadJobHandle(handle); // Use low priority to avoid blocking main thread critical tasks like rendering and physics.
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -1,6 +1,5 @@
using Ghost.Core;
using Ghost.Graphics;
using SharpCompress.Common;
using System.Collections.Concurrent;
namespace Ghost.Engine;

View File

@@ -442,6 +442,15 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
}
}
public Handle<GPUResource> CreateEmpty()
{
lock (_writeLock)
{
var id = _resources.Add(default, out var generation);
return new Handle<GPUResource>(id, generation);
}
}
public void* MapResource(Handle<GPUResource> handle, uint subResource, ResourceRange? readRange)
{
var r = GetResourceRecord(handle);

View File

@@ -142,6 +142,12 @@ public unsafe interface IResourceDatabase : IDisposable
/// <returns>The handle to the newly created shared resource.</returns>
Handle<GPUResource> CreateShared(Handle<GPUResource> src);
/// <summary>
/// Creates a slot in the resource database that contains no GPU resource, and returns a handle to that slot. This can be used as a placeholder for a resource that will be created or assigned later, allowing for deferred resource creation and management.
/// </summary>
/// <returns>The handle to the newly created empty resource slot.</returns>
Handle<GPUResource> CreateEmpty();
/// <summary>
/// Maps a subresource of a GPU resource for CPU access, specifying read and write ranges.
/// </summary>