diff --git a/src/heronarts/lx/LXBuffer16.java b/src/heronarts/lx/LXBuffer16.java new file mode 100644 index 0000000..fa28a71 --- /dev/null +++ b/src/heronarts/lx/LXBuffer16.java @@ -0,0 +1,5 @@ +package heronarts.lx; + +public interface LXBuffer16 { + public long[] getArray(); +} diff --git a/src/heronarts/lx/LXLayer.java b/src/heronarts/lx/LXLayer.java index 165b886..ad3a039 100644 --- a/src/heronarts/lx/LXLayer.java +++ b/src/heronarts/lx/LXLayer.java @@ -32,8 +32,8 @@ protected LXLayer(LX lx) { super(lx); } - protected LXLayer(LX lx, LXDeviceComponent buffer) { - super(lx, buffer); + protected LXLayer(LX lx, LXDeviceComponent component) { + super(lx, component); } @Override diff --git a/src/heronarts/lx/LXLayeredComponent.java b/src/heronarts/lx/LXLayeredComponent.java index d37d123..39d6cb3 100644 --- a/src/heronarts/lx/LXLayeredComponent.java +++ b/src/heronarts/lx/LXLayeredComponent.java @@ -21,6 +21,7 @@ package heronarts.lx; import heronarts.lx.color.LXColor; +import heronarts.lx.color.LXColor16; import heronarts.lx.color.LXPalette; import heronarts.lx.model.LXFixture; import heronarts.lx.model.LXPoint; @@ -30,24 +31,73 @@ import java.util.List; /** - * Base class for system components that run in the engine, which have common - * attributes, such as parameters, modulators, and layers. For instance, - * patterns, transitions, and effects are all LXComponents. + * Base class for system components that have a color buffer and run in the + * engine, with common attributes such as parameters, modulators, and layers. + * For instance, patterns, transitions, and effects are all LXLayeredComponents. + * Subclasses do their work mainly by implementing onLoop() to write into the + * color buffer. + * + * LXLayeredComponents have both an 8-bit color buffer (buffer) and a 16-bit + * color buffer (buffer16). In subclasses marked with LXLayeredComponent.Uses16, + * onLoop() should internally write only to the 16-bit buffer (whose contents + * will be automatically converted to 8-bit colors as needed). In subclasses + * not marked with Uses16, onLoop() should write only to the 8-bit buffer + * (whose contents will be automatically converted to 16-bit colors as needed). + * + * LXLayeredComponent subclasses can be marked as LXLayeredComponent.Buffered + * (which means they own their own buffers), or not (which means they operate + * on external buffers passed in via setBuffer()). + * + * For subclasses marked Buffered: + * Internal API: + * The implementation of onLoop() should write colors into the + * appropriate buffer (buffer16/colors16 for a Uses16 class, or + * buffer/colors otherwise). + * External API: + * getBuffer() returns the 8-bit colors (converting from 16 bits if needed). + * getBuffer16() returns the 16-bit colors (converting from 8 bits if needed). + * setBuffer() and setBuffer16() are illegal to call. + * + * For subclasses not marked Buffered: + * Internal API: + * The implementation of onLoop() should read and write the contents + * of the appropriate buffer (buffer16/colors16 for a Uses16 class, + * or buffer/colors otherwise). + * External API: + * getBuffer() and getBuffer16() are the same as above. + * setBuffer() takes an 8-bit LXBuffer and makes it the place where + * onLoop()'s results will appear (in a Uses16 class, its contents + * will be converted to 16 bits before onLoop() and then back to + * 8 bits after; in a non-Uses16 class no conversion is needed). + * setBuffer16() takes a 16-bit LXBuffer16 and makes it the place where + * onLoop()'s results will appear (in a non-Uses16 class, its + * contents will be converted to 8 bits before onLoop() and then + * back to 16 bits after; in a Uses16 class no conversion is needed). */ public abstract class LXLayeredComponent extends LXModelComponent implements LXLoopTask { + /** Marker interface for subclasses that work on the 16-bit buffer (buffer16). */ + public interface Uses16 {} - /** - * Marker interface for instances which own their own buffer. - */ + /** Marker interface for subclasses that want to own their own buffers. */ public interface Buffered {} public final Timer timer = constructTimer(); - protected final LX lx; - private LXBuffer buffer = null; + // In a Uses16 class, buffer is nullable and buffer16 is non-nullable. + // In a non-Uses16 class, buffer is non-nullable and buffer16 is nullable. + private LXBuffer buffer = null; // 8-bit color buffer + private LXBuffer16 buffer16 = null; // 16-bit color buffer + // These can be used as convenient aliases for buffer.getArray() and buffer16.getArray() + // in subclass implementations of onLoop() and run(). protected int[] colors = null; + protected long[] colors16 = null; + + // This tracks whether buffer and buffer16 have matching data, to avoid + // unnecessarily repeating a data conversion. Subclasses should never + // modify buffers outside of the abstract methods onLoop() and afterLayers(). + private boolean buffersInSync = false; private final List mutableLayers = new ArrayList(); protected final List layers = Collections.unmodifiableList(mutableLayers); @@ -55,47 +105,93 @@ public interface Buffered {} protected final LXPalette palette; protected LXLayeredComponent(LX lx) { - this(lx, (LXBuffer) null); + super(lx); + this.lx = lx; + palette = lx.palette; + if (this instanceof Buffered) { + if (this instanceof Uses16) { + buffer16 = new ModelBuffer16(lx); + colors16 = buffer16.getArray(); + } else { + buffer = new ModelBuffer(lx); + colors = buffer.getArray(); + } + } } protected LXLayeredComponent(LX lx, LXDeviceComponent component) { - this(lx, component.getBuffer()); + this(lx); + setBuffer(component); } - protected LXLayeredComponent(LX lx, LXBuffer buffer) { - super(lx); - if (this instanceof Buffered) { - if (buffer != null) { - throw new IllegalArgumentException("Cannot pass existing buffer to LXLayeredComponent.Buffered, has its own"); - } - buffer = new ModelBuffer(lx); - } - this.lx = lx; - this.palette = lx.palette; - if (buffer != null) { - this.buffer = buffer; - this.colors = buffer.getArray(); - } + protected LXLayeredComponent(LX lx, LXBuffer externalBuffer) { + this(lx); + setBuffer(externalBuffer); } + protected LXLayeredComponent(LX lx, LXBuffer16 externalBuffer16) { + this(lx); + setBuffer16(externalBuffer16); + } + + /** Gets the 8-bit color buffer (performing conversions if necessary). */ protected LXBuffer getBuffer() { - return this.buffer; + if (this instanceof Uses16) { + syncBufferFromBuffer16(); + } + return buffer; } + /** Gets the 16-bit color buffer (performing conversions if necessary). */ + protected LXBuffer16 getBuffer16() { + if (!(this instanceof Uses16)) { + syncBuffer16FromBuffer(); + } + return buffer16; + } + + /** Gets the array from the 8-bit color buffer (performing conversions if necessary). */ public int[] getColors() { return getBuffer().getArray(); } + /** Gets the array from the 16-bit color buffer (performing conversions if necessary). */ + public long[] getColors16() { + return getBuffer16().getArray(); + } + + /** Sets the buffer of another component as the buffer to read from and write to. */ protected LXLayeredComponent setBuffer(LXDeviceComponent component) { + if (component instanceof Uses16) { + return setBuffer16(component.getBuffer16()); + } else { + return setBuffer(component.getBuffer()); + } + } + + /** Sets an external 8-bit color buffer as the buffer to read from and write to. */ + protected LXLayeredComponent setBuffer(LXBuffer externalBuffer) { if (this instanceof Buffered) { - throw new UnsupportedOperationException("Cannot setBuffer on LXLayerdComponent.Buffered, owns its own buffer"); + throw new UnsupportedOperationException("Cannot setBuffer on a LXLayeredComponent.Buffered, owns its own buffer"); } - return setBuffer(component.getBuffer()); + buffer = externalBuffer; + colors = externalBuffer.getArray(); + if (this instanceof Uses16) { + syncBuffer16FromBuffer(); // make data visible to a 16-bit onLoop + } + return this; } - protected LXLayeredComponent setBuffer(LXBuffer buffer) { - this.buffer = buffer; - this.colors = buffer.getArray(); + /** Sets an external 16-bit color buffer as the buffer to read from and write to. */ + protected LXLayeredComponent setBuffer16(LXBuffer16 externalBuffer16) { + if (this instanceof Buffered) { + throw new UnsupportedOperationException("Cannot setBuffer on a LXLayeredComponent.Buffered, owns its own buffer"); + } + buffer16 = externalBuffer16; + colors16 = externalBuffer16.getArray(); + if (!(this instanceof Uses16)) { + syncBufferFromBuffer16(); // make data visible to an 8-bit onLoop + } return this; } @@ -107,12 +203,19 @@ public void loop(double deltaMs) { // reference. Even if a doofus assigns colors to something else, we'll reset it // here on each pass of the loop. Better than subclasses having to call getColors() // all the time. - this.colors = this.buffer.getArray(); + colors = buffer != null ? buffer.getArray() : null; + colors16 = buffer16 != null ? buffer16.getArray() : null; super.loop(deltaMs); onLoop(deltaMs); + buffersInSync = false; + for (LXLayer layer : this.mutableLayers) { - layer.setBuffer(this.buffer); + if (this instanceof Uses16) { + layer.setBuffer16(buffer16); + } else { + layer.setBuffer(buffer); + } // TODO(mcslee): is this best here or should it be in addLayer? layer.setModel(this.model); @@ -120,6 +223,18 @@ public void loop(double deltaMs) { layer.loop(deltaMs); } afterLayers(deltaMs); + buffersInSync = false; + + if (!(this instanceof Buffered)) { + // The buffers are external; we need to make the output from onLoop() and + // afterLayers() visible in the external buffers, converting if needed. + if (this instanceof Uses16 && buffer != null) { + syncBufferFromBuffer16(); + } + if (!(this instanceof Uses16) && buffer16 != null) { + syncBuffer16FromBuffer(); + } + } this.timer.loopNanos = System.nanoTime() - loopStart; } @@ -156,6 +271,52 @@ public void dispose() { super.dispose(); } + /** Ensures that buffer contains a copy of buffer16's contents. */ + protected void syncBufferFromBuffer16() { + if (buffer == null) { + buffer = new ModelBuffer(lx); + } + if (!buffersInSync) { + LXColor16.longsToInts(buffer16.getArray(), buffer.getArray()); + buffersInSync = true; + } + } + + /** Ensures that buffer16 contains a copy of buffer's contents. */ + protected void syncBuffer16FromBuffer() { + if (buffer16 == null) { + buffer16 = new ModelBuffer16(lx); + } + if (!buffersInSync) { + LXColor.intsToLongs(buffer.getArray(), buffer16.getArray()); + buffersInSync = true; + } + } + + // NOTE(ping): Most of the utility routines below are rarely used, + // and rarely or never chained. We won't reimplement them all for + // 16-bit color, just setColor and setColors. + + /** Sets the 16-bit color of a single point. */ + protected void setColor16(int i, long c) { + colors16[i] = c; + } + + /** Sets the 16-bit color of all points. */ + protected void setColors16(int c) { + for (int i = 0; i < colors16.length; i++) { + colors16[i] = c; + } + } + + /** Sets the 16-bit color of all points in a fixture. */ + protected void setColor16(LXFixture f, long c) { + for (LXPoint p : f.getPoints()) { + colors16[p.index] = c; + } + } + + /** * Sets the color of point i * diff --git a/src/heronarts/lx/ModelBuffer16.java b/src/heronarts/lx/ModelBuffer16.java new file mode 100644 index 0000000..cd83210 --- /dev/null +++ b/src/heronarts/lx/ModelBuffer16.java @@ -0,0 +1,26 @@ +package heronarts.lx; + +import heronarts.lx.model.LXModel; + +public class ModelBuffer16 implements LXBuffer16 { + private long[] array; + + public ModelBuffer16(LX lx) { + initArray(lx.model); + + lx.addListener(new LX.Listener() { + @Override + public void modelChanged(LX lx, LXModel model) { + initArray(model); + } + }); + } + + private void initArray(LXModel model) { + this.array = new long[model.size]; // initialized to 0 by Java + } + + public long[] getArray() { + return this.array; + } +}