Fixed compilation errors;

Added MaterialPalette
This commit is contained in:
2026-03-14 12:27:49 +09:00
parent 8a3b40b4f8
commit 912b320d8f
27 changed files with 1870 additions and 483 deletions

1125
meshlet-architecture.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -59,7 +59,7 @@ internal static class ActivationHandler
((EngineCore)App.GetService<IEngineContext>()).Init(); ((EngineCore)App.GetService<IEngineContext>()).Init();
}); });
await ((Core.AssetHandle.AssetService)App.GetService<IAssetService>()).Init(); // await ((Core.AssetHandle.AssetService)App.GetService<IAssetService>()).Init();
// TODO: Init other subsystems here. // TODO: Init other subsystems here.
// await Task.Delay(10000); // Wait 10 seconds to simulate work. // await Task.Delay(10000); // Wait 10 seconds to simulate work.

View File

@@ -63,7 +63,7 @@ public partial class App : Application
services.AddSingleton<IProgressService, ProgressService>(); services.AddSingleton<IProgressService, ProgressService>();
services.AddSingleton<IInspectorService, InspectorService>(); services.AddSingleton<IInspectorService, InspectorService>();
services.AddSingleton<IPreviewService, PreviewService>(); services.AddSingleton<IPreviewService, PreviewService>();
services.AddSingleton<IAssetService, AssetService>(); // services.AddSingleton<IAssetService, AssetService>();
services.AddSingleton<EngineEditorViewModel>(); services.AddSingleton<EngineEditorViewModel>();

View File

@@ -46,7 +46,7 @@
<!-- Named Style --> <!-- Named Style -->
<Style <Style
x:Key="ToolbarButton" x:Key="ToolbarButton"
BasedOn="{ThemeResource SubtleButtonStyle}" BasedOn="{StaticResource SubtleButtonStyle}"
TargetType="Button" /> TargetType="Button" />
<!-- Named Resource --> <!-- Named Resource -->

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 IAssetService _assetService;
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) // , IAssetService assetService)
{ {
_inspectorService = inspectorService; _inspectorService = inspectorService;
_assetService = assetService; // _assetService = assetService;
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); // _assetService.OpenAsset(SelectedItem.FullName);
return (null, 1); return (null, 1);
} }
} }

View File

@@ -7,7 +7,7 @@ namespace Ghost.Editor.ViewModels.Pages.EngineEditor;
internal partial class ProjectViewModel : ObservableObject internal partial class ProjectViewModel : ObservableObject
{ {
private readonly IAssetService _assetService; // private readonly IAssetService _assetService;
public ObservableCollection<ExplorerItem> SubDirectories public ObservableCollection<ExplorerItem> SubDirectories
{ {
@@ -35,15 +35,15 @@ internal partial class ProjectViewModel : ObservableObject
set; set;
} }
public ProjectViewModel(IAssetService assetService) // public ProjectViewModel(IAssetService assetService)
{ // {
_assetService = assetService; // _assetService = assetService;
var assetsRootItem = new ExplorerItem("Assets", Path.Combine(EditorApplication.ProjectPath, EditorApplication.ASSETS_FOLDER_NAME), true); // var assetsRootItem = new ExplorerItem("Assets", Path.Combine(EditorApplication.ProjectPath, EditorApplication.ASSETS_FOLDER_NAME), true);
LoadSubFolderRecursive(ref assetsRootItem); // LoadSubFolderRecursive(ref assetsRootItem);
SubDirectories.Add(assetsRootItem); // SubDirectories.Add(assetsRootItem);
} // }
private static void LoadSubFolderRecursive(ref ExplorerItem parentItem) private static void LoadSubFolderRecursive(ref ExplorerItem parentItem)
{ {
@@ -127,7 +127,7 @@ internal partial class ProjectViewModel : ObservableObject
} }
else else
{ {
_assetService.OpenAsset(SelectedAsset.FullName); // _assetService.OpenAsset(SelectedAsset.FullName);
} }
} }

View File

