From 510b7270c2d0dc57aab2e6305e735c2cec9e034f Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Thu, 2 Apr 2020 14:40:41 +0200 Subject: [PATCH] Improved UBO/SSBO implementation --- .../main/java/com/jme3/material/Material.java | 122 ++- .../java/com/jme3/material/Technique.java | 5 +- .../logic/DefaultTechniqueDefLogic.java | 3 +- .../logic/MultiPassLightingLogic.java | 3 +- .../SinglePassAndImageBasedLightingLogic.java | 7 +- .../logic/SinglePassLightingLogic.java | 3 +- .../logic/StaticPassLightingLogic.java | 3 +- .../material/logic/TechniqueDefLogic.java | 3 +- .../src/main/java/com/jme3/math/FastMath.java | 7 + .../java/com/jme3/renderer/RenderContext.java | 9 + .../main/java/com/jme3/renderer/Renderer.java | 14 +- .../java/com/jme3/renderer/opengl/GL.java | 4 + .../com/jme3/renderer/opengl/GLRenderer.java | 185 +++- .../java/com/jme3/shader/BufferObject.java | 864 +++--------------- .../com/jme3/shader/BufferObjectField.java | 77 -- .../com/jme3/shader/ShaderBufferBlock.java | 33 +- .../main/java/com/jme3/shader/Uniform.java | 2 + .../main/java/com/jme3/shader/VarType.java | 3 +- .../java/com/jme3/system/NullRenderer.java | 27 +- .../java/com/jme3/util/SafeArrayList.java | 18 + .../java/com/jme3/util/struct/Struct.java | 11 + .../com/jme3/util/struct/StructField.java | 67 ++ .../com/jme3/util/struct/StructUtils.java | 106 +++ .../jme3/util/struct/StructuredBuffer.java | 276 ++++++ .../util/struct/StructuredBufferLayout.java | 234 +++++ .../struct/StructuredBufferSTD140Layout.java | 66 ++ .../struct/serializers/Std140Serializer.java | 498 ++++++++++ 27 files changed, 1696 insertions(+), 954 deletions(-) delete mode 100644 jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java create mode 100644 jme3-core/src/main/java/com/jme3/util/struct/Struct.java create mode 100644 jme3-core/src/main/java/com/jme3/util/struct/StructField.java create mode 100644 jme3-core/src/main/java/com/jme3/util/struct/StructUtils.java create mode 100644 jme3-core/src/main/java/com/jme3/util/struct/StructuredBuffer.java create mode 100644 jme3-core/src/main/java/com/jme3/util/struct/StructuredBufferLayout.java create mode 100644 jme3-core/src/main/java/com/jme3/util/struct/StructuredBufferSTD140Layout.java create mode 100644 jme3-core/src/main/java/com/jme3/util/struct/serializers/Std140Serializer.java 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 3f27bb9a14..9f91f487bc 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -501,6 +501,18 @@ public void setParam(String name, VarType type, Object value) { } } + /** + * Pass a parameter to the material shader. The VarType will be obtained from the matdef. + * + * @param name the name of the parameter defined in the material definition (j3md) + * @param value the value of the parameter + */ + public void setParam(String name, Object value) { + MatParam p=getMaterialDef().getMaterialParam(name); + setParam(name,p.getVarType(),value); + } + + /** * Clear a parameter from this material. The parameter must exist * @param name the name of the parameter to clear @@ -678,8 +690,7 @@ public void setColor(String name, ColorRGBA value) { * @param value the buffer object. */ public void setUniformBufferObject(final String name, final BufferObject value) { - value.setBufferType(BufferObject.BufferType.UniformBufferObject); - setParam(name, VarType.BufferObject, value); + setParam(name, VarType.UniformBufferObject, value); } /** @@ -689,8 +700,7 @@ public void setUniformBufferObject(final String name, final BufferObject value) * @param value the buffer object. */ public void setShaderStorageBufferObject(final String name, final BufferObject value) { - value.setBufferType(BufferObject.BufferType.ShaderStorageBufferObject); - setParam(name, VarType.BufferObject, value); + setParam(name, VarType.ShaderStorageBufferObject, value); } /** @@ -788,7 +798,7 @@ public void selectTechnique(String name, final RenderManager renderManager) { sortingId = -1; } - private int applyOverrides(Renderer renderer, Shader shader, SafeArrayList overrides, int unit) { + private void applyOverrides(Renderer renderer, Shader shader, SafeArrayList overrides, BindUnits bindUnits) { for (MatParamOverride override : overrides.getArray()) { VarType type = override.getVarType(); @@ -801,29 +811,70 @@ private int applyOverrides(Renderer renderer, Shader shader, SafeArrayList worldOverrides, SafeArrayList forcedOverrides) { + - int unit = 0; + bindUnits.textureUnit=0; + bindUnits.bufferUnit=0; + + if (worldOverrides != null) { - unit = applyOverrides(renderer, shader, worldOverrides, unit); + applyOverrides(renderer, shader, worldOverrides, bindUnits); } if (forcedOverrides != null) { - unit = applyOverrides(renderer, shader, forcedOverrides, unit); + applyOverrides(renderer, shader, forcedOverrides, bindUnits); } for (int i = 0; i < paramValues.size(); i++) { @@ -831,41 +882,14 @@ private int updateShaderMaterialParameters(Renderer renderer, Shader shader, MatParam param = paramValues.getValue(i); VarType type = param.getVarType(); - if (isBO(type)) { - - final ShaderBufferBlock bufferBlock = shader.getBufferBlock(param.getPrefixedName()); - bufferBlock.setBufferObject((BufferObject) param.getValue()); - - } else { - - 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()); - } - } + updateShaderMaterialParameter(renderer,type,shader,param,bindUnits,false); } //TODO HACKY HACK remove this when texture unit is handled by the uniform. - return unit; + return bindUnits; } - /** - * Returns true if the type is Buffer Object's type. - * - * @param type the material parameter type. - * @return true if the type is Buffer Object's type. - */ - private boolean isBO(final VarType type) { - return type == VarType.BufferObject; - } + private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) { if (renderManager.getForcedRenderState() != null) { @@ -1019,13 +1043,13 @@ public void render(Geometry geometry, LightList lights, RenderManager renderMana renderManager.updateUniformBindings(shader); // Set material parameters - int unit = updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams()); + BindUnits units= updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams()); // Clear any uniforms not changed by material. resetUniformsNotSetByCurrent(shader); // Delegate rendering to the technique - technique.render(renderManager, shader, geometry, lights, unit); + technique.render(renderManager, shader, geometry, lights, units); } /** diff --git a/jme3-core/src/main/java/com/jme3/material/Technique.java b/jme3-core/src/main/java/com/jme3/material/Technique.java index a2fb1a0371..e534d36213 100644 --- a/jme3-core/src/main/java/com/jme3/material/Technique.java +++ b/jme3-core/src/main/java/com/jme3/material/Technique.java @@ -33,6 +33,7 @@ import com.jme3.asset.AssetManager; import com.jme3.light.LightList; +import com.jme3.material.Material.BindUnits; import com.jme3.material.TechniqueDef.LightMode; import com.jme3.material.logic.TechniqueDefLogic; import com.jme3.renderer.Caps; @@ -161,9 +162,9 @@ Shader makeCurrent(RenderManager renderManager, SafeArrayList * @param geometry The geometry to render * @param lights Lights which influence the geometry. */ - void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { TechniqueDefLogic logic = def.getLogic(); - logic.render(renderManager, shader, geometry, lights, lastTexUnit); + logic.render(renderManager, shader, geometry, lights, lastBindUnits); } /** diff --git a/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java index 86ce66391f..b0b4c580ae 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java @@ -34,6 +34,7 @@ import com.jme3.asset.AssetManager; import com.jme3.light.*; import com.jme3.material.TechniqueDef; +import com.jme3.material.Material.BindUnits; import com.jme3.math.ColorRGBA; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; @@ -89,7 +90,7 @@ protected static ColorRGBA getAmbientColor(LightList lightList, boolean removeLi @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { Renderer renderer = renderManager.getRenderer(); renderer.setShader(shader); renderMeshFromGeometry(renderer, geometry); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java index 8d4b639567..769019e82c 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java @@ -38,6 +38,7 @@ import com.jme3.light.SpotLight; import com.jme3.material.RenderState; import com.jme3.material.TechniqueDef; +import com.jme3.material.Material.BindUnits; import com.jme3.math.ColorRGBA; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; @@ -67,7 +68,7 @@ public MultiPassLightingLogic(TechniqueDef techniqueDef) { } @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { Renderer r = renderManager.getRenderer(); Uniform lightDir = shader.getUniform("g_LightDirection"); Uniform lightColor = shader.getUniform("g_LightColor"); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java index 513c35d6d9..f1a6c23c75 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java @@ -34,6 +34,7 @@ import com.jme3.asset.AssetManager; import com.jme3.light.*; import com.jme3.material.*; +import com.jme3.material.Material.BindUnits; import com.jme3.material.RenderState.BlendMode; import com.jme3.math.*; import com.jme3.renderer.*; @@ -245,17 +246,17 @@ private int setProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeDa } @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { int nbRenderedLights = 0; Renderer renderer = renderManager.getRenderer(); int batchSize = renderManager.getSinglePassLightBatchSize(); if (lights.size() == 0) { - updateLightListUniforms(shader, geometry, lights,batchSize, renderManager, 0, lastTexUnit); + updateLightListUniforms(shader, geometry, lights,batchSize, renderManager, 0, lastBindUnits.textureUnit); renderer.setShader(shader); renderMeshFromGeometry(renderer, geometry); } else { while (nbRenderedLights < lights.size()) { - nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastTexUnit); + nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastBindUnits.textureUnit); renderer.setShader(shader); renderMeshFromGeometry(renderer, geometry); } diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java index 67261742ec..60fa539cc5 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java @@ -40,6 +40,7 @@ import com.jme3.material.RenderState; import com.jme3.material.RenderState.BlendMode; import com.jme3.material.TechniqueDef; +import com.jme3.material.Material.BindUnits; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; import com.jme3.math.Vector4f; @@ -198,7 +199,7 @@ protected int updateLightListUniforms(Shader shader, Geometry g, LightList light } @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { int nbRenderedLights = 0; Renderer renderer = renderManager.getRenderer(); int batchSize = renderManager.getSinglePassLightBatchSize(); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java index 48995ba4f5..121439da2f 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java @@ -38,6 +38,7 @@ import com.jme3.light.PointLight; import com.jme3.light.SpotLight; import com.jme3.material.TechniqueDef; +import com.jme3.material.Material.BindUnits; import com.jme3.math.ColorRGBA; import com.jme3.math.Matrix4f; import com.jme3.math.Vector3f; @@ -171,7 +172,7 @@ private void updateLightListUniforms(Matrix4f viewMatrix, Shader shader, LightLi } @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { Renderer renderer = renderManager.getRenderer(); Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix(); updateLightListUniforms(viewMatrix, shader, lights); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java index e0921e869d..61faf4f921 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java @@ -33,6 +33,7 @@ import com.jme3.asset.AssetManager; import com.jme3.light.LightList; +import com.jme3.material.Material.BindUnits; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; import com.jme3.scene.Geometry; @@ -91,5 +92,5 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager * @param geometry The geometry to render * @param lights Lights which influence the geometry. */ - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit); + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits); } diff --git a/jme3-core/src/main/java/com/jme3/math/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java index 6358a142a3..acbfa06e14 100644 --- a/jme3-core/src/main/java/com/jme3/math/FastMath.java +++ b/jme3-core/src/main/java/com/jme3/math/FastMath.java @@ -1005,4 +1005,11 @@ public static float unInterpolateLinear(float value, float min, float max) { return (value - min) / (max - min); } + /** + * Round n to a multiple of p + */ + public static int toMultipleOf(int n, int p) { + return ((n - 1) | (p - 1)) + 1; + } + } diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java index c931531bc2..cb2cfbb7d8 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java @@ -31,10 +31,13 @@ */ package com.jme3.renderer; +import java.lang.ref.WeakReference; + import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; +import com.jme3.shader.BufferObject; import com.jme3.shader.Shader; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; @@ -221,6 +224,12 @@ public class RenderContext { */ public final Image[] boundTextures = new Image[16]; + + + public final WeakReference[] boundBO = new WeakReference[8]; + + + /** * IDList for texture units * diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java index ee11088789..9aa321a381 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -273,8 +273,15 @@ public interface Renderer { * * @param bo the buffer object to upload. */ - public void updateBufferData(BufferObject bo); - + public void updateShaderStorageBufferObjectData( BufferObject bo) ; + + /** + * Uploads data of the buffer object on the GPU. + * + * @param bo the buffer object to upload. + */ + public void updateUniformBufferObjectData( BufferObject bo) ; + /** * Deletes a vertex buffer from the GPU. * @param vb The vertex buffer to delete @@ -457,4 +464,7 @@ public interface Renderer { */ public int getDefaultAnisotropicFilter(); + public void setShaderStorageBufferObject(int bindingPoint, BufferObject bufferObject) ; + public void setUniformBufferObject(int bindingPoint, BufferObject bufferObject) ; + } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java index 9f7c5ac309..80d71a6df0 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java @@ -67,6 +67,7 @@ public interface GL { public static final int GL_DST_COLOR = 0x306; public static final int GL_DYNAMIC_DRAW = 0x88E8; public static final int GL_DYNAMIC_COPY = 0x88EA; + public static final int GL_DYNAMIC_READ = 0x88E9; public static final int GL_ELEMENT_ARRAY_BUFFER = 0x8893; public static final int GL_EQUAL = 0x202; public static final int GL_EXTENSIONS = 0x1F03; @@ -146,10 +147,13 @@ public interface GL { public static final int GL_SRC_ALPHA_SATURATE = 0x0308; public static final int GL_SRC_COLOR = 0x300; public static final int GL_STATIC_DRAW = 0x88E4; + public static final int GL_STATIC_READ = 0x88E5; + public static final int GL_STATIC_COPY = 0x88E6; public static final int GL_STENCIL_BUFFER_BIT = 0x400; public static final int GL_STENCIL_TEST = 0xB90; public static final int GL_STREAM_DRAW = 0x88E0; public static final int GL_STREAM_READ = 0x88E1; + public static final int GL_STREAM_COPY = 0x88E2; public static final int GL_TEXTURE = 0x1702; public static final int GL_TEXTURE0 = 0x84C0; public static final int GL_TEXTURE1 = 0x84C1; 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 23bd4d5f74..b423def06b 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 @@ -48,6 +48,7 @@ import com.jme3.shader.*; import com.jme3.shader.Shader.ShaderSource; import com.jme3.shader.Shader.ShaderType; +import com.jme3.shader.ShaderBufferBlock.BufferType; import com.jme3.texture.FrameBuffer; import com.jme3.texture.FrameBuffer.RenderBuffer; import com.jme3.texture.Image; @@ -60,6 +61,8 @@ import com.jme3.util.ListMap; import com.jme3.util.MipMapGenerator; import com.jme3.util.NativeObjectManager; +import com.jme3.util.struct.StructuredBufferLayout; + import jme3tools.shader.ShaderDebug; import java.nio.ByteBuffer; @@ -1275,46 +1278,55 @@ protected void updateShaderBufferBlock(final Shader shader, final ShaderBufferBl assert shader.getId() > 0; final BufferObject bufferObject = bufferBlock.getBufferObject(); - if (bufferObject.getUniqueId() == -1 || bufferObject.isUpdateNeeded()) { - updateBufferData(bufferObject); - } + final BufferType bufferType = bufferBlock.getType();; - if (!bufferBlock.isUpdateNeeded()) { - return; + + if ( bufferObject.isUpdateNeeded()) { + if(bufferType==BufferType.ShaderStorageBufferObject){ + updateShaderStorageBufferObjectData(bufferObject); + }else{ + updateUniformBufferObjectData(bufferObject); + } } + + int usage=resolveUsageHint(bufferObject.getAccessHint(),bufferObject.getNatureHint()); + if(usage==-1)return; // cpu only bindProgram(shader); final int shaderId = shader.getId(); - final BufferObject.BufferType bufferType = bufferObject.getBufferType(); - - bindBuffer(bufferBlock, bufferObject, shaderId, bufferType); - bufferBlock.clearUpdateNeeded(); - } - - private void bindBuffer(final ShaderBufferBlock bufferBlock, final BufferObject bufferObject, final int shaderId, - final BufferObject.BufferType bufferType) { + int bindingPoint=bufferObject.getBinding(); switch (bufferType) { case UniformBufferObject: { - final int blockIndex = gl3.glGetUniformBlockIndex(shaderId, bufferBlock.getName()); - gl3.glBindBufferBase(GL3.GL_UNIFORM_BUFFER, bufferObject.getBinding(), bufferObject.getId()); - gl3.glUniformBlockBinding(GL3.GL_UNIFORM_BUFFER, blockIndex, bufferObject.getBinding()); + // update shader bindpoint if needed + if(bufferBlock.isUpdateNeeded()||bufferBlock.getLocation()!=bindingPoint){ + int blockIndex = gl3.glGetUniformBlockIndex(shaderId, bufferBlock.getName()); + gl3.glBindBufferBase(GL3.GL_UNIFORM_BUFFER, bindingPoint+1, bufferObject.getId()); + if(blockIndex!=-1) gl3.glUniformBlockBinding(shaderId, blockIndex, bindingPoint+1); + bufferBlock.setLocation(bindingPoint); + } break; } case ShaderStorageBufferObject: { - final int blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, bufferBlock.getName()); - gl4.glShaderStorageBlockBinding(shaderId, blockIndex, bufferObject.getBinding()); - gl4.glBindBufferBase(GL4.GL_SHADER_STORAGE_BUFFER, bufferObject.getBinding(), bufferObject.getId()); + // update shader bindpoint if needed + if(bufferBlock.isUpdateNeeded()||bufferBlock.getLocation()!=bindingPoint){ + int blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, bufferBlock.getName()); + if(blockIndex!=-1) gl4.glShaderStorageBlockBinding(shaderId, blockIndex, bindingPoint+1); + bufferBlock.setLocation(bindingPoint); + } break; } default: { throw new IllegalArgumentException("Doesn't support binding of " + bufferType); } } + + bufferBlock.clearUpdateNeeded(); } + protected void updateShaderUniforms(Shader shader) { ListMap uniforms = shader.getUniformMap(); for (int i = 0; i < uniforms.size(); i++) { @@ -2564,6 +2576,34 @@ public void setTexture(int unit, Texture tex) { setupTextureParams(unit, tex); } + @Override + public void setUniformBufferObject(int bindingPoint, BufferObject bufferObject) { + if (bufferObject.isUpdateNeeded()) { + updateUniformBufferObjectData(bufferObject); + } + + if (context.boundBO[bindingPoint] == null || context.boundBO[bindingPoint].get() != bufferObject) { + gl3.glBindBufferBase(GL3.GL_UNIFORM_BUFFER, bindingPoint + 1, bufferObject.getId()); + bufferObject.setBinding(bindingPoint); + context.boundBO[bindingPoint] = bufferObject.getWeakRef(); + } + + bufferObject.setBinding(bindingPoint); + + } + + @Override + public void setShaderStorageBufferObject(int bindingPoint, BufferObject bufferObject) { + if (bufferObject.isUpdateNeeded()) { + updateShaderStorageBufferObjectData(bufferObject); + } + if (context.boundBO[bindingPoint] == null || context.boundBO[bindingPoint].get() != bufferObject) { + gl4.glBindBufferBase(GL4.GL_SHADER_STORAGE_BUFFER, bindingPoint + 1, bufferObject.getId()); + bufferObject.setBinding(bindingPoint); + context.boundBO[bindingPoint] = bufferObject.getWeakRef(); + } + bufferObject.setBinding(bindingPoint); + } /** * @deprecated Use modifyTexture(Texture2D dest, Image src, int destX, int destY, int srcX, int srcY, int areaW, int areaH) @@ -2716,23 +2756,73 @@ public void updateBufferData(VertexBuffer vb) { vb.clearUpdateNeeded(); } + + private int resolveUsageHint(BufferObject.AccessHint ah,BufferObject.NatureHint nh){ + switch(ah){ + case Default: + case Dynamic:{ + switch(nh){ + case Default: + case Draw: + return GL.GL_DYNAMIC_DRAW; + case Read: + return GL4.GL_DYNAMIC_READ; + case Copy: + return GL.GL_DYNAMIC_COPY; + } + } + case Stream:{ + switch(nh){ + case Default: + case Draw: + return GL.GL_STREAM_DRAW; + case Read: + return GL.GL_STREAM_READ; + case Copy: + return GL.GL_STREAM_COPY; + } + } + case Static:{ + switch(nh){ + case Default: + case Draw: + return GL.GL_STATIC_DRAW; + case Read: + return GL.GL_STATIC_READ; + case Copy: + return GL.GL_STATIC_COPY; + } + } + default: + } + return -1; + } + @Override - public void updateBufferData(final BufferObject bo) { + public void updateShaderStorageBufferObjectData( BufferObject bo) { + if (!caps.contains(Caps.ShaderStorageBufferObject)) throw new IllegalArgumentException("The current video hardware doesn't support shader storage buffer objects " ); + updateBufferData(GL4.GL_SHADER_STORAGE_BUFFER,bo); + } - int maxSize = Integer.MAX_VALUE; - final BufferObject.BufferType bufferType = bo.getBufferType(); + @Override + public void updateUniformBufferObjectData( BufferObject bo) { + if (!caps.contains(Caps.UniformBufferObject)) throw new IllegalArgumentException("The current video hardware doesn't support uniform buffer objects" ); + updateBufferData(GL4.GL_UNIFORM_BUFFER,bo); + } - if (!caps.contains(bufferType.getRequiredCaps())) { - throw new IllegalArgumentException("The current video hardware doesn't support " + bufferType); - } + + + private void updateBufferData(int type, BufferObject bo) { + + int bufferId = bo.getId(); - final ByteBuffer data = bo.computeData(maxSize); - if (data == null) { - throw new IllegalArgumentException("Can't upload BO without data."); + int usage=resolveUsageHint(bo.getAccessHint(),bo.getNatureHint()); + if(usage==-1){ + deleteBuffer(bo); + return; // CpuOnly } - int bufferId = bo.getId(); if (bufferId == -1) { // create buffer @@ -2745,24 +2835,29 @@ public void updateBufferData(final BufferObject bo) { objManager.registerObject(bo); } - data.rewind(); + StructuredBufferLayout.DirtyRegionsIterator it=bo.getDirtyRegions(); + StructuredBufferLayout.DirtyRegion reg; + + while((reg=it.getNext())!=null){ - switch (bufferType) { - case UniformBufferObject: { - gl3.glBindBuffer(GL3.GL_UNIFORM_BUFFER, bufferId); - gl3.glBufferData(GL4.GL_UNIFORM_BUFFER, data, GL3.GL_DYNAMIC_DRAW); - gl3.glBindBuffer(GL4.GL_UNIFORM_BUFFER, 0); - break; - } - case ShaderStorageBufferObject: { - gl4.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, bufferId); - gl4.glBufferData(GL4.GL_SHADER_STORAGE_BUFFER, data, GL4.GL_DYNAMIC_COPY); - gl4.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, 0); - break; - } - default: { - throw new IllegalArgumentException("Doesn't support binding of " + bufferType); + gl3.glBindBuffer(type, bufferId); + if(reg.full){ + ByteBuffer bbf=bo.getData(); + if(logger.isLoggable(java.util.logging.Level. FINER )){ + logger.log(java.util.logging.Level.FINER, + "Update full buffer {0} with {1} bytes",new Object[]{bo,bbf.remaining()} + ); + } + gl.glBufferData(type, bbf, usage); + }else{ + if(logger.isLoggable(java.util.logging.Level. FINER )){ + logger.log(java.util.logging.Level.FINER, + "Update region {0} of {1}",new Object[]{reg,bo} + ); + } + gl.glBufferSubData(type, reg.start,bo.getData(reg.start, reg.end)); } + gl3.glBindBuffer(type, 0); } bo.clearUpdateNeeded(); diff --git a/jme3-core/src/main/java/com/jme3/shader/BufferObject.java b/jme3-core/src/main/java/com/jme3/shader/BufferObject.java index 886f4a1abe..5b33e899cb 100644 --- a/jme3-core/src/main/java/com/jme3/shader/BufferObject.java +++ b/jme3-core/src/main/java/com/jme3/shader/BufferObject.java @@ -1,246 +1,112 @@ package com.jme3.shader; -import com.jme3.math.*; -import com.jme3.renderer.Caps; +import java.io.IOException; +import java.lang.ref.WeakReference; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.renderer.Renderer; -import com.jme3.util.BufferUtils; import com.jme3.util.NativeObject; -import com.jme3.util.SafeArrayList; - -import java.nio.ByteBuffer; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import com.jme3.util.struct.StructuredBuffer; +import com.jme3.util.struct.StructuredBufferLayout; /** - * The base implementation of BO. - * - * @author JavaSaBr +* A generic memory buffer + * @author Riccardo Balbo */ -public class BufferObject extends NativeObject { - - private static final Map, VarType> CLASS_TO_VAR_TYPE = new HashMap<>(); - - static { - CLASS_TO_VAR_TYPE.put(Float.class, VarType.Float); - CLASS_TO_VAR_TYPE.put(Integer.class, VarType.Int); - CLASS_TO_VAR_TYPE.put(Boolean.class, VarType.Boolean); - CLASS_TO_VAR_TYPE.put(Vector2f.class, VarType.Vector2); - CLASS_TO_VAR_TYPE.put(Vector3f.class, VarType.Vector3); - CLASS_TO_VAR_TYPE.put(ColorRGBA.class, VarType.Vector4); - CLASS_TO_VAR_TYPE.put(Quaternion.class, VarType.Vector4); - CLASS_TO_VAR_TYPE.put(Vector4f.class, VarType.Vector4); - - CLASS_TO_VAR_TYPE.put(Vector2f[].class, VarType.Vector2Array); - CLASS_TO_VAR_TYPE.put(Vector3f[].class, VarType.Vector3Array); - CLASS_TO_VAR_TYPE.put(Vector4f[].class, VarType.Vector4Array); - CLASS_TO_VAR_TYPE.put(ColorRGBA[].class, VarType.Vector4Array); - CLASS_TO_VAR_TYPE.put(Quaternion[].class, VarType.Vector4Array); - - CLASS_TO_VAR_TYPE.put(Matrix3f.class, VarType.Matrix3); - CLASS_TO_VAR_TYPE.put(Matrix4f.class, VarType.Matrix4); - CLASS_TO_VAR_TYPE.put(Matrix3f[].class, VarType.Matrix3Array); - CLASS_TO_VAR_TYPE.put(Matrix4f[].class, VarType.Matrix4Array); - } - - protected static VarType getVarTypeByValue(final Object value) { - - final VarType varType = CLASS_TO_VAR_TYPE.get(value.getClass()); - if (varType != null) { - return varType; - } else if (value instanceof Collection && ((Collection) value).isEmpty()) { - throw new IllegalArgumentException("Can't calculate a var type for the empty collection value[" + value + "]."); - } else if (value instanceof List) { - return getVarTypeByValue(((List) value).get(0)); - } else if (value instanceof Collection) { - return getVarTypeByValue(((Collection) value).iterator().next()); - } - - throw new IllegalArgumentException("Can't calculate a var type for the value " + value); - } - - public enum Layout { - std140, - /** unsupported yet */ - @Deprecated - std430, - } - - public enum BufferType { - ShaderStorageBufferObject(Caps.ShaderStorageBufferObject), - UniformBufferObject(Caps.UniformBufferObject), - ; - - private final Caps requiredCaps; - - BufferType(final Caps requiredCaps) { - this.requiredCaps = requiredCaps; - } +public class BufferObject extends StructuredBuffer { + public static enum AccessHint { /** - * Get the required caps. - * - * @return the required caps. + * The data store contents will be modified once and used many times. */ - public Caps getRequiredCaps() { - return requiredCaps; - } - } - - /** - * The fields of this BO. - */ - private final Map fields; - - /** - * The field's array. - */ - private final SafeArrayList fieldArray; - - /** - * The buffer's data layout. - */ - private final Layout layout; - - /** - * The binding number. - */ - private final int binding; - - /** - * The buffer's type. - */ - private BufferType bufferType; - - /** - * The previous data buffer. - */ - private ByteBuffer previousData; - - public BufferObject(final int binding, final Layout layout, final BufferType bufferType) { - this.handleRef = new Object(); - this.bufferType = bufferType; - this.binding = binding; - this.layout = layout; - this.fields = new HashMap<>(); - this.fieldArray = new SafeArrayList<>(BufferObjectField.class); - } + Static, + /** + * The data store contents will be modified once and used at most a few times. + */ + Stream, + /** + * The data store contents will be modified repeatedly and used many times. + */ + Dynamic, + /** + * Used only by the cpu. + */ + CpuOnly, - public BufferObject(final int binding, final Layout layout) { - this(binding, layout, BufferType.UniformBufferObject); + Default } - public BufferObject(final int binding, final BufferType bufferType) { - this(binding, Layout.std140, bufferType); - } + public static enum NatureHint { + /** + * The data store contents are modified by the application, and used as the source for GL drawing and image specification commands. + */ + Draw, + /** + * The data store contents are modified by reading data from the GL, and used to return that data when queried by the application. + */ + Read, + /** + * The data store contents are modified by reading data from the GL, and used as the source for GL drawing and image specification commands. + */ + Copy, - public BufferObject(final BufferType bufferType) { - this(1, Layout.std140, bufferType); + Default } - public BufferObject(final Layout layout) { - this(1, layout, BufferType.UniformBufferObject); - } - public BufferObject(final int binding) { - this(binding, Layout.std140, BufferType.UniformBufferObject); - } - public BufferObject() { - this(1, Layout.std140, BufferType.UniformBufferObject); - } - private BufferObject(final Void unused, final int id) { - super(id); - this.fieldArray = null; - this.fields = null; - this.layout = null; - this.binding = 0; - } + - /** - * Declares a filed in this BO. - * - * @param name the field's name. - * @param varType the field's type. - */ - public void declareField(final String name, final VarType varType) { - if (fields.containsKey(name)) { - throw new IllegalArgumentException("The field " + name + " is already declared."); - } + private AccessHint accessHint=AccessHint.Default; + private NatureHint natureHint=NatureHint.Default; - final BufferObjectField field = new BufferObjectField(name, varType); + private transient WeakReference weakRef; + private transient int binding; - fields.put(name, field); - fieldArray.add(field); + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(accessHint.ordinal(),"accessHint",0); + oc.write(natureHint.ordinal(),"natureHint",0); } - /** - * Gets the buffer's type. - * - * @return the buffer's type. - */ - public BufferType getBufferType() { - return bufferType; + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + accessHint=AccessHint.values()[ic.readInt("accessHint",0)]; + natureHint=NatureHint.values()[ic.readInt("natureHint",0)]; } - /** - * Sets the buffer's type. - * - * @param bufferType the buffer's type. - */ - public void setBufferType(final BufferType bufferType) { - if (getId() != -1) { - throw new IllegalStateException("Can't change buffer's type when this buffer is already initialized."); - } - this.bufferType = bufferType; + @Override + public BufferObject clone(){ + BufferObject vb = (BufferObject) super.clone(); + vb.binding = -1; + vb.weakRef=null; + return vb; } - /** - * Sets the value to the filed by the field's name. - * - * @param name the field's name. - * @param value the value. - */ - public void setFieldValue(final String name, final Object value) { - - BufferObjectField field = fields.get(name); - - if (field == null) { - declareField(name, getVarTypeByValue(value)); - field = fields.get(name); - } - - field.setValue(value); - setUpdateNeeded(); + + public BufferObject(){ + super(); } - /** - * Gets the current value of the field by the name. - * - * @param name the field name. - * @param the value's type. - * @return the current value. - */ - public T getFieldValue(final String name) { - - final BufferObjectField field = fields.get(name); - if (field == null) { - throw new IllegalArgumentException("Unknown a field with the name " + name); - } + protected BufferObject(int id){ + super(id); + } - return (T) field.getValue(); + public BufferObject(Class layoutDef) { + super(layoutDef); } - /** - * Get the binding number. - * - * @return the binding number. - */ public int getBinding() { return binding; } @@ -248,581 +114,57 @@ public int getBinding() { @Override public void resetObject() { this.id = -1; - setUpdateNeeded(); - } - - /** - * Computes the current binary data of this BO. - * - * @param maxSize the max data size. - * @return the current binary data of this BO. - */ - public ByteBuffer computeData(final int maxSize) { - - int estimateSize = 0; - - for (final BufferObjectField field : fieldArray) { - estimateSize += estimateSize(field); - } - - if(maxSize < estimateSize) { - throw new IllegalStateException("The estimated size(" + estimateSize + ") of this BO is bigger than " + - "maximum available size " + maxSize); - } - - if (previousData != null) { - if (previousData.capacity() < estimateSize) { - BufferUtils.destroyDirectBuffer(previousData); - previousData = null; - } else { - previousData.clear(); - } - } - - final ByteBuffer data = previousData == null ? BufferUtils.createByteBuffer(estimateSize) : previousData; - - for (final BufferObjectField field : fieldArray) { - writeField(field, data); - } - - data.flip(); - - this.previousData = data; - - return data; - } - - /** - * Estimates size of the field. - * - * @param field the field. - * @return the estimated size. - */ - protected int estimateSize(final BufferObjectField field) { - - switch (field.getType()) { - case Float: - case Int: { - if (layout == Layout.std140) { - return 16; - } - return 4; - } - case Boolean: { - if (layout == Layout.std140) { - return 16; - } - return 1; - } - case Vector2: { - return 4 * 2; - } - case Vector3: { - final int multiplier = layout == Layout.std140 ? 4 : 3; - return 4 * multiplier; - } - case Vector4: - return 16; - case IntArray: { - return estimate((int[]) field.getValue()); - } - case FloatArray: { - return estimate((float[]) field.getValue()); - } - case Vector2Array: { - return estimateArray(field.getValue(), 8); - } - case Vector3Array: { - final int multiplier = layout == Layout.std140 ? 16 : 12; - return estimateArray(field.getValue(), multiplier); - } - case Vector4Array: { - return estimateArray(field.getValue(), 16); - } - case Matrix3: { - final int multiplier = layout == Layout.std140 ? 16 : 12; - return 3 * 3 * multiplier; - } - case Matrix4: { - return 4 * 4 * 4; - } - case Matrix3Array: { - int multiplier = layout == Layout.std140 ? 16 : 12; - multiplier = 3 * 3 * multiplier; - return estimateArray(field.getValue(), multiplier); - } - case Matrix4Array: { - final int multiplier = 4 * 4 * 16; - return estimateArray(field.getValue(), multiplier); - } - default: { - throw new IllegalArgumentException("The type of BO field " + field.getType() + " doesn't support."); - } - } - } - - /** - * Estimates bytes count to present the value on GPU. - * - * @param value the value. - * @param multiplier the multiplier. - * @return the estimated bytes cunt. - */ - protected int estimateArray(final Object value, final int multiplier) { - - if (value instanceof Object[]) { - return ((Object[]) value).length * multiplier; - } else if (value instanceof Collection) { - return ((Collection) value).size() * multiplier; - } - - throw new IllegalArgumentException("Unexpected value " + value); - } - - /** - * Estimates bytes count to present the values on GPU. - * - * @param values the values. - * @return the estimated bytes cunt. - */ - protected int estimate(final float[] values) { - return values.length * 4; - } - - /** - * Estimates bytes count to present the values on GPU. - * - * @param values the values. - * @return the estimated bytes cunt. - */ - protected int estimate(final int[] values) { - return values.length * 4; - } - - /** - * Writes the field to the data buffer. - * - * @param field the field. - * @param data the data buffer. - */ - protected void writeField(final BufferObjectField field, final ByteBuffer data) { - - final Object value = field.getValue(); - - switch (field.getType()) { - case Int: { - data.putInt(((Number) value).intValue()); - if (layout == Layout.std140) { - data.putInt(0); - data.putLong(0); - } - break; - } - case Float: { - data.putFloat(((Number) value).floatValue()); - if (layout == Layout.std140) { - data.putInt(0); - data.putLong(0); - } - break; - } - case Boolean: - data.put((byte) (((Boolean) value) ? 1 : 0)); - if (layout == Layout.std140) { - data.putInt(0); - data.putLong(0); - data.putShort((short) 0); - data.put((byte) 0); - } - break; - case Vector2: - write(data, (Vector2f) value); - break; - case Vector3: - write(data, (Vector3f) value); - break; - case Vector4: - writeVec4(data, value); - break; - case IntArray: { - write(data, (int[]) value); - break; - } - case FloatArray: { - write(data, (float[]) value); - break; - } - case Vector2Array: { - writeVec2Array(data, value); - break; - } - case Vector3Array: { - writeVec3Array(data, value); - break; - } - case Vector4Array: { - writeVec4Array(data, value); - break; - } - case Matrix3: { - write(data, (Matrix3f) value); - break; - } - case Matrix4: { - write(data, (Matrix4f) value); - break; - } - case Matrix3Array: { - writeMat3Array(data, value); - break; - } - case Matrix4Array: { - writeMat4Array(data, value); - break; - } - default: { - throw new IllegalArgumentException("The type of BO field " + field.getType() + " doesn't support."); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeMat3Array(final ByteBuffer data, final Object value) { - - if (value instanceof Matrix3f[]) { - - final Matrix3f[] values = (Matrix3f[]) value; - for (final Matrix3f mat : values) { - write(data, mat); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Matrix3f mat : values.getArray()) { - write(data, mat); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Matrix3f mat : values) { - write(data, mat); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeMat4Array(final ByteBuffer data, final Object value) { - - if (value instanceof Matrix4f[]) { - - final Matrix4f[] values = (Matrix4f[]) value; - for (final Matrix4f mat : values) { - write(data, mat); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Matrix4f mat : values.getArray()) { - write(data, mat); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Matrix4f mat : values) { - write(data, mat); - } - } - } - - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeVec4Array(final ByteBuffer data, final Object value) { - - if (value instanceof Object[]) { - - final Object[] values = (Object[]) value; - for (final Object vec : values) { - writeVec4(data, vec); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Object vec : values.getArray()) { - writeVec4(data, vec); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Object vec : values) { - writeVec4(data, vec); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeVec3Array(final ByteBuffer data, final Object value) { - - if (value instanceof Vector3f[]) { - - final Vector3f[] values = (Vector3f[]) value; - for (final Vector3f vec : values) { - write(data, vec); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Vector3f vec : values.getArray()) { - write(data, vec); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Vector3f vec : values) { - write(data, vec); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeVec2Array(final ByteBuffer data, final Object value) { - - if (value instanceof Vector2f[]) { - - final Vector2f[] values = (Vector2f[]) value; - for (final Vector2f vec : values) { - write(data, vec); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Vector2f vec : values.getArray()) { - write(data, vec); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Vector2f vec : values) { - write(data, vec); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final float[] value) { - for (float val : value) { - data.putFloat(val); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final int[] value) { - for (int val : value) { - data.putInt(val); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeVec4(final ByteBuffer data, final Object value) { - - if (value == null) { - data.putLong(0).putLong(0); - } else if (value instanceof Vector4f) { - - final Vector4f vec4 = (Vector4f) value; - data.putFloat(vec4.getX()) - .putFloat(vec4.getY()) - .putFloat(vec4.getZ()) - .putFloat(vec4.getW()); - - } else if(value instanceof Quaternion) { - - final Quaternion vec4 = (Quaternion) value; - data.putFloat(vec4.getX()) - .putFloat(vec4.getY()) - .putFloat(vec4.getZ()) - .putFloat(vec4.getW()); - - } else if(value instanceof ColorRGBA) { - - final ColorRGBA vec4 = (ColorRGBA) value; - data.putFloat(vec4.getRed()) - .putFloat(vec4.getGreen()) - .putFloat(vec4.getBlue()) - .putFloat(vec4.getAlpha()); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final Vector3f value) { - - if (value == null) { - data.putLong(0).putInt(0); - } else { - data.putFloat(value.getX()) - .putFloat(value.getY()) - .putFloat(value.getZ()); - } - - if (layout == Layout.std140) { - data.putInt(0); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param x the x value. - * @param y the y value. - * @param z the z value. - */ - protected void write(final ByteBuffer data, final float x, final float y, final float z) { - - data.putFloat(x) - .putFloat(y) - .putFloat(z); - - if (layout == Layout.std140) { - data.putInt(0); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param x the x value. - * @param y the y value. - * @param z the z value. - * @param w the w value. - */ - protected void write(final ByteBuffer data, final float x, final float y, final float z, final float w) { - data.putFloat(x) - .putFloat(y) - .putFloat(z) - .putFloat(w); - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final Vector2f value) { - if (value == null) { - data.putLong(0); - } else { - data.putFloat(value.getX()).putFloat(value.getY()); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final Matrix3f value) { - write(data, value.get(0, 0), value.get(1, 0), value.get(2, 0)); - write(data, value.get(0, 1), value.get(1, 1), value.get(2, 1)); - write(data, value.get(0, 2), value.get(1, 2), value.get(2, 2)); - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final Matrix4f value) { - write(data, value.get(0, 0), value.get(1, 0), value.get(2, 0), value.get(3, 0)); - write(data, value.get(0, 1), value.get(1, 1), value.get(2, 1), value.get(3, 1)); - write(data, value.get(0, 2), value.get(1, 2), value.get(2, 2), value.get(3, 2)); - write(data, value.get(0, 3), value.get(1, 3), value.get(2, 3), value.get(3, 3)); + invalidateData(); } @Override public void deleteObject(final Object rendererObject) { - if (!(rendererObject instanceof Renderer)) { throw new IllegalArgumentException("This bo can't be deleted from " + rendererObject); } - ((Renderer) rendererObject).deleteBuffer(this); } @Override public NativeObject createDestructableClone() { - return new BufferObject(null, getId()); + return new BufferObject( getId()); } @Override protected void deleteNativeBuffers() { + invalidateData(); super.deleteNativeBuffers(); - if (previousData != null) { - BufferUtils.destroyDirectBuffer(previousData); - previousData = null; - } } @Override public long getUniqueId() { return ((long) OBJTYPE_BO << 32) | ((long) id); } + + public void setBinding(final int binding) { + this.binding = binding; + } + + public WeakReference getWeakRef() { + if (weakRef == null) weakRef = new WeakReference(this); + return weakRef; + } + + public AccessHint getAccessHint() { + return accessHint; + } + + public void setAccessHint(AccessHint accessHint) { + this.accessHint = accessHint; + setUpdateNeeded(true); + } + + public NatureHint getNatureHint() { + return natureHint; + } + + public void setNatureHint(NatureHint natureHint) { + this.natureHint = natureHint; + setUpdateNeeded(true); + } } diff --git a/jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java b/jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java deleted file mode 100644 index 798b418fc4..0000000000 --- a/jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.jme3.shader; - -import static java.util.Objects.requireNonNull; - -/** - * The class to describe a filed in BO. - * - * @author JavaSaBr - */ -public class BufferObjectField { - - - /** - * The field name. - */ - private final String name; - - /** - * The field type. - */ - private final VarType type; - - /** - * The field value. - */ - private Object value; - - public BufferObjectField(final String name, final VarType type) { - this.name = name; - this.type = type; - } - - /** - * Get the field name. - * - * @return the field name. - */ - public String getName() { - return name; - } - - /** - * Gets the field type. - * - * @return the field type. - */ - public VarType getType() { - return type; - } - - /** - * Gets the field value. - * - * @return the field value. - */ - public Object getValue() { - return value; - } - - /** - * Sets the field value. - * - * @param value the field value. - */ - public void setValue(final Object value) { - this.value = requireNonNull(value, "The field's value can't be null."); - } - - @Override - public String toString() { - return "BufferObjectField{" + - "name='" + name + '\'' + - ", type=" + type + - ", value=" + value + - '}'; - } -} diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java b/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java index 27b44d18b3..72ccaf4295 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java @@ -31,34 +31,45 @@ */ package com.jme3.shader; +import java.lang.ref.WeakReference; + /** * Implementation of shader's buffer block. * - * @author JavaSaBr + * @author JavaSaBr, Riccardo Balbo */ public class ShaderBufferBlock extends ShaderVariable { - + + public static enum BufferType{ + UniformBufferObject, + ShaderStorageBufferObject + } /** * Current used buffer object. */ protected BufferObject bufferObject; + protected WeakReference bufferObjectRef; + protected BufferType type; /** * Set the new buffer object. * * @param bufferObject the new buffer object. */ - public void setBufferObject(final BufferObject bufferObject) { - + public void setBufferObject(BufferType type, BufferObject bufferObject) { if (bufferObject == null) { throw new IllegalArgumentException("for storage block " + name + ": storageData cannot be null"); } - this.bufferObject = bufferObject; - + this.bufferObjectRef=new WeakReference(bufferObject); + this.type=type; updateNeeded = true; } + public BufferType getType(){ + return type; + } + /** * Return true if need to update this storage block. * @@ -90,4 +101,14 @@ public void reset(){ public BufferObject getBufferObject() { return bufferObject; } + + public WeakReference getBufferObjectRef() { + return bufferObjectRef; + } + + public void setBufferObjectRef(WeakReference bufferObjectRef) { + this.bufferObjectRef = bufferObjectRef; + } + + } diff --git a/jme3-core/src/main/java/com/jme3/shader/Uniform.java b/jme3-core/src/main/java/com/jme3/shader/Uniform.java index 30d5d72bf1..93a8c9ce60 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Uniform.java +++ b/jme3-core/src/main/java/com/jme3/shader/Uniform.java @@ -31,6 +31,7 @@ */ package com.jme3.shader; +import com.jme3.material.Material.BindUnits; import com.jme3.math.*; import com.jme3.util.BufferUtils; import com.jme3.util.TempVars; @@ -198,6 +199,7 @@ public void clearValue(){ } public void setValue(VarType type, Object value){ + assert !(value instanceof BindUnits); if (location == LOC_NOT_DEFINED) { return; } 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 2319e79037..6ed519642c 100644 --- a/jme3-core/src/main/java/com/jme3/shader/VarType.java +++ b/jme3-core/src/main/java/com/jme3/shader/VarType.java @@ -58,7 +58,8 @@ public enum VarType { TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow"), TextureCubeMap(false,true,"samplerCube"), Int("int"), - BufferObject(false, false, "custom"); + UniformBufferObject(false, false, "custom"), + ShaderStorageBufferObject(false, false, "custom") ; 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 156b2aabbe..42fc26808b 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java +++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java @@ -172,9 +172,6 @@ public void modifyTexture(Texture tex, Image pixels, int x, int y) { public void updateBufferData(VertexBuffer vb) { } - @Override - public void updateBufferData(BufferObject bo) { - } @Override public void deleteBuffer(VertexBuffer vb) { } @@ -254,4 +251,28 @@ public boolean getAlphaToCoverage() { public int getDefaultAnisotropicFilter() { return 0; } + + @Override + public void updateShaderStorageBufferObjectData(BufferObject bo) { + // TODO Auto-generated method stub + + } + + @Override + public void updateUniformBufferObjectData(BufferObject bo) { + // TODO Auto-generated method stub + + } + + @Override + public void setShaderStorageBufferObject(int bindingPoint, BufferObject bufferObject) { + // TODO Auto-generated method stub + + } + + @Override + public void setUniformBufferObject(int bindingPoint, BufferObject bufferObject) { + // TODO Auto-generated method stub + + } } diff --git a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java index 73759f03c9..b41b0b7bcd 100644 --- a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java +++ b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java @@ -33,6 +33,8 @@ import java.util.*; +import com.jme3.util.functional.NoArgFunction; + /** *

Provides a list with similar modification semantics to java.util.concurrent's * CopyOnWriteArrayList except that it is not concurrent and also provides @@ -317,6 +319,22 @@ public E set(int index, E element) { return getBuffer().set(index, element); } + public void expand(int index,NoArgFunction constructor){ + while(size()<=index) add(constructor!=null?constructor.eval():null); + } + + public E expandAndSet(int index, E element,NoArgFunction constructor){ + expand(index,constructor); + return getBuffer().set(index, element); + } + + + public E expandAndGet(int index, NoArgFunction constructor){ + expand(index,constructor); + return getBuffer().get(index); + } + + @Override public void add(int index, E element) { getBuffer().add(index, element); diff --git a/jme3-core/src/main/java/com/jme3/util/struct/Struct.java b/jme3-core/src/main/java/com/jme3/util/struct/Struct.java new file mode 100644 index 0000000000..c5eda3711e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/Struct.java @@ -0,0 +1,11 @@ +package com.jme3.util.struct; + +/** + * A Struct + * @author Riccardo Balbo + */ +public interface Struct { + + public T get( ); + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructField.java b/jme3-core/src/main/java/com/jme3/util/struct/StructField.java new file mode 100644 index 0000000000..ed623a7ecb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructField.java @@ -0,0 +1,67 @@ +package com.jme3.util.struct; + +import java.lang.reflect.Field; +import java.util.ArrayList; + +/** + * StructField + * + * @author Riccardo Balbo + */ +public class StructField { + + private final int position; + private T value; + private boolean isUpdateNeeded = true; + + Field[] fieldPtr; + + private void checkField(T value){ + if(value.getClass().isArray() && value.getClass().getComponentType().isPrimitive()){ + throw new RuntimeException(this.getClass()+" doesn't support primitive arrays. Please use the object wrappers. eg. change int[] to Integer[]"); + } + } + + public StructField(int position, T value) { + this.position = position; + checkField(value); + this.value = value; + } + + public StructField(T value) { + this(0, value); + } + + public int getPosition() { + return position; + } + + public T getValue() { + return value; + } + + public void setValue(T value) { + isUpdateNeeded = true; + checkField(value); + this.value = value; + } + + public T getValueForUpdate() { + isUpdateNeeded = true; + return value; + } + + public boolean isUpdateNeeded() { + return isUpdateNeeded; + } + + public void clearUpdateNeeded() { + isUpdateNeeded = false; + } + + @Override + public String toString() { + return value.toString(); + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructUtils.java b/jme3-core/src/main/java/com/jme3/util/struct/StructUtils.java new file mode 100644 index 0000000000..cc6d466bb1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructUtils.java @@ -0,0 +1,106 @@ +package com.jme3.util.struct; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.jme3.util.SafeArrayList; + +/** + * StructUtils + * @author Riccardo Balbo + */ +public class StructUtils { + private static final Comparator fieldComparator = new Comparator() { + @Override + public int compare(final StructField a, final StructField b) { + return a.getPosition() - b.getPosition(); + } + }; + + + public static List sortFields(final List fields) { + fields.sort(fieldComparator); + return fields; + } + + public static List getFromClass(final Struct struct) { + return getFromClass(struct, new ArrayList()); + } + + private static List getFromClass(final Struct struct, final List fieldList) { + final Class structClass = struct.getClass(); + final SafeArrayList structFields = new SafeArrayList(StructField.class); + try { + final Field[] fields = structClass.getDeclaredFields(); + for (final Field field : fields) { + fieldList.add(field); + + field.setAccessible(true); + final Object o = field.get(struct); + if (o instanceof StructField) { + final StructField so = (StructField) o; + if (so.getValue() instanceof Struct) { + final List subStruct = getFromClass((Struct) so.getValue(), fieldList); + final StructField> subField = new StructField>(so.getPosition(), subStruct); + subField.fieldPtr = fieldList.toArray(new Field[0]); + structFields.expandAndSet(so.getPosition(), subField, () -> null); + } else if (so.getValue().getClass().isArray()&&Struct.class.isAssignableFrom(so.getValue().getClass().getComponentType())) { + throw new RuntimeException("Array of structs not supported yet"); + } else { + so.fieldPtr = fieldList.toArray(new Field[0]); + structFields.expandAndSet(so.getPosition(), so, () -> null); + } + } + + fieldList.remove(fieldList.size() - 1); + + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + assert structFields.size() != 0; + return structFields; + } + + public StructField getValueFromRoot(final Struct root, final StructField field) throws IllegalArgumentException, IllegalAccessException { + Object co = root; + for (final Field f : field.fieldPtr) co = f.get(co); + return (StructField) co; + } + + public StructField getValue(final Struct struct, final StructField field) throws IllegalArgumentException, IllegalAccessException { + final Object co = field.fieldPtr[field.fieldPtr.length - 1].get(struct); + return (StructField) co; + } + + // public static SafeArrayList getFields(Class f) { + // SafeArrayList structFields = new SafeArrayList(Field.class); + // try { + // Field[] fields = f.getDeclaredFields(); + // for (Field field : fields) { + // field.setAccessible(true); + // Object o = field.get(f); + // if (o instanceof StructField) { + // StructField sfield = (StructField) o; + // structFields.expandAndSet(sfield.getPosition(), field, () -> null); + // } + // } + // } catch (Exception e) { + // throw new RuntimeException(e); + // } + // assert structFields.size()!=0; + // return structFields; + // } + + // public static StructField getFieldValue(Struct s, Field field) { + // try { + // return (StructField) field.get(s); + // } catch (Exception e) { + // throw new RuntimeException(); + // } + // } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructuredBuffer.java b/jme3-core/src/main/java/com/jme3/util/struct/StructuredBuffer.java new file mode 100644 index 0000000000..90b3c0c103 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructuredBuffer.java @@ -0,0 +1,276 @@ +package com.jme3.util.struct; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObject; +import com.jme3.util.struct.StructuredBufferLayout.DirtyRegionsIterator; +import com.jme3.util.struct.StructuredBufferLayout.LayoutRegion; + +/** + * + * @author Riccardo Balbo + */ +public abstract class StructuredBuffer extends NativeObject implements Cloneable, Savable { + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger( StructuredBuffer.class.getName()); + + private static Map> registeredLayouts = new ConcurrentHashMap>(); + + public static void registerLayout(String name, Class layout) { + registeredLayouts.put(name, layout); + } + + public static String getRegisteredLayoutName(Class layout) { + for (Entry> e : registeredLayouts.entrySet()) { + if(e.getValue().equals(layout)){ + return e.getKey(); + } + } + return null; + } + + public static Class getRegisteredLayoutFromName(String name){ + return registeredLayouts.get(name); + } + + + static{ + registerLayout("std140",StructuredBufferSTD140Layout.class); + } + + + protected ByteBuffer data = null; + private Class layoutDef; + private StructuredBufferLayout layout; + + private transient Class rootStruct; + private transient List resolvedFields; + private transient boolean invalidLayout = true; + + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(layout,"layout",null); + oc.write(data,"data",null); + assert layoutDef!=null; + String defname=getRegisteredLayoutName(layoutDef); + assert defname!=null; + oc.write(defname, "layoutDef", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + + layout= (StructuredBufferLayout) ic.readSavable("layout", null); + layout.markAllDirty(); + invalidateLayout(); + + data=ic.readByteBuffer("data",null); + + layoutDef=getRegisteredLayoutFromName( ic.readString("layoutDef", null)); + assert layoutDef!=null; + + } + + protected StructuredBuffer(Class layoutDef) { + super(); + this.layoutDef = layoutDef; + } + + /** + * Internal only + */ + protected StructuredBuffer() { + super(); + } + + /** + * Internal only + */ + protected StructuredBuffer(int id) { + super(id); + } + + + protected void invalidateLayout(){ + new Exception().printStackTrace(); + if(logger.isLoggable(java.util.logging.Level. FINE )){ + logger.log(java.util.logging.Level.FINE, + "Invalidate layout" + ); + } + invalidLayout = true; + resolvedFields = null; + rootStruct = null; + } + + protected void invalidateData() { + invalidateLayout(); + + if(logger.isLoggable(java.util.logging.Level. FINE )){ + logger.log(java.util.logging.Level.FINE, + "Invalidate data" + ); + } + if (data != null) { + BufferUtils.destroyDirectBuffer(data); + data = null; + setUpdateNeeded(false); + } + + } + + + + protected int updateData(List fields,int id){ + if (invalidLayout) { + invalidLayout = false; + try { + layout = layoutDef.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + layout.setLayout(fields); + if (logger.isLoggable(java.util.logging.Level.FINE)) { + logger.log(java.util.logging.Level.FINE, "Set layout for buffer {0} : \n{1}", new Object[] { this, layout }); + } + } + + if(logger.isLoggable(java.util.logging.Level. FINER )){ + logger.log(java.util.logging.Level.FINER, + "Serialize {0} fields" , new Object[]{fields.size()} + ); + } + boolean updateAll=false; + + if(data==null){ + updateAll = true; + data = BufferUtils.createByteBuffer(layout.getEnd()); + } + + for(StructField f:fields){ + Object v=f.getValue(); + if(v instanceof List&&((List)v).get(0) instanceof StructField){ + id=updateData((List)v,id); + f.clearUpdateNeeded(); + }else{ + LayoutRegion reg=layout.getRegion(id); + if (updateAll || f.isUpdateNeeded()) { + if(logger.isLoggable(java.util.logging.Level. FINER )){ + logger.log(java.util.logging.Level.FINER, + "Serialize {0} ({1}) in {2}" , new Object[]{f,this,reg} + ); + } + data.position(reg.start); + layout.serialize(data, f.getValue()); + assert data.position()==reg.end; + assert data.position()-reg.start==reg.size; + reg.dirty = true; + f.clearUpdateNeeded(); + setUpdateNeeded(false); + }else{ + if(logger.isLoggable(java.util.logging.Level. FINER )){ + logger.log(java.util.logging.Level.FINER, + "Already up to date. Skip {0} ({1}) in {2}" , new Object[]{f,this,reg} + ); + } + } + id++; + } + } + return id; + } + + public void updateDataAndLayout( List fields) { + invalidateData(); + updateData(fields); + } + + public void updateData(List fields){ + fields=StructUtils.sortFields(fields); + updateData(fields,0); + } + + public void updateData(Struct struct) { + if(rootStruct!=struct.getClass()){ + if(logger.isLoggable(java.util.logging.Level. FINE )){ + logger.log(java.util.logging.Level.FINE, + "Change in layout {0} =/= {1} ",new Object[]{rootStruct,struct.getClass()} + ); + } + invalidateData(); + resolvedFields=StructUtils.getFromClass(struct); + rootStruct=struct.getClass(); + } + updateData(resolvedFields,0); + } + + + + + public void setUpdateNeeded(boolean dirtyAll){ + if(dirtyAll){ + if(layout!=null)layout.markAllDirty(); + } + super.setUpdateNeeded(); + } + + + protected void rewindData(){ + data.position(layout.getStart()); + data.limit(layout.getEnd()); + } + + public ByteBuffer getData(){ + rewindData(); + return data; + } + + + public ByteBuffer getData(int start,int end){ + data.position(start); + data.limit(end); + return data; + } + + public DirtyRegionsIterator getDirtyRegions(){ + return layout.getDirtyRegions(); + } + + + @Override + public void resetObject() { + this.id = -1; + invalidateData(); + } + + @Override + protected void deleteNativeBuffers() { + invalidateData(); + super.deleteNativeBuffers(); + } + + + @Override + public StructuredBuffer clone(){ + StructuredBuffer clone= (StructuredBuffer) super.clone(); + rewindData(); + clone.data= BufferUtils.clone(data); + clone.invalidateLayout(); + clone.setUpdateNeeded(true); + return clone; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructuredBufferLayout.java b/jme3-core/src/main/java/com/jme3/util/struct/StructuredBufferLayout.java new file mode 100644 index 0000000000..c3a490ee9b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructuredBufferLayout.java @@ -0,0 +1,234 @@ +package com.jme3.util.struct; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; + +/** + * BufferLayout + */ +public abstract class StructuredBufferLayout implements Savable, Cloneable { + + private ArrayList regions = new ArrayList(); + private int bufferEnd=0; + + private transient DirtyRegionsIterator dirtyRegionsIterator; + + public static class LayoutRegion implements Savable,Cloneable{ + public int start=-1; + public int end=-1; + public boolean dirty = true; + + public int size = -1; + + public LayoutRegion(int size) { + this.size = size; + } + + public LayoutRegion(int start,int end) { + this.start=start; + this.end=end; + } + + @Override + public String toString(){ + return "Region [start="+start+", end="+end+", size="+size+", dirty="+dirty+"]"; + } + + + public LayoutRegion() { + + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(start,"start",0); + oc.write(end,"end",0); + oc.write(dirty,"dirty",false); + oc.write(size,"size",0); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + start=ic.readInt("start",0); + end=ic.readInt("end",0); + dirty=ic.readBoolean("dirty",false); + size=ic.readInt("size",0); + } + + @Override + public LayoutRegion clone() throws CloneNotSupportedException { + return (LayoutRegion) super.clone(); + } + } + + public static class DirtyRegion extends LayoutRegion { + public boolean full = false; + } + + @Override + public StructuredBufferLayout clone() throws CloneNotSupportedException { + StructuredBufferLayout clone= (StructuredBufferLayout) super.clone(); + for(LayoutRegion r:regions){ + clone.regions.add(r.clone()); + } + clone.dirtyRegionsIterator=null; + return clone; + } + + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.writeSavableArrayList(regions, "regions", null); + oc.write(bufferEnd, "bufferEnd", 0); + } + + @Override + @SuppressWarnings("unchecked") + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + regions=ic.readSavableArrayList("regions", null); + bufferEnd=ic.readInt("bufferEnd",0); + } + + + protected void clearLayout() { + regions.clear(); + regions.trimToSize(); + bufferEnd=0; + + } + + + protected int getBufferEnd(){ + return bufferEnd; + } + + protected void setBufferEnd(int n){ + bufferEnd=n; + } + + protected void addRegionToLayout(LayoutRegion lr) { + if( lr.size==-1){ + lr.size=lr.end-lr.start; + }else{ + lr.start = regions.size() == 0 ? 0 : regions.get(regions.size() - 1).end; + lr.end = lr.start + lr.size; + } + + bufferEnd=lr.end; + + regions.add(lr); + } + + public List getRegions(){ + return regions; + } + + public static class DirtyRegionsIterator { + private StructuredBufferLayout layout; + + public DirtyRegionsIterator(StructuredBufferLayout layout) { + this.layout = layout; + } + + private final DirtyRegion dirtyRegion = new DirtyRegion(); + private int pos = 0; + + public void rewind() { + pos = 0; + } + + public DirtyRegion getNext() { + int read = 0; + int dirtRegions=0; + boolean empty = true; + + + while (pos < layout.regions.size()) { + LayoutRegion dr = layout.regions.get(pos++); + if (dr.dirty) { + dr.dirty = false; + if (empty) { + dirtyRegion.start = read; + empty = false; + } + read += dr.size; + dirtRegions++; + }else{ + break; + } + + } + + if (empty) return null; + + if(dirtRegions==layout.countRegions()){ + dirtyRegion.full=true; + dirtyRegion.end = layout.getEnd(); + dirtyRegion.start=0; + }else{ + dirtyRegion.full=false; + dirtyRegion.end = read; + } + dirtyRegion.size=dirtyRegion.end-dirtyRegion.start; + + return dirtyRegion; + } + } + + public DirtyRegionsIterator getDirtyRegions() { + if(dirtyRegionsIterator==null)dirtyRegionsIterator= new DirtyRegionsIterator(this); + dirtyRegionsIterator.rewind(); + return dirtyRegionsIterator; + } + + + public LayoutRegion getRegion(int i) { + return regions.get(i); + } + + public int countRegions() { + return regions.size(); + } + + + + + public int getEnd() { + return bufferEnd; + } + + public int getStart() { + return regions.get(0).start; + } + + public void markAllDirty() { + for(LayoutRegion r:regions)r.dirty=true; + } + + @Override + public String toString(){ + StringBuilder sb=new StringBuilder(); + sb.append(getClass().getSimpleName()).append("{\n"); + for(LayoutRegion r:regions){ + sb.append(" ").append(r).append("\n"); + } + sb.append("}"); + return sb.toString(); + } + + + public abstract void serialize(ByteBuffer out,Object o); + public abstract void setLayout(List fields); +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructuredBufferSTD140Layout.java b/jme3-core/src/main/java/com/jme3/util/struct/StructuredBufferSTD140Layout.java new file mode 100644 index 0000000000..18124ead2b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructuredBufferSTD140Layout.java @@ -0,0 +1,66 @@ +package com.jme3.util.struct; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.jme3.util.struct.serializers.Std140Serializer; + +/** + * ClassBufferLayout + */ +public class StructuredBufferSTD140Layout extends StructuredBufferLayout{ + private final Std140Serializer serializer = new Std140Serializer(); + + public StructuredBufferSTD140Layout(){ + } + + // ArrayList structStack=new ArrayList(); + + + protected void clearLayout() { + super.clearLayout(); + } + + + public void setLayout(List fields){ + clearLayout(); + addToLayout(fields); + } + + private void addToLayout(List fields){ + for(StructField f:fields){ + Object v=f.getValue(); + if(v instanceof List&&((List)v).get(0) instanceof StructField){ // sub struct + setBufferEnd(serializer.align(getBufferEnd(),16)); + addToLayout((List)v); + setBufferEnd(serializer.align(getBufferEnd(),16)); + }else{ + int basicAlignment=serializer.getBasicAlignment(v); + int length=serializer.estimateSize(v); + setBufferEnd(serializer.align(getBufferEnd(),basicAlignment)); + LayoutRegion r=new LayoutRegion(getBufferEnd(),getBufferEnd()+length); + setBufferEnd(getBufferEnd()+length); + super.addRegionToLayout(r); + } + } + assert verifyRegions() ; + } + + private boolean verifyRegions(){ + int i=0; + for(LayoutRegion r:getRegions()){ + assert r.size==r.end-r.start : "Issue with region "+i+": "+r+" size doesn't seem to match"; + i++; + } + return true; + } + + + + + public void serialize(ByteBuffer out,Object o){ + serializer.serialize(out, o); + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/struct/serializers/Std140Serializer.java b/jme3-core/src/main/java/com/jme3/util/struct/serializers/Std140Serializer.java new file mode 100644 index 0000000000..a4cb85f87f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/serializers/Std140Serializer.java @@ -0,0 +1,498 @@ +package com.jme3.util.struct.serializers; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; + +/** + * Std140Serializer + * @author Riccardo Balbo + */ +public class Std140Serializer { + + + + + public static abstract class ObjectSerializer { + final Class type; + public ObjectSerializer(Class cls) { + type = cls; + } + public final Class getType() { + return type; + } + public abstract int length(Std140Serializer serializer, T obj); + public int basicAlignment(Std140Serializer serializer){ + return 4*4; + } + + public abstract void write(Std140Serializer serializer, ByteBuffer bbf, T obj); + } + + private ObjectSerializer getSerializer(Class cls) { + for (int i = registeredTypes.size() - 1; i >= 0; i--) { + ObjectSerializer sr = registeredTypes.get(i); + if (sr.getType().isAssignableFrom(cls)) { + return sr; + } + } + return null; + } + + public int estimateSize(Object o) { + ObjectSerializer s = getSerializer(o.getClass()); + return s.length(this, o); + } + + public int getBasicAlignment(Object o) { + ObjectSerializer s = getSerializer(o.getClass()); + return s.basicAlignment(this); + } + + + public int align(int pos,int basicAlignment){ + return FastMath.toMultipleOf(pos, basicAlignment); + } + + + public void serialize(ByteBuffer out, Object o) { + ObjectSerializer s = getSerializer(o.getClass()); + s.write(this, out, o); + } + + private final ArrayList registeredTypes = new ArrayList(); + + + public void registerSerializer(ObjectSerializer type) { + registeredTypes.add(type); + } + + { + + registerSerializer(new ObjectSerializer(Integer.class) { + @Override + public int length(Std140Serializer serializer, Integer obj) { + return 4; + } + + @Override + public int basicAlignment(Std140Serializer serializer) { + return 4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Integer obj) { + bbf.putInt(obj); + } + }); + + + + + registerSerializer(new ObjectSerializer(Integer[].class) { + @Override + public int length(Std140Serializer serializer, Integer[] obj) { + return 4*obj.length*4; + } + + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Integer[] obj) { + for(int i:obj){ + bbf.putInt(i); + bbf.putInt(0); + bbf.putInt(0); + bbf.putInt(0); + } + } + }); + + + + registerSerializer(new ObjectSerializer(Float.class) { + @Override + public int length(Std140Serializer serializer, Float obj) { + return 4; + } + + @Override + public int basicAlignment(Std140Serializer serializer) { + return 4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Float obj) { + bbf.putFloat(obj); + } + }); + + registerSerializer(new ObjectSerializer(Float[].class) { + @Override + public int length(Std140Serializer serializer, Float[] obj) { + return 4*obj.length*4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Float[] obj) { + for(float i:obj){ + bbf.putFloat(i); + bbf.putInt(0); + bbf.putInt(0); + bbf.putInt(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Boolean.class) { + @Override + public int length(Std140Serializer serializer, Boolean obj) { + return 4; + } + + @Override + public int basicAlignment(Std140Serializer serializer) { + return 4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Boolean obj) { + bbf.putInt(obj ? 1 : 0); + } + }); + + registerSerializer(new ObjectSerializer(Boolean[].class) { + @Override + public int length(Std140Serializer serializer, Boolean[] obj) { + return 4*obj.length*4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Boolean[] obj) { + for(boolean i:obj){ + bbf.putInt(i?1:0); + bbf.putInt(0); + bbf.putInt(0); + bbf.putInt(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Vector2f.class) { + @Override + public int length(Std140Serializer serializer, Vector2f obj) { + return 4 * 2; + } + + @Override + public int basicAlignment(Std140Serializer serializer) { + return 4*2; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Vector2f obj) { + bbf.putFloat(obj.x); + bbf.putFloat(obj.y); + } + }); + + registerSerializer(new ObjectSerializer(Vector2f[].class) { + @Override + public int length(Std140Serializer serializer, Vector2f[] obj) { + return 4*obj.length*4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Vector2f[] obj) { + for(Vector2f i:obj){ + bbf.putFloat(i.x); + bbf.putFloat(i.y); + bbf.putInt(0); + bbf.putInt(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Vector3f.class) { + @Override + public int length(Std140Serializer serializer, Vector3f obj) { + return 4 * 4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Vector3f obj) { + bbf.putFloat(obj.x); + bbf.putFloat(obj.y); + bbf.putFloat(obj.z); + bbf.putFloat(0); + + } + }); + + registerSerializer(new ObjectSerializer(Vector3f[].class) { + @Override + public int length(Std140Serializer serializer, Vector3f[] obj) { + return 4*obj.length*4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Vector3f[] obj) { + for(Vector3f i:obj){ + bbf.putFloat(i.x); + bbf.putFloat(i.y); + bbf.putFloat(i.z); + bbf.putInt(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Vector4f.class) { + @Override + public int length(Std140Serializer serializer, Vector4f obj) { + return 4 * 4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Vector4f obj) { + bbf.putFloat(obj.x); + bbf.putFloat(obj.y); + bbf.putFloat(obj.z); + bbf.putFloat(obj.w); + + } + }); + + registerSerializer(new ObjectSerializer(Vector4f[].class) { + @Override + public int length(Std140Serializer serializer, Vector4f[] obj) { + return 4*obj.length*4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Vector4f[] obj) { + for(Vector4f i:obj){ + bbf.putFloat(i.x); + bbf.putFloat(i.y); + bbf.putFloat(i.z); + bbf.putFloat(i.w); + } + } + }); + + + registerSerializer(new ObjectSerializer(ColorRGBA.class) { + @Override + public int length(Std140Serializer serializer, ColorRGBA obj) { + return 4 * 4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, ColorRGBA obj) { + bbf.putFloat(obj.r); + bbf.putFloat(obj.g); + bbf.putFloat(obj.b); + bbf.putFloat(obj.a); + } + }); + + registerSerializer(new ObjectSerializer(ColorRGBA[].class) { + @Override + public int length(Std140Serializer serializer, ColorRGBA[] obj) { + return 4*obj.length*4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, ColorRGBA[] obj) { + for(ColorRGBA i:obj){ + bbf.putFloat(i.r); + bbf.putFloat(i.g); + bbf.putFloat(i.b); + bbf.putFloat(i.a); + } + } + }); + + + registerSerializer(new ObjectSerializer(Quaternion.class) { + @Override + public int length(Std140Serializer serializer, Quaternion obj) { + return 4 * 4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Quaternion obj) { + bbf.putFloat(obj.getX()); + bbf.putFloat(obj.getY()); + bbf.putFloat(obj.getZ()); + bbf.putFloat(obj.getW()); + } + }); + + registerSerializer(new ObjectSerializer(Quaternion[].class) { + @Override + public int length(Std140Serializer serializer, Quaternion[] obj) { + return 4*obj.length*4; + } + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Quaternion[] obj) { + for(Quaternion i:obj){ + bbf.putFloat(i.getX()); + bbf.putFloat(i.getY()); + bbf.putFloat(i.getZ()); + bbf.putFloat(i.getW()); + } + } + }); + + registerSerializer(new ObjectSerializer(Matrix3f.class) { + @Override + public int length(Std140Serializer serializer, Matrix3f obj) { + return 3 * 4 * 4; + } + + final Vector3f tmp = new Vector3f(); + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Matrix3f obj) { + obj.getColumn(0, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + + obj.getColumn(1, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + + obj.getColumn(2, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + } + }); + + + // TODO: not sure if this is correct + registerSerializer(new ObjectSerializer(Matrix3f[].class) { + @Override + public int length(Std140Serializer serializer, Matrix3f[] obj) { + return 3 * 4 * 4 * obj.length; + } + + final Vector3f tmp = new Vector3f(); + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Matrix3f[] objs) { + for(Matrix3f obj:objs){ + obj.getColumn(0, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + + obj.getColumn(1, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + + obj.getColumn(2, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Matrix4f.class) { + @Override + public int length(Std140Serializer serializer, Matrix4f obj) { + return 4 * 4 * 4; + } + + final float[] tmpF = new float[4]; + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Matrix4f obj) { + obj.getColumn(0, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(1, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(2, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(3, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + } + }); + + + // TODO: not sure if this is correct + registerSerializer(new ObjectSerializer(Matrix4f[].class) { + @Override + public int length(Std140Serializer serializer, Matrix4f[] obj) { + return 4 * 4 * 4*obj.length; + } + + final float[] tmpF = new float[4]; + + @Override + public void write(Std140Serializer serializer, ByteBuffer bbf, Matrix4f[] objs) { + for(Matrix4f obj:objs){ + obj.getColumn(0, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(1, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(2, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(3, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + } + + } + }); + + } +}