From 22b6d7210729332cb3192d9701788a4f388db977 Mon Sep 17 00:00:00 2001 From: Ka-Ping Yee Date: Sun, 29 Apr 2018 23:35:58 -0700 Subject: [PATCH] Introduce PolyBuffer; use it to handle color space conversion in LXLayeredComponent. --- src/heronarts/lx/Buffer.java | 5 + src/heronarts/lx/LXBuffer.java | 2 +- src/heronarts/lx/LXEffect.java | 43 ++++- src/heronarts/lx/LXLayer.java | 55 +++++- src/heronarts/lx/LXLayeredComponent.java | 204 +++++++++++++++++------ src/heronarts/lx/LXPattern.java | 67 ++++++-- src/heronarts/lx/ModelBuffer.java | 2 +- src/heronarts/lx/ModelLongBuffer.java | 26 +++ src/heronarts/lx/PolyBuffer.java | 126 ++++++++++++++ 9 files changed, 449 insertions(+), 81 deletions(-) create mode 100644 src/heronarts/lx/Buffer.java create mode 100644 src/heronarts/lx/ModelLongBuffer.java create mode 100644 src/heronarts/lx/PolyBuffer.java diff --git a/src/heronarts/lx/Buffer.java b/src/heronarts/lx/Buffer.java new file mode 100644 index 0000000..2883f96 --- /dev/null +++ b/src/heronarts/lx/Buffer.java @@ -0,0 +1,5 @@ +package heronarts.lx; + +public interface Buffer { + public Object getArray(); // should return an array of a color type +} diff --git a/src/heronarts/lx/LXBuffer.java b/src/heronarts/lx/LXBuffer.java index 0e4c104..27eb3b1 100644 --- a/src/heronarts/lx/LXBuffer.java +++ b/src/heronarts/lx/LXBuffer.java @@ -20,6 +20,6 @@ package heronarts.lx; -public interface LXBuffer { +public interface LXBuffer extends Buffer { public int[] getArray(); } diff --git a/src/heronarts/lx/LXEffect.java b/src/heronarts/lx/LXEffect.java index 42c7898..9851aeb 100644 --- a/src/heronarts/lx/LXEffect.java +++ b/src/heronarts/lx/LXEffect.java @@ -57,6 +57,12 @@ public class Timer { private int index = -1; + // An alias for the 8-bit color buffer array, for compatibility with old-style + // implementations of run(deltaMs, amount) that directly read from and write + // into the "colors" array. Newer subclasses should instead implement + // run(deltaMs, amount, preferredSpace) and use polyBuffer.getArray(space). + protected int[] colors = null; + protected LXEffect(LX lx) { super(lx); this.label.setDescription("The name of this effect"); @@ -171,21 +177,46 @@ public final LXEffect disable() { @Override public final void onLoop(double deltaMs) { long runStart = System.nanoTime(); - double enabledDamped = this.enabledDamped.getValue(); - if (enabledDamped > 0) { - run(deltaMs, enabledDamped); + if (enabledDamped.getValue() > 0) { + run(deltaMs, enabledDamped.getValue(), polyBuffer.getFreshSpace()); } this.timer.runNanos = System.nanoTime() - runStart; } /** - * Implementation of the effect. Subclasses need to override this to implement - * their functionality. + * Old-style subclasses override this method to implement the effect + * by reading and modifying the "colors" array. New-style subclasses + * should override the other run() method instead; see below. + * + * @param deltaMs Number of milliseconds elapsed since last invocation + * @param enabledAmount The amount of the effect to apply, scaled from 0-1 + */ + @Deprecated + protected /* abstract */ void run(double deltaMs, double enabledAmount) { } + + /** + * Implements the effect. Subclasses should override this method to + * apply their effect to an array obtained from the polyBuffer. * * @param deltaMs Number of milliseconds elapsed since last invocation * @param enabledAmount The amount of the effect to apply, scaled from 0-1 + * @param preferredSpace A hint as to which color space to operate in for + * the greatest efficiency (performing the effect in a different color + * space will still work, but will necessitate color space conversion) */ - protected abstract void run(double deltaMs, double enabledAmount); + protected void run(double deltaMs, double enabledAmount, PolyBuffer.Space preferredSpace) { + // For compatibility, this invokes the method that previous subclasses were + // supposed to implement. Implementations of run(deltaMs, enabledAmount) + // are assumed to operate only on the "colors" array, and are not expected + // to have marked the buffer, so we mark the buffer modified here. + colors = polyBuffer.getArray(); + run(deltaMs, enabledAmount); + polyBuffer.markModified(); + + // New subclasses should override and replace this method with one that + // obtains a color array using polyBuffer.getArray(space), writes into + // that buffer, and then calls polyBuffer.markModified(space). + } @Override public void noteOnReceived(MidiNoteOn note) { diff --git a/src/heronarts/lx/LXLayer.java b/src/heronarts/lx/LXLayer.java index 165b886..9d7ab04 100644 --- a/src/heronarts/lx/LXLayer.java +++ b/src/heronarts/lx/LXLayer.java @@ -21,19 +21,27 @@ package heronarts.lx; /** - * A layer is a components that has a run method and operates on some other + * A layer is a component that has a run method and operates on some other * buffer component. The layer does not actually own the color buffer. An * effect is an example of a layer, or patterns may compose themselves from * multiple layers. */ public abstract class LXLayer extends LXLayeredComponent { + /** The requested color space. See setPreferredSpace(). */ + protected PolyBuffer.Space preferredSpace = PolyBuffer.Space.RGB8; + + // An alias for the 8-bit color buffer array, for compatibility with old-style + // implementations of run(deltaMs) that directly read from and write + // into the "colors" array. Newer subclasses should instead implement + // run(deltaMs, preferredSpace) and use polyBuffer.getArray(space). + protected int[] colors = null; 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 @@ -41,16 +49,51 @@ public String getLabel() { return "Layer"; } + /** + * Sets the color space in which this layer is requested to operate. + * This is a request for the run() method to operate in a particular + * color space, for efficiency. The run() method is not required to honor + * this request; this merely provides the information that using a + * different color space will necessitate an additional conversion. + * @param space + */ + public void setPreferredSpace(PolyBuffer.Space space) { + preferredSpace = space; + } + @Override protected final void onLoop(double deltaMs) { - run(deltaMs); + run(deltaMs, preferredSpace); } /** - * Run this layer. + * Old-style subclasses override this method to run the layer + * by reading and writing the "colors" array. New-style subclasses + * should override the other run() method instead; see below. * * @param deltaMs Milliseconds elapsed since last frame */ - public abstract void run(double deltaMs); + public /* abstract */ void run(double deltaMs) { } + /** + * Runs the layer. Subclasses should override this method. + * + * @param deltaMs Milliseconds elapsed since last frame + * @param preferredSpace A hint as to which color space to operate in for + * the greatest efficiency (writing the pattern in a different color + * space will still work, but will necessitate color space conversion) + */ + protected void run(double deltaMs, PolyBuffer.Space preferredSpace) { + // For compatibility, this invokes the method that previous subclasses + // were supposed to implement. Implementations of run(deltaMs) are + // assumed to operate only on the "colors" array, and are not expected + // to have marked the buffer, so we mark the buffer modified here. + colors = polyBuffer.getArray(); + run(deltaMs); + polyBuffer.markModified(); + + // New subclasses should override and replace this method with one that + // obtains a color array using polyBuffer.getArray(space), writes into + // that buffer, and then calls polyBuffer.markModified(space). + } } diff --git a/src/heronarts/lx/LXLayeredComponent.java b/src/heronarts/lx/LXLayeredComponent.java index d37d123..c029c47 100644 --- a/src/heronarts/lx/LXLayeredComponent.java +++ b/src/heronarts/lx/LXLayeredComponent.java @@ -30,24 +30,47 @@ 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. + * Patterns, transitions, and effects are all LXLayeredComponents. Subclasses + * do their work mainly by implementing onLoop() to write into the color buffer. + * + * The color buffer is actually a PolyBuffer, which manages a set of buffers, + * one for each color space. Subclasses should implement onLoop() so that it + * writes to just one of the color spaces in the PolyBuffer; the data will be + * automatically converted to other color spaces 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 fetch the array for a color + * space with polyBuffer.getArray(space), write colors into the array, + * and finally mark it modified with polyBuffer.markModified(space). + * External API: + * getPolyBuffer().getArray(space) gets the array for the requested + * color space, converting from the space in which the colors were + * written, if different. setPolyBuffer() is illegal to call. + * + * For subclasses not marked Buffered: + * Internal API: + * Same as above. + * External API: + * getPolyBuffer() is the same as above. setPolyBuffer(buffer) takes + * a PolyBuffer and makes it the buffer that onLoop() will read from + * and write into. */ public abstract class LXLayeredComponent extends LXModelComponent implements LXLoopTask { - - /** - * 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; - - protected int[] colors = null; + /** The PolyBuffer contains the color buffers for each of the color spaces. */ + protected PolyBuffer polyBuffer = null; private final List mutableLayers = new ArrayList(); protected final List layers = Collections.unmodifiableList(mutableLayers); @@ -55,47 +78,71 @@ public interface Buffered {} protected final LXPalette palette; protected LXLayeredComponent(LX lx) { - this(lx, (LXBuffer) null); + super(lx); + this.lx = lx; + palette = lx.palette; + polyBuffer = new PolyBuffer(lx); } protected LXLayeredComponent(LX lx, LXDeviceComponent component) { - this(lx, component.getBuffer()); + this(lx); + setBuffer(component); } - protected LXLayeredComponent(LX lx, LXBuffer buffer) { - super(lx); + /** Gets this component's combined color buffer. */ + protected PolyBuffer getPolyBuffer() { + return polyBuffer; + } + + /** Sets the buffer of another component as the buffer to read from and write to. */ + protected LXLayeredComponent setBuffer(LXDeviceComponent component) { + setPolyBuffer(component.polyBuffer); + return this; + } + + /** Sets an external buffer as the buffer to read from and write to. */ + protected LXLayeredComponent setPolyBuffer(PolyBuffer externalBuffer) { if (this instanceof Buffered) { - if (buffer != null) { - throw new IllegalArgumentException("Cannot pass existing buffer to LXLayeredComponent.Buffered, has its own"); - } - buffer = new ModelBuffer(lx); + throw new UnsupportedOperationException("Cannot set an external buffer in a Buffered LXLayeredComponent"); } - this.lx = lx; - this.palette = lx.palette; - if (buffer != null) { - this.buffer = buffer; - this.colors = buffer.getArray(); + polyBuffer = externalBuffer; + return this; + } + + /** + * Constructor that optionally sets an external 8-bit color buffer as the + * buffer to read from and write to. Maintained for compatibility. + */ + @Deprecated + protected LXLayeredComponent(LX lx, /* nullable */ LXBuffer externalBuffer) { + this(lx); + if (externalBuffer != null) { + setBuffer(externalBuffer); } } + /** Gets the 8-bit color buffer. Maintained for compatibility. */ + @Deprecated protected LXBuffer getBuffer() { - return this.buffer; + return (LXBuffer) polyBuffer.getBuffer(); } + /** Gets the 8-bit color buffer's array. Maintained for compatibility. */ + @Deprecated public int[] getColors() { - return getBuffer().getArray(); - } + return polyBuffer.getArray(); + } - protected LXLayeredComponent setBuffer(LXDeviceComponent component) { + /** + * Sets an external 8-bit color buffer as the buffer to read from and write to. + * Maintained for compatibility. + */ + @Deprecated + 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 set an external buffer in a Buffered LXLayeredComponent"); } - return setBuffer(component.getBuffer()); - } - - protected LXLayeredComponent setBuffer(LXBuffer buffer) { - this.buffer = buffer; - this.colors = buffer.getArray(); + polyBuffer.setBuffer(externalBuffer); return this; } @@ -103,16 +150,11 @@ protected LXLayeredComponent setBuffer(LXBuffer buffer) { public void loop(double deltaMs) { long loopStart = System.nanoTime(); - // This protects against subclasses from inappropriately nuking the colors buffer - // 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(); - super.loop(deltaMs); onLoop(deltaMs); + for (LXLayer layer : this.mutableLayers) { - layer.setBuffer(this.buffer); + layer.setPolyBuffer(polyBuffer); // TODO(mcslee): is this best here or should it be in addLayer? layer.setModel(this.model); @@ -121,12 +163,22 @@ public void loop(double deltaMs) { } afterLayers(deltaMs); + 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. + polyBuffer.sync(); + } + this.timer.loopNanos = System.nanoTime() - loopStart; } - protected /* abstract */ void onLoop(double deltaMs) {} + protected /* abstract */ void onLoop(double deltaMs) { + // Implementations should call markModified() if they modify the color buffer. + } - protected /* abstract */ void afterLayers(double deltaMs) {} + protected /* abstract */ void afterLayers(double deltaMs) { + // Implementations should call markModified() if they modify the color buffer. + } protected final LXLayer addLayer(LXLayer layer) { if (this.mutableLayers.contains(layer)) { @@ -156,6 +208,26 @@ public void dispose() { super.dispose(); } + protected void setColors(PolyBuffer.Space space, Object color) { + switch (space) { + case RGB8: + int[] intArray = (int[]) polyBuffer.getArray(space); + int intColor = (int) color; + for (int i = 0; i < intArray.length; i++) { + intArray[i] = intColor; + } + break; + case RGB16: + long[] longArray = (long[]) polyBuffer.getArray(space); + long longColor = (long) color; + for (int i = 0; i < longArray.length; i++) { + longArray[i] = longColor; + } + break; + } + polyBuffer.markModified(space); + } + /** * Sets the color of point i * @@ -163,8 +235,10 @@ public void dispose() { * @param c color * @return this */ + @Deprecated protected final LXLayeredComponent setColor(int i, int c) { - this.colors[i] = c; + getColors()[i] = c; + polyBuffer.markModified(); return this; } @@ -177,15 +251,21 @@ protected final LXLayeredComponent setColor(int i, int c) { * * @return this */ + @Deprecated protected final LXLayeredComponent blendColor(int i, int c, LXColor.Blend blendMode) { - this.colors[i] = LXColor.blend(this.colors[i], c, blendMode); + int[] colors = getColors(); + colors[i] = LXColor.blend(colors[i], c, blendMode); + polyBuffer.markModified(); return this; } + @Deprecated protected final LXLayeredComponent blendColor(LXFixture f, int c, LXColor.Blend blendMode) { + int[] colors = getColors(); for (LXPoint p : f.getPoints()) { - this.colors[p.index] = LXColor.blend(this.colors[p.index], c, blendMode); + colors[p.index] = LXColor.blend(colors[p.index], c, blendMode); } + polyBuffer.markModified(); return this; } @@ -196,8 +276,11 @@ protected final LXLayeredComponent blendColor(LXFixture f, int c, LXColor.Blend * @param c color * @return this */ + @Deprecated protected final LXLayeredComponent addColor(int i, int c) { - this.colors[i] = LXColor.add(this.colors[i], c); + int[] colors = getColors(); + colors[i] = LXColor.add(colors[i], c); + polyBuffer.markModified(); return this; } @@ -209,6 +292,7 @@ protected final LXLayeredComponent addColor(int i, int c) { * @param c color * @return this */ + @Deprecated protected final LXLayeredComponent addColor(int x, int y, int c) { return addColor(x + y * this.lx.width, c); } @@ -220,10 +304,13 @@ protected final LXLayeredComponent addColor(int x, int y, int c) { * @param c New color * @return this */ + @Deprecated protected final LXLayeredComponent addColor(LXFixture f, int c) { + int[] colors = getColors(); for (LXPoint p : f.getPoints()) { - this.colors[p.index] = LXColor.add(this.colors[p.index], c); + colors[p.index] = LXColor.add(colors[p.index], c); } + polyBuffer.markModified(); return this; } @@ -235,8 +322,10 @@ protected final LXLayeredComponent addColor(LXFixture f, int c) { * @param c color * @return this */ + @Deprecated protected final LXLayeredComponent setColor(int x, int y, int c) { - this.colors[x + y * this.lx.width] = c; + getColors()[x + y * this.lx.width] = c; + polyBuffer.markModified(); return this; } @@ -247,8 +336,9 @@ protected final LXLayeredComponent setColor(int x, int y, int c) { * @param y y-index * @return Color value */ + @Deprecated protected final int getColor(int x, int y) { - return this.colors[x + y * this.lx.width]; + return getColors()[x + y * this.lx.width]; } /** @@ -257,10 +347,13 @@ protected final int getColor(int x, int y) { * @param c Color * @return this */ + @Deprecated protected final LXLayeredComponent setColors(int c) { + int[] colors = getColors(); for (int i = 0; i < colors.length; ++i) { - this.colors[i] = c; + colors[i] = c; } + polyBuffer.markModified(); return this; } @@ -271,10 +364,13 @@ protected final LXLayeredComponent setColors(int c) { * @param c color * @return this */ + @Deprecated protected final LXLayeredComponent setColor(LXFixture f, int c) { + int[] colors = getColors(); for (LXPoint p : f.getPoints()) { - this.colors[p.index] = c; + colors[p.index] = c; } + polyBuffer.markModified(); return this; } @@ -283,8 +379,8 @@ protected final LXLayeredComponent setColor(LXFixture f, int c) { * * @return this */ + @Deprecated protected final LXLayeredComponent clearColors() { return setColors(0); } - } diff --git a/src/heronarts/lx/LXPattern.java b/src/heronarts/lx/LXPattern.java index 408d047..925f1dc 100644 --- a/src/heronarts/lx/LXPattern.java +++ b/src/heronarts/lx/LXPattern.java @@ -38,17 +38,21 @@ * colors for all the points. */ public abstract class LXPattern extends LXDeviceComponent implements LXComponent.Renamable, LXLayeredComponent.Buffered, LXMidiListener, LXOscComponent { - private int index = -1; - private int intervalBegin = -1; - private int intervalEnd = -1; + protected double runMs = 0; - public final BooleanParameter autoCycleEligible = new BooleanParameter("Cycle", true); + /** The requested color space. See setPreferredSpace(). */ + protected PolyBuffer.Space preferredSpace = PolyBuffer.Space.RGB8; - protected double runMs = 0; + // An alias for the 8-bit color buffer array, for compatibility with old-style + // implementations of run(deltaMs) that directly read from and write + // into the "colors" array. Newer subclasses should instead implement + // run(deltaMs, preferredSpace) and use polyBuffer.getArray(space). + protected int[] colors = null; + public final BooleanParameter autoCycleEligible = new BooleanParameter("Cycle", true); public final Timer timer = new Timer(); public class Timer { @@ -188,35 +192,72 @@ public final boolean isAutoCycleEligible() { return this.autoCycleEligible.isOn() && (!this.hasInterval() || this.isInInterval()); } + /** + * Sets the color space in which this pattern is requested to operate. + * This is a request for the run() method to operate in a particular + * color space, for efficiency. The run() method is not required to honor + * this request; this merely provides the information that using a + * different color space will necessitate an additional conversion. + * @param space + */ + public void setPreferredSpace(PolyBuffer.Space space) { + preferredSpace = space; + } + @Override protected final void onLoop(double deltaMs) { long runStart = System.nanoTime(); this.runMs += deltaMs; - this.run(deltaMs); + this.run(deltaMs, preferredSpace); this.timer.runNanos = System.nanoTime() - runStart; } /** - * Main pattern loop function. Invoked in a render loop. Subclasses must - * implement this function. + * Old-style subclasses override this method to implement the pattern + * by writing colors into the "colors" array. New-style subclasses + * should override the other run() method instead; see below. + * + * @param deltaMs Number of milliseconds elapsed since last invocation + */ + @Deprecated + protected /* abstract */ void run(double deltaMs) { } + + /** + * Implements the pattern. Subclasses should override this method to + * write colors into an array obtained from the polyBuffer. * * @param deltaMs Number of milliseconds elapsed since last invocation + * @param preferredSpace A hint as to which color space to operate in for + * the greatest efficiency (writing the pattern in a different color + * space will still work, but will necessitate color space conversion) */ - protected abstract void run(double deltaMs); + protected void run(double deltaMs, PolyBuffer.Space preferredSpace) { + // For compatibility, this invokes the method that previous subclasses + // were supposed to implement. Implementations of run(deltaMs) are + // assumed to operate only on the "colors" array, and are not expected + // to have marked the buffer, so we mark the buffer modified here. + colors = polyBuffer.getArray(); + run(deltaMs); + polyBuffer.markModified(); + + // New subclasses should override and replace this method with one that + // obtains a color array using polyBuffer.getArray(space), writes into + // that buffer, and then calls polyBuffer.markModified(space). + } /** * Subclasses may override this method. It will be invoked when the pattern is * about to become active. Patterns may take care of any initialization needed * or reset parameters if desired. */ - public/* abstract */void onActive() { + public /* abstract */ void onActive() { } /** * Subclasses may override this method. It will be invoked when the pattern is * no longer active. Resources may be freed if desired. */ - public/* abstract */void onInactive() { + public /* abstract */ void onInactive() { } /** @@ -225,14 +266,14 @@ protected final void onLoop(double deltaMs) { * is not invoked on an already-running pattern. It is only called on the new * pattern. */ - public/* abstract */void onTransitionStart() { + public /* abstract */ void onTransitionStart() { } /** * Subclasses may override this method. It will be invoked when the transition * into this pattern is complete. */ - public/* abstract */void onTransitionEnd() { + public /* abstract */ void onTransitionEnd() { } @Override diff --git a/src/heronarts/lx/ModelBuffer.java b/src/heronarts/lx/ModelBuffer.java index 4ba8518..8c11578 100644 --- a/src/heronarts/lx/ModelBuffer.java +++ b/src/heronarts/lx/ModelBuffer.java @@ -22,7 +22,7 @@ import heronarts.lx.model.LXModel; -public class ModelBuffer implements LXBuffer { +public class ModelBuffer implements Buffer, LXBuffer { private int[] array; diff --git a/src/heronarts/lx/ModelLongBuffer.java b/src/heronarts/lx/ModelLongBuffer.java new file mode 100644 index 0000000..6fdfe73 --- /dev/null +++ b/src/heronarts/lx/ModelLongBuffer.java @@ -0,0 +1,26 @@ +package heronarts.lx; + +import heronarts.lx.model.LXModel; + +public class ModelLongBuffer implements Buffer { + private long[] array; + + public ModelLongBuffer(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/PolyBuffer.java b/src/heronarts/lx/PolyBuffer.java new file mode 100644 index 0000000..de2b815 --- /dev/null +++ b/src/heronarts/lx/PolyBuffer.java @@ -0,0 +1,126 @@ +package heronarts.lx; + +import heronarts.lx.color.LXColor; +import heronarts.lx.color.LXColor16; + +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; + +/** + * Manages a set of color buffers of various color spaces, converting color values + * between them automatically as needed. Clients should call markModified() + * after writing into any buffer; then getBuffer() will convert the data when necessary. + * Buffers are allocated on demand; if only one is used, no memory is wasted on any others. + */ +public class PolyBuffer { + public enum Space {RGB8, RGB16}; + + private LX lx = null; + private Map buffers = new EnumMap<>(Space.class); + private Set freshSpaces = EnumSet.noneOf(Space.class); + + public PolyBuffer(LX lx) { + this.lx = lx; + } + + public Buffer getBuffer(Space space) { + updateBuffer(space); + return buffers.get(space); + } + + public Object getArray(Space space) { + return getBuffer(space).getArray(); + } + + public void markModified(Space space) { + assert buffers.get(space) != null; + freshSpaces = EnumSet.of(space); + } + + public Space getFreshSpace() { + for (Space space : freshSpaces) { + return space; + } + return DEFAULT_SPACE; + } + + public boolean isFresh(Space space) { + return freshSpaces.contains(space); + } + + protected Buffer createBuffer(Space space) { + switch (space) { + case RGB8: + return new ModelBuffer(lx); + case RGB16: + return new ModelLongBuffer(lx); + default: + return null; + } + } + + protected void updateBuffer(Space space) { + if (!isFresh(space)) { + if (buffers.get(space) == null) { + buffers.put(space, createBuffer(space)); + } + Buffer buffer = buffers.get(space); + switch (space) { + case RGB8: + if (isFresh(Space.RGB16)) { + LXColor16.longsToInts((long[]) getArray(Space.RGB16), + (int[]) buffer.getArray()); + } + break; + case RGB16: + if (isFresh(Space.RGB8)) { + LXColor.intsToLongs((int[]) getArray(Space.RGB8), + (long[]) buffer.getArray()); + } + break; + } + freshSpaces.add(space); + } + } + + // The methods below provide support for old-style use of the PolyBuffer + // as if it were only an RGB8 buffer. + + @Deprecated + private static final Space DEFAULT_SPACE = Space.RGB8; + + @Deprecated + public Buffer getBuffer() { + return getBuffer(DEFAULT_SPACE); + } + + @Deprecated + public int[] getArray() { + return (int[]) getBuffer(DEFAULT_SPACE).getArray(); + } + + @Deprecated + public void setBuffer(Buffer buffer) { + buffers.clear(); + buffers.put(DEFAULT_SPACE, buffer); + if (buffer != null) { + markModified(DEFAULT_SPACE); + } + } + + @Deprecated + public void markModified() { + markModified(DEFAULT_SPACE); + } + + @Deprecated + public void sync() { + for (Space space : Space.values()) { + if (buffers.get(space) != null && !isFresh(space)) { + updateBuffer(space); + } + } + } +}