using System.Collections.Generic; using Godot; using System; using Rokojori.Extensions; namespace Rokojori { /** Shared pool of full-size DrawableTexture2D buffers, keyed by size and format. Compositing one texture holds up to three full-size buffers at once (the target, the scratch buffer and the ping-pong buffer). At 4096 Rgbah that is roughly 128 MB each, so they are rented for the duration of a composite and returned immediately afterwards - allocating them per composite would thrash VRAM, and keeping them alive per texture would multiply by the number of stacks in a scene. Buffers are not cleared on rent. Every consumer either Setup()s them or fully overwrites the rect it reads, so clearing would be wasted work. Session 3's incremental redraw will keep buffers instead of returning them, which is why renting goes through a pool rather than through new(). */ public class TextureStackBufferPool { public static readonly TextureStackBufferPool shared = new TextureStackBufferPool(); class Key { public int width; public int height; public TextureStackFormat format; public override int GetHashCode() { return ( width * 397 ) ^ ( height * 31 ) ^ (int) format; } public override bool Equals( object other ) { var key = other as Key; return key != null && key.width == width && key.height == height && key.format == format; } } Dictionary> _free = new Dictionary>(); /** The key a rented buffer was created under. Tracked here rather than read back off the buffer, because DrawableTexture2D exposes SetFormat without a matching getter. */ Dictionary _keys = new Dictionary(); public int rented => _keys.Count; public DrawableTexture2D Rent( int width, int height, TextureStackFormat format, Color clearColor ) { var key = new Key(){ width = width, height = height, format = format }; List buffers; var buffer = (DrawableTexture2D) null; if ( _free.TryGetValue( key, out buffers ) && buffers.Count > 0 ) { buffer = buffers[ buffers.Count - 1 ]; buffers.RemoveAt( buffers.Count - 1 ); } else { buffer = new DrawableTexture2D(); } // Setup() re-allocates and clears, which is what makes a reused buffer safe. buffer.Setup( width, height, format.ToDrawableFormat(), clearColor, false ); _keys[ buffer ] = key; return buffer; } public void Return( DrawableTexture2D buffer ) { if ( buffer == null ) { return; } Key key; if ( ! _keys.TryGetValue( buffer, out key ) ) { // Not ours - the caller allocated it, so it is theirs to keep. return; } _keys.Remove( buffer ); List buffers; if ( ! _free.TryGetValue( key, out buffers ) ) { buffers = new List(); _free[ key ] = buffers; } if ( ! buffers.Contains( buffer ) ) { buffers.Add( buffer ); } } /** Drops every pooled buffer. Nothing calls this automatically - it is the escape hatch for freeing VRAM after a one-off 4k bake. */ public void Clear() { _free.Clear(); } } }