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

@@ -9,12 +9,14 @@ namespace Ghost.Editor.Core.AssetHandler;
public static class AssetHandlerRegistry public static class AssetHandlerRegistry
{ {
private static readonly Dictionary<string, IAssetHandler> s_byExtension; private static readonly Dictionary<string, IAssetHandler> s_byExtension;
private static readonly Dictionary<string, AssetType> s_typeByExtension;
private static readonly Dictionary<Guid, IAssetHandler> s_byTypeId; private static readonly Dictionary<Guid, IAssetHandler> s_byTypeId;
private static readonly Dictionary<Guid, int> s_versionByTypeId; private static readonly Dictionary<Guid, int> s_versionByTypeId;
static AssetHandlerRegistry() static AssetHandlerRegistry()
{ {
s_byExtension = new Dictionary<string, IAssetHandler>(StringComparer.OrdinalIgnoreCase); s_byExtension = new Dictionary<string, IAssetHandler>(StringComparer.OrdinalIgnoreCase);
s_typeByExtension = new Dictionary<string, AssetType>(StringComparer.OrdinalIgnoreCase);
s_byTypeId = new Dictionary<Guid, IAssetHandler>(); s_byTypeId = new Dictionary<Guid, IAssetHandler>();
s_versionByTypeId = new Dictionary<Guid, int>(); s_versionByTypeId = new Dictionary<Guid, int>();
} }
@@ -28,6 +30,7 @@ public static class AssetHandlerRegistry
{ {
var normalizedExt = ext.StartsWith('.') ? ext : "." + ext; var normalizedExt = ext.StartsWith('.') ? ext : "." + ext;
s_byExtension[normalizedExt] = handler; s_byExtension[normalizedExt] = handler;
s_typeByExtension[normalizedExt] = handler.TargetAssetType;
} }
} }
@@ -59,4 +62,15 @@ public static class AssetHandlerRegistry
{ {
return s_byExtension.Keys; return s_byExtension.Keys;
} }
public static AssetType GetAssetTypeByExtension(string extension)
{
if (string.IsNullOrEmpty(extension))
{
return AssetType.Unknown;
}
var normalized = extension.StartsWith('.') ? extension : "." + extension;
return s_typeByExtension.GetValueOrDefault(normalized, AssetType.Unknown);
}
} }

View File

