using Ghost.Core; using Ghost.Engine.Components; using Ghost.Graphics; using Ghost.Graphics.Core; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.Mathematics; using System.Collections.Concurrent; namespace Ghost.Engine.RenderPipeline; internal sealed class GhostRenderPayload : IRenderPayload { public struct UpdateInstanceRequest { public MeshInstance meshInstance; public float4x4 localToWorld; public uint instanceId; } public struct RemoveInstanceRequest { public uint instanceId; public uint swapWithInstanceId; } private readonly GhostRenderPipeline _renderPipeline; private UnsafeList _renderRequests; private readonly ConcurrentQueue _updateRequest; private readonly ConcurrentQueue _removeRequest; private uint _instanceCountBefore; private uint _instanceCount; public ReadOnlySpan RenderRequests => _renderRequests; public ConcurrentQueue UpdateRequest => _updateRequest; public ConcurrentQueue RemoveRequest => _removeRequest; public uint InstanceCountBefore => _instanceCountBefore; public uint InstanceCount => _instanceCount; public GhostRenderPayload(GhostRenderPipeline renderPipeline) { _renderPipeline = renderPipeline; _renderRequests = new UnsafeList(4, Misaki.HighPerformance.LowLevel.Buffer.AllocationHandle.Persistent); _updateRequest = new ConcurrentQueue(); _removeRequest = new ConcurrentQueue(); } // NOTE: This is not thread safe. public void AddRenderRequest(ref readonly RenderRequest renderRequest) { _renderRequests.Add(renderRequest); } public uint AddInstance(float4x4 ltw, ref readonly MeshInstance meshInstance) { var index = _renderPipeline.GPUScene.AddInstance(); _updateRequest.Enqueue(new UpdateInstanceRequest { instanceId = index, localToWorld = ltw, meshInstance = meshInstance }); return index; } public void UpdateInstance(uint instanceId, float4x4 ltw, ref readonly MeshInstance meshInstance) { _updateRequest.Enqueue(new UpdateInstanceRequest { instanceId = instanceId, localToWorld = ltw, meshInstance = meshInstance }); } public void RemoveInstance(uint instanceId) { var swapWithInstanceId = _renderPipeline.GPUScene.RemoveInstance(instanceId); if (swapWithInstanceId != uint.MaxValue) { _removeRequest.Enqueue(new RemoveInstanceRequest { instanceId = instanceId, swapWithInstanceId = swapWithInstanceId }); } } public void BeginRecord() { _instanceCountBefore = _renderPipeline.GPUScene.InstanceCount; } public void EndRecord() { // We capture the count here to prevent that main thread continues to add more requests for next frame while the render thread is still processing current frame's requests. _instanceCount = _renderPipeline.GPUScene.InstanceCount; Logger.DebugAssert(_instanceCount == _instanceCountBefore + (uint)_updateRequest.Count - (uint)_removeRequest.Count); } public void Reset() { _renderRequests.Clear(); _updateRequest.Clear(); _removeRequest.Clear(); } public void Dispose() { _renderRequests.Dispose(); } } internal class GhostRenderPipelineSettings : IRenderPipelineSettings { public IRenderPipeline CreatePipeline(RenderSystem renderSystem) { return new GhostRenderPipeline(renderSystem); } public IRenderPayload CreatePayload(RenderSystem renderSystem, IRenderPipeline _renderPipeline) { return new GhostRenderPayload((GhostRenderPipeline)_renderPipeline); } }