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

172 lines
5.1 KiB
C#

using System.Collections.Generic;
using Godot;
using System;
using Rokojori.Extensions;
namespace Rokojori
{
/** The layer list, and nothing else.
TextureStack is deliberately pure data: no size, no format, no buffers,
no cached output. All of that lives on the consuming texture
(TextureStackTexture2D), so one stack can be composited at 256 for a
preview and at 4096 for the final without the two fighting over a cache.
<marked>Layer order: layers[ 0 ] is the top layer</marked> - the one that
composites last, over everything else. Same reading as
TextureCombinerStack, Photoshop and every other layer list.
Session 2 had this the other way around, on the grounds that the
compositor is a draw-order system. That was the implementation's
convenience beating the authoring view, which is the wrong trade for a
resource edited by hand. The compositor still works in draw order
internally and goes through DrawLayerAt()/ToDrawIndex() to get there, so
the flip lives in exactly one place and the incremental-redraw indices -
which are draw indices - never had to change.
*/
[Tool]
[GlobalClass]
public partial class TextureStack:Resource
{
TextureStackLayer[] _layers = new TextureStackLayer[ 0 ];
/** Top first. Use DrawLayerAt() to walk them bottom-up.
The setter emits changed, which is what a consuming
TextureStackTexture2D listens to - Godot does not emit it for a plain
exported field on a scripted Resource, only for one with a setter that
does it by hand. See the change-hooks block in TextureStackTexture2D.
*/
[Export]
public TextureStackLayer[] layers
{
get => _layers;
set { _layers = value; TextureStackNotify.Changed( this ); }
}
public int numLayers => _layers == null ? 0 : _layers.Length;
/* DRAW ORDER
--------------------------------------------------------------------------
The array is top-first, the compositor is bottom-first. These two are the
only place that knows it, and they are inverses of each other - the same
mapping in both directions, because reversing an index is its own inverse.
*/
/** The layer at a draw index - 0 is the bottom layer, drawn first. */
public TextureStackLayer DrawLayerAt( int drawIndex )
{
if ( _layers == null || drawIndex < 0 || drawIndex >= _layers.Length )
{
return null;
}
return _layers[ _layers.Length - 1 - drawIndex ];
}
/** Array index -> draw index, for turning "this layer changed" into "redraw
from here up". Also draw index -> array index, being its own inverse.
*/
public int ToDrawIndex( int arrayIndex )
{
return numLayers - 1 - arrayIndex;
}
/** True if any layer needs the accumulation handed in as a source, which
is what makes the compositor allocate a ping-pong buffer.
*/
public bool needsPingPong
{
get
{
if ( _layers == null )
{
return false;
}
foreach ( var layer in _layers )
{
if ( layer == null || ! layer.visible )
{
continue;
}
if ( TextureStackCompositor.NeedsCompositeShader( layer ) )
{
return true;
}
}
return false;
}
}
/** The most intermediate buffers any single layer rents while it draws -
an effect layer chains passes through them. Only the maximum matters,
because they are rented and returned one layer at a time.
*/
public int maxExtraLayerBuffers
{
get
{
if ( _layers == null )
{
return 0;
}
var most = 0;
foreach ( var layer in _layers )
{
if ( layer == null || ! layer.visible )
{
continue;
}
most = Mathf.Max( most, layer.extraBuffers );
}
return most;
}
}
/** Every Resource a consumer has to listen to for this stack to stay in
sync - the layers themselves and, recursively, everything they read.
Collected as ( resource, draw index ) pairs so an edit three levels down
still becomes an incremental redraw from the right layer. Layers that
belong to a nested stack report their *owner's* draw index, because that
is the layer the outer composite has to replay from.
*/
public void CollectChangeSources( List<TextureStackChangeSource> into, int drawIndex = -1 )
{
if ( _layers == null )
{
return;
}
for ( var i = 0; i < _layers.Length; i++ )
{
var layer = _layers[ i ];
if ( layer == null )
{
continue;
}
// -1 means "this is the top-level stack, use each layer's own index".
// A nested stack passes its owner's index down instead.
var index = drawIndex < 0 ? ToDrawIndex( i ) : drawIndex;
TextureStackChangeSource.Add( into, layer, index );
layer.CollectChangeSources( into, index );
}
}
}
}