@@ -7,8 +7,7 @@ namespace Ghost.Engine.Components;
public struct MeshInstance : IComponent public struct MeshInstance : IComponent
{ {
public Handle<Mesh> mesh; public Handle<Mesh> mesh;
// NOTE: This will be the first material, we can access other materials by the bindless index of the first material + the local index stored in the meshlet. public int materialPaletteIndex;
public Handle<Material> materialStart;
public ShadowCastingMode shadowCastingMode; public ShadowCastingMode shadowCastingMode;
public RenderingLayerMask renderingLayerMask; public RenderingLayerMask renderingLayerMask;
public bool staticShadowCaster; public bool staticShadowCaster;

View File

@@ -42,9 +42,8 @@ public class RenderExtractionSystem : ISystem
renderList.Add(new RenderRecord renderList.Add(new RenderRecord
{ {
localToWorld = localToWorld.matrix, localToWorld = localToWorld.matrix,
// TODO: Get mesh and material from palette. This requires some changes to ISharedComponent since it's now fully functional right now. mesh = meshInstance.mesh,
// mesh = meshInstance.meshIndex, materialPaletteIndex = meshInstance.materialPaletteIndex,
// material = meshInstance.materialIndex,
renderingLayerMask = meshInstance.renderingLayerMask, renderingLayerMask = meshInstance.renderingLayerMask,
}, 0); }, 0);

View File

@@ -6,7 +6,7 @@ namespace Ghost.Entities;
public interface IJobChunk public interface IJobChunk
{ {
void Execute(ChunkView view, int threadIndex); void Execute(ChunkView view, ref readonly JobExecutionContext ctx);
} }
internal unsafe struct ChunkInfo internal unsafe struct ChunkInfo
@@ -22,12 +22,12 @@ internal unsafe struct JobChunkBatch<TJob> : IJobParallelFor
public TJob userJob; public TJob userJob;
public ReadOnlyUnsafeCollection<ChunkInfo> chunkInfos; public ReadOnlyUnsafeCollection<ChunkInfo> chunkInfos;
public void Execute(int loopIndex, int threadIndex) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
var info = chunkInfos[loopIndex]; var info = chunkInfos[loopIndex];
var view = new ChunkView(in *info.pArchetype, in *info.pChunk); var view = new ChunkView(in *info.pArchetype, in *info.pChunk);
userJob.Execute(view, threadIndex); userJob.Execute(view, in ctx);
} }
} }
@@ -35,7 +35,7 @@ internal struct DisposeJobChunk : IJob
{ {
public UnsafeList<ChunkInfo> list; public UnsafeList<ChunkInfo> list;
public void Execute(int threadIndex) public void Execute(ref readonly JobExecutionContext ctx)
{ {
list.Dispose(); list.Dispose();
} }

View File

@@ -18,7 +18,7 @@ namespace Ghost.Entities;
public interface IJobEntity<<#= generics #>> public interface IJobEntity<<#= generics #>>
<#= restrictions #> <#= restrictions #>
{ {
void Execute(Entity entity, <#= AppendParameters(i, "ref T{0} component{0}") #>, int threadIndex); void Execute(Entity entity, <#= AppendParameters(i, "ref T{0} component{0}") #>, ref readonly JobExecutionContext ctx);
} }
internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
@@ -38,12 +38,12 @@ internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
<# for (var j = 0; j < i; j++){ #> <# for (var j = 0; j < i; j++){ #>
public UnsafeList<int> offsets<#= j #>; public UnsafeList<int> offsets<#= j #>;
public UnsafeList<int> bitsOffsets<#= j #>; public UnsafeList<int> bitsOffsets<#= j #>;
public UnsafeList<int> versionindices<#= j #>; public UnsafeList<int> versionIndices<#= j #>;
<# } #> <# } #>
public int version; public int version;
public void Execute(int loopIndex, int threadIndex) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
// 1. Get the specific pChunk for this thread // 1. Get the specific pChunk for this thread
var pChunk = (byte*)chunks[loopIndex]; var pChunk = (byte*)chunks[loopIndex];
@@ -53,7 +53,7 @@ internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
<# for (var j = 0; j < i; j++){ #> <# for (var j = 0; j < i; j++){ #>
var off<#= j #> = offsets<#= j #>[loopIndex]; var off<#= j #> = offsets<#= j #>[loopIndex];
var enableOff<#= j #> = bitsOffsets<#= j #>[loopIndex]; var enableOff<#= j #> = bitsOffsets<#= j #>[loopIndex];
var versionIndex<#= j #> = versionindices<#= j #>[loopIndex]; var versionIndex<#= j #> = versionIndices<#= j #>[loopIndex];
<# } #> <# } #>
var pEntity = (Entity*)(pChunk + entityOffset[loopIndex]); var pEntity = (Entity*)(pChunk + entityOffset[loopIndex]);
@@ -79,7 +79,7 @@ internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
} }
<# } #> <# } #>
userJob.Execute(pEntity[i], <#= AppendParameters(i, "ref ptr{0}[i]") #>, threadIndex); userJob.Execute(pEntity[i], <#= AppendParameters(i, "ref ptr{0}[i]") #>, in ctx);
} }
} }
} }
@@ -102,10 +102,10 @@ public unsafe partial struct EntityQuery
<# for (var j = 0; j < i; j++){ #> <# for (var j = 0; j < i; j++){ #>
public UnsafeList<int> offsets<#= j #>; public UnsafeList<int> offsets<#= j #>;
public UnsafeList<int> bitsOffsets<#= j #>; public UnsafeList<int> bitsOffsets<#= j #>;
public UnsafeList<int> versionindices<#= j #>; public UnsafeList<int> versionIndices<#= j #>;
<# } #> <# } #>
public void Execute(int threadIndex) public void Execute(ref readonly JobExecutionContext ctx)
{ {
chunks.Dispose(); chunks.Dispose();
chunkVersions.Dispose(); chunkVersions.Dispose();
@@ -115,7 +115,7 @@ public unsafe partial struct EntityQuery
<# for (var j = 0; j < i; j++){ #> <# for (var j = 0; j < i; j++){ #>
offsets<#= j #>.Dispose(); offsets<#= j #>.Dispose();
bitsOffsets<#= j #>.Dispose(); bitsOffsets<#= j #>.Dispose();
versionindices<#= j #>.Dispose(); versionIndices<#= j #>.Dispose();
<# } #> <# } #>
} }
@@ -160,8 +160,7 @@ public unsafe partial struct EntityQuery
// Get offsets ONCE per archetype // Get offsets ONCE per archetype
<# for (var j = 0; j < i; j++){ #> <# for (var j = 0; j < i; j++){ #>
var layout<#= j #> = arch.GetLayout(ComponentTypeID<T<#= j #>>.Value) var layout<#= j #> = arch.GetLayout(ComponentTypeID<T<#= j #>>.Value).GetValueOrThrow();
.GetValueOrThrow();
<# } #> <# } #>
// Add all chunks from this archetype // Add all chunks from this archetype
@@ -195,7 +194,7 @@ public unsafe partial struct EntityQuery
<# for (var j = 0; j < i; j++){ #> <# for (var j = 0; j < i; j++){ #>
offsets<#= j #> = offsets<#= j #>, offsets<#= j #> = offsets<#= j #>,
bitsOffsets<#= j #> = bitsOffsets<#= j #>, bitsOffsets<#= j #> = bitsOffsets<#= j #>,
versionindices<#= j #> = versionIndices<#= j #>, versionIndices<#= j #> = versionIndices<#= j #>,
<# } #> <# } #>
version = world.Version, version = world.Version,
@@ -229,7 +228,7 @@ public unsafe partial struct EntityQuery
<# for (var j = 0; j < i; j++){ #> <# for (var j = 0; j < i; j++){ #>
offsets<#= j #> = offsets<#= j #>, offsets<#= j #> = offsets<#= j #>,
bitsOffsets<#= j #> = bitsOffsets<#= j #>, bitsOffsets<#= j #> = bitsOffsets<#= j #>,
versionindices<#= j #> = versionIndices<#= j #>, versionIndices<#= j #> = versionIndices<#= j #>,
<# } #> <# } #>
}; };

