Refactor instance update flow, asset registry, and texture IO

- Renamed AddInstanceRequest to UpdateInstanceRequest; unified add/update logic for GPU instances
- Introduced UpdateGPUInstanceSystem to handle changed MeshInstance components
- Replaced QueryBuilder.Create() with QueryBuilder.New() for consistency
- Switched versioning in ChunkView HasChanged/HasStructuralChanged to uint
- Added extension-to-AssetType mapping in AssetHandlerRegistry
- Changed TextureAssetHandler/Processor to use nint for image data
- Enhanced DDS cache: read mipmap count, handle invalid files
- Updated ProjectBrowserViewModel to use IAssetRegistry
- Upgraded Misaki.HighPerformance and System.IO.Hashing packages
- Set DependencyChainCapacity in JobSchedulerDesc
- Fixed instance buffer logic in GhostRenderPipeline
- Miscellaneous cleanups and namespace improvements
This commit is contained in:
2026-04-22 15:36:49 +09:00
parent cb4092179f
commit 884611181a
19 changed files with 150 additions and 55 deletions

View File

@@ -20,13 +20,13 @@
<ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.8" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.0.0" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.14">
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.0" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.15">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
<PackageReference Include="System.IO.Hashing" Version="10.0.6" />
<PackageReference Include="System.IO.Hashing" Version="10.0.7" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
</ItemGroup>

View File

@@ -21,6 +21,7 @@ public sealed partial class EngineCore : IDisposable
{
ThreadCount = Environment.ProcessorCount - 2, // We -2 here, one for main thread, one for render thread
ThreadPriority = ThreadPriority.Normal,
DependencyChainCapacity = 8192,
};
_jobScheduler = new JobScheduler(in desc);

View File

