Fixed compilation errors;
Added MaterialPalette
This commit is contained in:
@@ -59,7 +59,7 @@ internal static class ActivationHandler
|
||||
((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.
|
||||
// await Task.Delay(10000); // Wait 10 seconds to simulate work.
|
||||
|
||||
@@ -63,7 +63,7 @@ public partial class App : Application
|
||||
services.AddSingleton<IProgressService, ProgressService>();
|
||||
services.AddSingleton<IInspectorService, InspectorService>();
|
||||
services.AddSingleton<IPreviewService, PreviewService>();
|
||||
services.AddSingleton<IAssetService, AssetService>();
|
||||
// services.AddSingleton<IAssetService, AssetService>();
|
||||
|
||||
services.AddSingleton<EngineEditorViewModel>();
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<!-- Named Style -->
|
||||
<Style
|
||||
x:Key="ToolbarButton"
|
||||
BasedOn="{ThemeResource SubtleButtonStyle}"
|
||||
BasedOn="{StaticResource SubtleButtonStyle}"
|
||||
TargetType="Button" />
|
||||
|
||||
<!-- Named Resource -->
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Ghost.Editor.ViewModels.Controls;
|
||||
internal partial class ProjectBrowserViewModel : ObservableObject
|
||||
{
|
||||
private readonly IInspectorService _inspectorService;
|
||||
private readonly IAssetService _assetService;
|
||||
// private readonly IAssetService _assetService;
|
||||
|
||||
private readonly Dictionary<string, ExplorerItem> _pathToDirectoryItemMap = new();
|
||||
private ExplorerItem? _selectedItem;
|
||||
@@ -40,10 +40,10 @@ internal partial class ProjectBrowserViewModel : ObservableObject
|
||||
get; set;
|
||||
} = string.Empty;
|
||||
|
||||
public ProjectBrowserViewModel(IInspectorService inspectorService, IAssetService assetService)
|
||||
public ProjectBrowserViewModel(IInspectorService inspectorService) // , IAssetService assetService)
|
||||
{
|
||||
_inspectorService = inspectorService;
|
||||
_assetService = assetService;
|
||||
// _assetService = assetService;
|
||||
|
||||
var assetsRootItem = new ExplorerItem(EditorApplication.ASSETS_FOLDER_NAME, Path.Combine(EditorApplication.ProjectPath, EditorApplication.ASSETS_FOLDER_NAME), true);
|
||||
LoadSubFolderRecursive(assetsRootItem);
|
||||
@@ -109,7 +109,7 @@ internal partial class ProjectBrowserViewModel : ObservableObject
|
||||
}
|
||||
else
|
||||
{
|
||||
_assetService.OpenAsset(SelectedItem.FullName);
|
||||
// _assetService.OpenAsset(SelectedItem.FullName);
|
||||
return (null, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Ghost.Editor.ViewModels.Pages.EngineEditor;
|
||||
|
||||
internal partial class ProjectViewModel : ObservableObject
|
||||
{
|
||||
private readonly IAssetService _assetService;
|
||||
// private readonly IAssetService _assetService;
|
||||
|
||||
public ObservableCollection<ExplorerItem> SubDirectories
|
||||
{
|
||||
@@ -35,15 +35,15 @@ internal partial class ProjectViewModel : ObservableObject
|
||||
set;
|
||||
}
|
||||
|
||||
public ProjectViewModel(IAssetService assetService)
|
||||
{
|
||||
_assetService = assetService;
|
||||
// public ProjectViewModel(IAssetService assetService)
|
||||
// {
|
||||
// _assetService = assetService;
|
||||
|
||||
var assetsRootItem = new ExplorerItem("Assets", Path.Combine(EditorApplication.ProjectPath, EditorApplication.ASSETS_FOLDER_NAME), true);
|
||||
LoadSubFolderRecursive(ref assetsRootItem);
|
||||
// var assetsRootItem = new ExplorerItem("Assets", Path.Combine(EditorApplication.ProjectPath, EditorApplication.ASSETS_FOLDER_NAME), true);
|
||||
// LoadSubFolderRecursive(ref assetsRootItem);
|
||||
|
||||
SubDirectories.Add(assetsRootItem);
|
||||
}
|
||||
// SubDirectories.Add(assetsRootItem);
|
||||
// }
|
||||
|
||||
private static void LoadSubFolderRecursive(ref ExplorerItem parentItem)
|
||||
{
|
||||
@@ -127,7 +127,7 @@ internal partial class ProjectViewModel : ObservableObject
|
||||
}
|
||||
else
|
||||
{
|
||||
_assetService.OpenAsset(SelectedAsset.FullName);
|
||||
// _assetService.OpenAsset(SelectedAsset.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ namespace Ghost.Engine.Components;
|
||||
public struct MeshInstance : IComponent
|
||||
{
|
||||
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 Handle<Material> materialStart;
|
||||
public int materialPaletteIndex;
|
||||
public ShadowCastingMode shadowCastingMode;
|
||||
public RenderingLayerMask renderingLayerMask;
|
||||
public bool staticShadowCaster;
|
||||
|
||||
@@ -42,9 +42,8 @@ public class RenderExtractionSystem : ISystem
|
||||
renderList.Add(new RenderRecord
|
||||
{
|
||||
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.meshIndex,
|
||||
// material = meshInstance.materialIndex,
|
||||
mesh = meshInstance.mesh,
|
||||
materialPaletteIndex = meshInstance.materialPaletteIndex,
|
||||
renderingLayerMask = meshInstance.renderingLayerMask,
|
||||
|
||||
}, 0);
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Ghost.Entities;
|
||||
|
||||
public interface IJobChunk
|
||||
{
|
||||
void Execute(ChunkView view, int threadIndex);
|
||||
void Execute(ChunkView view, ref readonly JobExecutionContext ctx);
|
||||
}
|
||||
|
||||
internal unsafe struct ChunkInfo
|
||||
@@ -22,12 +22,12 @@ internal unsafe struct JobChunkBatch<TJob> : IJobParallelFor
|
||||
public TJob userJob;
|
||||
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 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 void Execute(int threadIndex)
|
||||
public void Execute(ref readonly JobExecutionContext ctx)
|
||||
{
|
||||
list.Dispose();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ namespace Ghost.Entities;
|
||||
public interface IJobEntity<<#= generics #>>
|
||||
<#= 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
|
||||
@@ -38,12 +38,12 @@ internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
public UnsafeList<int> offsets<#= j #>;
|
||||
public UnsafeList<int> bitsOffsets<#= j #>;
|
||||
public UnsafeList<int> versionindices<#= j #>;
|
||||
public UnsafeList<int> versionIndices<#= j #>;
|
||||
|
||||
<# } #>
|
||||
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
|
||||
var pChunk = (byte*)chunks[loopIndex];
|
||||
@@ -53,7 +53,7 @@ internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
var off<#= j #> = offsets<#= 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]);
|
||||
@@ -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++){ #>
|
||||
public UnsafeList<int> offsets<#= 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();
|
||||
chunkVersions.Dispose();
|
||||
@@ -115,7 +115,7 @@ public unsafe partial struct EntityQuery
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
offsets<#= 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
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
var layout<#= j #> = arch.GetLayout(ComponentTypeID<T<#= j #>>.Value)
|
||||
.GetValueOrThrow();
|
||||
var layout<#= j #> = arch.GetLayout(ComponentTypeID<T<#= j #>>.Value).GetValueOrThrow();
|
||||
<# } #>
|
||||
|
||||
// Add all chunks from this archetype
|
||||
@@ -195,7 +194,7 @@ public unsafe partial struct EntityQuery
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
offsets<#= j #> = offsets<#= j #>,
|
||||
bitsOffsets<#= j #> = bitsOffsets<#= j #>,
|
||||
versionindices<#= j #> = versionIndices<#= j #>,
|
||||
versionIndices<#= j #> = versionIndices<#= j #>,
|
||||
|
||||
<# } #>
|
||||
version = world.Version,
|
||||
@@ -229,7 +228,7 @@ public unsafe partial struct EntityQuery
|
||||
<# for (var j = 0; j < i; j++){ #>
|
||||
offsets<#= j #> = offsets<#= j #>,
|
||||
bitsOffsets<#= j #> = bitsOffsets<#= j #>,
|
||||
versionindices<#= j #> = versionIndices<#= j #>,
|
||||
versionIndices<#= j #> = versionIndices<#= j #>,
|
||||
|
||||
<# } #>
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.Core.Contracts;
|
||||
|
||||
@@ -7,5 +8,5 @@ public interface IRenderPass
|
||||
{
|
||||
void Initialize(ref readonly RenderingContext ctx);
|
||||
void Build(RenderGraph graph, Identifier<RGTexture> backbuffer);
|
||||
void Cleanup(IResourceManager resourceManager);
|
||||
void Cleanup(IResourceManager resourceManager, IResourceDatabase resourceDatabase);
|
||||
}
|
||||
|
||||
242
src/Runtime/Ghost.Graphics/Core/MaterialPaletteStore.cs
Normal file
242
src/Runtime/Ghost.Graphics/Core/MaterialPaletteStore.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,64 @@ using Misaki.HighPerformance.Mathematics.Geometry;
|
||||
|
||||
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
|
||||
{
|
||||
private UnsafeList<Vertex> _vertices;
|
||||
private UnsafeList<uint> _indices;
|
||||
private MeshletMeshData _meshletData;
|
||||
|
||||
internal bool IsMeshDataDirty
|
||||
{
|
||||
@@ -88,6 +141,14 @@ public struct Mesh : IResourceReleasable
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the meshlet buffer on the GPU.
|
||||
/// </summary>
|
||||
public Handle<GraphicsBuffer> MeshLetBuffer
|
||||
{
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the mesh data buffer on the GPU.
|
||||
/// </summary>
|
||||
@@ -112,6 +173,7 @@ public struct Mesh : IResourceReleasable
|
||||
{
|
||||
_vertices.Dispose();
|
||||
_indices.Dispose();
|
||||
_meshletData.Dispose();
|
||||
}
|
||||
|
||||
public readonly void ReleaseResource(IResourceDatabase database)
|
||||
@@ -120,6 +182,7 @@ public struct Mesh : IResourceReleasable
|
||||
|
||||
database.ReleaseResource(VertexBuffer.AsResource());
|
||||
database.ReleaseResource(IndexBuffer.AsResource());
|
||||
database.ReleaseResource(MeshLetBuffer.AsResource());
|
||||
database.ReleaseResource(ObjectDataBuffer.AsResource());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public record struct RenderRecord
|
||||
public struct RenderRecord
|
||||
{
|
||||
public float4x4 localToWorld;
|
||||
public Handle<Mesh> mesh;
|
||||
public Handle<Material> materialOffset;
|
||||
public int materialPaletteIndex;
|
||||
public RenderingLayerMask renderingLayerMask;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed class RenderGraph : IDisposable
|
||||
);
|
||||
|
||||
_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);
|
||||
|
||||
_blackboard = new RenderGraphBlackboard();
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Ghost.Graphics.RenderGraphModule;
|
||||
internal sealed class RenderGraphCompiler
|
||||
{
|
||||
private readonly IResourceManager _resourceManager;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly RenderGraphResourceRegistry _resources;
|
||||
private readonly ResourceAliasingManager _aliasingManager;
|
||||
private readonly RenderGraphNativePassBuilder _nativePassBuilder;
|
||||
@@ -19,12 +21,16 @@ internal sealed class RenderGraphCompiler
|
||||
|
||||
public RenderGraphCompiler(
|
||||
IResourceManager resourceManager,
|
||||
IResourceDatabase resourceDatabase,
|
||||
IResourceAllocator resourceAllocator,
|
||||
RenderGraphResourceRegistry resources,
|
||||
ResourceAliasingManager aliasingManager,
|
||||
RenderGraphNativePassBuilder nativePassBuilder,
|
||||
RenderGraphCompilationCache compilationCache)
|
||||
{
|
||||
_resourceManager = resourceManager;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_resources = resources;
|
||||
_aliasingManager = aliasingManager;
|
||||
_nativePassBuilder = nativePassBuilder;
|
||||
@@ -208,10 +214,10 @@ internal sealed class RenderGraphCompiler
|
||||
continue;
|
||||
}
|
||||
|
||||
_resourceManager.ResourceDatabase.ReleaseResource(res.backingResource);
|
||||
_resourceDatabase.ReleaseResource(res.backingResource);
|
||||
}
|
||||
|
||||
_resourceManager.ResourceDatabase.ReleaseResource(_resourceHeap);
|
||||
_resourceDatabase.ReleaseResource(_resourceHeap);
|
||||
}
|
||||
|
||||
if (_aliasingManager.Heap.size == 0)
|
||||
@@ -227,7 +233,7 @@ internal sealed class RenderGraphCompiler
|
||||
HeapType = HeapType.Default
|
||||
};
|
||||
|
||||
_resourceHeap = _resourceManager.ResourceAllocator.Allocate(in allocationDesc, "RenderGraphResourceHeap");
|
||||
_resourceHeap = _resourceAllocator.Allocate(in allocationDesc, "RenderGraphResourceHeap");
|
||||
if (_resourceHeap.IsInvalid)
|
||||
{
|
||||
return Error.InvalidState;
|
||||
@@ -253,11 +259,11 @@ internal sealed class RenderGraphCompiler
|
||||
if (res.type == RenderGraphResourceType.Texture)
|
||||
{
|
||||
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)
|
||||
{
|
||||
res.backingResource = _resourceManager.ResourceAllocator.CreateBuffer(in res.bufferDesc, res.name, ops).AsResource();
|
||||
res.backingResource = _resourceAllocator.CreateBuffer(in res.bufferDesc, res.name, ops).AsResource();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -374,11 +380,11 @@ internal sealed class RenderGraphCompiler
|
||||
{
|
||||
if (!res.isImported)
|
||||
{
|
||||
_resourceManager.ResourceDatabase.ReleaseResource(res.backingResource);
|
||||
_resourceDatabase.ReleaseResource(res.backingResource);
|
||||
}
|
||||
}
|
||||
|
||||
_resourceManager.ResourceDatabase.ReleaseResource(_resourceHeap);
|
||||
_resourceDatabase.ReleaseResource(_resourceHeap);
|
||||
_resourceHeap = Handle<GPUResource>.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Ghost.Graphics.RenderGraphModule;
|
||||
public interface IRenderGraphContext
|
||||
{
|
||||
IResourceManager ResourceManager { get; }
|
||||
IResourceDatabase ResourceDatabase { get; }
|
||||
|
||||
Handle<GPUResource> GetActualResource(Identifier<RGResource> resource);
|
||||
Handle<Texture> GetActualTexture(Identifier<RGTexture> texture);
|
||||
@@ -58,6 +59,7 @@ internal sealed class RenderGraphContext : IRasterRenderContext, IComputeRenderC
|
||||
private int _activeMeshIndexCount;
|
||||
|
||||
public IResourceManager ResourceManager => _resourceManager;
|
||||
public IResourceDatabase ResourceDatabase => _resourceDatabase;
|
||||
|
||||
public int ActiveMeshIndexCount => _activeMeshIndexCount;
|
||||
|
||||
|
||||
@@ -291,12 +291,12 @@ internal class MeshRenderPass : IRenderPass
|
||||
ref var matRef = ref r.Value;
|
||||
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,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@@ -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(_material);
|
||||
resourceManager.ReleaseShader(_shader);
|
||||
resourceManager.ReleaseMesh(_mesh);
|
||||
resourceManager.ResourceDatabase.ReleaseSampler(_sampler);
|
||||
resourceDatabase.ReleaseSampler(_sampler);
|
||||
|
||||
if (_textures != null)
|
||||
{
|
||||
foreach (var texture in _textures)
|
||||
{
|
||||
resourceManager.ResourceDatabase.ReleaseResource(texture.AsResource());
|
||||
resourceDatabase.ReleaseResource(texture.AsResource());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ public unsafe partial class GhostRenderPipeline : IRenderPipeline
|
||||
return;
|
||||
}
|
||||
|
||||
_renderGraph.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
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>
|
||||
/// Determines whether a shader with the specified identifier exists in the collection.
|
||||
/// </summary>
|
||||
@@ -101,6 +133,8 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
private UnsafeSlotMap<Material> _materials;
|
||||
private UnsafeList<Shader> _shaders; // TODO: Use SlotMap?
|
||||
|
||||
private readonly MaterialPaletteStore _materialPalettes;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public ResourceManager(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase)
|
||||
@@ -111,6 +145,7 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
_meshes = new UnsafeSlotMap<Mesh>(64, Allocator.Persistent);
|
||||
_materials = new UnsafeSlotMap<Material>(64, Allocator.Persistent);
|
||||
_shaders = new UnsafeList<Shader>(16, Allocator.Persistent);
|
||||
_materialPalettes = new MaterialPaletteStore();
|
||||
}
|
||||
|
||||
~ResourceManager()
|
||||
@@ -168,7 +203,7 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var material = new Material();
|
||||
if (material.SetShader(shader, this) != Error.None)
|
||||
if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None)
|
||||
{
|
||||
return Handle<Material>.Invalid;
|
||||
}
|
||||
@@ -249,6 +284,45 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
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)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
@@ -303,6 +377,7 @@ internal sealed class ResourceManager : IResourceManager, IDisposable
|
||||
_meshes.Dispose();
|
||||
_materials.Dispose();
|
||||
_shaders.Dispose();
|
||||
_materialPalettes.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -280,4 +280,4 @@ public static unsafe class MeshBuilder
|
||||
vertices[i].tangent = new float4(t.x, t.y, t.z, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Ghost.Entities.Test;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -15,9 +15,9 @@ internal struct TestEntityQueryJob : IJobEntity<Transform>
|
||||
|
||||
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>();
|
||||
for (var i = 0; i < view.Count; i++)
|
||||
@@ -54,7 +54,7 @@ public partial class EntityQueryTest : ITest
|
||||
|
||||
var testJob = new TestChunkQueryJob();
|
||||
var handle = query.ScheduleChunkParallel(testJob, 1, JobHandle.Invalid);
|
||||
_jobScheduler.WaitComplete(handle);
|
||||
_jobScheduler.Wait(handle);
|
||||
|
||||
query.ForEach<Transform>((e, ref t) =>
|
||||
{
|
||||
|
||||
@@ -19,8 +19,7 @@ internal class SystemTest : ITest
|
||||
|
||||
group.SortSystems();
|
||||
|
||||
var api = new SystemAPI();
|
||||
_world.SystemManager.InitializeAll(in api);
|
||||
_world.SystemManager.InitializeAll(new TimeData());
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using Ghost.DSL.Generator;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
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;
|
||||
#if false
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#if false
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.UnitTest;
|
||||
@@ -432,3 +433,4 @@ public class AssetDatabaseIntegrationTest
|
||||
CheckInternalErrors();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user