View File

@@ -1,5 +1,6 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.RenderGraphModule; using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.Core.Contracts; namespace Ghost.Graphics.Core.Contracts;
@@ -7,5 +8,5 @@ public interface IRenderPass
{ {
void Initialize(ref readonly RenderingContext ctx); void Initialize(ref readonly RenderingContext ctx);
void Build(RenderGraph graph, Identifier<RGTexture> backbuffer); void Build(RenderGraph graph, Identifier<RGTexture> backbuffer);
void Cleanup(IResourceManager resourceManager); void Cleanup(IResourceManager resourceManager, IResourceDatabase resourceDatabase);
} }

View File

@@ -0,0 +1,242 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Core;
public readonly struct MaterialPaletteInfo
{
public int MaterialCount
{
get;
init;
}
}
internal sealed class MaterialPaletteStore : IDisposable
{
private struct Entry : IDisposable
{
public UnsafeList<Handle<Material>> materials;
public int refCount;
public ulong lookupHash;
public int nextFree;
public readonly bool IsAllocated => materials.IsCreated;
public readonly bool IsActive => refCount > 0 && materials.IsCreated;
public void Dispose()
{
materials.Dispose();
}
}
private UnsafeList<Entry> _entries;
private UnsafeHashMap<ulong, int> _lookup;
private int _freeListHead;
private bool _disposed;
public MaterialPaletteStore(int initialCapacity = 16)
{
if (initialCapacity <= 0)
{
initialCapacity = 16;
}
_entries = new UnsafeList<Entry>(initialCapacity + 1, Allocator.Persistent);
_lookup = new UnsafeHashMap<ulong, int>(initialCapacity * 2, Allocator.Persistent);
_freeListHead = 0;
_entries.Add(new Entry
{
materials = default,
refCount = int.MaxValue,
lookupHash = 0,
nextFree = -1,
});
}
~MaterialPaletteStore()
{
Dispose();
}
private int AllocateEntry()
{
if (_freeListHead != 0)
{
int index = _freeListHead;
ref var entry = ref _entries[index];
_freeListHead = entry.nextFree;
entry.nextFree = -1;
return index;
}
int newIndex = _entries.Count;
_entries.Add(default);
return newIndex;
}
private static ulong ComputeLookupHash(ReadOnlySpan<Handle<Material>> materials, ulong seed)
{
const ulong offset = 14695981039346656037UL;
ulong hash = offset ^ seed;
hash = Mix(hash, (ulong)materials.Length);
foreach (var material in materials)
{
hash = Mix(hash, (uint)material.ID);
hash = Mix(hash, (uint)material.Generation);
}
return hash;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Mix(ulong hash, ulong value)
{
const ulong prime = 1099511628211UL;
hash ^= value;
hash *= prime;
return hash;
}
public int InsertOrGet(ReadOnlySpan<Handle<Material>> materials)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (materials.Length == 0)
{
return 0;
}
ulong hash = ComputeLookupHash(materials, 0);
while (_lookup.TryGetValue(hash, out var existingIndex))
{
ref var entry = ref _entries[existingIndex];
if (entry.IsActive && materials.SequenceEqual(entry.materials.AsSpan()))
{
entry.refCount++;
return existingIndex;
}
hash = ComputeLookupHash(materials, hash);
}
int index = AllocateEntry();
ref var newEntry = ref _entries[index];
newEntry.lookupHash = hash;
newEntry.refCount = 1;
newEntry.nextFree = -1;
if (!newEntry.materials.IsCreated)
{
newEntry.materials = new UnsafeList<Handle<Material>>(materials.Length, Allocator.Persistent);
}
else
{
newEntry.materials.Clear();
}
for (int i = 0; i < materials.Length; i++)
{
newEntry.materials.Add(materials[i]);
}
_lookup.Add(hash, index);
return index;
}
public bool IsValid(int paletteIndex)
{
if (paletteIndex == 0)
{
return true;
}
return paletteIndex > 0
&& paletteIndex < _entries.Count
&& _entries[paletteIndex].IsActive;
}
public MaterialPaletteInfo GetInfo(int paletteIndex)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!IsValid(paletteIndex))
{
return default;
}
if (paletteIndex == 0)
{
return default;
}
return new MaterialPaletteInfo
{
MaterialCount = _entries[paletteIndex].materials.Count,
};
}
public Handle<Material> GetMaterial(int paletteIndex, int localMaterialIndex)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!IsValid(paletteIndex) || paletteIndex == 0)
{
return Handle<Material>.Invalid;
}
ref var entry = ref _entries[paletteIndex];
if ((uint)localMaterialIndex >= (uint)entry.materials.Count)
{
return Handle<Material>.Invalid;
}
return entry.materials[localMaterialIndex];
}
public void Release(int paletteIndex)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (paletteIndex == 0 || !IsValid(paletteIndex))
{
return;
}
ref var entry = ref _entries[paletteIndex];
entry.refCount--;
if (entry.refCount > 0)
{
return;
}
_lookup.Remove(entry.lookupHash);
entry.materials.Clear();
entry.nextFree = _freeListHead;
_freeListHead = paletteIndex;
}
public void Dispose()
{
if (_disposed)
{
return;
}
for (int i = 0; i < _entries.Count; i++)
{
_entries[i].Dispose();
}
_entries.Dispose();
_lookup.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -9,11 +9,64 @@ using Misaki.HighPerformance.Mathematics.Geometry;
namespace Ghost.Graphics.Core; namespace Ghost.Graphics.Core;
// TODO: Support sub-meshes and meshlets. public struct Meshlet
{
public SphereBounds boundingSphere; // 16 bytes
public AABB boundingBox; // 24 bytes
public uint vertexOffset; // offset into meshlet vertex index array
public uint triangleOffset; // offset into packed triangle array
public uint groupIndex; // owning group
public float parentError; // geometric refinement error carried into runtime LOD tests
public byte vertexCount; // max 64
public byte triangleCount; // max 124
public byte localMaterialIndex; // mesh-local material slot
public byte lodLevel; // this meshlet's LOD level
}
public struct MeshletGroup
{
public SphereBounds boundingSphere; // 16 bytes
public AABB boundingBox; // 24 bytes
public float parentError; // error of refining to the previous level
public uint meshletStartIndex; // contiguous meshlet range
public uint meshletCount; // number of meshlets in the group
public uint lodLevel; // group LOD level
}
public struct MeshletHierarchyNode
{
public SphereBounds boundingSphere; // 16 bytes
public AABB boundingBox; // 24 bytes
public float maxParentError; // maximum error in this subtree
public uint nodeData; // packed leaf/internal metadata
}
public struct MeshletMeshData : IDisposable
{
public UnsafeList<Meshlet> meshlets;
public UnsafeList<MeshletGroup> groups;
public UnsafeList<MeshletHierarchyNode> hierarchyNodes;
public UnsafeList<uint> meshletVertices;
public UnsafeList<byte> meshletTriangles;
public int lodLevelCount;
public int materialSlotCount;
public void Dispose()
{
meshlets.Dispose();
groups.Dispose();
hierarchyNodes.Dispose();
meshletVertices.Dispose();
meshletTriangles.Dispose();
}
}
// TODO: Support and meshlets.
public struct Mesh : IResourceReleasable public struct Mesh : IResourceReleasable
{ {
private UnsafeList<Vertex> _vertices; private UnsafeList<Vertex> _vertices;
private UnsafeList<uint> _indices; private UnsafeList<uint> _indices;
private MeshletMeshData _meshletData;
internal bool IsMeshDataDirty internal bool IsMeshDataDirty
{ {
@@ -88,6 +141,14 @@ public struct Mesh : IResourceReleasable
get; internal set; get; internal set;
} }
/// <summary>
/// Gets the handle to the meshlet buffer on the GPU.
/// </summary>
public Handle<GraphicsBuffer> MeshLetBuffer
{
get; internal set;
}
/// <summary> /// <summary>
/// Gets the handle to the mesh data buffer on the GPU. /// Gets the handle to the mesh data buffer on the GPU.
/// </summary> /// </summary>
@@ -112,6 +173,7 @@ public struct Mesh : IResourceReleasable
{ {
_vertices.Dispose(); _vertices.Dispose();
_indices.Dispose(); _indices.Dispose();
_meshletData.Dispose();
} }
public readonly void ReleaseResource(IResourceDatabase database) public readonly void ReleaseResource(IResourceDatabase database)
@@ -120,6 +182,7 @@ public struct Mesh : IResourceReleasable
database.ReleaseResource(VertexBuffer.AsResource()); database.ReleaseResource(VertexBuffer.AsResource());
database.ReleaseResource(IndexBuffer.AsResource()); database.ReleaseResource(IndexBuffer.AsResource());
database.ReleaseResource(MeshLetBuffer.AsResource());
database.ReleaseResource(ObjectDataBuffer.AsResource()); database.ReleaseResource(ObjectDataBuffer.AsResource());
} }
} }

