rokojori_action_library/Runtime/Procedural/Textures/TextureStack/TextureStackBufferPool.cs

125 lines
3.5 KiB
C#

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<Key,List<DrawableTexture2D>> _free = new Dictionary<Key,List<DrawableTexture2D>>();
/** 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<DrawableTexture2D,Key> _keys = new Dictionary<DrawableTexture2D,Key>();
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<DrawableTexture2D> 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<DrawableTexture2D> buffers;
if ( ! _free.TryGetValue( key, out buffers ) )
{
buffers = new List<DrawableTexture2D>();
_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();
}
}
}