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/LXChannel.java b/src/heronarts/lx/LXChannel.java index 1c189e1..5e46e2d 100644 --- a/src/heronarts/lx/LXChannel.java +++ b/src/heronarts/lx/LXChannel.java @@ -640,7 +640,7 @@ public LXBus disableAutoTransition() { /** * Enable automatic transition from pattern to pattern on this channel * - * @param autoTransitionThresholdTransition time in seconds + * @param autoTransitionThreshold time in seconds * @return */ public LXBus enableAutoTransition(double autoTransitionThreshold) { 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 cc3da67..aac939b 100644 --- a/src/heronarts/lx/LXLayeredComponent.java +++ b/src/heronarts/lx/LXLayeredComponent.java @@ -22,6 +22,7 @@ import heronarts.lx.color.BlendMode; 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; @@ -31,24 +32,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); @@ -56,47 +106,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"); + } + buffer = externalBuffer; + colors = externalBuffer.getArray(); + if (this instanceof Uses16) { + syncBuffer16FromBuffer(); // make data visible to a 16-bit onLoop } - return setBuffer(component.getBuffer()); + 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; } @@ -108,12 +204,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); @@ -121,6 +224,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; } @@ -157,6 +272,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; + } +} diff --git a/src/heronarts/lx/color/LXColor.java b/src/heronarts/lx/color/LXColor.java index 4cb275d..d133c48 100644 --- a/src/heronarts/lx/color/LXColor.java +++ b/src/heronarts/lx/color/LXColor.java @@ -57,12 +57,19 @@ public static byte blue(int argb) { } public static long toLong(int argb) { - return LXColor16.rgba( - (int) red(argb) << 8, - (int) green(argb) << 8, - (int) blue(argb) << 8, - (int) alpha(argb) << 8); + // If we were to shift left by 8, then 0xff would become 0xff00. + // Instead, we multiply by 0x0101, so that 0xff becomes 0xffff. + return LXColor16.rgba( + (int) red(argb) * 0x0101, + (int) green(argb) * 0x0101, + (int) blue(argb) * 0x0101, + (int) alpha(argb) * 0x0101); } + + public static void intsToLongs(int[] ints, long[] longs) { + for (int i = 0; i < ints.length; i++) longs[i] = toLong(ints[i]); + } + /** * Hue of a color from 0-360 * diff --git a/src/heronarts/lx/color/LXColor16.java b/src/heronarts/lx/color/LXColor16.java index 6c7b00c..8612ed1 100644 --- a/src/heronarts/lx/color/LXColor16.java +++ b/src/heronarts/lx/color/LXColor16.java @@ -40,6 +40,10 @@ public static int toInt(long argb) { return LXColor.rgba(red(argb) >> 8, green(argb) >> 8, blue(argb) >> 8, alpha(argb) >> 8); } + public static void longsToInts(long[] longs, int[] ints) { + for (int i = 0; i < longs.length; i++) ints[i] = toInt(longs[i]); + } + /** * Hue of a color from 0-360 *