View File

@@ -5,11 +5,11 @@ using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.Core; namespace Ghost.Graphics.Core;
public record struct RenderRecord public struct RenderRecord
{ {
public float4x4 localToWorld; public float4x4 localToWorld;
public Handle<Mesh> mesh; public Handle<Mesh> mesh;
public Handle<Material> materialOffset; public int materialPaletteIndex;
public RenderingLayerMask renderingLayerMask; public RenderingLayerMask renderingLayerMask;
} }

View File

@@ -64,7 +64,7 @@ public sealed class RenderGraph : IDisposable
); );
_nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources); _nativePassBuilder = new RenderGraphNativePassBuilder(_objectPool, _resources);
_compiler = new RenderGraphCompiler(_resourceManager, _resources, _aliasingManager, _nativePassBuilder, _compilationCache); _compiler = new RenderGraphCompiler(_resourceManager, _resourceDatabase, _resourceAllocator, _resources, _aliasingManager, _nativePassBuilder, _compilationCache);
_executor = new RenderGraphExecutor(_resourceManager, _resourceDatabase, _resources, _context); _executor = new RenderGraphExecutor(_resourceManager, _resourceDatabase, _resources, _context);
_blackboard = new RenderGraphBlackboard(); _blackboard = new RenderGraphBlackboard();

