using Ghost.Core; using Misaki.HighPerformance.Buffer; namespace Ghost.RenderGraph.Concept; /// /// Object pool for reusing allocated objects across frames. /// This is key to minimizing GC allocations after the first frame. /// internal sealed class RenderGraphObjectPool { private static readonly List s_allocatedPools = new(); private class SharedObjectPoolBase { public SharedObjectPoolBase() { } public virtual void Clear() { } } private class SharedObjectPool : SharedObjectPoolBase where T : class, new() { private static readonly ObjectPool s_pool = AllocatePool(); private static ObjectPool AllocatePool() { var newPool = new ObjectPool(() => new T(), null); // Storing instance to clear the static pool of the same type if needed s_allocatedPools.Add(new SharedObjectPool()); return newPool; } /// /// Clear the pool using SharedObjectPool instance. /// /// public override void Clear() { s_pool.Reset(); } /// /// Rent a new instance from the pool. /// /// // FIX: ObjectPool.Rent() has a critical bug that it will put the newly created object into the pool directly and give out the same instance again. // This will cause multiple renters to get the same instance. public static T Rent() => s_pool.Rent(); /// /// Return an object to the pool. /// /// instance to release. public static void Return(T toRelease) => s_pool.Return(toRelease); } public T Rent() where T : class, new() { return SharedObjectPool.Rent(); } public void Return(T obj) where T : class, new() { SharedObjectPool.Return(obj); } public void Clear() { for (var i = 0; i < s_allocatedPools.Count; i++) { s_allocatedPools[i].Clear(); } } } /// /// Represents a resource in the render graph (texture or buffer). /// internal sealed class RenderGraphResource { public RenderGraphResourceType type; public int index; public TextureDescriptor textureDescriptor; public BufferDescriptor bufferDescriptor; public bool isImported; public int firstUsePass = -1; public int lastUsePass = -1; public int producerPass = -1; public List consumerPasses = new(4); public int refCount; public void Reset() { type = RenderGraphResourceType.Texture; index = -1; textureDescriptor = default; bufferDescriptor = default; isImported = false; firstUsePass = -1; lastUsePass = -1; producerPass = -1; consumerPasses.Clear(); refCount = 0; } } /// /// Registry for managing all resources in the render graph. /// Uses pooling to minimize allocations after the first frame. /// Uses a single unified list for both textures and buffers with global indexing. /// internal sealed class RenderGraphResourceRegistry { private readonly List _resources = new(64); private readonly RenderGraphObjectPool _pool = new(); public int ResourceCount => _resources.Count; public int TextureResourceCount { get { int count = 0; for (int i = 0; i < _resources.Count; i++) { if (_resources[i].type == RenderGraphResourceType.Texture) count++; } return count; } } public int BufferResourceCount { get { int count = 0; for (int i = 0; i < _resources.Count; i++) { if (_resources[i].type == RenderGraphResourceType.Buffer) count++; } return count; } } public void BeginFrame() { // Return all resources to pool for (var i = 0; i < _resources.Count; i++) { _pool.Return(_resources[i]); } _resources.Clear(); } public Identifier ImportTexture(TextureDescriptor descriptor) { var resource = _pool.Rent(); resource.type = RenderGraphResourceType.Texture; resource.index = _resources.Count; resource.textureDescriptor = descriptor; resource.isImported = true; _resources.Add(resource); return new Identifier(resource.index); } public Identifier CreateTexture(TextureDescriptor descriptor) { var resource = _pool.Rent(); resource.type = RenderGraphResourceType.Texture; resource.index = _resources.Count; resource.textureDescriptor = descriptor; resource.isImported = false; _resources.Add(resource); return new Identifier(resource.index); } public Identifier ImportBuffer(BufferDescriptor descriptor) { var resource = _pool.Rent(); resource.type = RenderGraphResourceType.Buffer; resource.index = _resources.Count; resource.bufferDescriptor = descriptor; resource.isImported = true; _resources.Add(resource); return new Identifier(resource.index); } public Identifier CreateBuffer(BufferDescriptor descriptor) { var resource = _pool.Rent(); resource.type = RenderGraphResourceType.Buffer; resource.index = _resources.Count; resource.bufferDescriptor = descriptor; resource.isImported = false; _resources.Add(resource); return new Identifier(resource.index); } public RenderGraphResource GetResource(Identifier resource) { return _resources[resource.Value]; } public RenderGraphResource GetResource(Identifier texture) { return _resources[texture.Value]; } public RenderGraphResource GetResource(Identifier buffer) { return _resources[buffer.Value]; } /// /// Gets resource by global index. Use this when iterating over all resources. /// public RenderGraphResource GetResourceByIndex(int index) { return _resources[index]; } public void SetProducer(Identifier resourceID, int passIndex) { var resource = GetResource(resourceID); resource.producerPass = passIndex; if (resource.firstUsePass < 0) { resource.firstUsePass = passIndex; } } public void AddConsumer(Identifier resourceID, int passIndex) { var resource = GetResource(resourceID); resource.consumerPasses.Add(passIndex); resource.lastUsePass = passIndex; if (resource.firstUsePass < 0) { resource.firstUsePass = passIndex; } } }