using Ghost.Core; using Ghost.Core.Graphics; using Ghost.Graphics.Core; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Graphics.Services; public sealed partial class ResourceManager : IDisposable { private const uint _PALETTE_BUFFER_INITIAL_CAPACITY = 64; private readonly struct ResourceReturnEntry { public readonly Handle handle; public readonly ulong returnFrame; public ResourceReturnEntry(Handle handle, ulong returnFrame) { this.handle = handle; this.returnFrame = returnFrame; } } private readonly IRenderDevice _renderDevice; private readonly IResourceAllocator _resourceAllocator; private readonly IResourceDatabase _resourceDatabase; private UnsafeSlotMap _meshes; private UnsafeSlotMap _materials; private UnsafeSlotMap _shaders; private UnsafeSlotMap _computeShaders; private readonly MaterialPaletteStore _materialPalettes; // Persistent GPU buffers for the two-buffer material palette indirection. private Handle _paletteOffsetBuffer; private Handle _materialIndexBuffer; private uint _paletteOffsetCapacity; private uint _materialIndexCapacity; // TODO: Any better way? System.Threading.Lock is very fast though, it use spin lock before entering kernel. // rw lock slim is an option but it has more overhead on read. Because more than 90% of the time we are reading, it may not be a good option. // Plus UnsafeSlotMap use jagged array internally, which means we can have concurrent read and write, but not add and remove, on different slots without any issue, so we only need to lock when writing to those slots. private readonly Lock _meshWriteLock; private readonly Lock _materialWriteLock; private readonly Lock _shaderWriteLock; private readonly Lock _computeShaderWriteLock; private ulong _submittedFrame; private bool _disposed; /// /// Returns the bindless descriptor heap index for the palette offset GPU buffer. /// Valid after the first call. /// public uint PaletteOffsetBufferBindlessIndex => _resourceDatabase.GetBindlessIndex(_paletteOffsetBuffer.AsResource()); /// /// Returns the bindless descriptor heap index for the material index GPU buffer. /// Valid after the first call. /// public uint MaterialIndexBufferBindlessIndex => _resourceDatabase.GetBindlessIndex(_materialIndexBuffer.AsResource()); public ResourceManager(IRenderDevice renderDevice, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase) { _renderDevice = renderDevice; _resourceAllocator = resourceAllocator; _resourceDatabase = resourceDatabase; _meshes = new UnsafeSlotMap(64, AllocationHandle.Persistent); _materials = new UnsafeSlotMap(64, AllocationHandle.Persistent); _shaders = new UnsafeSlotMap(16, AllocationHandle.Persistent); _computeShaders = new UnsafeSlotMap(16, AllocationHandle.Persistent); _materialPalettes = new MaterialPaletteStore(); _meshWriteLock = new Lock(); _materialWriteLock = new Lock(); _shaderWriteLock = new Lock(); _computeShaderWriteLock = new Lock(); // Create initial GPU palette buffers. These grow on demand in UploadMaterialPaletteData. _paletteOffsetCapacity = _PALETTE_BUFFER_INITIAL_CAPACITY; _materialIndexCapacity = _PALETTE_BUFFER_INITIAL_CAPACITY * 4; _paletteOffsetBuffer = CreatePaletteBuffer(_paletteOffsetCapacity, "PaletteOffsetBuffer"); _materialIndexBuffer = CreatePaletteBuffer(_materialIndexCapacity, "MaterialIndexBuffer"); } ~ResourceManager() { Dispose(); } internal void BeginFrame(ulong submittedFrame) { Logger.DebugAssert(!_disposed); _submittedFrame = submittedFrame; } internal void EndFrame(ulong completedFrame) { Logger.DebugAssert(!_disposed); _materialPalettes.EndFrame(_submittedFrame, completedFrame); EndFramePool(completedFrame); } /// /// Creates a new mesh from the specified vertex and index data. /// /// A UnsafeList containing the vertices that define the geometry of the mesh. /// A UnsafeList containing the indices that specify how vertices are connected to form primitives. /// Indicates whether the mesh is expected to be updated frequently. If true, the underlying GPU buffers will be created with upload heap type for better CPU write performance. /// The name of the mesh. /// An representing the newly created mesh. public unsafe Handle CreateMesh(UnsafeList vertices, UnsafeList indices, bool dynamic = false, string? name = null) { Logger.DebugAssert(!_disposed); var vertexBufferDesc = new BufferDesc { Size = (uint)(vertices.Count * sizeof(Vertex)), Stride = (uint)sizeof(Vertex), Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw, HeapType = dynamic ? HeapType.Upload : HeapType.Default, }; var indexBufferDesc = new BufferDesc { Size = (uint)(indices.Count * sizeof(uint)), Stride = sizeof(uint), Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw, HeapType = dynamic ? HeapType.Upload : HeapType.Default, }; var meshDataBufferDesc = new BufferDesc { Size = (uint)sizeof(MeshData), Stride = (uint)sizeof(MeshData), Usage = BufferUsage.Raw | BufferUsage.ShaderResource, HeapType = dynamic ? HeapType.Upload : HeapType.Default, }; var hasName = name != null; var vertexBuffer = _resourceAllocator.CreateBuffer(in vertexBufferDesc, hasName ? $"{name}_VertexBuffer" : "VertexBuffer"); var indexBuffer = _resourceAllocator.CreateBuffer(in indexBufferDesc, hasName ? $"{name}_IndexBuffer" : "IndexBuffer"); var meshDataBuffer = _resourceAllocator.CreateBuffer(in meshDataBufferDesc, hasName ? $"{name}_MeshDataBuffer" : "MeshDataBuffer"); var mesh = new Mesh { Vertices = vertices, Indices = indices, VertexBuffer = vertexBuffer, IndexBuffer = indexBuffer, MeshDataBuffer = meshDataBuffer, }; lock (_meshWriteLock) { var id = _meshes.Add(mesh, out var generation); return new Handle(id, generation); } } /// /// Creates a new material instance using the specified shader. /// /// The identifier of the shader to associate with the new material. /// The name of the material. /// An representing the newly created material. public Handle CreateMaterial(Handle shader, string? name = null) { Logger.DebugAssert(!_disposed); var material = new Material(); if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None) { return Handle.Invalid; } lock (_materialWriteLock) { var id = _materials.Add(material, out var generation); return new Handle(id, generation); } } /// /// Creates a new shader and returns its unique identifier. /// /// An representing the newly created shader. /// The viewGroup containing the shader's properties and passes. public Handle CreateGraphicsShader(GraphicsShaderDescriptor descriptor) { Logger.DebugAssert(!_disposed); var shader = new Shader(descriptor); lock (_shaderWriteLock) { var id = _shaders.Add(shader, out var generation); return new Handle(id, generation); } } public Handle CreateComputeShader(ComputeShaderDescriptor descriptor) { Logger.DebugAssert(!_disposed); var computeShader = new ComputeShader(descriptor); lock (_computeShaderWriteLock) { var id = _computeShaders.Add(computeShader, out var generation); return new Handle(id, generation); } } /// /// Determines whether a mesh with the specified Handle exists. /// /// The handle of the mesh to check for existence. Cannot be null. /// true if a mesh with the specified Handle exists; otherwise, false. public bool HasMesh(Handle handle) { Logger.DebugAssert(!_disposed); return _meshes.Contains(handle.ID, handle.Generation); } /// /// Returns a reference to the mesh associated with the specified handle. /// /// The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined. /// A result containing a reference to the mesh corresponding to the specified handle, or an error status if the handle is invalid. public RefResult GetMeshReference(Handle handle) { ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { return Error.NotFound; } return RefResult.Success(ref mesh); } /// /// Releases the mesh heap associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources. /// /// The handle of the mesh to release. Must refer to a mesh that was previously created and not already released. public void ReleaseMesh(Handle handle) { Logger.DebugAssert(!_disposed); lock (_meshWriteLock) { ref var mesh = ref _meshes.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { return; } _meshes.Remove(handle.ID, handle.Generation); mesh.ReleaseResource(_resourceDatabase); } } /// /// Determines whether a material with the specified handle exists in the collection. /// /// The handle of the material to check for existence. /// true if a material with the specified handle exists; otherwise, false. public bool HasMaterial(Handle handle) { Logger.DebugAssert(!_disposed); return _materials.Contains(handle.ID, handle.Generation); } /// /// Gets a reference to the material associated with the specified handle. /// /// The handle of the material to retrieve. Must refer to a valid material. /// A result containing a reference to the material corresponding to the specified handle, or an error status if the handle is invalid. public RefResult GetMaterialReference(Handle handle) { ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { return Error.NotFound; } return RefResult.Success(ref material); } /// /// Releases the material associated with the specified handle, making it available for reuse or disposal. /// /// The handle of the material to release. Must refer to a material that has been previously acquired. public void ReleaseMaterial(Handle handle) { Logger.DebugAssert(!_disposed); lock (_materialWriteLock) { ref var material = ref _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { return; } _materials.Remove(handle.ID, handle.Generation); material.ReleaseResource(_resourceDatabase); } } /// /// Returns an existing material palette index for the specified material sequence or creates a new one. /// /// The ordered material list for the palette. /// The palette index. Index 0 represents an empty palette. public int GetOrCreateMaterialPalette(ReadOnlySpan> materials) { Logger.DebugAssert(!_disposed); foreach (var material in materials) { if (material.IsInvalid || !HasMaterial(material)) { return 0; } } return _materialPalettes.InsertOrGet(materials); } /// /// Determines whether the specified material palette index is valid. /// /// The palette index to validate. public bool HasMaterialPalette(Identifier paletteID) { Logger.DebugAssert(!_disposed); return _materialPalettes.IsValid(paletteID); } /// /// Gets metadata for a material palette entry. /// /// The palette index to query. public MaterialPalette GetMaterialPaletteInfo(Identifier paletteID) { Logger.DebugAssert(!_disposed); return _materialPalettes.GetInfo(paletteID); } /// /// Gets a material handle from a palette entry by local material index. /// /// The palette index to query. /// The material slot inside the palette. public Handle GetMaterialPaletteMaterial(Identifier paletteID, int localMaterialIndex) { Logger.DebugAssert(!_disposed); return _materialPalettes.GetMaterial(paletteID, localMaterialIndex); } /// /// Resolves dirty material palette data and uploads it to the GPU. /// Must be called once per frame on the render thread, before any draw calls. /// Handles buffer growth with copy-on-resize semantics (same pattern as GPUScene). /// public void UploadMaterialPaletteData(RenderContext ctx) { Logger.DebugAssert(!_disposed); if (!_materialPalettes.IsGpuDirty) { return; } // Resolve material handles → bindless CBuffer indices. _materialPalettes.ResolveMaterialIndices(static (materialHandle, state) => { var self = (ResourceManager)state!; var r = self.GetMaterialReference(materialHandle); if (r.IsFailure || !r.Value._cBufferCache.IsCreated) { return 0u; } return self._resourceDatabase.GetBindlessIndex(r.Value._cBufferCache.GpuResource.AsResource()); }, this); var offsets = _materialPalettes.PaletteOffsets; var indices = _materialPalettes.MaterialIndices; _materialPalettes.GetDirtyRanges( out var offsetStart, out var offsetEnd, out var indicesStart, out var indicesEnd); // ── Resize PaletteOffsetBuffer if needed ── if ((uint)offsets.Length > _paletteOffsetCapacity) { var newCapacity = Math.Max(_paletteOffsetCapacity * 2, (uint)offsets.Length); var newBuffer = CreatePaletteBuffer(newCapacity, "PaletteOffsetBuffer_Resized"); ctx.CommandBuffer.CopyBuffer(newBuffer, _paletteOffsetBuffer, 0, 0, _paletteOffsetCapacity * sizeof(uint)); _resourceDatabase.ReleaseResource(_paletteOffsetBuffer.AsResource()); _paletteOffsetBuffer = newBuffer; _paletteOffsetCapacity = newCapacity; // Full upload needed after resize. offsetStart = 0; offsetEnd = offsets.Length; } // ── Resize MaterialIndexBuffer if needed ── if ((uint)indices.Length > _materialIndexCapacity) { var newCapacity = Math.Max(_materialIndexCapacity * 2, (uint)indices.Length); var newBuffer = CreatePaletteBuffer(newCapacity, "MaterialIndexBuffer_Resized"); ctx.CommandBuffer.CopyBuffer(newBuffer, _materialIndexBuffer, 0, 0, _materialIndexCapacity * sizeof(uint)); _resourceDatabase.ReleaseResource(_materialIndexBuffer.AsResource()); _materialIndexBuffer = newBuffer; _materialIndexCapacity = newCapacity; indicesStart = 0; indicesEnd = indices.Length; } // ── Upload dirty ranges ── if (offsetEnd > offsetStart) { var dirtyOffsets = offsets.Slice(offsetStart, offsetEnd - offsetStart); ctx.UploadBufferRange(_paletteOffsetBuffer, dirtyOffsets, (uint)(offsetStart * sizeof(uint))); } if (indicesEnd > indicesStart) { var dirtyIndices = indices.Slice(indicesStart, indicesEnd - indicesStart); ctx.UploadBufferRange(_materialIndexBuffer, dirtyIndices, (uint)(indicesStart * sizeof(uint))); } _materialPalettes.ClearDirty(); } private Handle CreatePaletteBuffer(uint capacity, string name) { var desc = new BufferDesc { Size = capacity * sizeof(uint), Stride = sizeof(uint), Usage = BufferUsage.Raw | BufferUsage.ShaderResource, HeapType = HeapType.Default, }; return _resourceAllocator.CreateBuffer(in desc, name); } /// /// Releases the material palette associated with the specified palette ID. /// /// The palette index to release. public void ReleaseMaterialPalette(Identifier paletteID) { Logger.DebugAssert(!_disposed); _materialPalettes.Release(paletteID); } /// /// Determines whether a shader with the specified identifier exists in the collection. /// /// The identifier of the shader to check for existence. /// true if a shader with the specified identifier exists; otherwise, false. public bool HasShader(Handle id) { Logger.DebugAssert(!_disposed); return _shaders.Contains(id.ID, id.Generation); } /// /// Returns a reference to the shader associated with the specified identifier. /// /// The identifier of the shader to retrieve. Must refer to a valid shader. /// A result containing a reference to the shader corresponding to the specified identifier, or an error status if the identifier is invalid. public RefResult GetShaderReference(Handle handle) { ref var shader = ref _shaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { return Error.NotFound; } return RefResult.Success(ref shader); } /// /// Releases the shader associated with the specified identifier, freeing any resources allocated to it. /// /// The identifier of the shader to release. Must refer to a valid, previously created shader. public void ReleaseShader(Handle handle) { Logger.DebugAssert(!_disposed); lock (_shaderWriteLock) { ref var shader = ref _shaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { return; } _shaders.Remove(handle.ID, handle.Generation); shader.ReleaseResource(_resourceDatabase); } } /// /// Determines whether a compute shader with the specified identifier exists in the collection. /// /// The identifier of the compute shader to check for existence. /// true if a compute shader with the specified identifier exists; otherwise, false. public bool HasComputeShader(Handle id) { Logger.DebugAssert(!_disposed); return _computeShaders.Contains(id.ID, id.Generation); } /// /// Returns a reference to the compute shader associated with the specified identifier. /// /// The identifier of the compute shader to retrieve. Must refer to a valid ComputeShader. /// A result containing a reference to the compute shader corresponding to the specified identifier, or an error status if the identifier is invalid. public RefResult GetComputeShaderReference(Handle handle) { ref var computeShader = ref _computeShaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { return Error.NotFound; } return RefResult.Success(ref computeShader); } /// /// Releases the compute shader associated with the specified identifier, freeing any resources allocated to it. /// /// The identifier of the compute shader to release. Must refer to a valid, previously created ComputeShader. public void ReleaseComputeShader(Handle handle) { Logger.DebugAssert(!_disposed); lock (_computeShaderWriteLock) { ref var computeShader = ref _computeShaders.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) { return; } _computeShaders.Remove(handle.ID, handle.Generation); computeShader.ReleaseResource(_resourceDatabase); } } public void Dispose() { if (_disposed) { return; } foreach (ref var mesh in _meshes) { mesh.ReleaseResource(_resourceDatabase); } foreach (ref var material in _materials) { material.ReleaseResource(_resourceDatabase); } foreach (ref var shader in _shaders) { shader.ReleaseResource(_resourceDatabase); } _meshes.Dispose(); _materials.Dispose(); _shaders.Dispose(); _materialPalettes.Dispose(); _resourceDatabase.ReleaseResource(_paletteOffsetBuffer.AsResource()); _resourceDatabase.ReleaseResource(_materialIndexBuffer.AsResource()); DisposePool(); _disposed = true; GC.SuppressFinalize(this); } }