View File

@@ -10,6 +10,8 @@ namespace Ghost.Graphics.RenderGraphModule;
internal sealed class RenderGraphCompiler internal sealed class RenderGraphCompiler
{ {
private readonly IResourceManager _resourceManager; private readonly IResourceManager _resourceManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly IResourceAllocator _resourceAllocator;
private readonly RenderGraphResourceRegistry _resources; private readonly RenderGraphResourceRegistry _resources;
private readonly ResourceAliasingManager _aliasingManager; private readonly ResourceAliasingManager _aliasingManager;
private readonly RenderGraphNativePassBuilder _nativePassBuilder; private readonly RenderGraphNativePassBuilder _nativePassBuilder;
@@ -19,12 +21,16 @@ internal sealed class RenderGraphCompiler
public RenderGraphCompiler( public RenderGraphCompiler(
IResourceManager resourceManager, IResourceManager resourceManager,
IResourceDatabase resourceDatabase,
IResourceAllocator resourceAllocator,
RenderGraphResourceRegistry resources, RenderGraphResourceRegistry resources,
ResourceAliasingManager aliasingManager, ResourceAliasingManager aliasingManager,
RenderGraphNativePassBuilder nativePassBuilder, RenderGraphNativePassBuilder nativePassBuilder,
RenderGraphCompilationCache compilationCache) RenderGraphCompilationCache compilationCache)
{ {
_resourceManager = resourceManager; _resourceManager = resourceManager;
_resourceDatabase = resourceDatabase;
_resourceAllocator = resourceAllocator;
_resources = resources; _resources = resources;
_aliasingManager = aliasingManager; _aliasingManager = aliasingManager;
_nativePassBuilder = nativePassBuilder; _nativePassBuilder = nativePassBuilder;
@@ -208,10 +214,10 @@ internal sealed class RenderGraphCompiler
continue; continue;
} }
_resourceManager.ResourceDatabase.ReleaseResource(res.backingResource); _resourceDatabase.ReleaseResource(res.backingResource);
} }
_resourceManager.ResourceDatabase.ReleaseResource(_resourceHeap); _resourceDatabase.ReleaseResource(_resourceHeap);
} }
if (_aliasingManager.Heap.size == 0) if (_aliasingManager.Heap.size == 0)
@@ -227,7 +233,7 @@ internal sealed class RenderGraphCompiler
HeapType = HeapType.Default HeapType = HeapType.Default
}; };
_resourceHeap = _resourceManager.ResourceAllocator.Allocate(in allocationDesc, "RenderGraphResourceHeap"); _resourceHeap = _resourceAllocator.Allocate(in allocationDesc, "RenderGraphResourceHeap");
if (_resourceHeap.IsInvalid) if (_resourceHeap.IsInvalid)
{ {
return Error.InvalidState; return Error.InvalidState;
@@ -253,11 +259,11 @@ internal sealed class RenderGraphCompiler
if (res.type == RenderGraphResourceType.Texture) if (res.type == RenderGraphResourceType.Texture)
{ {
var textureDesc = res.rgTextureDesc.ToTextureDesc(res.resolvedWidth, res.resolvedHeight); var textureDesc = res.rgTextureDesc.ToTextureDesc(res.resolvedWidth, res.resolvedHeight);
res.backingResource = _resourceManager.ResourceAllocator.CreateTexture(in textureDesc, res.name, ops).AsResource(); res.backingResource = _resourceAllocator.CreateTexture(in textureDesc, res.name, ops).AsResource();
} }
else if (res.type == RenderGraphResourceType.Buffer) else if (res.type == RenderGraphResourceType.Buffer)
{ {
res.backingResource = _resourceManager.ResourceAllocator.CreateBuffer(in res.bufferDesc, res.name, ops).AsResource(); res.backingResource = _resourceAllocator.CreateBuffer(in res.bufferDesc, res.name, ops).AsResource();
} }
else else
{ {
@@ -374,11 +380,11 @@ internal sealed class RenderGraphCompiler
{ {
if (!res.isImported) if (!res.isImported)
{ {
_resourceManager.ResourceDatabase.ReleaseResource(res.backingResource); _resourceDatabase.ReleaseResource(res.backingResource);
} }
} }
_resourceManager.ResourceDatabase.ReleaseResource(_resourceHeap); _resourceDatabase.ReleaseResource(_resourceHeap);
_resourceHeap = Handle<GPUResource>.Invalid; _resourceHeap = Handle<GPUResource>.Invalid;
} }
} }

