172 lines
5.1 KiB
C#
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 );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|