@@ -1,10 +1,8 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Engine.AssetLoader;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using ImageMagick; using ImageMagick;
using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ghost.Editor.Core.AssetHandler; namespace Ghost.Editor.Core.AssetHandler;
@@ -339,8 +337,8 @@ internal class TextureAssetHandler : IAssetHandler
try try
{ {
using var image = new MagickImage(sourceStream); using var image = new MagickImage(sourceStream);
var pixels = image.GetPixels().GetValues(); var pixels = image.GetPixelsUnsafe().GetAreaPointer(0, 0, image.Width, image.Height);
if (pixels == null) if (pixels == 0)
{ {
return Result.Failure("Failed to retrieve pixel data from the source image."); return Result.Failure("Failed to retrieve pixel data from the source image.");
} }

View File

@@ -1,6 +1,4 @@
using Ghost.Graphics.RHI;
using Ghost.Nvtt; using Ghost.Nvtt;
using ImageMagick;
using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel;
using System.IO.Hashing; using System.IO.Hashing;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -26,7 +24,7 @@ internal static class TextureProcessor
{ {
private readonly string _outputPath; private readonly string _outputPath;
private readonly float[] _image; private readonly nint _image;
private readonly uint _depth; private readonly uint _depth;
private readonly uint _width; private readonly uint _width;
private readonly uint _height; private readonly uint _height;
@@ -38,7 +36,7 @@ internal static class TextureProcessor
public Task Task => _completionSource.Task; public Task Task => _completionSource.Task;
public NvttPipelineTask(string outputPath, float[] image, uint width, uint height, uint depth, TextureAssetSettings settings) public NvttPipelineTask(string outputPath, nint image, uint width, uint height, uint depth, TextureAssetSettings settings)
{ {
_outputPath = outputPath; _outputPath = outputPath;
_image = image; _image = image;
@@ -60,10 +58,7 @@ internal static class TextureProcessor
? NvttInputFormat.NVTT_InputFormat_RGBA_32F ? NvttInputFormat.NVTT_InputFormat_RGBA_32F
: NvttInputFormat.NVTT_InputFormat_BGRA_8UB; // we'll swizzle RB below : NvttInputFormat.NVTT_InputFormat_BGRA_8UB; // we'll swizzle RB below
fixed (void* pData = _image) pSurface.Get()->SetImageData(inputFormat, (int)_width, (int)_height, 1, (void*)_image, NvttBoolean.NVTT_True, null);
{
pSurface.Get()->SetImageData(inputFormat, (int)_width, (int)_height, 1, pData, NvttBoolean.NVTT_True, null);
}
// stb gives us RGBA byte order; NVTT BGRA_8UB reads it as BGRA, // stb gives us RGBA byte order; NVTT BGRA_8UB reads it as BGRA,
// so channels R and B are swapped — fix with swizzle(2,1,0,3). // so channels R and B are swapped — fix with swizzle(2,1,0,3).
@@ -164,7 +159,7 @@ internal static class TextureProcessor
} }
} }
public static async ValueTask<(string cachePath, int mipmapCount)> CompressToCacheAsync(string cachesFolderPath, Guid assetId, float[] image, uint width, uint height, uint depth, TextureAssetSettings settings, CancellationToken cancellationToken) public static async ValueTask<(string cachePath, int mipmapCount)> CompressToCacheAsync(string cachesFolderPath, Guid assetId, nint image, uint width, uint height, uint depth, TextureAssetSettings settings, CancellationToken cancellationToken)
{ {
var settingsHash = ComputeSettingsHash(settings); var settingsHash = ComputeSettingsHash(settings);
var cacheFileName = $"texturecache_{assetId:N}_{settingsHash:X16}.dds"; var cacheFileName = $"texturecache_{assetId:N}_{settingsHash:X16}.dds";
@@ -176,15 +171,37 @@ internal static class TextureProcessor
if (File.Exists(cachePath)) if (File.Exists(cachePath))
{ {
// TODO: Implement mipmap count retrieval from existing cache file using var fs = new FileStream(cachePath, FileMode.Open, FileAccess.Read);
return (cachePath, 0); using var reader = new BinaryReader(fs);
} if (reader.ReadUInt32() != 0x20534444)
{
foreach (var stale in Directory.EnumerateFiles(cachesFolderPath, $"texturecache_{assetId:N}_*.dds")) File.Delete(cachePath);
{ goto ScheduleWork;
File.Delete(stale); }
// Read dwFlags (Offset 8)
// Skip dwSize (4 bytes), then read dwFlags (4 bytes)
reader.BaseStream.Seek(4, SeekOrigin.Current);
var flags = reader.ReadUInt32();
// The DDSD_MIPMAPCOUNT flag is 0x00020000
var hasMipMapFlag = (flags & 0x00020000) != 0;
// Read dwMipMapCount (Offset 28)
reader.BaseStream.Seek(28, SeekOrigin.Begin);
var mipMapCount = reader.ReadUInt32();
// Return the correct count
// If the flag is missing, or the count says 0, there is still 1 main image.
if (!hasMipMapFlag || mipMapCount == 0)
{
return (cachePath, 1);
}
return (cachePath, (int)mipMapCount);
} }
ScheduleWork:
var workItem = new NvttPipelineTask(cachePath, image, width, height, depth, settings); var workItem = new NvttPipelineTask(cachePath, image, width, height, depth, settings);
ThreadPool.UnsafeQueueUserWorkItem(workItem, true); ThreadPool.UnsafeQueueUserWorkItem(workItem, true);
await workItem.Task.WaitAsync(cancellationToken).ConfigureAwait(false); await workItem.Task.WaitAsync(cancellationToken).ConfigureAwait(false);

View File

@@ -1,5 +1,4 @@
using Ghost.Editor.Core.AssetHandler; using Ghost.Editor.Core.AssetHandler;
using Ghost.Engine;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
namespace Ghost.Editor.Core.Services; namespace Ghost.Editor.Core.Services;

View File

@@ -341,7 +341,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
} }
var tasks = new Task<Result>[_dirtyAssets.Count]; var tasks = new Task<Result>[_dirtyAssets.Count];
var i = 0; var i = 0;
foreach (var id in _dirtyAssets) foreach (var id in _dirtyAssets)
{ {

View File

@@ -10,7 +10,7 @@ namespace Ghost.Editor.ViewModels.Controls;
internal partial class ProjectBrowserViewModel : ObservableObject internal partial class ProjectBrowserViewModel : ObservableObject
{ {
private readonly IInspectorService _inspectorService; private readonly IInspectorService _inspectorService;
// private readonly IAssetService _assetService; private readonly IAssetRegistry _assetRegistry;
private readonly Dictionary<string, ExplorerItem> _pathToDirectoryItemMap = new(); private readonly Dictionary<string, ExplorerItem> _pathToDirectoryItemMap = new();
private ExplorerItem? _selectedItem; private ExplorerItem? _selectedItem;
@@ -40,10 +40,10 @@ internal partial class ProjectBrowserViewModel : ObservableObject
get; set; get; set;
} = string.Empty; } = string.Empty;
public ProjectBrowserViewModel(IInspectorService inspectorService) // , IAssetService assetService) public ProjectBrowserViewModel(IInspectorService inspectorService, IAssetRegistry assetRegistry)
{ {
_inspectorService = inspectorService; _inspectorService = inspectorService;
// _assetService = assetService; _assetRegistry = assetRegistry;
var assetsRootItem = new ExplorerItem(EditorApplication.ASSETS_FOLDER_NAME, Path.Combine(EditorApplication.ProjectPath, EditorApplication.ASSETS_FOLDER_NAME), true); var assetsRootItem = new ExplorerItem(EditorApplication.ASSETS_FOLDER_NAME, Path.Combine(EditorApplication.ProjectPath, EditorApplication.ASSETS_FOLDER_NAME), true);
LoadSubFolderRecursive(assetsRootItem); LoadSubFolderRecursive(assetsRootItem);
@@ -109,7 +109,7 @@ internal partial class ProjectBrowserViewModel : ObservableObject
} }
else else
{ {
// _assetService.OpenAsset(SelectedItem.FullName); // _assetRegistry.OpenAsset(SelectedItem.FullName);
return (null, 1); return (null, 1);
} }
} }

View File

@@ -70,6 +70,7 @@
x:Name="ContentFrame" x:Name="ContentFrame"
Grid.Row="2" Grid.Row="2"
IsNavigationStackEnabled="False" />--> IsNavigationStackEnabled="False" />-->
<!-- Edit View -->
<Grid Grid.Row="1" Background="{ThemeResource LayerFillColorDefaultBrush}"> <Grid Grid.Row="1" Background="{ThemeResource LayerFillColorDefaultBrush}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -390,6 +391,7 @@
Margin="8,0" Margin="8,0"
PlaceholderText="Search components..." /> PlaceholderText="Search components..." />
<!-- Components List -->
<ListView <ListView
Grid.Row="2" Grid.Row="2"
Padding="4,2,0,2" Padding="4,2,0,2"
@@ -406,6 +408,7 @@
<TextBlock Text="Test" /> <TextBlock Text="Test" />
</ListView> </ListView>
<!-- Component Properties for Selected Component -->
<ScrollView <ScrollView
Grid.Row="3" Grid.Row="3"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}" BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"

View File

@@ -20,13 +20,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.8" /> <PackageReference Include="Misaki.HighPerformance" Version="1.0.8" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.0.0" /> <PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.0" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.14"> <PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.15">
<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.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" /> <PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
</ItemGroup> </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 ThreadCount = Environment.ProcessorCount - 2, // We -2 here, one for main thread, one for render thread
ThreadPriority = ThreadPriority.Normal, ThreadPriority = ThreadPriority.Normal,
DependencyChainCapacity = 8192,
}; };
_jobScheduler = new JobScheduler(in desc); _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) 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. // 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 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>(), Stride = (uint)MemoryUtility.SizeOf<UpdateInstanceData>(),
Usage = BufferUsage.Structured | BufferUsage.ShaderResource, Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
HeapType = HeapType.Upload HeapType = HeapType.Upload
@@ -57,7 +57,7 @@ internal partial class GhostRenderPipeline
var pAddData = (UpdateInstanceData*)resourceDatabase.MapResource(addBuffer.AsResource(), 0, null); var pAddData = (UpdateInstanceData*)resourceDatabase.MapResource(addBuffer.AsResource(), 0, null);
var i = 0; 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); var (mesh, error) = resourceManager.GetMeshReference(addRequest.meshInstance.mesh);
if (error.IsFailure) if (error.IsFailure)
@@ -95,7 +95,7 @@ internal partial class GhostRenderPipeline
{ {
var addDesc = new BufferDesc 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>(), Stride = (uint)MemoryUtility.SizeOf<RemoveInstanceData>(),
Usage = BufferUsage.Structured | BufferUsage.ShaderResource, Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
HeapType = HeapType.Upload HeapType = HeapType.Upload

View File

@@ -10,7 +10,7 @@ namespace Ghost.Engine.RenderPipeline;
internal sealed class GhostRenderPayload : IRenderPayload internal sealed class GhostRenderPayload : IRenderPayload
{ {
public struct AddInstanceRequest public struct UpdateInstanceRequest
{ {
public MeshInstance meshInstance; public MeshInstance meshInstance;
public float4x4 localToWorld; public float4x4 localToWorld;
@@ -27,7 +27,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
private UnsafeList<RenderRequest> _renderRequests; private UnsafeList<RenderRequest> _renderRequests;
private readonly ConcurrentQueue<AddInstanceRequest> _addRequest; private readonly ConcurrentQueue<UpdateInstanceRequest> _updateRequest;
private readonly ConcurrentQueue<RemoveInstanceRequest> _removeRequest; private readonly ConcurrentQueue<RemoveInstanceRequest> _removeRequest;
private uint _instanceCountBefore; private uint _instanceCountBefore;
@@ -35,7 +35,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
public ReadOnlySpan<RenderRequest> RenderRequests => _renderRequests; public ReadOnlySpan<RenderRequest> RenderRequests => _renderRequests;
public ConcurrentQueue<AddInstanceRequest> AddRequest => _addRequest; public ConcurrentQueue<UpdateInstanceRequest> UpdateRequest => _updateRequest;
public ConcurrentQueue<RemoveInstanceRequest> RemoveRequest => _removeRequest; public ConcurrentQueue<RemoveInstanceRequest> RemoveRequest => _removeRequest;
public uint InstanceCountBefore => _instanceCountBefore; public uint InstanceCountBefore => _instanceCountBefore;
public uint InstanceCount => _instanceCount; public uint InstanceCount => _instanceCount;
@@ -45,7 +45,7 @@ internal sealed class GhostRenderPayload : IRenderPayload
_renderPipeline = renderPipeline; _renderPipeline = renderPipeline;
_renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent); _renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent);
_addRequest = new ConcurrentQueue<AddInstanceRequest>(); _updateRequest = new ConcurrentQueue<UpdateInstanceRequest>();
_removeRequest = new ConcurrentQueue<RemoveInstanceRequest>(); _removeRequest = new ConcurrentQueue<RemoveInstanceRequest>();
} }
@@ -59,10 +59,15 @@ internal sealed class GhostRenderPayload : IRenderPayload
{ {
var index = _renderPipeline.GPUScene.AddInstance(); 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; 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) public void RemoveInstance(uint instanceId)
{ {
var swapWithInstanceId = _renderPipeline.GPUScene.RemoveInstance(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. // 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; _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() public void Reset()
{ {
_renderRequests.Clear(); _renderRequests.Clear();
_addRequest.Clear(); _updateRequest.Clear();
_removeRequest.Clear(); _removeRequest.Clear();
} }

View File

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

View File

@@ -18,7 +18,7 @@ internal class RemoveGPUInstanceSystem : SystemBase
{ {
_renderSystem = systemAPI.World.GetService<RenderSystem>(); _renderSystem = systemAPI.World.GetService<RenderSystem>();
_gpuInstanceQueryID = QueryBuilder.Create() _gpuInstanceQueryID = QueryBuilder.New()
.WithAll<GPUInstanceRef>() .WithAll<GPUInstanceRef>()
.WithAbsent<MeshInstance>() .WithAbsent<MeshInstance>()
.Build(systemAPI.World, true); .Build(systemAPI.World, true);
@@ -29,6 +29,7 @@ internal class RemoveGPUInstanceSystem : SystemBase
protected override void OnUpdate(ref readonly SystemAPI systemAPI) protected override void OnUpdate(ref readonly SystemAPI systemAPI)
{ {
var payload = (GhostRenderPayload)_renderSystem.GetCurrentFramePayload(); var payload = (GhostRenderPayload)_renderSystem.GetCurrentFramePayload();
payload.BeginRecord();
ref var gpuInstanceQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_gpuInstanceQueryID); 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> /// <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> /// <returns>true if the component's current version is less than or equal to the specified version; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasChanged(Identifier<IComponent> id, int version) public bool HasChanged(Identifier<IComponent> id, uint version)
{ {
var layout = GetLayout(id); var layout = GetLayout(id);
return version < _pVersion[layout.versionIndex]; 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> /// <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> /// <returns>true if the component of space T has changed since the specified version; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasChanged<T>(int version) public readonly bool HasChanged<T>(uint version)
where T : unmanaged, IComponent where T : unmanaged, IComponent
{ {
var layout = GetLayout(ComponentTypeID<T>.Value); 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> /// <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> /// <returns>true if the chunk's structure has changed since the specified version; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool HasStructuralChanged(int version) public readonly bool HasStructuralChanged(uint version)
{ {
return version < _structuralVersion; return version < _structuralVersion;
} }
@@ -502,7 +502,7 @@ public ref partial struct QueryBuilder : IDisposable
_rw = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle); _rw = new UnsafeList<Identifier<IComponent>>(4, _scope.AllocationHandle);
} }
public static QueryBuilder Create() public static QueryBuilder New()
{ {
return new QueryBuilder(); return new QueryBuilder();
} }

View File

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

View File

@@ -1,10 +1,5 @@
using Ghost.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Services; 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; namespace Ghost.Graphics;

View File

@@ -15,7 +15,7 @@ internal class CameraMovingSystem : ISystem
public void Initialize(ref readonly SystemAPI systemAPI) public void Initialize(ref readonly SystemAPI systemAPI)
{ {
_cameraQueryID = QueryBuilder.Create() _cameraQueryID = QueryBuilder.New()
.WithAll<Camera, LocalToWorld>() .WithAll<Camera, LocalToWorld>()
.Build(systemAPI.World, true); .Build(systemAPI.World, true);