View File

@@ -8,6 +8,7 @@ namespace Ghost.Graphics.RenderGraphModule;
public interface IRenderGraphContext public interface IRenderGraphContext
{ {
IResourceManager ResourceManager { get; } IResourceManager ResourceManager { get; }
IResourceDatabase ResourceDatabase { get; }
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource); Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
Handle<Texture> GetActualTexture(Identifier<RGTexture> texture); Handle<Texture> GetActualTexture(Identifier<RGTexture> texture);
@@ -58,6 +59,7 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
private int _activeMeshIndexCount; private int _activeMeshIndexCount;
public IResourceManager ResourceManager => _resourceManager; public IResourceManager ResourceManager => _resourceManager;
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public int ActiveMeshIndexCount => _activeMeshIndexCount; public int ActiveMeshIndexCount => _activeMeshIndexCount;

View File

@@ -291,12 +291,12 @@ internal class MeshRenderPass : IRenderPass
ref var matRef = ref r.Value; ref var matRef = ref r.Value;
var blitProps = new ShaderProperties_Hidden_Blit var blitProps = new ShaderProperties_Hidden_Blit
{ {
mainTex = ctx.ResourceManager.ResourceDatabase.GetBindlessIndex(ctx.GetActualResource(data.source.AsResource())), mainTex = ctx.ResourceDatabase.GetBindlessIndex(ctx.GetActualResource(data.source.AsResource())),
sampler_mainTex = (uint)data.sampler.Value, sampler_mainTex = (uint)data.sampler.Value,
}; };
matRef.SetPropertyCache(in blitProps).ThrowIfFailed(); matRef.SetPropertyCache(in blitProps).ThrowIfFailed();
matRef.UploadData(ctx.CommandBuffer, ctx.ResourceManager.ResourceDatabase); matRef.UploadData(ctx.CommandBuffer, ctx.ResourceDatabase);
ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle<Texture>.Invalid); ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle<Texture>.Invalid);
@@ -307,20 +307,20 @@ internal class MeshRenderPass : IRenderPass
} }
} }
public void Cleanup(IResourceManager resourceManager) public void Cleanup(IResourceManager resourceManager, IResourceDatabase resourceDatabase)
{ {
resourceManager.ReleaseMaterial(_blitMaterial); resourceManager.ReleaseMaterial(_blitMaterial);
resourceManager.ReleaseMaterial(_material); resourceManager.ReleaseMaterial(_material);
resourceManager.ReleaseShader(_shader); resourceManager.ReleaseShader(_shader);
resourceManager.ReleaseMesh(_mesh); resourceManager.ReleaseMesh(_mesh);
resourceManager.ResourceDatabase.ReleaseSampler(_sampler); resourceDatabase.ReleaseSampler(_sampler);
if (_textures != null) if (_textures != null)
{ {
foreach (var texture in _textures) foreach (var texture in _textures)
{ {
resourceManager.ResourceDatabase.ReleaseResource(texture.AsResource()); resourceDatabase.ReleaseResource(texture.AsResource());
} }
} }
} }