@@ -41,13 +41,13 @@ internal partial class GhostRenderPipeline
private static unsafe Handle<GPUBuffer> CreateUpdateInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
{
// TODO: This should also include update requests like transform update, material update, etc.
var totalUpdateCount = ghostPayload.AddRequest.Count; // + ghostPayload.UpdateRequest.Count;
var totalUpdateCount = ghostPayload.UpdateRequest.Count; // + ghostPayload.UpdateRequest.Count;
if (!ghostPayload.AddRequest.IsEmpty)
if (!ghostPayload.UpdateRequest.IsEmpty)
{
var addDesc = new BufferDesc
{
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<UpdateInstanceData>(),
Size = (nuint)ghostPayload.UpdateRequest.Count * MemoryUtility.SizeOf<UpdateInstanceData>(),
Stride = (uint)MemoryUtility.SizeOf<UpdateInstanceData>(),
Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
HeapType = HeapType.Upload
@@ -57,7 +57,7 @@ internal partial class GhostRenderPipeline
var pAddData = (UpdateInstanceData*)resourceDatabase.MapResource(addBuffer.AsResource(), 0, null);
var i = 0;
while (ghostPayload.AddRequest.TryDequeue(out var addRequest))
while (ghostPayload.UpdateRequest.TryDequeue(out var addRequest))
{
var (mesh, error) = resourceManager.GetMeshReference(addRequest.meshInstance.mesh);
if (error.IsFailure)
@@ -95,7 +95,7 @@ internal partial class GhostRenderPipeline
{
var addDesc = new BufferDesc
{
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<RemoveInstanceData>(),
Size = (nuint)ghostPayload.UpdateRequest.Count * MemoryUtility.SizeOf<RemoveInstanceData>(),
Stride = (uint)MemoryUtility.SizeOf<RemoveInstanceData>(),
Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
HeapType = HeapType.Upload

View File

@@ -10,7 +10,7 @@ namespace Ghost.Engine.RenderPipeline;
internal sealed class GhostRenderPayload : IRenderPayload
{
public struct AddInstanceRequest
public struct UpdateInstanceRequest
{
public MeshInstance meshInstance;
public float4x4 localToWorld;
@@ -27,7 +27,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
private UnsafeList<RenderRequest> _renderRequests;
private readonly ConcurrentQueue<AddInstanceRequest> _addRequest;
private readonly ConcurrentQueue<UpdateInstanceRequest> _updateRequest;
private readonly ConcurrentQueue<RemoveInstanceRequest> _removeRequest;
private uint _instanceCountBefore;
@@ -35,7 +35,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
public ReadOnlySpan<RenderRequest> RenderRequests => _renderRequests;
public ConcurrentQueue<AddInstanceRequest> AddRequest => _addRequest;
public ConcurrentQueue<UpdateInstanceRequest> UpdateRequest => _updateRequest;
public ConcurrentQueue<RemoveInstanceRequest> RemoveRequest => _removeRequest;
public uint InstanceCountBefore => _instanceCountBefore;
public uint InstanceCount => _instanceCount;
@@ -45,7 +45,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
_renderPipeline = renderPipeline;
_renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent);
_addRequest = new ConcurrentQueue<AddInstanceRequest>();
_updateRequest = new ConcurrentQueue<UpdateInstanceRequest>();
_removeRequest = new ConcurrentQueue<RemoveInstanceRequest>();
}
@@ -59,10 +59,15 @@ internal sealed class GhostRenderPayload : IRenderPayload
{
var index = _renderPipeline.GPUScene.AddInstance();
_addRequest.Enqueue(new AddInstanceRequest { instanceId = index, localToWorld = ltw, meshInstance = meshInstance });
_updateRequest.Enqueue(new UpdateInstanceRequest { instanceId = index, localToWorld = ltw, meshInstance = meshInstance });
return index;
}
public void UpdateInstance(uint instanceId, float4x4 ltw, ref readonly MeshInstance meshInstance)
{
_updateRequest.Enqueue(new UpdateInstanceRequest { instanceId = instanceId, localToWorld = ltw, meshInstance = meshInstance });
}
public void RemoveInstance(uint instanceId)
{
var swapWithInstanceId = _renderPipeline.GPUScene.RemoveInstance(instanceId);
@@ -81,13 +86,13 @@ internal sealed class GhostRenderPayload : IRenderPayload
{
// We capture the count here to prevent that main thread continues to add more requests for next frame while the render thread is still processing current frame's requests.
_instanceCount = _renderPipeline.GPUScene.InstanceCount;
Logger.DebugAssert(_instanceCount == _instanceCountBefore + (uint)_addRequest.Count - (uint)_removeRequest.Count);
Logger.DebugAssert(_instanceCount == _instanceCountBefore + (uint)_updateRequest.Count - (uint)_removeRequest.Count);
}
public void Reset()
{
_renderRequests.Clear();
_addRequest.Clear();
_updateRequest.Clear();
_removeRequest.Clear();
}

View File

@@ -7,8 +7,8 @@ using Misaki.HighPerformance.Utilities;
namespace Ghost.Engine.Systems;
[UpdateAfter<RemoveGPUInstanceSystem>]
[RenderPipelineSystem<GhostRenderPipelineSettings>]
[UpdateAfter<UpdateGPUInstanceSystem>]
internal class AddGPUInstanceSystem : SystemBase
{
private RenderSystem _renderSystem = null!;
@@ -19,7 +19,7 @@ internal class AddGPUInstanceSystem : SystemBase
{
_renderSystem = systemAPI.World.GetService<RenderSystem>();
_meshInstanceQueryID = QueryBuilder.Create()
_meshInstanceQueryID = QueryBuilder.New()
.WithAll<MeshInstance, LocalToWorld>()
.WithAbsent<GPUInstanceRef>()
.Build(systemAPI.World, true);

View File

@@ -18,7 +18,7 @@ internal class RemoveGPUInstanceSystem : SystemBase
{
_renderSystem = systemAPI.World.GetService<RenderSystem>();
_gpuInstanceQueryID = QueryBuilder.Create()
_gpuInstanceQueryID = QueryBuilder.New()
.WithAll<GPUInstanceRef>()
.WithAbsent<MeshInstance>()
.Build(systemAPI.World, true);
@@ -29,6 +29,7 @@ internal class RemoveGPUInstanceSystem : SystemBase
protected override void OnUpdate(ref readonly SystemAPI systemAPI)
{
var payload = (GhostRenderPayload)_renderSystem.GetCurrentFramePayload();
payload.BeginRecord();
ref var gpuInstanceQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_gpuInstanceQueryID);
@@ -47,4 +48,4 @@ internal class RemoveGPUInstanceSystem : SystemBase
}
}
}
}
}

View File

@@ -0,0 +1,56 @@
using Ghost.Core;
using Ghost.Engine.Components;
using Ghost.Engine.RenderPipeline;
using Ghost.Entities;
using Ghost.Graphics;
using Misaki.HighPerformance.Utilities;
namespace Ghost.Engine.Systems;
[RenderPipelineSystem<GhostRenderPipelineSettings>]
[UpdateAfter<RemoveGPUInstanceSystem>]
[UpdateBefore<AddGPUInstanceSystem>]
internal class UpdateGPUInstanceSystem : SystemBase
{
private RenderSystem _renderSystem = null!;
private Identifier<EntityQuery> _gpuInstanceQueryID;
protected override void OnInitialize(ref readonly SystemAPI systemAPI)
{
_renderSystem = systemAPI.World.GetService<RenderSystem>();
_gpuInstanceQueryID = QueryBuilder.New()
.WithAll<LocalToWorld, MeshInstance, GPUInstanceRef>()
.Build(systemAPI.World, true);
RequireQueryForUpdate(_gpuInstanceQueryID);
}
protected override void OnUpdate(ref readonly SystemAPI systemAPI)
{
var playload = (GhostRenderPayload)_renderSystem.GetCurrentFramePayload();
ref var instanceQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_gpuInstanceQueryID);
foreach (var chunk in instanceQuery.GetChunkIterator())
{
if (!chunk.HasChanged<MeshInstance>(LastSystemVersion))
{
continue;
}
var ltws = chunk.GetComponentData<LocalToWorld>();
var meshs = chunk.GetComponentData<MeshInstance>();
var gpuInstances = chunk.GetComponentData<GPUInstanceRef>();
for (var i = 0; i < chunk.EntityCount; i++)
{
ref readonly var ltw = ref ltws.GetElementUnsafe(i);
ref readonly var mesh = ref meshs.GetElementUnsafe(i);
ref readonly var instance = ref gpuInstances.GetElementUnsafe(i);
playload.UpdateInstance(instance.gpuSceneIndex, ltw.matrix, in mesh);
}
}
}
}

View File

@@ -130,7 +130,7 @@ public readonly unsafe ref struct ChunkView
/// <param name="version">The version number to compare against the component's current version. Must be greater than or equal to zero.</param>
/// <returns>true if the component's current version is less than or equal to the specified version; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasChanged(Identifier<IComponent> id, int version)
public bool HasChanged(Identifier<IComponent> id, uint version)
{
var layout = GetLayout(id);
return version < _pVersion[layout.versionIndex];
@@ -144,7 +144,7 @@ public readonly unsafe ref struct ChunkView
/// <param name="version">The version number to compare against the current version of the component.</param>
/// <returns>true if the component of space T has changed since the specified version; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasChanged<T>(int version)
public readonly bool HasChanged<T>(uint version)
where T : unmanaged, IComponent
{
var layout = GetLayout(ComponentTypeID<T>.Value);
@@ -157,7 +157,7 @@ public readonly unsafe ref struct ChunkView
/// <param name="version">The version number to compare against the chunk's structural version.</param>
/// <returns>true if the chunk's structure has changed since the specified version; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasStructuralChanged(int version)
public readonly bool HasStructuralChanged(uint version)
{
return version < _structuralVersion;
}
@@ -502,7 +502,7 @@ public ref partial struct QueryBuilder : IDisposable
_rw = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
}
public static QueryBuilder Create()
public static QueryBuilder New()
{
return new QueryBuilder();
}

View File

@@ -28,11 +28,17 @@ public abstract class SystemBase : ISystem
{
private UnsafeList<int> _requiredQueries;
/// <summary>
/// Gets the world that the system is running on currently.
/// </summary>
public World World
{
get; init;
} = null!;
/// <summary>
/// Gets the last version that the system update.
/// </summary>
public uint LastSystemVersion
{
get; internal set;

View File

@@ -1,10 +1,5 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics;