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

View File

@@ -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.

View File

@@ -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>();

View File

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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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 #>,
<# } #>
};

View File

@@ -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);
}

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;
// 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());
}
}

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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());
}
}
}

View File

@@ -61,6 +61,8 @@ public unsafe partial class GhostRenderPipeline : IRenderPipeline
return;
}
_renderGraph.Dispose();
_disposed = true;
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>
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);

View File

@@ -280,4 +280,4 @@ public static unsafe class MeshBuilder
vertices[i].tangent = new float4(t.x, t.y, t.z, w);
}
}
}
}

View File

@@ -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) =>
{

View File

@@ -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()

View File

@@ -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

View File

@@ -1,3 +1,4 @@
#if false
using Ghost.Core;
namespace Ghost.UnitTest;
@@ -432,3 +433,4 @@ public class AssetDatabaseIntegrationTest
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);
}
}