View File

@@ -61,6 +61,8 @@ public unsafe partial class GhostRenderPipeline : IRenderPipeline
return; return;
} }
_renderGraph.Dispose();
_disposed = true; _disposed = true;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }

View File

@@ -71,6 +71,38 @@ public interface IResourceManager
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param> /// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
void ReleaseMaterial(Handle<Material> handle); void ReleaseMaterial(Handle<Material> handle);
/// <summary>
/// Returns an existing material palette index for the specified material sequence or creates a new one.
/// </summary>
/// <param name="materials">The ordered material list for the palette.</param>
/// <returns>The palette index. Index 0 represents an empty palette.</returns>
int GetOrCreateMaterialPalette(ReadOnlySpan<Handle<Material>> materials);
/// <summary>
/// Determines whether the specified material palette index is valid.
/// </summary>
/// <param name="paletteIndex">The palette index to validate.</param>
bool HasMaterialPalette(int paletteIndex);
/// <summary>
/// Gets metadata for a material palette entry.
/// </summary>
/// <param name="paletteIndex">The palette index to query.</param>
MaterialPaletteInfo GetMaterialPaletteInfo(int paletteIndex);
/// <summary>
/// Gets a material handle from a palette entry by local material index.
/// </summary>
/// <param name="paletteIndex">The palette index to query.</param>
/// <param name="localMaterialIndex">The material slot inside the palette.</param>
Handle<Material> GetMaterialPaletteMaterial(int paletteIndex, int localMaterialIndex);
/// <summary>
/// Releases a material palette reference previously returned by <see cref="GetOrCreateMaterialPalette(ReadOnlySpan{Handle{Material}})"/>.
/// </summary>
/// <param name="paletteIndex">The palette index to release.</param>
void ReleaseMaterialPalette(int paletteIndex);
/// <summary> /// <summary>
/// Determines whether a shader with the specified identifier exists in the collection. /// Determines whether a shader with the specified identifier exists in the collection.
/// </summary> /// </summary>
@@ -101,6 +133,8 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
private UnsafeSlotMap<Material> _materials; private UnsafeSlotMap<Material> _materials;
private UnsafeList<Shader> _shaders; // TODO: Use SlotMap? private UnsafeList<Shader> _shaders; // TODO: Use SlotMap?
private readonly MaterialPaletteStore _materialPalettes;
private bool _disposed; private bool _disposed;
public ResourceManager(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase) public ResourceManager(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
@@ -111,6 +145,7 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent); _meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent);
_materials = new UnsafeSlotMap<Material>(64, Allocator.Persistent); _materials = new UnsafeSlotMap<Material>(64, Allocator.Persistent);
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent); _shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
_materialPalettes = new MaterialPaletteStore();
} }
~ResourceManager() ~ResourceManager()
@@ -168,7 +203,7 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
var material = new Material(); var material = new Material();
if (material.SetShader(shader, this) != Error.None) if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None)
{ {
return Handle<Material>.Invalid; return Handle<Material>.Invalid;
} }
@@ -249,6 +284,45 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
material.ReleaseResource(_resourceDatabase); material.ReleaseResource(_resourceDatabase);
} }
public int GetOrCreateMaterialPalette(ReadOnlySpan<Handle<Material>> materials)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var material in materials)
{
if (material.IsInvalid || !HasMaterial(material))
{
return 0;
}
}
return _materialPalettes.InsertOrGet(materials);
}
public bool HasMaterialPalette(int paletteIndex)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materialPalettes.IsValid(paletteIndex);
}
public MaterialPaletteInfo GetMaterialPaletteInfo(int paletteIndex)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materialPalettes.GetInfo(paletteIndex);
}
public Handle<Material> GetMaterialPaletteMaterial(int paletteIndex, int localMaterialIndex)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materialPalettes.GetMaterial(paletteIndex, localMaterialIndex);
}
public void ReleaseMaterialPalette(int paletteIndex)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_materialPalettes.Release(paletteIndex);
}
public bool HasShader(Identifier<Shader> id) public bool HasShader(Identifier<Shader> id)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
@@ -303,6 +377,7 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
_meshes.Dispose(); _meshes.Dispose();
_materials.Dispose(); _materials.Dispose();
_shaders.Dispose(); _shaders.Dispose();
_materialPalettes.Dispose();
_disposed = true; _disposed = true;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);

View File

