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:
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(stale);
|
File.Delete(cachePath);
|
||||||
|
goto ScheduleWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
56
src/Runtime/Ghost.Engine/Systems/UpdateGPUInstanceSystem.cs
Normal file
56
src/Runtime/Ghost.Engine/Systems/UpdateGPUInstanceSystem.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user