diff --git a/jme3-core/src/main/java/com/jme3/shader/DefineList.java b/jme3-core/src/main/java/com/jme3/shader/DefineList.java index d9b5782aa..d368f2494 100644 --- a/jme3-core/src/main/java/com/jme3/shader/DefineList.java +++ b/jme3-core/src/main/java/com/jme3/shader/DefineList.java @@ -36,144 +36,160 @@ import java.util.List; /** * The new define list. - * + * * @author Kirill Vainer */ public final class DefineList { public static final int MAX_DEFINES = 64; - private long hash; - private final int[] vals; + private long isSet; + private final int[] values; public DefineList(int numValues) { if (numValues < 0 || numValues > MAX_DEFINES) { throw new IllegalArgumentException("numValues must be between 0 and 64"); } - vals = new int[numValues]; + values = new int[numValues]; } - + private DefineList(DefineList original) { - this.hash = original.hash; - this.vals = new int[original.vals.length]; - System.arraycopy(original.vals, 0, vals, 0, vals.length); + this.isSet = original.isSet; + this.values = new int[original.values.length]; + System.arraycopy(original.values, 0, values, 0, values.length); + } + + private void rangeCheck(int id) { + assert 0 <= id && id < values.length; + } + + public boolean isSet(int id) { + rangeCheck(id); + return (isSet & (1L << id)) != 0; + } + + public void unset(int id) { + rangeCheck(id); + isSet &= ~(1L << id); + values[id] = 0; } public void set(int id, int val) { - assert 0 <= id && id < 64; - if (val != 0) { - hash |= (1L << id); - } else { - hash &= ~(1L << id); - } - vals[id] = val; + rangeCheck(id); + isSet |= (1L << id); + values[id] = val; } - + public void set(int id, float val) { set(id, Float.floatToIntBits(val)); } - + public void set(int id, boolean val) { - set(id, val ? 1 : 0); + if (val) { + set(id, 1); + } else { + // Because #ifdef usage is very common in shaders, unset the define + // instead of setting it to 0 for booleans. + unset(id); + } } public void set(int id, VarType type, Object value) { - if (value == null) { - set(id, 0); - return; - } - - switch (type) { - case Int: - set(id, (Integer) value); - break; - case Float: - set(id, (Float) value); - break; - case Boolean: - set(id, ((Boolean) value)); - break; - default: - set(id, 1); - break; + if (value != null) { + switch (type) { + case Int: + set(id, (Integer) value); + break; + case Float: + set(id, (Float) value); + break; + case Boolean: + set(id, ((Boolean) value)); + break; + default: + set(id, 1); + break; + } + } else { + unset(id); } } public void setAll(DefineList other) { - for (int i = 0; i < other.vals.length; i++) { - if (other.vals[i] != 0) { - vals[i] = other.vals[i]; + for (int i = 0; i < other.values.length; i++) { + if (other.isSet(i)) { + set(i, other.getInt(i)); } } } public void clear() { - hash = 0; - Arrays.fill(vals, 0); + isSet = 0; + Arrays.fill(values, 0); } public boolean getBoolean(int id) { - return vals[id] != 0; + return values[id] != 0; } - + public float getFloat(int id) { - return Float.intBitsToFloat(vals[id]); + return Float.intBitsToFloat(values[id]); } - + public int getInt(int id) { - return vals[id]; + return values[id]; } - + @Override public int hashCode() { - return (int)((hash >> 32) ^ hash); + return (int) ((isSet >> 32) ^ isSet); } @Override public boolean equals(Object other) { - DefineList o = (DefineList) other; - if (hash == o.hash) { - for (int i = 0; i < vals.length; i++) { - if (vals[i] != o.vals[i]) return false; - } - return true; - } - return false; + DefineList o = (DefineList) other; + if (isSet == o.isSet) { + for (int i = 0; i < values.length; i++) { + if (values[i] != o.values[i]) { + return false; + } + } + return true; + } + return false; } public DefineList deepClone() { - return new DefineList(this); + return new DefineList(this); } - + public void generateSource(StringBuilder sb, List defineNames, List defineTypes) { - for (int i = 0; i < vals.length; i++) { - if (vals[i] != 0) { - String defineName = defineNames.get(i); - - sb.append("#define "); - sb.append(defineName); - sb.append(" "); - - if (defineTypes != null && defineTypes.get(i) == VarType.Float) { - float val = Float.intBitsToFloat(vals[i]); - if (Float.isInfinite(val) || Float.isNaN(val)) { - throw new IllegalArgumentException( - "GLSL does not support NaN " - + "or Infinite float literals"); - } - sb.append(val); - } else { - sb.append(vals[i]); + for (int i = 0; i < values.length; i++) { + if (!isSet(i)) { + continue; + } + + sb.append("#define ").append(defineNames.get(i)).append(' '); + + if (defineTypes != null && defineTypes.get(i) == VarType.Float) { + float val = Float.intBitsToFloat(values[i]); + if (Float.isInfinite(val) || Float.isNaN(val)) { + throw new IllegalArgumentException( + "GLSL does not support NaN " + + "or Infinite float literals"); } - - sb.append("\n"); + sb.append(val); + } else { + sb.append(values[i]); } + + sb.append('\n'); } } - + public String generateSource(List defineNames, List defineTypes) { StringBuilder sb = new StringBuilder(); generateSource(sb, defineNames, defineTypes); return sb.toString(); } -} \ No newline at end of file +} diff --git a/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java b/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java index 35812b7c3..4c2c02b93 100644 --- a/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java +++ b/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java @@ -40,7 +40,7 @@ import org.junit.Test; import static org.junit.Assert.*; public class DefineListTest { - + private static final List DEFINE_NAMES = Arrays.asList("BOOL_VAR", "INT_VAR", "FLOAT_VAR"); private static final List DEFINE_TYPES = Arrays.asList(VarType.Boolean, VarType.Int, VarType.Float); private static final int NUM_DEFINES = DEFINE_NAMES.size(); @@ -53,49 +53,49 @@ public class DefineListTest { public void testHashCollision() { DefineList dl1 = new DefineList(64); DefineList dl2 = new DefineList(64); - + // Try to cause a hash collision // (since bit #32 is aliased to bit #1 in 32-bit ints) dl1.set(0, 123); dl1.set(32, 0); - + dl2.set(32, 0); dl2.set(0, 123); - + assert dl1.hashCode() == dl2.hashCode(); assert dl1.equals(dl2); } - + @Test public void testGetSet() { DefineList dl = new DefineList(NUM_DEFINES); - + assertFalse(dl.getBoolean(BOOL_VAR)); assertEquals(dl.getInt(INT_VAR), 0); assertEquals(dl.getFloat(FLOAT_VAR), 0f, 0f); - + dl.set(BOOL_VAR, true); dl.set(INT_VAR, -1); dl.set(FLOAT_VAR, Float.NaN); - + assertTrue(dl.getBoolean(BOOL_VAR)); assertEquals(dl.getInt(INT_VAR), -1); assertTrue(Float.isNaN(dl.getFloat(FLOAT_VAR))); } - + private String generateSource(DefineList dl) { StringBuilder sb = new StringBuilder(); dl.generateSource(sb, DEFINE_NAMES, DEFINE_TYPES); return sb.toString(); } - + @Test public void testSourceInitial() { DefineList dl = new DefineList(NUM_DEFINES); assert dl.hashCode() == 0; assert generateSource(dl).equals(""); } - + @Test public void testSourceBooleanDefine() { DefineList dl = new DefineList(NUM_DEFINES); @@ -103,35 +103,47 @@ public class DefineListTest { dl.set(BOOL_VAR, true); assert dl.hashCode() == 1; assert generateSource(dl).equals("#define BOOL_VAR 1\n"); - + dl.set(BOOL_VAR, false); assert dl.hashCode() == 0; assert generateSource(dl).equals(""); + + dl.set(BOOL_VAR, true); + assert dl.hashCode() == 1; + assert generateSource(dl).equals("#define BOOL_VAR 1\n"); + + dl.unset(BOOL_VAR); + assert dl.hashCode() == 0; + assert generateSource(dl).equals(""); } - + @Test public void testSourceIntDefine() { DefineList dl = new DefineList(NUM_DEFINES); int hashCodeWithInt = 1 << INT_VAR; - + dl.set(INT_VAR, 123); assert dl.hashCode() == hashCodeWithInt; assert generateSource(dl).equals("#define INT_VAR 123\n"); - + dl.set(INT_VAR, 0); - assert dl.hashCode() == 0; - assert generateSource(dl).equals(""); - + assert dl.hashCode() == hashCodeWithInt; + assert generateSource(dl).equals("#define INT_VAR 0\n"); + dl.set(INT_VAR, -99); assert dl.hashCode() == hashCodeWithInt; assert generateSource(dl).equals("#define INT_VAR -99\n"); - + dl.set(INT_VAR, Integer.MAX_VALUE); assert dl.hashCode() == hashCodeWithInt; assert generateSource(dl).equals("#define INT_VAR 2147483647\n"); + + dl.unset(INT_VAR); + assert dl.hashCode() == 0; + assert generateSource(dl).equals(""); } - + @Test public void testSourceFloatDefine() { DefineList dl = new DefineList(NUM_DEFINES); @@ -139,162 +151,193 @@ public class DefineListTest { dl.set(FLOAT_VAR, 1f); assert dl.hashCode() == (1 << FLOAT_VAR); assert generateSource(dl).equals("#define FLOAT_VAR 1.0\n"); - + dl.set(FLOAT_VAR, 0f); - assert dl.hashCode() == 0; - assert generateSource(dl).equals(""); - + assert dl.hashCode() == (1 << FLOAT_VAR); + assert generateSource(dl).equals("#define FLOAT_VAR 0.0\n"); + dl.set(FLOAT_VAR, -1f); assert generateSource(dl).equals("#define FLOAT_VAR -1.0\n"); - + dl.set(FLOAT_VAR, FastMath.FLT_EPSILON); assert generateSource(dl).equals("#define FLOAT_VAR 1.1920929E-7\n"); - + dl.set(FLOAT_VAR, FastMath.PI); assert generateSource(dl).equals("#define FLOAT_VAR 3.1415927\n"); - + try { dl.set(FLOAT_VAR, Float.NaN); generateSource(dl); assert false; - } catch (IllegalArgumentException ex) { } - + } catch (IllegalArgumentException ex) { + } + try { dl.set(FLOAT_VAR, Float.POSITIVE_INFINITY); generateSource(dl); assert false; - } catch (IllegalArgumentException ex) { } - + } catch (IllegalArgumentException ex) { + } + try { dl.set(FLOAT_VAR, Float.NEGATIVE_INFINITY); generateSource(dl); assert false; - } catch (IllegalArgumentException ex) { } + } catch (IllegalArgumentException ex) { + } } - + @Test public void testEqualsAndHashCode() { DefineList dl1 = new DefineList(NUM_DEFINES); DefineList dl2 = new DefineList(NUM_DEFINES); - - assertTrue(dl1.hashCode() == 0); + + assertEquals(0, dl1.hashCode()); + assertEquals(0, dl2.hashCode()); assertEquals(dl1, dl2); - + dl1.set(BOOL_VAR, true); - - assertTrue(dl1.hashCode() == 1); - assertNotSame(dl1, dl2); - + + assertEquals(1, dl1.hashCode()); + assertEquals(0, dl2.hashCode()); + assertNotEquals(dl1, dl2); + dl2.set(BOOL_VAR, true); - + + assertEquals(1, dl1.hashCode()); + assertEquals(1, dl2.hashCode()); assertEquals(dl1, dl2); - + dl1.set(INT_VAR, 2); - - assertTrue(dl1.hashCode() == (1|2)); - assertNotSame(dl1, dl2); - + + assertEquals(1 | 2, dl1.hashCode()); + assertEquals(1, dl2.hashCode()); + assertNotEquals(dl1, dl2); + dl2.set(INT_VAR, 2); - + + assertEquals(1 | 2, dl1.hashCode()); + assertEquals(1 | 2, dl2.hashCode()); assertEquals(dl1, dl2); - + dl1.set(BOOL_VAR, false); - - assertTrue(dl1.hashCode() == 2); - assertNotSame(dl1, dl2); + + assertEquals(2, dl1.hashCode()); + assertEquals(1 | 2, dl2.hashCode()); + assertNotEquals(dl1, dl2); + + dl2.unset(BOOL_VAR); + + assertEquals(2, dl1.hashCode()); + assertEquals(2, dl2.hashCode()); + assertEquals(dl1, dl2); // unset is the same as false + + dl1.unset(BOOL_VAR); + assertEquals(2, dl1.hashCode()); + assertEquals(2, dl2.hashCode()); + assertEquals(dl1, dl2); } - + @Test public void testDeepClone() { DefineList dl1 = new DefineList(NUM_DEFINES); DefineList dl2 = dl1.deepClone(); - - assertFalse(dl1 == dl2); - assertTrue(dl1.equals(dl2)); - assertTrue(dl1.hashCode() == dl2.hashCode()); - + + assertNotSame(dl1, dl2); + assertEquals(dl1, dl2); + assertEquals(dl1.hashCode(), dl2.hashCode()); + dl1.set(BOOL_VAR, true); dl2 = dl1.deepClone(); - - assertTrue(dl1.equals(dl2)); - assertTrue(dl1.hashCode() == dl2.hashCode()); - + + assertEquals(dl1, dl2); + assertEquals(dl1.hashCode(), dl2.hashCode()); + + dl1.set(BOOL_VAR, false); + dl2 = dl1.deepClone(); + + assertEquals(dl1, dl2); + assertEquals(dl1.hashCode(), dl2.hashCode()); + dl1.set(INT_VAR, 123); - - assertFalse(dl1.equals(dl2)); - assertFalse(dl1.hashCode() == dl2.hashCode()); - + + assertNotEquals(dl1, dl2); + assertNotEquals(dl1.hashCode(), dl2.hashCode()); + dl2 = dl1.deepClone(); - - assertTrue(dl1.equals(dl2)); - assertTrue(dl1.hashCode() == dl2.hashCode()); + + assertEquals(dl1, dl2); + assertEquals(dl1.hashCode(), dl2.hashCode()); } - + @Test public void testGenerateSource() { DefineList dl = new DefineList(NUM_DEFINES); - + assertEquals("", generateSource(dl)); - + dl.set(BOOL_VAR, true); - + assertEquals("#define BOOL_VAR 1\n", generateSource(dl)); - + dl.set(INT_VAR, 123); - - assertEquals("#define BOOL_VAR 1\n" + - "#define INT_VAR 123\n", generateSource(dl)); - + + assertEquals("#define BOOL_VAR 1\n" + + "#define INT_VAR 123\n", generateSource(dl)); + dl.set(BOOL_VAR, false); - + assertEquals("#define INT_VAR 123\n", generateSource(dl)); - + dl.set(BOOL_VAR, true); - + // should have predictable ordering based on defineId - assertEquals("#define BOOL_VAR 1\n" + - "#define INT_VAR 123\n", generateSource(dl)); + assertEquals("#define BOOL_VAR 1\n" + + "#define INT_VAR 123\n", generateSource(dl)); + + dl.unset(BOOL_VAR); + assertEquals("#define INT_VAR 123\n", generateSource(dl)); } - - private static String doLookup(HashMap map, boolean boolVal, int intVal, float floatVal) { + + private static String doLookup(HashMap map, Boolean boolVal, Integer intVal, Float floatVal) { DefineList dl = new DefineList(NUM_DEFINES); - dl.set(BOOL_VAR, boolVal); - dl.set(INT_VAR, intVal); - dl.set(FLOAT_VAR, floatVal); + dl.set(BOOL_VAR, VarType.Boolean, boolVal); + dl.set(INT_VAR, VarType.Int, intVal); + dl.set(FLOAT_VAR, VarType.Float, floatVal); return map.get(dl); } - + @Test public void testHashLookup() { - String STR_EMPTY = "This is an empty define list"; - String STR_INT = "This define list has an int value"; - String STR_BOOL = "This define list just has boolean value set"; - String STR_BOOL_INT = "This define list has both a boolean and int value"; + String STR_EMPTY = "This is an empty define list"; + String STR_INT = "This define list has an int value"; + String STR_BOOL = "This define list just has boolean value set"; + String STR_BOOL_INT = "This define list has both a boolean and int value"; String STR_BOOL_INT_FLOAT = "This define list has a boolean, int, and float value"; - - HashMap map = new HashMap(); - + + HashMap map = new HashMap<>(); + DefineList lookup = new DefineList(NUM_DEFINES); - + map.put(lookup.deepClone(), STR_EMPTY); - + lookup.set(BOOL_VAR, true); map.put(lookup.deepClone(), STR_BOOL); - + lookup.set(BOOL_VAR, false); lookup.set(INT_VAR, 123); map.put(lookup.deepClone(), STR_INT); - + lookup.set(BOOL_VAR, true); map.put(lookup.deepClone(), STR_BOOL_INT); - + lookup.set(FLOAT_VAR, FastMath.PI); map.put(lookup.deepClone(), STR_BOOL_INT_FLOAT); - - assertEquals(doLookup(map, false, 0, 0f), STR_EMPTY); - assertEquals(doLookup(map, false, 123, 0f), STR_INT); - assertEquals(doLookup(map, true, 0, 0f), STR_BOOL); - assertEquals(doLookup(map, true, 123, 0f), STR_BOOL_INT); - assertEquals(doLookup(map, true, 123, FastMath.PI), STR_BOOL_INT_FLOAT); + + assertEquals(STR_EMPTY, doLookup(map, null, null, null)); + assertEquals(STR_INT, doLookup(map, false, 123, null)); + assertEquals(STR_BOOL, doLookup(map, true, null, null)); + assertEquals(STR_BOOL_INT, doLookup(map, true, 123, null)); + assertEquals(STR_BOOL_INT_FLOAT, doLookup(map, true, 123, FastMath.PI)); } }