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

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