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. Layer order: layers[ 0 ] is the top layer - 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 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 ); } } } }