diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index e2b030e93..f8cac2b74 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -46,10 +46,7 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Geometry; -import com.jme3.shader.Shader; -import com.jme3.shader.Uniform; -import com.jme3.shader.UniformBindingManager; -import com.jme3.shader.VarType; +import com.jme3.shader.*; import com.jme3.texture.Image; import com.jme3.texture.Texture; import com.jme3.texture.image.ColorSpace; @@ -794,20 +791,29 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { } for (int i = 0; i < paramValues.size(); i++) { + MatParam param = paramValues.getValue(i); VarType type = param.getVarType(); - Uniform uniform = shader.getUniform(param.getPrefixedName()); - if (uniform.isSetByCurrentMaterial()) { - continue; - } + if(type == VarType.ShaderStorageBufferObject) { + + final StorageBlock storageBlock = shader.getStorageBlock(name); + storageBlock.setValue(param.getValue()); - if (type.isTextureType()) { - renderer.setTexture(unit, (Texture) param.getValue()); - uniform.setValue(VarType.Int, unit); - unit++; } else { - uniform.setValue(type, param.getValue()); + + Uniform uniform = shader.getUniform(param.getPrefixedName()); + if (uniform.isSetByCurrentMaterial()) { + continue; + } + + if (type.isTextureType()) { + renderer.setTexture(unit, (Texture) param.getValue()); + uniform.setValue(VarType.Int, unit); + unit++; + } else { + uniform.setValue(type, param.getValue()); + } } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index d8e73a36e..d381ffd07 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -394,7 +394,11 @@ public enum Caps { /** * GPU can provide and accept binary shaders. */ - BinaryShader; + BinaryShader, + /** + * Supporting working with ShaderStorageBufferObjects. + */ + ShaderStorageBufferObject; /** * Returns true if given the renderer capabilities, the texture diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java index b6d644ba4..ccf3c3a7b 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java @@ -51,6 +51,8 @@ public interface GL4 extends GL3 { * Accepted by the {@code target} parameters of BindBuffer, BufferData, BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData, and GetBufferPointerv. */ public static final int GL_SHADER_STORAGE_BUFFER = 0x90D2; + public static final int GL_SHADER_STORAGE_BLOCK = 0x92E6; + public static final int GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS = 0x90DD; /** *

Reference Page

@@ -60,4 +62,28 @@ public interface GL4 extends GL3 { * @param count the new value for the parameter given by {@code pname} */ public void glPatchParameter(int count); + + /** + * Returns the unsigned integer index assigned to a resource named name in the interface type programInterface of + * program object program. + * + * @param program the name of a program object whose resources to query. + * @param programInterface a token identifying the interface within program containing the resource named name. + * @param name the name of the resource to query the index of. + * @return the index of a named resource within a program. + */ + public int glGetProgramResourceIndex(int program, int programInterface, String name); + + /** + * Cchanges the active shader storage block with an assigned index of storageBlockIndex in program object program. + * storageBlockIndex must be an active shader storage block index in program. storageBlockBinding must be less + * than the value of {@code #GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS}. If successful, glShaderStorageBlockBinding specifies + * that program will use the data store of the buffer object bound to the binding point storageBlockBinding to + * read and write the values of the buffer variables in the shader storage block identified by storageBlockIndex. + * + * @param program the name of a program object whose resources to query. + * @param storageBlockIndex The index storage block within the program. + * @param storageBlockBinding The index storage block binding to associate with the specified storage block. + */ + public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 42cfb0b2d..d9b905e17 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -45,12 +45,9 @@ import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.shader.Attribute; -import com.jme3.shader.Shader; +import com.jme3.shader.*; import com.jme3.shader.Shader.ShaderSource; import com.jme3.shader.Shader.ShaderType; -import com.jme3.shader.ShaderStorageBufferObject; -import com.jme3.shader.Uniform; import com.jme3.texture.FrameBuffer; import com.jme3.texture.FrameBuffer.RenderBuffer; import com.jme3.texture.Image; @@ -481,6 +478,10 @@ public final class GLRenderer implements Renderer { } } + if (hasExtension("GL_ARB_shader_storage_buffer_object")) { + caps.add(Caps.ShaderStorageBufferObject); + } + // Print context information logger.log(Level.INFO, "OpenGL Renderer Information\n" + " * Vendor: {0}\n" + @@ -1184,20 +1185,35 @@ public final class GLRenderer implements Renderer { Integer i = (Integer) uniform.getValue(); gl.glUniform1i(loc, i.intValue()); break; - case ShaderStorageBufferObject: { + default: + throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); + } + } - final ShaderStorageBufferObject ssbo = (ShaderStorageBufferObject) uniform.getValue(); - if (ssbo.getUniqueId() == -1 || ssbo.isUpdateNeeded()) { - updateBufferData(ssbo); - } + /** + * Update the storage block for the shader. + * + * @param shader the shader. + * @param storageBlock the storage block. + */ + protected void updateShaderStorageBlock(final Shader shader, final StorageBlock storageBlock) { + int shaderId = shader.getId(); + assert storageBlock.getName() != null; + assert shader.getId() > 0; - break; - } - default: - throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); + bindProgram(shader); + + final ShaderStorageBufferObject storageData = (ShaderStorageBufferObject) storageBlock.getStorageData(); + if (storageData.getUniqueId() == -1 || storageData.isUpdateNeeded()) { + updateBufferData(storageData); } + + final int blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, storageBlock.getName()); + + gl4.glShaderStorageBlockBinding(shaderId, blockIndex, storageData.getBinding()); + gl4.glBindBufferBase(GL4.GL_SHADER_STORAGE_BUFFER, storageData.getBinding(), storageData.getId()); } protected void updateShaderUniforms(Shader shader) { @@ -1210,12 +1226,17 @@ public final class GLRenderer implements Renderer { } } - protected void updateShaderStorageBlocks(Shader shader) { - ListMap uniforms = shader.getUniformMap(); + /** + * Updates all shader's storage blocks. + * + * @param shader the shader. + */ + protected void updateShaderStorageBlocks(final Shader shader) { + final ListMap uniforms = shader.getStorageBlockMap(); for (int i = 0; i < uniforms.size(); i++) { - Uniform uniform = uniforms.getValue(i); - if (uniform.isUpdateNeeded()) { - updateUniform(shader, uniform); + final StorageBlock storageBlock = uniforms.getValue(i); + if (storageBlock.isUpdateNeeded()) { + updateShaderStorageBlock(shader, storageBlock); } } } @@ -1438,6 +1459,7 @@ public final class GLRenderer implements Renderer { assert shader.getId() > 0; updateShaderUniforms(shader); + updateShaderStorageBlocks(shader); bindProgram(shader); } } @@ -2529,6 +2551,10 @@ public final class GLRenderer implements Renderer { @Override public void updateBufferData(final ShaderStorageBufferObject ssbo) { + if (!caps.contains(Caps.ShaderStorageBufferObject)) { + throw new IllegalArgumentException("The current video hardware doesn't support SSBO."); + } + final ByteBuffer data = ssbo.getData(); if (data == null) { throw new IllegalArgumentException("Can't upload SSBO without data."); diff --git a/jme3-core/src/main/java/com/jme3/shader/Shader.java b/jme3-core/src/main/java/com/jme3/shader/Shader.java index 79c317093..ce20b0edf 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Shader.java +++ b/jme3-core/src/main/java/com/jme3/shader/Shader.java @@ -413,6 +413,11 @@ public final class Shader extends NativeObject { uniform.reset(); // fixes issue with re-initialization } } + if (storageBlocks != null) { + for (StorageBlock storageBlock : storageBlocks.values()) { + storageBlock.reset(); + } + } if (attribs != null) { for (Entry entry : attribs) { entry.getValue().location = ShaderVariable.LOC_UNKNOWN; diff --git a/jme3-core/src/main/java/com/jme3/shader/StorageBlock.java b/jme3-core/src/main/java/com/jme3/shader/StorageBlock.java index 6a905cee0..fd386d498 100644 --- a/jme3-core/src/main/java/com/jme3/shader/StorageBlock.java +++ b/jme3-core/src/main/java/com/jme3/shader/StorageBlock.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 jMonkeyEngine + * Copyright (c) 2009-2018 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,425 +31,63 @@ */ 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; - +/** + * Implementation of shader's storage block. + * + * @author JavaSaBr + */ 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 + * Current used buffer object. */ - protected VarType varType; + protected Object storageData; /** - * Binding to a renderer value, or null if user-defined uniform + * Set the new storage data. + * + * @param storageData the new storage data */ - 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; - } + public void setStorageData(final Object storageData) { - 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 (storageData == null) { + throw new IllegalArgumentException("for storage block " + name + ": storageData cannot be null"); } - if (varType != null && varType != type) { - throw new IllegalArgumentException("Expected a " + varType.name() + " value!"); - } + this.storageData = storageData; - 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; - } - + /** + * Return true if need to update this storage block. + * + * @return true if need to update this storage block. + */ public boolean isUpdateNeeded(){ return updateNeeded; } + /** + * Clear the flag {@link #isUpdateNeeded()}. + */ public void clearUpdateNeeded(){ updateNeeded = false; } + /** + * Reset this storage block. + */ public void reset(){ - setByCurrentMaterial = false; - location = -2; updateNeeded = true; } - public void deleteNativeBuffers() { - if (value instanceof Buffer) { - BufferUtils.destroyDirectBuffer((Buffer)value); - value = null; // ???? - } + /** + * Get the current storage data. + * + * @return the current storage data. + */ + public Object getStorageData() { + return storageData; } }