getBoundUniforms() {
return boundUniforms;
}
@@ -320,6 +366,7 @@ public final class Shader extends NativeObject {
return getClass().getSimpleName() +
"[numSources=" + shaderSourceList.size() +
", numUniforms=" + uniforms.size() +
+ ", numStorageBlocks=" + storageBlocks.size() +
", shaderSources=" + getSources() + "]";
}
@@ -343,7 +390,7 @@ public final class Shader extends NativeObject {
* Resets all uniforms that do not have the "set-by-current-material" flag
* to their default value (usually all zeroes or false).
* When a uniform is modified, that flag is set, to remove the flag,
- * use {@link #clearUniformsSetByCurrent() }.
+ * use {@link #clearUniformsSetByCurrentFlag() }.
*/
public void resetUniformsNotSetByCurrent() {
int size = uniforms.size();
diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderStorageBufferObject.java b/jme3-core/src/main/java/com/jme3/shader/ShaderStorageBufferObject.java
new file mode 100644
index 000000000..5ed5e5154
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/shader/ShaderStorageBufferObject.java
@@ -0,0 +1,144 @@
+package com.jme3.shader;
+
+import com.jme3.renderer.Renderer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.NativeObject;
+
+import java.nio.ByteBuffer;
+
+/**
+ * The implementation of SSBO.
+ *
+ * @author JavaSaBr
+ */
+public class ShaderStorageBufferObject extends NativeObject {
+
+ /**
+ * The buffer's data.
+ */
+ private ByteBuffer data;
+
+ /**
+ * The binding number.
+ */
+ private int binding;
+
+ public ShaderStorageBufferObject() {
+ this.handleRef = new Object();
+ this.binding = 1;
+ }
+
+ public ShaderStorageBufferObject(final int id) {
+ super(id);
+ }
+
+ /**
+ * Set the binding number.
+ *
+ * @param binding the binding number.
+ */
+ public void setBinding(final int binding) {
+ this.binding = binding;
+ }
+
+ /**
+ * Get the binding number.
+ *
+ * @return the binding number.
+ */
+ public int getBinding() {
+ return binding;
+ }
+
+ /**
+ * Called to initialize the data in the {@link ShaderStorageBufferObject}. Must only
+ * be called once.
+ *
+ * @param data the native byte buffer.
+ */
+ public void setupData(final ByteBuffer data) {
+
+ if (id != -1) {
+ throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again.");
+ } else if (data.isReadOnly()) {
+ throw new IllegalArgumentException("VertexBuffer data cannot be read-only.");
+ }
+
+ this.data = data;
+
+ setUpdateNeeded();
+ }
+
+ /**
+ * Called to update the data in the buffer with new data. Can only
+ * be called after {@link #setupData(java.nio.ByteBuffer) }
+ * has been called. Note that it is fine to call this method on the
+ * data already set, e.g. ssbo.updateData(ssbo.getData()), this will just
+ * set the proper update flag indicating the data should be sent to the GPU
+ * again.
+ *
+ * It is allowed to specify a buffer with different capacity than the
+ * originally set buffer.
+ *
+ * @param data the native data buffer to set.
+ */
+ public void updateData(final ByteBuffer data) {
+
+ if (data == null) {
+ throw new IllegalArgumentException("SSBO can't be without data buffer.");
+ }
+
+ // Check if the data buffer is read-only which is a sign
+ // of a bug on the part of the caller
+ if (data.isReadOnly()) {
+ throw new IllegalArgumentException("SSBO's data cannot be read-only.");
+ }
+
+ this.data = data;
+
+ setUpdateNeeded();
+ }
+
+ /**
+ * Get the buffer's data.
+ *
+ * @return the buffer's data.
+ */
+ public ByteBuffer getData() {
+ return data;
+ }
+
+ @Override
+ public void resetObject() {
+ this.id = -1;
+ setUpdateNeeded();
+ }
+
+ @Override
+ public void deleteObject(final Object rendererObject) {
+
+ if (rendererObject instanceof Renderer) {
+ throw new IllegalArgumentException("This ssbo can't be deleted from " + rendererObject);
+ }
+
+ ((Renderer) rendererObject).deleteBuffer(this);
+ }
+
+ @Override
+ protected void deleteNativeBuffers() {
+ super.deleteNativeBuffers();
+ if (data != null) {
+ BufferUtils.destroyDirectBuffer(data);
+ }
+ }
+
+ @Override
+ public NativeObject createDestructableClone() {
+ return new ShaderStorageBufferObject(id);
+ }
+
+ @Override
+ public long getUniqueId() {
+ return ((long) OBJTYPE_SSBO << 32) | ((long) id);
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/shader/StorageBlock.java b/jme3-core/src/main/java/com/jme3/shader/StorageBlock.java
new file mode 100644
index 000000000..6a905cee0
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/shader/StorageBlock.java
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2009-2017 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shader;
+
+import com.jme3.math.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+
+import java.nio.Buffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+public class StorageBlock extends ShaderVariable {
+
+ private static final Integer ZERO_INT = 0;
+ private static final Float ZERO_FLT = Float.valueOf(0);
+ private static final FloatBuffer ZERO_BUF = BufferUtils.createFloatBuffer(4*4);
+
+ /**
+ * Currently set value of the uniform.
+ */
+ protected Object value = null;
+
+ /**
+ * For arrays or matrices, efficient format
+ * that can be sent to GL faster.
+ */
+ protected FloatBuffer multiData = null;
+
+ /**
+ * Type of uniform
+ */
+ protected VarType varType;
+
+ /**
+ * Binding to a renderer value, or null if user-defined uniform
+ */
+ protected UniformBinding binding;
+
+ /**
+ * Used to track which uniforms to clear to avoid
+ * values leaking from other materials that use that shader.
+ */
+ protected boolean setByCurrentMaterial = false;
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 31 * hash + (this.value != null ? this.value.hashCode() : 0);
+ hash = 31 * hash + (this.varType != null ? this.varType.hashCode() : 0);
+ hash = 31 * hash + (this.binding != null ? this.binding.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ final StorageBlock other = (StorageBlock) obj;
+ if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) {
+ return false;
+ }
+ return this.binding == other.binding && this.varType == other.varType;
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder sb = new StringBuilder();
+ sb.append("Uniform[name=");
+ sb.append(name);
+ if (varType != null){
+ sb.append(", type=");
+ sb.append(varType);
+ sb.append(", value=");
+ sb.append(value);
+ }else{
+ sb.append(", value=");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ public void setBinding(UniformBinding binding){
+ this.binding = binding;
+ }
+
+ public UniformBinding getBinding(){
+ return binding;
+ }
+
+ public VarType getVarType() {
+ return varType;
+ }
+
+ public Object getValue(){
+ return value;
+ }
+
+ public FloatBuffer getMultiData() {
+ return multiData;
+ }
+
+ public boolean isSetByCurrentMaterial() {
+ return setByCurrentMaterial;
+ }
+
+ public void clearSetByCurrentMaterial(){
+ setByCurrentMaterial = false;
+ }
+
+ public void clearValue(){
+ updateNeeded = true;
+
+ if (multiData != null){
+ multiData.clear();
+
+ while (multiData.remaining() > 0){
+ ZERO_BUF.clear();
+ ZERO_BUF.limit( Math.min(multiData.remaining(), 16) );
+ multiData.put(ZERO_BUF);
+ }
+
+ multiData.clear();
+
+ return;
+ }
+
+ if (varType == null) {
+ return;
+ }
+
+ switch (varType){
+ case Int:
+ this.value = ZERO_INT;
+ break;
+ case Boolean:
+ this.value = Boolean.FALSE;
+ break;
+ case Float:
+ this.value = ZERO_FLT;
+ break;
+ case Vector2:
+ if (this.value != null) {
+ ((Vector2f) this.value).set(Vector2f.ZERO);
+ }
+ break;
+ case Vector3:
+ if (this.value != null) {
+ ((Vector3f) this.value).set(Vector3f.ZERO);
+ }
+ break;
+ case Vector4:
+ if (this.value != null) {
+ if (this.value instanceof ColorRGBA) {
+ ((ColorRGBA) this.value).set(ColorRGBA.BlackNoAlpha);
+ } else if (this.value instanceof Vector4f) {
+ ((Vector4f) this.value).set(Vector4f.ZERO);
+ } else {
+ ((Quaternion) this.value).set(Quaternion.ZERO);
+ }
+ }
+ break;
+ default:
+ // won't happen because those are either textures
+ // or multidata types
+ }
+ }
+
+ public void setValue(VarType type, Object value){
+ if (location == LOC_NOT_DEFINED) {
+ return;
+ }
+
+ if (varType != null && varType != type) {
+ throw new IllegalArgumentException("Expected a " + varType.name() + " value!");
+ }
+
+ if (value == null) {
+ throw new IllegalArgumentException("for uniform " + name + ": value cannot be null");
+ }
+
+ setByCurrentMaterial = true;
+
+ switch (type){
+ case Matrix3:
+ if (value.equals(this.value)) {
+ return;
+ }
+ Matrix3f m3 = (Matrix3f) value;
+ if (multiData == null) {
+ multiData = BufferUtils.createFloatBuffer(9);
+ }
+ m3.fillFloatBuffer(multiData, true);
+ multiData.clear();
+ if (this.value == null) {
+ this.value = new Matrix3f(m3);
+ } else {
+ ((Matrix3f)this.value).set(m3);
+ }
+ break;
+ case Matrix4:
+ if (value.equals(this.value)) {
+ return;
+ }
+ Matrix4f m4 = (Matrix4f) value;
+ if (multiData == null) {
+ multiData = BufferUtils.createFloatBuffer(16);
+ }
+ m4.fillFloatBuffer(multiData, true);
+ multiData.clear();
+ if (this.value == null) {
+ this.value = new Matrix4f(m4);
+ } else {
+ ((Matrix4f)this.value).copy(m4);
+ }
+ break;
+ case IntArray:
+ int[] ia = (int[]) value;
+ if (this.value == null) {
+ this.value = BufferUtils.createIntBuffer(ia);
+ } else {
+ this.value = BufferUtils.ensureLargeEnough((IntBuffer)this.value, ia.length);
+ }
+ ((IntBuffer)this.value).clear();
+ break;
+ case FloatArray:
+ float[] fa = (float[]) value;
+ if (multiData == null) {
+ multiData = BufferUtils.createFloatBuffer(fa);
+ } else {
+ multiData = BufferUtils.ensureLargeEnough(multiData, fa.length);
+ }
+ multiData.put(fa);
+ multiData.clear();
+ break;
+ case Vector2Array:
+ Vector2f[] v2a = (Vector2f[]) value;
+ if (multiData == null) {
+ multiData = BufferUtils.createFloatBuffer(v2a);
+ } else {
+ multiData = BufferUtils.ensureLargeEnough(multiData, v2a.length * 2);
+ }
+ for (int i = 0; i < v2a.length; i++) {
+ BufferUtils.setInBuffer(v2a[i], multiData, i);
+ }
+ multiData.clear();
+ break;
+ case Vector3Array:
+ Vector3f[] v3a = (Vector3f[]) value;
+ if (multiData == null) {
+ multiData = BufferUtils.createFloatBuffer(v3a);
+ } else {
+ multiData = BufferUtils.ensureLargeEnough(multiData, v3a.length * 3);
+ }
+ for (int i = 0; i < v3a.length; i++) {
+ BufferUtils.setInBuffer(v3a[i], multiData, i);
+ }
+ multiData.clear();
+ break;
+ case Vector4Array:
+ Vector4f[] v4a = (Vector4f[]) value;
+ if (multiData == null) {
+ multiData = BufferUtils.createFloatBuffer(v4a);
+ } else {
+ multiData = BufferUtils.ensureLargeEnough(multiData, v4a.length * 4);
+ }
+ for (int i = 0; i < v4a.length; i++) {
+ BufferUtils.setInBuffer(v4a[i], multiData, i);
+ }
+ multiData.clear();
+ break;
+ case Matrix3Array:
+ Matrix3f[] m3a = (Matrix3f[]) value;
+ if (multiData == null) {
+ multiData = BufferUtils.createFloatBuffer(m3a.length * 9);
+ } else {
+ multiData = BufferUtils.ensureLargeEnough(multiData, m3a.length * 9);
+ }
+ for (int i = 0; i < m3a.length; i++) {
+ m3a[i].fillFloatBuffer(multiData, true);
+ }
+ multiData.clear();
+ break;
+ case Matrix4Array:
+ Matrix4f[] m4a = (Matrix4f[]) value;
+ if (multiData == null) {
+ multiData = BufferUtils.createFloatBuffer(m4a.length * 16);
+ } else {
+ multiData = BufferUtils.ensureLargeEnough(multiData, m4a.length * 16);
+ }
+ for (int i = 0; i < m4a.length; i++) {
+ m4a[i].fillFloatBuffer(multiData, true);
+ }
+ multiData.clear();
+ break;
+ case Vector2:
+ if (value.equals(this.value)) {
+ return;
+ }
+ if (this.value == null) {
+ this.value = new Vector2f((Vector2f) value);
+ } else {
+ ((Vector2f) this.value).set((Vector2f) value);
+ }
+ break;
+ case Vector3:
+ if (value.equals(this.value)) {
+ return;
+ }
+ if (this.value == null) {
+ this.value = new Vector3f((Vector3f) value);
+ } else {
+ ((Vector3f) this.value).set((Vector3f) value);
+ }
+ break;
+ case Vector4:
+ if (value.equals(this.value)) {
+ return;
+ }
+
+ TempVars vars = TempVars.get();
+ Vector4f vec4 = vars.vect4f1;
+ //handle the null case
+ if (this.value == null) {
+ try {
+ this.value = value.getClass().newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new IllegalArgumentException("Cannot instanciate param of class " + value.getClass().getCanonicalName());
+ }
+ }
+ //feed the pivot vec 4 with the correct value
+ if (value instanceof ColorRGBA) {
+ ColorRGBA c = (ColorRGBA) value;
+ vec4.set(c.r, c.g, c.b, c.a);
+ } else if (value instanceof Vector4f) {
+ vec4.set((Vector4f) value);
+ } else {
+ Quaternion q = (Quaternion) value;
+ vec4.set(q.getX(), q.getY(), q.getZ(), q.getW());
+ }
+
+ //feed this.value with the collected values.
+ if (this.value instanceof ColorRGBA) {
+ ((ColorRGBA) this.value).set(vec4.x, vec4.y, vec4.z, vec4.w);
+ } else if (value instanceof Vector4f) {
+ ((Vector4f) this.value).set(vec4);
+ } else {
+ ((Quaternion) this.value).set(vec4.x, vec4.y, vec4.z, vec4.w);
+ }
+ vars.release();
+ break;
+ // Only use check if equals optimization for primitive values
+ case Int:
+ case Float:
+ case Boolean:
+ if (value.equals(this.value)) {
+ return;
+ }
+ this.value = value;
+ break;
+ default:
+ this.value = value;
+ break;
+ }
+
+// if (multiData != null) {
+// this.value = multiData;
+// }
+
+ varType = type;
+ updateNeeded = true;
+ }
+
+ public void setVector4Length(int length){
+ if (location == -1) {
+ return;
+ }
+
+ multiData = BufferUtils.ensureLargeEnough(multiData, length * 4);
+ value = multiData;
+ varType = VarType.Vector4Array;
+ updateNeeded = true;
+ setByCurrentMaterial = true;
+ }
+
+ public void setVector4InArray(float x, float y, float z, float w, int index){
+ if (location == -1) {
+ return;
+ }
+
+ if (varType != null && varType != VarType.Vector4Array) {
+ throw new IllegalArgumentException("Expected a " + varType.name() + " value!");
+ }
+
+ multiData.position(index * 4);
+ multiData.put(x).put(y).put(z).put(w);
+ multiData.rewind();
+ updateNeeded = true;
+ setByCurrentMaterial = true;
+ }
+
+ public boolean isUpdateNeeded(){
+ return updateNeeded;
+ }
+
+ public void clearUpdateNeeded(){
+ updateNeeded = false;
+ }
+
+ public void reset(){
+ setByCurrentMaterial = false;
+ location = -2;
+ updateNeeded = true;
+ }
+
+ public void deleteNativeBuffers() {
+ if (value instanceof Buffer) {
+ BufferUtils.destroyDirectBuffer((Buffer)value);
+ value = null; // ????
+ }
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/shader/VarType.java b/jme3-core/src/main/java/com/jme3/shader/VarType.java
index 6e33eb5b3..25ea22c9a 100644
--- a/jme3-core/src/main/java/com/jme3/shader/VarType.java
+++ b/jme3-core/src/main/java/com/jme3/shader/VarType.java
@@ -58,8 +58,7 @@ public enum VarType {
TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow"),
TextureCubeMap(false,true,"samplerCube"),
Int("int"),
- Struct(false, false, "dynamic"),
- StructArray(false, false, "dynamic");
+ ShaderStorageBufferObject(false, false, "dynamic");
private boolean usesMultiData = false;
private boolean textureType = false;
diff --git a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java
index 1eb2b1533..6b39e2879 100644
--- a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java
+++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java
@@ -46,6 +46,7 @@ import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.shader.Shader;
import com.jme3.shader.Shader.ShaderSource;
+import com.jme3.shader.ShaderStorageBufferObject;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
@@ -148,9 +149,17 @@ public class NullRenderer implements Renderer {
public void updateBufferData(VertexBuffer vb) {
}
+ @Override
+ public void updateBufferData(ShaderStorageBufferObject ssbo) {
+ }
+
public void deleteBuffer(VertexBuffer vb) {
}
+ @Override
+ public void deleteBuffer(ShaderStorageBufferObject ssbo) {
+ }
+
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
}
diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObject.java b/jme3-core/src/main/java/com/jme3/util/NativeObject.java
index 508e6623d..d660c13a3 100644
--- a/jme3-core/src/main/java/com/jme3/util/NativeObject.java
+++ b/jme3-core/src/main/java/com/jme3/util/NativeObject.java
@@ -52,7 +52,8 @@ public abstract class NativeObject implements Cloneable {
OBJTYPE_SHADERSOURCE = 5,
OBJTYPE_AUDIOBUFFER = 6,
OBJTYPE_AUDIOSTREAM = 7,
- OBJTYPE_FILTER = 8;
+ OBJTYPE_FILTER = 8,
+ OBJTYPE_SSBO = 9;
/**
* The object manager to which this NativeObject is registered to.