@@ -7,7 +7,7 @@ namespace Ghost.Entities.Test;
internal struct TestEntityQueryJob : IJobEntity<Transform> internal struct TestEntityQueryJob : IJobEntity<Transform>
{ {
public readonly void Execute(Entity entity, ref Transform transform, int threadIndex) public readonly void Execute(Entity entity, ref Transform transform, ref readonly JobExecutionContext ctx)
{ {
transform.position += new float3(5, 5, 5); transform.position += new float3(5, 5, 5);
} }
@@ -15,9 +15,9 @@ internal struct TestEntityQueryJob : IJobEntity<Transform>
internal struct TestChunkQueryJob : IJobChunk internal struct TestChunkQueryJob : IJobChunk
{ {
public readonly void Execute(ChunkView view, int threadIndex) public readonly void Execute(ChunkView view, ref readonly JobExecutionContext ctx)
{ {
var random = new random((uint)threadIndex + 1u); var random = new random((uint)ctx.ThreadIndex + 1u);
var transforms = view.GetComponentDataRW<Transform>(); var transforms = view.GetComponentDataRW<Transform>();
for (var i = 0; i < view.Count; i++) for (var i = 0; i < view.Count; i++)
@@ -54,7 +54,7 @@ public partial class EntityQueryTest : ITest
var testJob = new TestChunkQueryJob(); var testJob = new TestChunkQueryJob();
var handle = query.ScheduleChunkParallel(testJob, 1, JobHandle.Invalid); var handle = query.ScheduleChunkParallel(testJob, 1, JobHandle.Invalid);
_jobScheduler.WaitComplete(handle); _jobScheduler.Wait(handle);
query.ForEach<Transform>((e, ref t) => query.ForEach<Transform>((e, ref t) =>
{ {

View File

@@ -19,8 +19,7 @@ internal class SystemTest : ITest
group.SortSystems(); group.SortSystems();
var api = new SystemAPI(); _world.SystemManager.InitializeAll(new TimeData());
_world.SystemManager.InitializeAll(in api);
} }
public void Cleanup() public void Cleanup()

View File

@@ -1,7 +1,8 @@
using Ghost.DSL.Generator;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Numerics; using System.Numerics;
//ShaderStructGenerator.GenerateHLSL([typeof(TestStruct), typeof(TestEnum), typeof(TestEnumFlags)], PackingRules.Exact, "C:/Users/Misaki/Downloads/Archive/Test.cs.hlsl"); ShaderStructGenerator.GenerateHLSL([typeof(TestStruct), typeof(TestEnum), typeof(TestEnumFlags)], PackingRules.Exact, "C:/Users/Misaki/Downloads/Archive/Test.cs.hlsl");
//return; //return;
#if false #if false

View File

@@ -1,3 +1,4 @@
#if false
using Ghost.Core; using Ghost.Core;
namespace Ghost.UnitTest; namespace Ghost.UnitTest;
@@ -432,3 +433,4 @@ public class AssetDatabaseIntegrationTest
CheckInternalErrors(); CheckInternalErrors();
} }
} }
#endif

View File

@@ -1,92 +0,0 @@
using System.Text.Json;
namespace Ghost.UnitTest;
[TestClass]
public class AssetMetaTest
{
[TestMethod]
public void TestMetaSerialization()
{
var meta = new AssetMeta
{
Guid = Guid.NewGuid(),
Version = 1,
Tags = new List<string> { "Test", "Asset" }
};
var json = JsonSerializer.Serialize(meta, new JsonSerializerOptions { WriteIndented = true });
Assert.IsNotNull(json);
Assert.Contains("Guid", json);
Assert.Contains("Version", json);
Assert.Contains("Tags", json);
}
[TestMethod]
public void TestMetaDeserialization()
{
var guid = Guid.NewGuid();
var json = $@"{{
""Guid"": ""{guid}"",
""Version"": 1,
""Tags"": [""Test"", ""Asset""]
}}";
var meta = JsonSerializer.Deserialize<AssetMeta>(json);
Assert.IsNotNull(meta);
Assert.AreEqual(guid, meta.Guid);
Assert.AreEqual(1, meta.Version);
Assert.HasCount(2, meta.Tags);
Assert.Contains("Test", meta.Tags);
}
[TestMethod]
public void TestMetaWithSettings()
{
var meta = new AssetMeta
{
Guid = Guid.NewGuid(),
Version = 1
};
// Add importer settings using the new API
var settings = new TextImporterSettings
{
Encoding = "UTF-8",
TrimWhitespace = true
};
meta.SetImporterSettings("TextImporter", settings);
var json = JsonSerializer.Serialize(meta, new JsonSerializerOptions { WriteIndented = true });
var deserialized = JsonSerializer.Deserialize<AssetMeta>(json);
Assert.IsNotNull(deserialized);
Assert.Contains("TextImporter", deserialized.ImporterSettings.Keys);
// Test retrieving the settings
var retrievedSettings = deserialized.GetImporterSettings<TextImporterSettings>("TextImporter");
Assert.IsNotNull(retrievedSettings);
Assert.AreEqual("UTF-8", retrievedSettings.Encoding);
Assert.IsTrue(retrievedSettings.TrimWhitespace);
}
[TestMethod]
public void TestFileHashAndDependenciesNotSerialized()
{
var meta = new AssetMeta
{
Guid = Guid.NewGuid(),
Version = 1
};
var json = JsonSerializer.Serialize(meta, new JsonSerializerOptions { WriteIndented = true });
// FileHash and Dependencies should NOT be in the serialized JSON
Assert.DoesNotContain("FileHash", json);
Assert.DoesNotContain("Dependencies", json);
}
}