Jelajahi Sumber

UBO/SSBO Improvements, Structs, BufferObject and sub data update (#1782)

 reimplement UBOs and SSBOs in a more convenient and performant way.
Riccardo Balbo 1 tahun lalu
induk
melakukan
0b40bba3e5
57 mengubah file dengan 4653 tambahan dan 1068 penghapusan
  1. 1 0
      jme-angle/src/native/angle
  2. 82 66
      jme3-core/src/main/java/com/jme3/material/Material.java
  3. 4 3
      jme3-core/src/main/java/com/jme3/material/Technique.java
  4. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java
  5. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java
  6. 5 4
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  7. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java
  8. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java
  9. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java
  10. 10 1
      jme3-core/src/main/java/com/jme3/math/FastMath.java
  11. 16 1
      jme3-core/src/main/java/com/jme3/renderer/RenderContext.java
  12. 13 3
      jme3-core/src/main/java/com/jme3/renderer/Renderer.java
  13. 4 0
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
  14. 152 50
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  15. 0 834
      jme3-core/src/main/java/com/jme3/shader/BufferObject.java
  16. 0 77
      jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java
  17. 33 8
      jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java
  18. 4 2
      jme3-core/src/main/java/com/jme3/shader/Uniform.java
  19. 4 4
      jme3-core/src/main/java/com/jme3/shader/VarType.java
  20. 395 0
      jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java
  21. 180 0
      jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferRegion.java
  22. 120 0
      jme3-core/src/main/java/com/jme3/shader/bufferobject/DirtyRegionsIterator.java
  23. 147 0
      jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java
  24. 542 0
      jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/RawLayout.java
  25. 614 0
      jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/Std140Layout.java
  26. 21 5
      jme3-core/src/main/java/com/jme3/system/NullRenderer.java
  27. 43 0
      jme3-core/src/main/java/com/jme3/util/struct/Struct.java
  28. 135 0
      jme3-core/src/main/java/com/jme3/util/struct/StructField.java
  29. 137 0
      jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java
  30. 217 0
      jme3-core/src/main/java/com/jme3/util/struct/StructUtils.java
  31. 63 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanArrayField.java
  32. 51 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanField.java
  33. 64 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAArrayField.java
  34. 52 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAField.java
  35. 63 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/FloatArrayField.java
  36. 51 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/FloatField.java
  37. 63 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/IntArrayField.java
  38. 51 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/IntField.java
  39. 64 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fArrayField.java
  40. 52 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fField.java
  41. 64 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fArrayField.java
  42. 52 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fField.java
  43. 64 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionArrayField.java
  44. 52 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionField.java
  45. 64 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructArrayField.java
  46. 44 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructField.java
  47. 64 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fArrayField.java
  48. 52 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fField.java
  49. 64 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fArrayField.java
  50. 52 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fField.java
  51. 64 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fArrayField.java
  52. 52 0
      jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fField.java
  53. 218 0
      jme3-core/src/test/java/com/jme3/util/StructTest.java
  54. 149 0
      jme3-examples/src/main/java/jme3test/material/TestUBO.java
  55. 107 0
      jme3-examples/src/main/resources/jme3test/ubo/TestUBO.frag
  56. 16 0
      jme3-examples/src/main/resources/jme3test/ubo/TestUBO.j3md
  57. 7 0
      jme3-examples/src/main/resources/jme3test/ubo/TestUBO.vert

+ 1 - 0
jme-angle/src/native/angle

@@ -0,0 +1 @@
+Subproject commit 2319607679d7781ff9bab5e821a34574ecb0bcc3

+ 82 - 66
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -47,6 +47,7 @@ import com.jme3.renderer.TextureUnitException;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.scene.Geometry;
 import com.jme3.shader.*;
+import com.jme3.shader.bufferobject.BufferObject;
 import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
 import com.jme3.texture.image.ColorSpace;
@@ -87,6 +88,16 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
     private boolean receivesShadows = false;
     private int sortingId = -1;
 
+    /**
+     * Track bind ids for textures and buffers
+     * Used internally 
+     */
+    public static class BindUnits {
+        public int textureUnit = 0;
+        public int bufferUnit = 0;
+    }
+    private BindUnits bindUnits = new BindUnits();
+
     public Material(MaterialDef def) {
         if (def == null) {
             throw new IllegalArgumentException("Material definition cannot be null");
@@ -506,6 +517,18 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         }
     }
 
+    /**
+     * Pass a parameter to the material shader.
+     *
+     * @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
@@ -685,8 +708,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * @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);
     }
 
     /**
@@ -696,8 +718,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * @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);
     }
 
     /**
@@ -797,7 +818,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         sortingId = -1;
     }
 
-    private int applyOverrides(Renderer renderer, Shader shader, SafeArrayList<MatParamOverride> overrides, int unit) {
+    private void applyOverrides(Renderer renderer, Shader shader, SafeArrayList<MatParamOverride> overrides, BindUnits bindUnits) {
         for (MatParamOverride override : overrides.getArray()) {
             VarType type = override.getVarType();
 
@@ -810,36 +831,64 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             Uniform uniform = shader.getUniform(override.getPrefixedName());
 
             if (override.getValue() != null) {
-                if (type.isTextureType()) {
-                    try {
-                        renderer.setTexture(unit, (Texture) override.getValue());
-                    } catch (TextureUnitException exception) {
-                        int numTexParams = unit + 1;
-                        String message = "Too many texture parameters ("
-                                + numTexParams + ") assigned\n to " + toString();
-                        throw new IllegalStateException(message);
-                    }
-                    uniform.setValue(VarType.Int, unit);
-                    unit++;
-                } else {
-                    uniform.setValue(type, override.getValue());
-                }
+                updateShaderMaterialParameter(renderer, type, shader, override, bindUnits, true);
             } else {
                 uniform.clearValue();
             }
         }
-        return unit;
     }
 
-    private int updateShaderMaterialParameters(Renderer renderer, Shader shader,
-                                               SafeArrayList<MatParamOverride> worldOverrides, SafeArrayList<MatParamOverride> forcedOverrides) {
 
-        int unit = 0;
+    private void updateShaderMaterialParameter(Renderer renderer, VarType type, Shader shader, MatParam param, BindUnits unit, boolean override) {
+        if (type == VarType.UniformBufferObject || type == VarType.ShaderStorageBufferObject) {
+            ShaderBufferBlock bufferBlock = shader.getBufferBlock(param.getPrefixedName());
+            BufferObject bufferObject = (BufferObject) param.getValue();
+
+            ShaderBufferBlock.BufferType btype;
+            if (type == VarType.ShaderStorageBufferObject) {
+                btype = ShaderBufferBlock.BufferType.ShaderStorageBufferObject;
+                bufferBlock.setBufferObject(btype, bufferObject);
+                renderer.setShaderStorageBufferObject(unit.bufferUnit, bufferObject); // TODO: probably not needed
+            } else {
+                btype = ShaderBufferBlock.BufferType.UniformBufferObject;
+                bufferBlock.setBufferObject(btype, bufferObject);
+                renderer.setUniformBufferObject(unit.bufferUnit, bufferObject); // TODO: probably not needed
+            }
+            unit.bufferUnit++;
+        } else {
+            Uniform uniform = shader.getUniform(param.getPrefixedName());
+            if (!override && uniform.isSetByCurrentMaterial()) return;
+
+            if (type.isTextureType()) {
+                try {
+                    renderer.setTexture(unit.textureUnit, (Texture) param.getValue());
+                } catch (TextureUnitException exception) {
+                    int numTexParams = unit.textureUnit + 1;
+                    String message = "Too many texture parameters (" + numTexParams + ") assigned\n to " + toString();
+                    throw new IllegalStateException(message);
+                }
+                uniform.setValue(VarType.Int, unit.textureUnit);
+                unit.textureUnit++;
+            } else {
+                uniform.setValue(type, param.getValue());
+            }
+        }
+    }
+
+
+
+
+    private BindUnits updateShaderMaterialParameters(Renderer renderer, Shader shader, SafeArrayList<MatParamOverride> worldOverrides,
+            SafeArrayList<MatParamOverride> forcedOverrides) {
+
+        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++) {
@@ -847,48 +896,15 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             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()) {
-                    try {
-                        renderer.setTexture(unit, (Texture) param.getValue());
-                    } catch (TextureUnitException exception) {
-                        int numTexParams = unit + 1;
-                        String message = "Too many texture parameters ("
-                                + numTexParams + ") assigned\n to " + toString();
-                        throw new IllegalStateException(message);
-                    }
-                    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;
+        // TODO HACKY HACK remove this when texture unit is handled by the
+        // uniform.
+        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(Geometry geometry, RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
         if (renderManager.getForcedRenderState() != null) {
@@ -1064,13 +1080,13 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         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);
     }
 
     /**

+ 4 - 3
jme3-core/src/main/java/com/jme3/material/Technique.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,6 +33,7 @@ package com.jme3.material;
 
 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;
@@ -162,9 +163,9 @@ public final class Technique {
      * @param lights Lights which influence the geometry.
      * @param lastTexUnit the index of the most recently used texture unit
      */
-    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);
     }
     
     /**

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -34,6 +34,7 @@ package com.jme3.material.logic;
 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;
@@ -91,7 +92,7 @@ public class DefaultTechniqueDefLogic implements TechniqueDefLogic {
 
 
     @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);

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -38,6 +38,7 @@ import com.jme3.light.PointLight;
 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 final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
     }
 
     @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");

+ 5 - 4
jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -34,6 +34,7 @@ package com.jme3.material.logic;
 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.*;
@@ -262,17 +263,17 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
     }
 
     @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);
             }

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -40,6 +40,7 @@ import com.jme3.light.SpotLight;
 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;
@@ -206,7 +207,7 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
     }
 
     @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();

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -38,6 +38,7 @@ import com.jme3.light.LightList;
 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 @@ public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic {
     }
 
     @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);

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,6 +33,7 @@ package com.jme3.material.logic;
 
 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;
@@ -92,5 +93,5 @@ public interface TechniqueDefLogic {
      * @param lights Lights which influence the geometry.
      * @param lastTexUnit the index of the most recently used texture unit
      */
-    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);
 }

+ 10 - 1
jme3-core/src/main/java/com/jme3/math/FastMath.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -1135,4 +1135,13 @@ final public class FastMath {
     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;
+    }
+
+
 }

+ 16 - 1
jme3-core/src/main/java/com/jme3/renderer/RenderContext.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -38,6 +38,7 @@ import com.jme3.shader.Shader;
 import com.jme3.texture.FrameBuffer;
 import com.jme3.texture.Image;
 import java.lang.ref.WeakReference;
+import com.jme3.shader.bufferobject.BufferObject;
 
 /**
  * Represents the current state of the graphics library. This class is used
@@ -49,6 +50,11 @@ public class RenderContext {
      */
     public static final int maxTextureUnits = 16;
 
+    /**
+     * Number of buffer object units that JME supports.
+     */
+    public static final int maxBufferObjectUnits = 8;
+
     /**
      * Criteria for culling faces.
      *
@@ -256,6 +262,15 @@ public class RenderContext {
     public final WeakReference<Image> boundTextures[]
             = new WeakReference[maxTextureUnits];
 
+
+    /**
+     * Current bound buffer object IDs for each buffer object unit.
+     *
+     * @see Renderer#setUniformBufferObject(int, com.jme3.shader.BufferObject)
+     * @see Renderer#setShaderStorageBufferObject(int, com.jme3.shader.BufferObject)
+     */
+    public final WeakReference<BufferObject>[] boundBO = new WeakReference[maxBufferObjectUnits];
+
     /**
      * IDList for texture units.
      *

+ 13 - 3
jme3-core/src/main/java/com/jme3/renderer/Renderer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -35,7 +35,7 @@ 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.bufferobject.BufferObject;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Shader.ShaderSource;
 import com.jme3.system.AppSettings;
@@ -306,8 +306,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.
      *
@@ -533,4 +540,7 @@ public interface Renderer {
      */
     public FrameBuffer getCurrentFrameBuffer();
 
+    public void setShaderStorageBufferObject(int bindingPoint, BufferObject bufferObject) ;
+    public void setUniformBufferObject(int bindingPoint, BufferObject bufferObject) ;
+    
 }

+ 4 - 0
jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java

@@ -68,6 +68,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;
@@ -148,10 +149,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;

+ 152 - 50
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2023 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -50,6 +50,10 @@ import com.jme3.shader.Shader.ShaderSource;
 import com.jme3.shader.Shader.ShaderType;
 import com.jme3.system.JmeSystem;
 import com.jme3.system.Platform;
+import com.jme3.shader.ShaderBufferBlock.BufferType;
+import com.jme3.shader.bufferobject.BufferObject;
+import com.jme3.shader.bufferobject.BufferRegion;
+import com.jme3.shader.bufferobject.DirtyRegionsIterator;
 import com.jme3.texture.FrameBuffer;
 import com.jme3.texture.FrameBuffer.RenderBuffer;
 import com.jme3.texture.Image;
@@ -61,7 +65,9 @@ import com.jme3.texture.image.LastTextureState;
 import com.jme3.util.BufferUtils;
 import com.jme3.util.ListMap;
 import com.jme3.util.MipMapGenerator;
+import com.jme3.util.NativeObject;
 import com.jme3.util.NativeObjectManager;
+
 import jme3tools.shader.ShaderDebug;
 
 import java.lang.ref.WeakReference;
@@ -1445,44 +1451,61 @@ public final class GLRenderer implements Renderer {
         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());
+                setUniformBufferObject(bindingPoint, bufferObject); // rebind buffer if needed
+                if (bufferBlock.isUpdateNeeded()) {
+                    int blockIndex = bufferBlock.getLocation();
+                    if (blockIndex < 0) {
+                        blockIndex = gl3.glGetUniformBlockIndex(shaderId, bufferBlock.getName());
+                        bufferBlock.setLocation(blockIndex);
+                    }
+                    if (bufferBlock.getLocation() != NativeObject.INVALID_ID) {
+                        gl3.glUniformBlockBinding(shaderId, bufferBlock.getLocation(), 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());
+                setShaderStorageBufferObject(bindingPoint, bufferObject); // rebind buffer if needed
+                if (bufferBlock.isUpdateNeeded() ) {
+                    int blockIndex = bufferBlock.getLocation();
+                    if (blockIndex < 0) {
+                        blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, bufferBlock.getName());
+                        bufferBlock.setLocation(blockIndex);
+                    }
+                    if (bufferBlock.getLocation() != NativeObject.INVALID_ID) {
+                        gl4.glShaderStorageBlockBinding(shaderId, bufferBlock.getLocation(), bindingPoint);
+                    }
+                }
                 break;
             }
             default: {
                 throw new IllegalArgumentException("Doesn't support binding of " + bufferType);
             }
         }
+
+        bufferBlock.clearUpdateNeeded();
     }
 
     protected void updateShaderUniforms(Shader shader) {
@@ -1543,7 +1566,7 @@ public final class GLRenderer implements Renderer {
 
             source.setId(id);
             if (debug && caps.contains(Caps.GLDebug)) {
-                if(source.getName() != null) glext.glObjectLabel(GLExt.GL_SHADER, id, source.getName());
+                if(source.getName()!=null) glext.glObjectLabel(GLExt.GL_SHADER, id, source.getName());
             }
         } else {
             throw new RendererException("Cannot recompile shader source");
@@ -2200,7 +2223,7 @@ public final class GLRenderer implements Renderer {
 
             context.boundFB = fb;
             if (debug && caps.contains(Caps.GLDebug)) {
-                 if (fb.getName() != null) glext.glObjectLabel(GL3.GL_FRAMEBUFFER, fb.getId(), fb.getName());
+                if (fb.getName() != null) glext.glObjectLabel(GL3.GL_FRAMEBUFFER, fb.getId(), fb.getName());
             }
         }
     }
@@ -2730,6 +2753,42 @@ public final class GLRenderer implements Renderer {
         }
     }
 
+    @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, bufferObject.getId());
+            bufferObject.setBinding(bindingPoint);
+            context.boundBO[bindingPoint] = bufferObject.getWeakRef();
+        }
+
+        bufferObject.setBinding(bindingPoint);
+
+        if (debug && caps.contains(Caps.GLDebug)) {
+            if (bufferObject.getName() != null) glext.glObjectLabel(GLExt.GL_BUFFER, bufferObject.getId(), bufferObject.getName());
+        }
+
+    }
+
+    @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, bufferObject.getId());
+            bufferObject.setBinding(bindingPoint);
+            context.boundBO[bindingPoint] = bufferObject.getWeakRef();
+        }
+        bufferObject.setBinding(bindingPoint);
+
+        if (debug && caps.contains(Caps.GLDebug)) {
+            if (bufferObject.getName() != null) glext.glObjectLabel(GLExt.GL_BUFFER, bufferObject.getId(), bufferObject.getName());
+        }
+    }
 
     /**
      * @deprecated Use modifyTexture(Texture2D dest, Image src, int destX, int destY, int srcX, int srcY, int areaW, int areaH)
@@ -2897,23 +2956,63 @@ public final class GLRenderer implements Renderer {
         vb.clearUpdateNeeded();
     }
 
-    @Override
-    public void updateBufferData(final BufferObject bo) {
-
-        int maxSize = Integer.MAX_VALUE;
+    private int resolveUsageHint(BufferObject.AccessHint ah, BufferObject.NatureHint nh) {
+        switch (ah) {
+            case Dynamic: {
+                switch (nh) {
+                    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 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 Draw:
+                        return GL.GL_STATIC_DRAW;
+                    case Read:
+                        return GL.GL_STATIC_READ;
+                    case Copy:
+                        return GL.GL_STATIC_COPY;
+                }
+            }
+            default:
+        }
+        return -1;
+    }
 
-        final BufferObject.BufferType bufferType = bo.getBufferType();
+    @Override
+    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);
+    }
 
-        if (!caps.contains(bufferType.getRequiredCaps())) {
-            throw new IllegalArgumentException("The current video hardware doesn't support " + bufferType);
-        }
+    @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);
+    }
 
-        final ByteBuffer data = bo.computeData(maxSize);
-        if (data == null) {
-            throw new IllegalArgumentException("Can't upload BO without data.");
+    private void updateBufferData(int type, BufferObject bo) {
+        int bufferId = bo.getId();
+        int usage = resolveUsageHint(bo.getAccessHint(), bo.getNatureHint());
+        if (usage == -1) {
+            deleteBuffer(bo);
+            return; // CpuOnly
         }
 
-        int bufferId = bo.getId();
         if (bufferId == -1) {
 
             // create buffer
@@ -2926,26 +3025,29 @@ public final class GLRenderer implements Renderer {
             objManager.registerObject(bo);
         }
 
-        data.rewind();
+        DirtyRegionsIterator it = bo.getDirtyRegions();
+        BufferRegion reg;
 
-        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);
+        while ((reg = it.next()) != null) {
+            gl3.glBindBuffer(type, bufferId);
+            if (reg.isFullBufferRegion()) {
+                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);
+                gl3.glBindBuffer(type, 0);
+                reg.clearDirty();
                 break;
-            }
-            default: {
-                throw new IllegalArgumentException("Doesn't support binding of " + bufferType);
+            } 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.getStart(), reg.getData());
+                gl3.glBindBuffer(type, 0);
+                reg.clearDirty();
             }
         }
-
         bo.clearUpdateNeeded();
     }
 

+ 0 - 834
jme3-core/src/main/java/com/jme3/shader/BufferObject.java

@@ -1,834 +0,0 @@
-package com.jme3.shader;
-
-import com.jme3.math.*;
-import com.jme3.renderer.Caps;
-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;
-
-/**
- * The base implementation of BO.
- *
- * @author JavaSaBr
- */
-public class BufferObject extends NativeObject {
-
-    private static final Map<Class<?>, 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;
-        }
-
-        /**
-         * Get the required caps.
-         *
-         * @return the required caps.
-         */
-        public Caps getRequiredCaps() {
-            return requiredCaps;
-        }
-    }
-
-    /**
-     * The fields of this BO.
-     */
-    private final Map<String, BufferObjectField> fields;
-
-    /**
-     * The field's array.
-     */
-    private final SafeArrayList<BufferObjectField> 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);
-    }
-
-    public BufferObject(final int binding, final Layout layout) {
-        this(binding, layout, BufferType.UniformBufferObject);
-    }
-
-    public BufferObject(final int binding, final BufferType bufferType) {
-        this(binding, Layout.std140, bufferType);
-    }
-
-    public BufferObject(final BufferType bufferType) {
-        this(1, Layout.std140, bufferType);
-    }
-
-    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.");
-        }
-
-        final BufferObjectField field = new BufferObjectField(name, varType);
-
-        fields.put(name, field);
-        fieldArray.add(field);
-    }
-
-    /**
-     * Gets the buffer's type.
-     *
-     * @return the buffer's type.
-     */
-    public BufferType getBufferType() {
-        return bufferType;
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * 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();
-    }
-
-    /**
-     * Gets the current value of the field by the name.
-     *
-     * @param name the field name.
-     * @param <T> the value's type.
-     * @return the current value.
-     */
-    @SuppressWarnings("unchecked")
-    public <T> T getFieldValue(final String name) {
-
-        final BufferObjectField field = fields.get(name);
-        if (field == null) {
-            throw new IllegalArgumentException("Unknown a field with the name " + name);
-        }
-
-        return (T) field.getValue();
-    }
-
-    /**
-     * Get the binding number.
-     *
-     * @return the binding number.
-     */
-    public int getBinding() {
-        return binding;
-    }
-
-    @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 byte count to present the value on the GPU.
-     *
-     * @param value      the value.
-     * @param multiplier the multiplier.
-     * @return the estimated byte count.
-     */
-    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 byte count to present the values on the GPU.
-     *
-     * @param values the values.
-     * @return the estimated byte count.
-     */
-    protected int estimate(final float[] values) {
-        return values.length * 4;
-    }
-
-    /**
-     * Estimates byte count to present the values on the GPU.
-     *
-     * @param values the values.
-     * @return the estimated byte count.
-     */
-    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.
-     */
-    @SuppressWarnings("unchecked")
-    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<Matrix3f> values = (SafeArrayList<Matrix3f>) value;
-            for (final Matrix3f mat : values.getArray()) {
-                write(data, mat);
-            }
-
-        } else if(value instanceof Collection) {
-
-            final Collection<Matrix3f> values = (Collection<Matrix3f>) 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.
-     */
-    @SuppressWarnings("unchecked")
-    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<Matrix4f> values = (SafeArrayList<Matrix4f>) value;
-            for (final Matrix4f mat : values.getArray()) {
-                write(data, mat);
-            }
-
-        } else if(value instanceof Collection) {
-
-            final Collection<Matrix4f> values = (Collection<Matrix4f>) 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.
-     */
-    @SuppressWarnings("unchecked")
-    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<Object> values = (SafeArrayList<Object>) value;
-            for (final Object vec : values.getArray()) {
-                writeVec4(data, vec);
-            }
-
-        } else if(value instanceof Collection) {
-
-            final Collection<Object> values = (Collection<Object>) 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.
-     */
-    @SuppressWarnings("unchecked")
-    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<Vector3f> values = (SafeArrayList<Vector3f>) value;
-            for (final Vector3f vec : values.getArray()) {
-                write(data, vec);
-            }
-
-        } else if(value instanceof Collection) {
-
-            final Collection<Vector3f> values = (Collection<Vector3f>) 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.
-     */
-    @SuppressWarnings("unchecked")
-    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<Vector2f> values = (SafeArrayList<Vector2f>) value;
-            for (final Vector2f vec : values.getArray()) {
-                write(data, vec);
-            }
-
-        } else if(value instanceof Collection) {
-
-            final Collection<Vector2f> values = (Collection<Vector2f>) 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));
-    }
-
-    @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());
-    }
-
-    @Override
-    protected void deleteNativeBuffers() {
-        super.deleteNativeBuffers();
-        if (previousData != null) {
-            BufferUtils.destroyDirectBuffer(previousData);
-            previousData = null;
-        }
-    }
-
-    @Override
-    public long getUniqueId() {
-        return ((long) OBJTYPE_BO << 32) | (0xffffffffL & (long) id);
-    }
-}

+ 0 - 77
jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java

@@ -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 +
-            '}';
-    }
-}

+ 33 - 8
jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2018 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,34 +31,49 @@
  */
 package com.jme3.shader;
 
+import java.lang.ref.WeakReference;
+
+import com.jme3.shader.bufferobject.BufferObject;
+
 /**
  * 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<BufferObject> bufferObjectRef;
+    protected BufferType type;
 
     /**
      * Set the new buffer object.
      *
-     * @param bufferObject 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");
         }
-
+        if (bufferObject == this.bufferObject && type == this.type) return;
         this.bufferObject = bufferObject;
-
+        this.bufferObjectRef = new WeakReference<BufferObject>(bufferObject);
+        this.type = type;
         updateNeeded = true;
     }
 
+    public BufferType getType() {
+        return type;
+    }
+
     /**
      * Return true if need to update this storage block.
      *
@@ -78,7 +93,8 @@ public class ShaderBufferBlock extends ShaderVariable {
     /**
      * Reset this storage block.
      */
-    public void reset(){
+    public void reset() {
+        location = -1;
         updateNeeded = true;
     }
 
@@ -90,4 +106,13 @@ public class ShaderBufferBlock extends ShaderVariable {
     public BufferObject getBufferObject() {
         return bufferObject;
     }
+
+    public WeakReference<BufferObject> getBufferObjectRef() {
+        return bufferObjectRef;
+    }
+
+    public void setBufferObjectRef(WeakReference<BufferObject> bufferObjectRef) {
+        this.bufferObjectRef = bufferObjectRef;
+    }
+
 }

+ 4 - 2
jme3-core/src/main/java/com/jme3/shader/Uniform.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -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;
@@ -197,7 +198,8 @@ public class Uniform extends ShaderVariable {
         }
     }
 
-    public void setValue(VarType type, Object value){
+    public void setValue(VarType type, Object value) {
+        assert !(value instanceof BindUnits);
         if (location == LOC_NOT_DEFINED) {
             return;
         }

+ 4 - 4
jme3-core/src/main/java/com/jme3/shader/VarType.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,8 +31,8 @@
  */
 package com.jme3.shader;
 import com.jme3.math.*;
+import com.jme3.shader.bufferobject.BufferObject;
 import com.jme3.texture.*;
-import com.jme3.shader.BufferObject;
 public enum VarType {
 
     Float("float",float.class,Float.class),
@@ -60,8 +60,8 @@ public enum VarType {
     TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow",TextureArray.class,Texture.class),
     TextureCubeMap(false,true,"samplerCube",TextureCubeMap.class,Texture.class),
     Int("int",int.class,Integer.class),
-    BufferObject(false, false, "custom", BufferObject.class);
-
+    UniformBufferObject(false, false, "custom",BufferObject.class), 
+    ShaderStorageBufferObject(false, false, "custom",BufferObject.class);
 
     private boolean usesMultiData = false;
     private boolean textureType = false;

+ 395 - 0
jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java

@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shader.bufferobject;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+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;
+import com.jme3.renderer.Renderer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.NativeObject;
+
+/**
+ * A generic memory buffer that can be divided in logical regions
+ * 
+ * @author Riccardo Balbo
+ */
+public class BufferObject extends NativeObject implements Savable {
+    /**
+     * Hint to suggest the renderer how to access this buffer
+     */
+    public static enum AccessHint {
+        /**
+         * The data store contents will be modified once and used many times.
+         */
+        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
+    }
+
+    /**
+     * Hint to suggest the renderer how the data should be used
+     */
+    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
+    }
+
+    private AccessHint accessHint = AccessHint.Dynamic;
+    private NatureHint natureHint = NatureHint.Draw;
+
+    private transient WeakReference<BufferObject> weakRef;
+    private transient int binding = -1;
+    protected transient DirtyRegionsIterator dirtyRegionsIterator;
+
+    protected ByteBuffer data = null;
+    protected ArrayList<BufferRegion> regions = new ArrayList<BufferRegion>();
+    private String name;
+
+    public BufferObject() {
+        super();
+    }
+
+  
+    protected BufferObject(int id) {
+        super(id);
+    }
+
+  
+    /**
+     * Internal use only. Indicates that the object has changed and its state
+     * needs to be updated. Mark all the regions as dirty.
+     */
+    public final void setUpdateNeeded() {
+        setUpdateNeeded(true);
+    }
+
+    /**
+     * Indicates that the object has changed and its state needs to be updated.
+     * 
+     * @param dirtyAll
+     *            mark all regions for update
+     */
+    public void setUpdateNeeded(boolean dirtyAll) {
+        if (dirtyAll) markAllRegionsDirty();
+        updateNeeded = true;
+    }
+
+
+    /**
+     * Get binding point
+     * 
+     * @return the binding point
+     */
+    public int getBinding() {
+        return binding;
+    }
+
+
+    /**
+     * Initialize an empty buffer object of the given length
+     * 
+     * @param length expected length of the buffer object
+     */
+    public void initializeEmpty(int length) {
+        if (data != null) {
+            BufferUtils.destroyDirectBuffer(data);
+        }
+        this.data = BufferUtils.createByteBuffer(length);
+    }
+
+
+    /**
+     * Transfer remaining bytes of passed buffer to the internal buffer of this buffer object
+     * 
+     * @param data ByteBuffer containing the data to pass
+     */
+    public void setData(ByteBuffer data) {
+        if (data != null) {
+            BufferUtils.destroyDirectBuffer(data);
+        }
+        this.data = BufferUtils.createByteBuffer(data.limit() - data.position());
+        this.data.put(data);
+    }
+
+
+  
+    /**
+     * Rewind and return buffer data
+     * 
+     * @return
+     */
+    public ByteBuffer getData() {
+        if (regions.size() == 0) {
+            if (data == null) data = BufferUtils.createByteBuffer(0);
+        } else {
+            int regionsEnd = regions.get(regions.size() - 1).getEnd();
+            if (data == null) {
+                data = BufferUtils.createByteBuffer(regionsEnd + 1);
+            } else if (data.limit() < regionsEnd) {
+                // new buffer
+                ByteBuffer newData = BufferUtils.createByteBuffer(regionsEnd + 1);
+
+                // copy old buffer in new buffer
+                if (newData.limit() < data.limit()) data.limit(newData.limit());
+                newData.put(data);
+
+                // destroy old buffer
+                BufferUtils.destroyDirectBuffer(data);
+
+                data = newData;
+            }
+        }
+        data.rewind();
+        return data;
+    }
+
+    /**
+     * Get dirty regions
+     * 
+     * @return Helper object to iterate through dirty regions
+     */
+    public DirtyRegionsIterator getDirtyRegions() {
+        if (dirtyRegionsIterator == null) dirtyRegionsIterator = new DirtyRegionsIterator(this);
+        dirtyRegionsIterator.rewind();
+        return dirtyRegionsIterator;
+    }
+
+    /**
+     * Reset layour definition
+     */
+    public void unsetRegions() {
+        regions.clear();
+        regions.trimToSize();
+    }
+
+    /**
+     * Add a region at the end of the layout
+     * 
+     * @param lr
+     */
+    public void setRegions(List<BufferRegion> lr) {
+        regions.clear();
+        regions.addAll(lr);
+        regions.trimToSize();
+        setUpdateNeeded();
+    }
+
+  
+    /**
+     * Return all the regions of this layout
+     * 
+     * @return ordered list of regions
+     */
+    public BufferRegion getRegion(int i) {
+        BufferRegion region = regions.get(i);
+        region.bo = this;
+        return region;
+    }
+
+    /**
+     * Mark all regions as dirty
+     */
+    public void markAllRegionsDirty() {
+        for (BufferRegion r : regions) r.markDirty();
+    }
+
+
+    @Override
+    public void resetObject() {
+        this.id = -1;
+    }
+
+    @Override
+    protected void deleteNativeBuffers() {
+        super.deleteNativeBuffers();
+        if (data != null) BufferUtils.destroyDirectBuffer(data);
+    }
+
+    @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(getId());
+    }
+
+    @Override
+    public long getUniqueId() {
+        return ((long) OBJTYPE_BO << 32) | (0xffffffffL & (long) id);
+    }
+
+    /**
+     * Set binding point
+     * @param binding binding point
+     */
+    public void setBinding(final int binding) {
+        this.binding = binding;
+    }
+
+    public WeakReference<BufferObject> getWeakRef() {
+        if (weakRef == null) weakRef = new WeakReference<BufferObject>(this);
+        return weakRef;
+    }
+
+    public AccessHint getAccessHint() {
+        return accessHint;
+    }
+
+    /**
+     * Set AccessHint to hint the renderer on how to access this data. 
+     * 
+     * @param natureHint
+     */
+    public void setAccessHint(AccessHint accessHint) {
+        this.accessHint = accessHint;
+        setUpdateNeeded();
+    }
+
+    public NatureHint getNatureHint() {
+        return natureHint;
+    }
+
+    /**
+     * Set NatureHint to hint the renderer on how to use this data. 
+     * 
+     * @param natureHint
+     */
+    public void setNatureHint(NatureHint natureHint) {
+        this.natureHint = natureHint;
+        setUpdateNeeded();
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(accessHint.ordinal(), "accessHint", 0);
+        oc.write(natureHint.ordinal(), "natureHint", 0);
+        oc.writeSavableArrayList(regions, "regions", null);
+        oc.write(data, "data", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        accessHint = AccessHint.values()[ic.readInt("accessHint", 0)];
+        natureHint = NatureHint.values()[ic.readInt("natureHint", 0)];
+        regions.addAll(ic.readSavableArrayList("regions", null));
+        data = ic.readByteBuffer("data", null);
+        setUpdateNeeded(true);
+    }
+
+    @Override
+    public BufferObject clone() {
+        BufferObject clone = (BufferObject) super.clone();
+        clone.binding = -1;
+        clone.weakRef = null;
+        clone.data = BufferUtils.clone(data);
+        clone.regions = new ArrayList<BufferRegion>();
+        assert clone.regions != regions;
+        for (BufferRegion r : regions) {
+            clone.regions.add(r.clone());
+        }
+        clone.dirtyRegionsIterator = null;
+
+        clone.setUpdateNeeded();
+        return clone;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName()).append("{\n");
+        for (BufferRegion r : regions) {
+            sb.append("    ").append(r).append("\n");
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Get name of the buffer object
+     * 
+     * @return the name of this buffer object, can be null
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set name for debugging purposes
+     * 
+     * @param name
+     *            the name of this buffer object
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+}

+ 180 - 0
jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferRegion.java

@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shader.bufferobject;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+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;
+
+/**
+ * A slice of a buffer
+ * 
+ * @author Riccardo Balbo
+ *
+ */
+public class BufferRegion implements Savable, Cloneable {
+    protected int start = -1;
+    protected int end = -1;
+    protected boolean dirty = true;
+    protected boolean fullBufferRegion = false;
+    protected BufferObject bo;
+    protected ByteBuffer slice;
+    protected ByteBuffer source;
+
+    public BufferRegion(int start, int end) {
+        this.start = start;
+        this.end = end;
+    }
+
+    public BufferRegion() {
+
+    }
+
+    /**
+     * Rewind and get a ByteBuffer pointing to this region of the main buffer
+     * 
+     * @return ByteBuffer
+     */
+    public ByteBuffer getData() {
+        ByteBuffer d = bo.getData();
+        if (source == null || d != source || slice == null) {
+            source = d;
+            int currentPos = source.position();
+            int currentLimit = source.limit();
+            assert end < source.capacity() : "Can't set limit at " + end + " on capacity " + source.capacity();
+            source.limit(end + 1);
+            source.position(start);
+            slice = source.slice();
+            slice.order(source.order());
+            assert slice.limit() == (end - start + 1) : "Capacity is " + slice.limit() + " but " + (end - start + 1) + " expected";
+            source.limit(currentLimit);
+            source.position(currentPos);
+        }
+        slice.rewind();
+        return slice;
+    }
+
+    /**
+     * Get beginning of the region
+     * 
+     * @return position in the buffer
+     */
+    public int getStart() {
+        return start;
+    }
+
+    /**
+     * Get end of the region
+     * 
+     * @return position in the buffer
+     */
+    public int getEnd() {
+        return end;
+    }
+
+    /**
+     * Get the length of this region
+     * 
+     * @return the length of this region
+     */
+    public int length() {
+        return end - start + 1;
+    }
+
+    /**
+     * Returns true of the region is dirty
+     * 
+     * @return
+     */
+    public boolean isDirty() {
+        return dirty;
+    }
+
+    /**
+     * Mark this region for update
+     */
+    public void markDirty() {
+        dirty = true;
+    }
+
+    /**
+     * Clear this region mark
+     */
+    public void clearDirty() {
+        dirty = false;
+    }
+
+    /**
+     * Returns true if this region includes the entire buffer. Can be used for
+     * optimization.
+     * 
+     * @return
+     */
+    public boolean isFullBufferRegion() {
+        return fullBufferRegion;
+    }
+
+    @Override
+    public String toString() {
+        return "Region [start=" + start + ", end=" + end + ", size=" + (end - start) + ", dirty=" + dirty + "]";
+    }
+
+    @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);
+    }
+
+    @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);
+    }
+
+    @Override
+    public BufferRegion clone() {
+        try {
+            return (BufferRegion) super.clone();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 120 - 0
jme3-core/src/main/java/com/jme3/shader/bufferobject/DirtyRegionsIterator.java

@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shader.bufferobject;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An helper class that iterates and merges dirty regions
+ * 
+ * @author Riccardo Balbo
+ */
+public class DirtyRegionsIterator implements Iterator<BufferRegion> {
+
+    private static class DirtyRegion extends BufferRegion {
+        List<BufferRegion> regions = new ArrayList<BufferRegion>();
+
+        @Override
+        public ByteBuffer getData() {
+            ByteBuffer d = bo.getData();
+            if (source == null || d != source || slice == null) {
+                source = d;
+                int currentPos = source.position();
+                int currentLimit = source.limit();
+                source.limit(source.capacity());
+                source.position(0);
+                slice = source.slice();
+                source.limit(currentLimit);
+                source.position(currentPos);
+            }
+            slice.limit(end);
+            slice.position(start);
+            return slice;
+        }
+
+        public void clearDirty() {
+            regions.forEach(BufferRegion::clearDirty);
+            super.clearDirty();
+        }
+    }
+
+    private BufferObject bufferObject;
+
+    public DirtyRegionsIterator(BufferObject bufferObject) {
+        this.bufferObject = bufferObject;
+    }
+
+    private final DirtyRegion dirtyRegion = new DirtyRegion();
+    private int pos = 0;
+
+    public void rewind() {
+        pos = 0;
+    }
+
+    public boolean hasNext() {
+        return pos < bufferObject.regions.size();
+    }
+
+    public BufferRegion next() {
+
+        dirtyRegion.bo = bufferObject;
+        dirtyRegion.regions.clear();
+
+        if (bufferObject.regions.size() == 0) {
+            if (!bufferObject.isUpdateNeeded()) return null;
+            dirtyRegion.fullBufferRegion = true;
+            dirtyRegion.end = bufferObject.getData().limit();
+            dirtyRegion.start = 0;
+            return dirtyRegion;
+        }
+
+
+        while (pos < bufferObject.regions.size()) {
+            BufferRegion dr = bufferObject.regions.get(pos++);
+            if (dr.isDirty()) {
+                if (dirtyRegion.regions.size() == 0) dirtyRegion.start = dr.start;
+                dirtyRegion.end = dr.end;
+                dirtyRegion.regions.add(dr);
+            } else if (dirtyRegion.regions.size() != 0) break;
+        }
+
+        if (dirtyRegion.regions.size() == 0) return null;
+        dirtyRegion.fullBufferRegion = dirtyRegion.regions.size() == bufferObject.regions.size();
+        dirtyRegion.markDirty();
+
+        return dirtyRegion;
+    }
+
+}

+ 147 - 0
jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java

@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shader.bufferobject.layout;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.jme3.math.FastMath;
+import com.jme3.util.functional.Function;
+
+/**
+ * Layout serializer for buffers
+ * 
+ * @author Riccardo Balbo
+ */
+public abstract class  BufferLayout {
+
+    public static abstract class ObjectSerializer<T> {
+        private Function<Boolean, Object> filter;
+
+        public ObjectSerializer(Class<T> cls) {
+            this(obj -> {
+                Class<?> objc = obj instanceof Class ? (Class<?>) obj : obj.getClass();
+                return cls.isAssignableFrom(objc);
+            });
+
+        }
+
+        public ObjectSerializer(Function<Boolean, Object> filter) {
+            this.filter = filter;
+        }
+
+        public final boolean canSerialize(Object obj) {
+            return filter.eval(obj);
+        }
+
+        public abstract int length(BufferLayout layout, T obj);
+
+        public abstract int basicAlignment(BufferLayout layout, T obj);
+
+        public abstract void write(BufferLayout layout, ByteBuffer bbf, T obj);
+    }
+
+    protected List<ObjectSerializer<?>> serializers = new ArrayList<ObjectSerializer<?>>();
+
+    protected ObjectSerializer<?> getSerializer(Object obj) {
+        for (int i = serializers.size() - 1; i >= 0; i--) {
+            ObjectSerializer<?> sr = serializers.get(i);
+            if (sr.canSerialize(obj)) return sr;
+            
+        }
+        throw new RuntimeException("Serializer not found for " + obj + " of type " + obj.getClass());
+    }
+
+    /**
+     * Register a serializer
+     * 
+     * @param type
+     */
+    protected void registerSerializer(ObjectSerializer<?> serializer) {
+        serializers.add(serializer);
+    }
+    
+    /**
+     * Estimate size of Object when serialized accordingly with std140
+     * 
+     * @param o
+     *            the object to serialize
+     * @return the size
+     */
+    public int estimateSize(Object o) {
+        ObjectSerializer s = getSerializer(o);
+        return s.length(this, o);
+    }
+    /**
+     * Get basic alignment of Object when serialized accordingly with std140
+     * 
+     * @param o
+     *            the object to serialize
+     * @return the basic alignment
+     */
+
+    public int getBasicAlignment(Object o) {
+        ObjectSerializer s = getSerializer(o);
+        return s.basicAlignment(this, o);
+    }
+    /**
+     * Align a position to the given basicAlignment
+     * 
+     * @param pos
+     *            the position to align
+     * @param basicAlignment
+     *            the basic alignment
+     * @return the aligned position
+     */
+    public int align(int pos, int basicAlignment) {
+        return pos==0?pos:FastMath.toMultipleOf(pos, basicAlignment);
+    }
+
+    /**
+     * Serialize an object accordingly with the std140 layout and write the
+     * result to a BufferObject
+     * 
+     * @param out
+     *            the output BufferObject where the object will be serialized
+     *            (starting from the current position)
+     * @param o
+     *            the Object to serialize
+     */
+    public void write(ByteBuffer out, Object o) {
+        ObjectSerializer s = getSerializer(o);        
+        s.write(this, out, o);
+    }
+
+
+    public abstract String getId();
+}

+ 542 - 0
jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/RawLayout.java

@@ -0,0 +1,542 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shader.bufferobject.layout;
+
+import java.nio.ByteBuffer;
+
+import com.jme3.math.ColorRGBA;
+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;
+
+/**
+ * Simple serializer
+ * 
+ * @author Riccardo Balbo
+ */
+public class RawLayout extends BufferLayout {
+    public RawLayout() {
+        // Init default serializers
+        registerSerializer(new ObjectSerializer<byte[]>(byte[].class) {
+            @Override
+            public int length(BufferLayout serializer, byte[] obj) {
+                return obj.length;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, byte[] obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, byte[] obj) {
+                bbf.put(obj);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Integer>(Integer.class) {
+            @Override
+            public int length(BufferLayout serializer, Integer obj) {
+                return 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Integer obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Integer obj) {
+                bbf.putInt(obj);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Boolean>(Boolean.class) {
+            @Override
+            public int length(BufferLayout serializer, Boolean obj) {
+                return 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Boolean obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Boolean obj) {
+                bbf.putInt(obj ? 1 : 0);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Float>(Float.class) {
+            @Override
+            public int length(BufferLayout serializer, Float obj) {
+                return 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Float obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Float obj) {
+                bbf.putFloat(obj);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Vector2f>(Vector2f.class) {
+            @Override
+            public int length(BufferLayout serializer, Vector2f obj) {
+                return 4 * 2;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector2f obj) {
+                return 1;
+
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Vector2f obj) {
+                bbf.putFloat(obj.x);
+                bbf.putFloat(obj.y);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<ColorRGBA>(ColorRGBA.class) {
+            @Override
+            public int length(BufferLayout serializer, ColorRGBA obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, ColorRGBA obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, ColorRGBA obj) {
+                bbf.putFloat(obj.r);
+                bbf.putFloat(obj.g);
+                bbf.putFloat(obj.b);
+                bbf.putFloat(obj.a);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Quaternion>(Quaternion.class) {
+            @Override
+            public int length(BufferLayout serializer, Quaternion obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Quaternion obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Quaternion obj) {
+                bbf.putFloat(obj.getX());
+                bbf.putFloat(obj.getY());
+                bbf.putFloat(obj.getZ());
+                bbf.putFloat(obj.getW());
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Vector4f>(Vector4f.class) {
+            @Override
+            public int length(BufferLayout serializer, Vector4f obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector4f obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Vector4f obj) {
+                bbf.putFloat(obj.x);
+                bbf.putFloat(obj.y);
+                bbf.putFloat(obj.z);
+                bbf.putFloat(obj.w);
+
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Vector3f>(Vector3f.class) {
+            @Override
+            public int length(BufferLayout serializer, Vector3f obj) {
+                return 4 * 3;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector3f obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Vector3f obj) {
+                bbf.putFloat(obj.x);
+                bbf.putFloat(obj.y);
+                bbf.putFloat(obj.z);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Integer[]>(Integer[].class) {
+            @Override
+            public int length(BufferLayout serializer, Integer[] obj) {
+                return 4 * obj.length;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Integer[] obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Integer[] obj) {
+                for (int i : obj) {
+                    bbf.putInt(i);
+                }
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Float[]>(Float[].class) {
+            @Override
+            public int length(BufferLayout serializer, Float[] obj) {
+                return 4 * obj.length;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Float[] obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Float[] obj) {
+                for (float i : obj) {
+                    bbf.putFloat(i);
+                }
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Boolean[]>(Boolean[].class) {
+            @Override
+            public int length(BufferLayout serializer, Boolean[] obj) {
+                return 4 * obj.length;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Boolean[] obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Boolean[] obj) {
+                for (boolean i : obj) {
+                    bbf.putInt(i ? 1 : 0);
+
+                }
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Vector2f[]>(Vector2f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Vector2f[] obj) {
+                return 4 * obj.length * 2;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector2f[] obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Vector2f[] obj) {
+                for (Vector2f i : obj) {
+                    bbf.putFloat(i.x);
+                    bbf.putFloat(i.y);
+
+                }
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Vector3f[]>(Vector3f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Vector3f[] obj) {
+                return 4 * obj.length * 3;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector3f[] obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Vector3f[] obj) {
+                for (Vector3f i : obj) {
+                    bbf.putFloat(i.x);
+                    bbf.putFloat(i.y);
+                    bbf.putFloat(i.z);
+                }
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Vector4f[]>(Vector4f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Vector4f[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector4f[] obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout 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[]>(ColorRGBA[].class) {
+            @Override
+            public int length(BufferLayout serializer, ColorRGBA[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, ColorRGBA[] obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout 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[]>(Quaternion[].class) {
+            @Override
+            public int length(BufferLayout serializer, Quaternion[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Quaternion[] obj) {
+                return 1;
+            }
+
+            @Override
+            public void write(BufferLayout 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>(Matrix3f.class) {
+            @Override
+            public int length(BufferLayout serializer, Matrix3f obj) {
+                return 3 * 4 * 3;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Matrix3f obj) {
+                return 1;
+            }
+
+            final Vector3f tmp = new Vector3f();
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Matrix3f obj) {
+                obj.getColumn(0, tmp);
+                bbf.putFloat(tmp.x);
+                bbf.putFloat(tmp.y);
+                bbf.putFloat(tmp.z);
+
+                obj.getColumn(1, tmp);
+                bbf.putFloat(tmp.x);
+                bbf.putFloat(tmp.y);
+                bbf.putFloat(tmp.z);
+
+                obj.getColumn(2, tmp);
+                bbf.putFloat(tmp.x);
+                bbf.putFloat(tmp.y);
+                bbf.putFloat(tmp.z);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Matrix4f>(Matrix4f.class) {
+            @Override
+            public int length(BufferLayout serializer, Matrix4f obj) {
+                return 4 * 4 * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Matrix4f obj) {
+                return 1;
+            }
+
+            final float[] tmpF = new float[4];
+
+            @Override
+            public void write(BufferLayout 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]);
+
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Matrix3f[]>(Matrix3f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Matrix3f[] obj) {
+                return 3 * 4 * 3 * obj.length;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Matrix3f[] obj) {
+                return 1;
+            }
+
+            final Vector3f tmp = new Vector3f();
+
+            @Override
+            public void write(BufferLayout 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);
+
+                    obj.getColumn(1, tmp);
+                    bbf.putFloat(tmp.x);
+                    bbf.putFloat(tmp.y);
+                    bbf.putFloat(tmp.z);
+
+                    obj.getColumn(2, tmp);
+                    bbf.putFloat(tmp.x);
+                    bbf.putFloat(tmp.y);
+                    bbf.putFloat(tmp.z);
+                }
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Matrix4f[]>(Matrix4f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Matrix4f[] obj) {
+                return 4 * 4 * 4 * obj.length;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Matrix4f[] obj) {
+                return 1;
+            }
+
+            final float[] tmpF = new float[4];
+
+            @Override
+            public void write(BufferLayout 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]);
+                }
+
+            }
+        });
+
+    }
+
+    @Override
+    public String getId() {
+        return "raw";
+    }
+}

+ 614 - 0
jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/Std140Layout.java

@@ -0,0 +1,614 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shader.bufferobject.layout;
+
+import java.nio.ByteBuffer;
+
+import com.jme3.math.ColorRGBA;
+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;
+
+/**
+ * Serializer that respects the Std140 layout
+ * (https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159)
+ * (www.opengl.org/registry/specs/ARB/uniform_buffer_object.txt)
+ * 
+ * @author Riccardo Balbo
+ */
+public class Std140Layout extends BufferLayout {
+    public Std140Layout() {
+        // Init default serializers
+        // 1. If the member is a scalar consuming N basic machine units, the
+        // base alignment is N .
+        registerSerializer(new ObjectSerializer<Integer>(Integer.class) {
+            @Override
+            public int length(BufferLayout serializer, Integer obj) {
+                return 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Integer obj) {
+                return 4;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Integer obj) {
+                bbf.putInt(obj);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Boolean>(Boolean.class) {
+            @Override
+            public int length(BufferLayout serializer, Boolean obj) {
+                return 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Boolean obj) {
+                return 4;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Boolean obj) {
+                bbf.putInt(obj ? 1 : 0);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Float>(Float.class) {
+            @Override
+            public int length(BufferLayout serializer, Float obj) {
+                return 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Float obj) {
+                return 4;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Float obj) {
+                bbf.putFloat(obj);
+            }
+        });
+
+        // 2. If the member is a two- or four-component vector with components
+        // consuming N basic machine units, the base alignment is 2N or 4N ,
+        // respectively
+        registerSerializer(new ObjectSerializer<Vector2f>(Vector2f.class) {
+            @Override
+            public int length(BufferLayout serializer, Vector2f obj) {
+                return 4 * 2;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector2f obj) {
+                return 4 * 2;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Vector2f obj) {
+                bbf.putFloat(obj.x);
+                bbf.putFloat(obj.y);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<ColorRGBA>(ColorRGBA.class) {
+            @Override
+            public int length(BufferLayout serializer, ColorRGBA obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, ColorRGBA obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, ColorRGBA obj) {
+                bbf.putFloat(obj.r);
+                bbf.putFloat(obj.g);
+                bbf.putFloat(obj.b);
+                bbf.putFloat(obj.a);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Quaternion>(Quaternion.class) {
+            @Override
+            public int length(BufferLayout serializer, Quaternion obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Quaternion obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Quaternion obj) {
+                bbf.putFloat(obj.getX());
+                bbf.putFloat(obj.getY());
+                bbf.putFloat(obj.getZ());
+                bbf.putFloat(obj.getW());
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Vector4f>(Vector4f.class) {
+            @Override
+            public int length(BufferLayout serializer, Vector4f obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector4f obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Vector4f obj) {
+                bbf.putFloat(obj.x);
+                bbf.putFloat(obj.y);
+                bbf.putFloat(obj.z);
+                bbf.putFloat(obj.w);
+
+            }
+        });
+
+        // 3. If the member is a three-component vector with components
+        // consuming N
+        // basic machine units, the base alignment is 4N
+        registerSerializer(new ObjectSerializer<Vector3f>(Vector3f.class) {
+            @Override
+            public int length(BufferLayout serializer, Vector3f obj) {
+                return 4 * 3;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector3f obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout serializer, ByteBuffer bbf, Vector3f obj) {
+                bbf.putFloat(obj.x);
+                bbf.putFloat(obj.y);
+                bbf.putFloat(obj.z);
+                // bbf.putFloat(0);
+
+            }
+        });
+
+        // 4. If the member is an array of scalars or vectors, the base
+        // alignment and array
+        // stride are set to match the base alignment of a single array element,
+        // according
+        // to rules (1), (2), and (3), and rounded up to the base alignment of a
+        // vec4. The
+        // array may have padding at the end; the base offset of the member
+        // following
+        // the array is rounded up to the next multiple of the base alignment.
+
+        registerSerializer(new ObjectSerializer<Integer[]>(Integer[].class) {
+            @Override
+            public int length(BufferLayout serializer, Integer[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Integer[] obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout 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[]>(Float[].class) {
+            @Override
+            public int length(BufferLayout serializer, Float[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Float[] obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout 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[]>(Boolean[].class) {
+            @Override
+            public int length(BufferLayout serializer, Boolean[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Boolean[] obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout 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[]>(Vector2f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Vector2f[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector2f[] obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout 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[]>(Vector3f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Vector3f[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector3f[] obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout 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[]>(Vector4f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Vector4f[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Vector4f[] obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout 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[]>(ColorRGBA[].class) {
+            @Override
+            public int length(BufferLayout serializer, ColorRGBA[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, ColorRGBA[] obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout 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[]>(Quaternion[].class) {
+            @Override
+            public int length(BufferLayout serializer, Quaternion[] obj) {
+                return 4 * obj.length * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Quaternion[] obj) {
+                return 4 * 4;
+            }
+
+            @Override
+            public void write(BufferLayout 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());
+                }
+            }
+        });
+
+        // 5. If the member is a column-major matrix with C columns and R rows,
+        // the
+        // matrix is stored identically to an array of C column vectors with R
+        // compo-
+        // nents each, according to rule (4).
+
+        registerSerializer(new ObjectSerializer<Matrix3f>(Matrix3f.class) {
+            @Override
+            public int length(BufferLayout serializer, Matrix3f obj) {
+                return 3 * 4 * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Matrix3f obj) {
+                return 4 * 4;
+            }
+
+            final Vector3f tmp = new Vector3f();
+
+            @Override
+            public void write(BufferLayout 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);
+            }
+        });
+
+        registerSerializer(new ObjectSerializer<Matrix4f>(Matrix4f.class) {
+            @Override
+            public int length(BufferLayout serializer, Matrix4f obj) {
+                return 4 * 4 * 4;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Matrix4f obj) {
+                return 4 * 4;
+            }
+
+            final float[] tmpF = new float[4];
+
+            @Override
+            public void write(BufferLayout 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]);
+
+            }
+        });
+
+        // 6. If the member is an array of S column-major matrices with C
+        // columns and
+        // R rows, the matrix is stored identically to a row of S × C column
+        // vectors
+        // with R components each, according to rule (4).
+
+        registerSerializer(new ObjectSerializer<Matrix3f[]>(Matrix3f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Matrix3f[] obj) {
+                return 3 * 4 * 4 * obj.length;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Matrix3f[] obj) {
+                return 4 * 4;
+            }
+
+            final Vector3f tmp = new Vector3f();
+
+            @Override
+            public void write(BufferLayout 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[]>(Matrix4f[].class) {
+            @Override
+            public int length(BufferLayout serializer, Matrix4f[] obj) {
+                return 4 * 4 * 4 * obj.length;
+            }
+
+            @Override
+            public int basicAlignment(BufferLayout serializer, Matrix4f[] obj) {
+                return 4 * 4;
+            }
+
+            final float[] tmpF = new float[4];
+
+            @Override
+            public void write(BufferLayout 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]);
+                }
+
+            }
+        });
+
+        // 7. If the member is a row-major matrix with C columns and R rows, the
+        // matrix
+        // is stored identically to an array of R row vectors with C components
+        // each,
+        // according to rule (4).
+
+        // Nothing: jme matrices are column-major
+
+        // 8. If the member is an array of S row-major matrices with C columns
+        // and R
+        // rows, the matrix is stored identically to a row of S × R row vectors
+        // with C
+        // components each, according to rule (4)
+
+        // Nothing: jme matrices are column-major
+
+        // 9. If the member is a structure, the base alignment of the structure
+        // is N , where
+        // N is the largest base alignment value of any of its members, and
+        // rounded
+        // up to the base alignment of a vec4. The individual members of this
+        // sub-
+        // structure are then assigned offsets by applying this set of rules
+        // recursively,
+        // where the base offset of the first member of the sub-structure is
+        // equal to the
+        // aligned offset of the structure. The structure may have padding at
+        // the end;
+        // the base offset of the member following the sub-structure is rounded
+        // up to
+        // the next multiple of the base alignment of the structure.
+
+        // IMPLEMENTED AT A HIGHER LEVEL
+
+        // 10. If the member is an array of S structures, the S elements of the
+        // array are laid
+        // out in order, according to rule (9)
+
+        // IMPLEMENTED AT A HIGHER LEVEL
+
+    }
+
+    @Override
+    public String getId() {
+        return "std140";
+    }
+}

+ 21 - 5
jme3-core/src/main/java/com/jme3/system/NullRenderer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -42,9 +42,9 @@ import com.jme3.renderer.Statistics;
 import com.jme3.renderer.TextureUnitException;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.VertexBuffer;
-import com.jme3.shader.BufferObject;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Shader.ShaderSource;
+import com.jme3.shader.bufferobject.BufferObject;
 import com.jme3.texture.FrameBuffer;
 import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
@@ -179,9 +179,6 @@ public class NullRenderer implements Renderer {
     public void updateBufferData(VertexBuffer vb) {
     }
 
-    @Override
-    public void updateBufferData(BufferObject bo) {
-    }
     @Override
     public void deleteBuffer(VertexBuffer vb) {
     }
@@ -298,4 +295,23 @@ public class NullRenderer implements Renderer {
     public FrameBuffer getCurrentFrameBuffer() {
         return null;
     }
+    
+    public void updateShaderStorageBufferObjectData(BufferObject bo) {
+
+    }
+
+    @Override
+    public void updateUniformBufferObjectData(BufferObject bo) {
+
+    }
+
+    @Override
+    public void setShaderStorageBufferObject(int bindingPoint, BufferObject bufferObject) {
+
+    }
+
+    @Override
+    public void setUniformBufferObject(int bindingPoint, BufferObject bufferObject) {
+
+    }
 }

+ 43 - 0
jme3-core/src/main/java/com/jme3/util/struct/Struct.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct;
+
+/**
+ * Classes implementing this interface are considered struct-like constructs.
+ * Class fields using one of the com.jme3.util.struct.fields.* classes as type
+ * will be considered part of the struct and must be declared as final,
+ * everything else will be ignored.
+ * 
+ * @author Riccardo Balbo
+ */
+public interface Struct {
+}

+ 135 - 0
jme3-core/src/main/java/com/jme3/util/struct/StructField.java

@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct;
+
+/**
+ * A field of a struct
+ * 
+ * @author Riccardo Balbo
+ */
+public abstract class StructField<T> {
+
+    private int position;
+    protected T value;
+    protected boolean isUpdateNeeded = true;
+    private String name;
+    private int depth = 0;
+    private int group = 0;
+
+    protected StructField(int position, String name, T value) {
+        this.position = position;
+        this.value = value;
+        this.name = name;
+    }
+
+    /**
+     * Get depth of the field
+     * 
+     * @return
+     */
+    public int getDepth() {
+        return depth;
+    }
+
+    /**
+     * Get the group to which this field belongs (eg. a parent struct)
+     * 
+     * @return id of the group
+     */
+    public int getGroup() {
+        return group;
+    }
+
+    void setGroup(int group) {
+        this.group = group;
+    }
+
+    void setDepth(int depth) {
+        this.depth = depth;
+    }
+
+    void setPosition(int position) {
+        this.position = position;
+    }
+
+    /**
+     * Get position inside the struct
+     * 
+     * @return position inside the struct
+     */
+    public int getPosition() {
+        return position;
+    }
+
+    /**
+     * Get value of this field
+     * 
+     * @return value
+     */
+    public T getValue() {
+        return value;
+    }
+
+    /**
+     * Check if field needs update
+     * 
+     * @return
+     */
+    public boolean isUpdateNeeded() {
+        return isUpdateNeeded;
+    }
+
+    /**
+     * Clear update needed used internally
+     */
+    public void clearUpdateNeeded() {
+        isUpdateNeeded = false;
+    }
+
+    /**
+     * Get simple name of the field
+     * 
+     * @return
+     */
+    public String getName() {
+        String friendlyName;
+        if (name != null) friendlyName = name;
+        else friendlyName = value.getClass().getSimpleName();
+        return friendlyName;
+    }
+
+    @Override
+    public String toString() {
+        return "StructField[" + getName() + "] = " + value.toString();
+    }
+
+}

+ 137 - 0
jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java

@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+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.shader.bufferobject.BufferObject;
+import com.jme3.shader.bufferobject.layout.Std140Layout;
+
+/**
+ * A BufferObject containing a struct serialized with Std140 layout.
+ * 
+ * @author Riccardo Balbo
+ */
+public class StructStd140BufferObject extends BufferObject {
+    private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(StructStd140BufferObject.class.getName());
+    private transient Class<? extends Struct> rootStruct;
+    private transient List<StructField<?>> resolvedFields;
+    private final Std140Layout std140 = new Std140Layout();
+
+    /**
+     * Create an empty Struct buffer
+     * 
+     * @param str
+     */
+    public StructStd140BufferObject() {
+    }
+
+    /**
+     * Internal only
+     */
+    public StructStd140BufferObject(int id) {
+        super(id);
+    }
+
+    /**
+     * Create a Struct buffer from a Struct
+     * @param str the source struct
+     */
+    public StructStd140BufferObject(Struct str) {
+        this();
+        update(str);
+    }
+
+    private void loadLayout(Struct struct) {
+        ArrayList<Field> classFields = new ArrayList<Field>();
+        resolvedFields = StructUtils.getFields(struct, classFields);
+        for (Field field : classFields) {
+            if (!Modifier.isFinal(field.getModifiers())) throw new RuntimeException("Can't load layout for " + struct + " every field must be final");
+        }
+        rootStruct = struct.getClass();
+        StructUtils.setStd140BufferLayout(resolvedFields, std140, this);
+    }
+
+    /**
+     * Update data and layout (when needed) using a Struct class.
+     * 
+     * @param struct
+     */
+    public void update(Struct struct) {
+        boolean forceUpdate = false;
+        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() });
+            }
+            loadLayout(struct);
+            forceUpdate = true;
+        }
+        StructUtils.updateBufferData(resolvedFields, forceUpdate, std140, this);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(rootStruct.getName(), "rootClass", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        try {
+            String rootClass = ic.readString("rootClass", null);
+            if (rootClass == null) throw new Exception("rootClass is undefined");
+            Class<? extends Struct> rootStructClass = (Class<? extends Struct>) Class.forName(rootClass);
+            Struct rootStruct = rootStructClass.newInstance();
+            loadLayout(rootStruct);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public StructStd140BufferObject clone() {
+        StructStd140BufferObject clone = (StructStd140BufferObject) super.clone();
+        return clone;
+    }
+
+}

+ 217 - 0
jme3-core/src/main/java/com/jme3/util/struct/StructUtils.java

@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import com.jme3.shader.bufferobject.BufferObject;
+import com.jme3.shader.bufferobject.BufferRegion;
+import com.jme3.shader.bufferobject.layout.BufferLayout;
+import com.jme3.shader.bufferobject.layout.Std140Layout;
+
+/**
+ * StructUtils
+ * 
+ * @author Riccardo Balbo
+ */
+public class StructUtils {
+    private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(StructUtils.class.getName());
+
+    private static final Comparator<StructField<?>> fieldComparator = new Comparator<StructField<?>>() {
+        @Override
+        public int compare(final StructField<?> a, final StructField<?> b) {
+            return a.getPosition() - b.getPosition();
+        }
+    };
+
+    /**
+     * In-place sort a List of StructFields accordingly to their position
+     * 
+     * @param fields
+     *            list to sort
+     * @return the passed list
+     */
+    public static List<StructField<?>> sortFields(List<StructField<?>> fields) {
+        fields.sort(fieldComparator);
+        return fields;
+    }
+
+    /**
+     * Get sorted List of StructFields from a Struct object
+     * 
+     * @param struct
+     *            the struct object
+     * @return the sorted list
+     */
+    public static List<StructField<?>> getFields(Struct struct) {
+        return getFields(struct, 0, null);
+    }
+
+    public static List<StructField<?>> getFields(Struct struct, ArrayList<Field> classFields) {
+        return getFields(struct, 0, classFields);
+    }
+
+    private static List<StructField<?>> getFields(Struct struct, int depth, ArrayList<Field> classFields) {// ,
+                                                                                                           // final
+                                                                                                           // List<Field>
+                                                                                                           // fieldList)
+                                                                                                           // {
+        ArrayList<StructField<?>> structFields = new ArrayList<StructField<?>>();
+
+        Class<? extends Struct> structClass = struct.getClass();
+        try {
+            // for each class field
+            // Extract class fields into a StructField List
+            // (Note: class methods are iterated in undefined order)
+            Field[] fields = structClass.getDeclaredFields();
+            for (Field field : fields) {
+                field.setAccessible(true);
+
+                Object o = field.get(struct);
+                if (o instanceof StructField) {
+                    if (classFields != null) classFields.add(field);
+                    StructField<?> so = (StructField<?>) o;
+                    structFields.add(so);
+                }
+            }
+
+            // Sort by position
+            sortFields(structFields);
+
+            ArrayList<StructField<?>> expandedStructFields = new ArrayList<StructField<?>>();
+
+            // Expand sub struct and arrays to flat list
+            for (int i = 0; i < structFields.size(); i++) {
+                StructField<?> so = structFields.get(i);
+                if (so.getValue() instanceof Struct) { // substruct
+                    List<StructField<?>> subStruct = getFields((Struct) so.getValue(), depth + 1, classFields);
+                    expandedStructFields.addAll(subStruct);
+                } else if (so.getValue().getClass().isArray() && Struct.class.isAssignableFrom(so.getValue().getClass().getComponentType())) { // array
+                                                                                                                                               // of
+                                                                                                                                               // substruct
+
+                    Struct[] subA = (Struct[]) so.getValue();
+                    for (int j = 0; j < subA.length; j++) {
+                        Struct sub = subA[j];
+                        List<StructField<?>> subStruct = getFields(sub, depth + 1, classFields);
+                        expandedStructFields.addAll(subStruct);
+                    }
+
+                } else {
+                    so.setDepth(depth);
+                    so.setGroup(struct.hashCode());
+                    expandedStructFields.add(so);
+                }
+            }
+            structFields = expandedStructFields;
+
+            // Recompute positions in flat list
+            int i = 0;
+            for (StructField<?> so : structFields) {
+                so.setPosition(i);
+                i++;
+            }
+
+        } catch (final Exception e) {
+            throw new RuntimeException(e);
+        }
+        assert structFields.size() != 0;
+        return structFields;
+    }
+
+    public static BufferObject setStd140BufferLayout(List<StructField<?>> fields, Std140Layout serializer, BufferObject out) {// ,
+                                                                                                                              // final
+                                                                                                                              // List<Field>
+                                                                                                                              // fieldList)
+                                                                                                                              // {
+
+        int pos = -1;
+
+        List<BufferRegion> regions = new ArrayList<BufferRegion>();
+
+        for (int i = 0; i < fields.size(); i++) {
+            StructField<?> f = fields.get(i);
+            Object v = f.getValue();
+
+            int basicAlignment = serializer.getBasicAlignment(v);
+            int length = serializer.estimateSize(v);
+
+            int start = serializer.align(pos + 1, basicAlignment);
+            int end = start + length - 1;
+
+            if ((i == fields.size() - 1) || f.getGroup()!= fields.get(i + 1).getGroup()){// > fields.get(i + 1).getDepth()) {
+                end = (serializer.align(end, 16)) - 1;
+            }
+
+            BufferRegion r = new BufferRegion(start, end);
+            regions.add(r);
+            pos = end;
+        }
+
+        out.setRegions(regions);
+
+        return out;
+    }
+
+    /**
+     * Update data using a List of StructFields The current layout will be
+     * maintained unless previously invalidated
+     * 
+     * @param fields
+     *            sorted list of struct fields
+     */
+    public static void updateBufferData(List<StructField<?>> fields, boolean forceUpdate, BufferLayout layout, BufferObject out) {
+        boolean updateNeeded = false;
+        for (StructField<?> f : fields) {
+            if (forceUpdate || f.isUpdateNeeded()) {
+
+                BufferRegion region = out.getRegion(f.getPosition());
+                if (logger.isLoggable(java.util.logging.Level.FINER)) {
+                    logger.log(java.util.logging.Level.FINER, "Serialize {0} in {1} ", new Object[] { f, region });
+                }
+                layout.write(region.getData(), f.getValue());
+                region.markDirty();
+                f.clearUpdateNeeded();
+                updateNeeded = true;
+            } else {
+                if (logger.isLoggable(java.util.logging.Level.FINER)) {
+                    logger.log(java.util.logging.Level.FINER, "Already up to date. Skip {0}  ", new Object[] { f });
+                }
+            }
+        }
+        if (updateNeeded) out.setUpdateNeeded(false);
+    }
+
+}

+ 63 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanArrayField.java

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.util.struct.StructField;
+
+public class BooleanArrayField extends StructField<Boolean[]> {
+
+    public BooleanArrayField(int position, String name, Boolean[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public BooleanArrayField(int position, String name, int length) {
+        super(position, name, new Boolean[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = false;
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Boolean[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 51 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanField.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.util.struct.StructField;
+
+public class BooleanField extends StructField<Boolean> {
+
+    public BooleanField(int position, String name, Boolean value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Set value for this field and mark for update
+     * 
+     * @param value
+     */
+    public void setValue(Boolean value) {
+        isUpdateNeeded = true;
+        this.value = value;
+    }
+}

+ 64 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAArrayField.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.util.struct.StructField;
+
+public class ColorRGBAArrayField extends StructField<ColorRGBA[]> {
+
+    public ColorRGBAArrayField(int position, String name, ColorRGBA[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public ColorRGBAArrayField(int position, String name, int length) {
+        super(position, name, new ColorRGBA[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = new ColorRGBA();
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public ColorRGBA[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 52 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAField.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.util.struct.StructField;
+
+public class ColorRGBAField extends StructField<ColorRGBA> {
+
+    public ColorRGBAField(int position, String name, ColorRGBA value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public ColorRGBA getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 63 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/FloatArrayField.java

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.util.struct.StructField;
+
+public class FloatArrayField extends StructField<Float[]> {
+
+    public FloatArrayField(int position, String name, Float[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public FloatArrayField(int position, String name, int length) {
+        super(position, name, new Float[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = 0f;
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Float[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 51 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/FloatField.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.util.struct.StructField;
+
+public class FloatField extends StructField<Float> {
+
+    public FloatField(int position, String name, Float value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Set value for this field and mark for update
+     * 
+     * @param value
+     */
+    public void setValue(Float value) {
+        isUpdateNeeded = true;
+        this.value = value;
+    }
+}

+ 63 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/IntArrayField.java

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.util.struct.StructField;
+
+public class IntArrayField extends StructField<Integer[]> {
+
+    public IntArrayField(int position, String name, Integer[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public IntArrayField(int position, String name, Integer length) {
+        super(position, name, new Integer[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = 0;
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Integer[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 51 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/IntField.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.util.struct.StructField;
+
+public class IntField extends StructField<Integer> {
+
+    public IntField(int position, String name, Integer value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Set value for this field and mark for update
+     * 
+     * @param value
+     */
+    public void setValue(Integer value) {
+        isUpdateNeeded = true;
+        this.value = value;
+    }
+}

+ 64 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fArrayField.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Matrix3f;
+import com.jme3.util.struct.StructField;
+
+public class Matrix3fArrayField extends StructField<Matrix3f[]> {
+
+    public Matrix3fArrayField(int position, String name, Matrix3f[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public Matrix3fArrayField(int position, String name, int length) {
+        super(position, name, new Matrix3f[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = new Matrix3f();
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Matrix3f[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 52 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fField.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Matrix3f;
+import com.jme3.util.struct.StructField;
+
+public class Matrix3fField extends StructField<Matrix3f> {
+
+    public Matrix3fField(int position, String name, Matrix3f value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Matrix3f getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 64 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fArrayField.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Matrix4f;
+import com.jme3.util.struct.StructField;
+
+public class Matrix4fArrayField extends StructField<Matrix4f[]> {
+
+    public Matrix4fArrayField(int position, String name, Matrix4f[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public Matrix4fArrayField(int position, String name, int length) {
+        super(position, name, new Matrix4f[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = new Matrix4f();
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Matrix4f[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 52 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fField.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Matrix4f;
+import com.jme3.util.struct.StructField;
+
+public class Matrix4fField extends StructField<Matrix4f> {
+
+    public Matrix4fField(int position, String name, Matrix4f value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Matrix4f getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 64 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionArrayField.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Quaternion;
+import com.jme3.util.struct.StructField;
+
+public class QuaternionArrayField extends StructField<Quaternion[]> {
+
+    public QuaternionArrayField(int position, String name, Quaternion[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public QuaternionArrayField(int position, String name, int length) {
+        super(position, name, new Quaternion[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = new Quaternion();
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Quaternion[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 52 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionField.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Quaternion;
+import com.jme3.util.struct.StructField;
+
+public class QuaternionField extends StructField<Quaternion> {
+
+    public QuaternionField(int position, String name, Quaternion value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Quaternion getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 64 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructArrayField.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+
+import com.jme3.util.struct.Struct;
+import com.jme3.util.struct.StructField;
+
+public class SubStructArrayField<T extends Struct> extends StructField<T[]> {
+
+    public SubStructArrayField(int position, String name, T[] value) {
+        super(position, name, value);
+        initializeToZero((Class<? extends T>) value[0].getClass());
+    }
+
+    public SubStructArrayField(int position, String name, int length, Class<? extends T> structClass) {
+        super(position, name, (T[]) Array.newInstance(structClass, length));
+        initializeToZero(structClass);
+    }
+
+    private void initializeToZero(Class<? extends T> structClass) {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) try {
+                Constructor<? extends T> constructor = structClass.getDeclaredConstructor();
+                constructor.setAccessible(true);
+                value[i] = constructor.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException("Can't create new instance of " + structClass + " default constructor is missing? ",e);
+            }
+        }
+    }
+
+}

+ 44 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructField.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.util.struct.Struct;
+import com.jme3.util.struct.StructField;
+
+
+public class SubStructField<T extends Struct> extends StructField<T> {
+
+    public SubStructField(int position, String name, T value) {
+        super(position, name, value);
+    }
+
+}

+ 64 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fArrayField.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Vector2f;
+import com.jme3.util.struct.StructField;
+
+public class Vector2fArrayField extends StructField<Vector2f[]> {
+
+    public Vector2fArrayField(int position, String name, Vector2f[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public Vector2fArrayField(int position, String name, int length) {
+        super(position, name, new Vector2f[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = new Vector2f();
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Vector2f[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 52 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fField.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Vector2f;
+import com.jme3.util.struct.StructField;
+
+public class Vector2fField extends StructField<Vector2f> {
+
+    public Vector2fField(int position, String name, Vector2f value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Vector2f getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 64 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fArrayField.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Vector3f;
+import com.jme3.util.struct.StructField;
+
+public class Vector3fArrayField extends StructField<Vector3f[]> {
+
+    public Vector3fArrayField(int position, String name, Vector3f[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public Vector3fArrayField(int position, String name, int length) {
+        super(position, name, new Vector3f[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = new Vector3f();
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Vector3f[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 52 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fField.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Vector3f;
+import com.jme3.util.struct.StructField;
+
+public class Vector3fField extends StructField<Vector3f> {
+
+    public Vector3fField(int position, String name, Vector3f value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Vector3f getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 64 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fArrayField.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Vector4f;
+import com.jme3.util.struct.StructField;
+
+public class Vector4fArrayField extends StructField<Vector4f[]> {
+    
+    public Vector4fArrayField(int position, String name, Vector4f[] value) {
+        super(position, name, value);
+        initializeToZero();
+    }
+
+    public Vector4fArrayField(int position, String name, int length) {
+        super(position, name, new Vector4f[length]);
+        initializeToZero();
+    }
+
+    private void initializeToZero() {
+        for (int i = 0; i < value.length; i++) {
+            if (value[i] == null) value[i] = new Vector4f();
+        }
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Vector4f[] getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 52 - 0
jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fField.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2024 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util.struct.fields;
+
+import com.jme3.math.Vector4f;
+import com.jme3.util.struct.StructField;
+
+public class Vector4fField extends StructField<Vector4f> {
+
+    public Vector4fField(int position, String name, Vector4f value) {
+        super(position, name, value);
+    }
+
+    /**
+     * Get value and mark field for update
+     * 
+     * @return
+     */
+    public Vector4f getValueForUpdate() {
+        isUpdateNeeded = true;
+        return value;
+    }
+}

+ 218 - 0
jme3-core/src/test/java/com/jme3/util/StructTest.java

@@ -0,0 +1,218 @@
+package com.jme3.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.ByteBuffer;
+
+import com.jme3.shader.bufferobject.BufferObject;
+import com.jme3.shader.bufferobject.BufferRegion;
+import com.jme3.shader.bufferobject.DirtyRegionsIterator;
+import com.jme3.shader.bufferobject.layout.Std140Layout;
+import com.jme3.util.struct.Struct;
+import com.jme3.util.struct.StructField;
+import com.jme3.util.struct.StructUtils;
+import com.jme3.util.struct.fields.*;
+
+import org.junit.Test;
+
+public class StructTest {
+    static class SubStruct implements Struct {
+        public final IntField subIntField0 = new IntField(0, "subIntField0", 100);
+        public final FloatField subFloatField1 = new FloatField(1, "subFloatField1", 100f);
+
+    }
+
+    static class TestStruct implements Struct {
+        public final IntField intField0 = new IntField(0, "intField0", 100);
+        public final FloatField floatField1 = new FloatField(1, "floatField1", 100f);
+        public final FloatArrayField floatFieldArray2 = new FloatArrayField(2, "floatFieldArray2", new Float[] { 100f, 200f, 300f });
+        public final SubStructField<SubStruct> structField3 = new SubStructField<SubStruct>(3, "structField3", new SubStruct());
+        public final SubStructArrayField<SubStruct> structArrayField5 = new SubStructArrayField<SubStruct>(5, "structArrayField5", new SubStruct[] { new SubStruct(), new SubStruct() });
+        public final BooleanField boolField6 = new BooleanField(6, "boolField6", true);
+    }
+
+    @Test
+    public void testFieldsExtraction() {
+        TestStruct test = new TestStruct();
+        java.util.List<StructField<?>> fields = StructUtils.getFields(test);
+        String checkString = "";
+        for (StructField<?> f : fields) {
+            String s = f.getPosition() + " " + f.getName() + " " + f.getDepth() + "\n";
+            checkString += s;
+        }
+        String expectedString = "0 intField0 0\n1 floatField1 0\n2 floatFieldArray2 0\n3 subIntField0 1\n4 subFloatField1 1\n5 subIntField0 1\n6 subFloatField1 1\n7 subIntField0 1\n8 subFloatField1 1\n9 boolField6 0\n";
+        assertEquals(expectedString, checkString);
+    }
+
+    @Test
+    public void testStd140Serialization() {
+        TestStruct test = new TestStruct();
+        java.util.List<StructField<?>> fields = StructUtils.getFields(test);
+
+        Std140Layout layout = new Std140Layout();
+        BufferObject bo = new BufferObject();
+        StructUtils.setStd140BufferLayout(fields, layout, bo);
+        System.out.println(bo.getData().getInt());
+        
+        StructUtils.updateBufferData(fields, false, layout, bo);
+
+        ByteBuffer bbf = bo.getData();
+
+        String expectedData = "100 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 0 0 0 0 0 0 72 67 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -106 67 0 0 0 0 0 0 0 0 0 0 0 0 100 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 100 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 100 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ";
+        String actualData = "";
+        while (bbf.hasRemaining()) {
+            actualData += bbf.get() + " ";
+        }
+        assertEquals(expectedData, actualData);
+    }
+
+    @Test
+    public void testStd140PartialUpdate() {
+        TestStruct test = new TestStruct();
+
+        Std140Layout layout = new Std140Layout();
+        BufferObject bo = new BufferObject();
+
+        java.util.List<StructField<?>> fields = StructUtils.getFields(test);
+        StructUtils.setStd140BufferLayout(fields, layout, bo);
+        int bolength = bo.getData().limit();
+        assertEquals(128, bolength);
+        assertEquals(128, bo.getData().capacity());
+        
+        int nUpdated;
+
+        // Update full
+        System.out.println("Test Full Update");
+        StructUtils.updateBufferData(fields, false, layout, bo);
+        DirtyRegionsIterator dirtyI = bo.getDirtyRegions();
+        nUpdated = 0;
+        assertTrue(bo.isUpdateNeeded());
+        while (true) {
+            BufferRegion region = dirtyI.next();
+            if (region == null) break;
+            int start = region.getStart();
+            int end = region.getEnd();
+            System.out.println("Update from " + start + " to " + end + " in buffer of length " + bolength);
+            assertEquals(0, start);
+            assertEquals(127,end);
+            assertTrue(region.isFullBufferRegion());
+            assertTrue(region.isDirty());
+            region.clearDirty();
+            assertFalse(region.isDirty());
+            nUpdated++;
+        }
+        bo.clearUpdateNeeded();
+        assertFalse(bo.isUpdateNeeded());
+        assertEquals(1, nUpdated);
+
+
+        // Update nothing
+        System.out.println("Test No Update");
+        fields = StructUtils.getFields(test);
+        StructUtils.updateBufferData(fields, false, layout, bo);
+        dirtyI = bo.getDirtyRegions();
+        assertFalse(bo.isUpdateNeeded());
+        nUpdated = 0;
+        while (true) {
+            BufferRegion region = dirtyI.next();
+            if (region == null) break;
+
+            assertFalse("Update not expected", true);
+            nUpdated++;
+        }
+        bo.clearUpdateNeeded();
+        assertFalse(bo.isUpdateNeeded());
+        assertEquals(0, nUpdated);
+
+        // Update something
+        System.out.println("Test Partial Update");
+        test.floatField1.setValue(2f);
+        StructUtils.updateBufferData(fields, false, layout, bo);
+        dirtyI = bo.getDirtyRegions();
+        assertTrue(bo.isUpdateNeeded());
+        nUpdated = 0;
+        while (true) {
+            BufferRegion region = dirtyI.next();
+            if (region == null) break;
+            assertTrue(region.isDirty());
+
+            int start = region.getStart();
+            int end = region.getEnd();
+            System.out.println("Update from " + start + " to " + end + " in buffer of length " + bolength);
+            assertEquals(4, start);
+            assertEquals(7, end);
+            assertFalse(region.isFullBufferRegion());
+            assertTrue(region.isDirty());
+            region.clearDirty();
+            nUpdated++;
+        }
+        bo.clearUpdateNeeded();
+        assertFalse(bo.isUpdateNeeded());
+        assertEquals(1, nUpdated);
+
+        // Update something2
+        System.out.println("Test Partial Update 2");
+        test.floatField1.setValue(2f);
+        test.boolField6.setValue(true);
+        StructUtils.updateBufferData(fields, false, layout, bo);
+        dirtyI = bo.getDirtyRegions();
+        assertTrue(bo.isUpdateNeeded());
+        nUpdated = 0;
+        while (true) {
+            BufferRegion region = dirtyI.next();
+            if (region == null) break;
+            assertTrue(region.isDirty());
+
+            int start = region.getStart();
+            int end = region.getEnd();
+            System.out.println("Update from " + start + " to " + end + " in buffer of length " + bolength);
+            if (nUpdated == 0) {
+                assertEquals(4, start);
+                assertEquals(7, end);
+            } else {
+                assertEquals(112, start);
+                assertEquals(127, end);
+            }
+            assertFalse(region.isFullBufferRegion());
+            assertTrue(region.isDirty());
+            region.clearDirty();
+            nUpdated++;
+        }
+        bo.clearUpdateNeeded();
+        assertFalse(bo.isUpdateNeeded());
+        assertEquals(2, nUpdated);
+
+
+        // Update substruct 
+        System.out.println("Test Partial Update Substruct");
+        test.structField3.getValue().subIntField0.setValue(3);
+        StructUtils.updateBufferData(fields, false, layout, bo);
+        dirtyI = bo.getDirtyRegions();
+        assertTrue(bo.isUpdateNeeded());
+        nUpdated = 0;
+        while (true) {
+            BufferRegion region = dirtyI.next();
+            if (region == null) break;
+            assertTrue(region.isDirty());
+
+            int start = region.getStart();
+            int end = region.getEnd();
+            System.out.println("Update from " + start + " to " + end + " in buffer of length " + bolength);
+            assertEquals(64, start);
+            assertEquals(67, end);
+            assertFalse(region.isFullBufferRegion());
+            assertTrue(region.isDirty());
+            region.clearDirty();
+            nUpdated++;
+        }
+        bo.clearUpdateNeeded();
+        assertFalse(bo.isUpdateNeeded());
+        assertEquals(1, nUpdated);
+
+
+
+
+    }
+}

+ 149 - 0
jme3-examples/src/main/java/jme3test/material/TestUBO.java

@@ -0,0 +1,149 @@
+package jme3test.material;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.AppSettings;
+import com.jme3.util.struct.Struct;
+import com.jme3.util.struct.StructStd140BufferObject;
+import com.jme3.util.struct.fields.ColorRGBAField;
+import com.jme3.util.struct.fields.FloatField;
+import com.jme3.util.struct.fields.IntField;
+import com.jme3.util.struct.fields.Matrix3fField;
+import com.jme3.util.struct.fields.Matrix4fField;
+import com.jme3.util.struct.fields.SubStructArrayField;
+import com.jme3.util.struct.fields.SubStructField;
+import com.jme3.util.struct.fields.Vector3fField;
+
+/**
+ * This is an example of UBO usage and an unit test for the Struct Buffer Object.
+ * If everything works as expected, a green square should appear, if not the square will be red.
+ * 
+ * RenderDOC can be used to see the UBO on the gpu.
+ */
+public class TestUBO extends SimpleApplication {
+    
+    public final static class PointLight implements Struct {
+        public final Vector3fField position = new Vector3fField(0, "position", new Vector3f());
+        public final FloatField radius = new FloatField(1, "radius", 1f);
+        public final ColorRGBAField color = new ColorRGBAField(2, "color", new ColorRGBA());
+        
+        PointLight() {
+        }
+
+        PointLight(Vector3f position, float radius, ColorRGBA color) {
+            this.position.getValueForUpdate().set(position);
+            this.color.getValueForUpdate().set(color);
+            this.radius.setValue(radius);
+        }
+    }
+
+    public final static class DirectLight implements Struct {
+        public final Vector3fField direction = new Vector3fField(0, "direction", new Vector3f());
+        public final ColorRGBAField color = new ColorRGBAField(1, "color", new ColorRGBA());
+
+        DirectLight() {
+        }
+
+        DirectLight(Vector3f direction, ColorRGBA color) {
+            this.direction.getValueForUpdate().set(direction);
+            this.color.getValueForUpdate().set(color);
+        }
+    }
+
+    public final static class Lights implements Struct {
+        public final IntField nDirectLights = new IntField(0, "nDirectLights", 1);
+
+        public final SubStructField<DirectLight> test1 = new SubStructField<DirectLight>(1, "test1", new DirectLight(
+            new Vector3f(0,1,0),
+            ColorRGBA.Blue
+        ));
+
+        public final IntField test2 = new IntField(2, "test2", 111);
+
+        public final SubStructField<PointLight> test3 = new SubStructField<PointLight>(3, "test3", new PointLight(
+            new Vector3f(7,9,7),
+            99f,
+            ColorRGBA.Red
+        ));
+
+        public final IntField test4 = new IntField(4, "test4", 222);
+
+        public final Matrix3fField test5 = new Matrix3fField(5,"test5",new Matrix3f(1,2,3,4,5,6,7,8,9));
+        public final IntField test6 = new IntField(6, "test6", 333);
+
+        public final Matrix4fField test7 = new Matrix4fField(7, "test7", new Matrix4f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16));
+        public final IntField test8 = new IntField(8, "test8", 444);
+
+        public final SubStructArrayField<DirectLight> directLights = new SubStructArrayField<DirectLight>(9, "directLights",  new DirectLight[] {
+                new DirectLight(new Vector3f(0, 0, 1), ColorRGBA.Green),
+        });
+        
+        public final IntField nPointLights = new IntField(10, "nPointLights", 2);
+
+        public final SubStructArrayField<PointLight> pointLights = new SubStructArrayField<PointLight>(11, "pointLights", new PointLight[] {
+                new PointLight(new Vector3f(5, 9, 7), 9f, ColorRGBA.Red),
+                new PointLight(new Vector3f(5, 10, 7), 8f, ColorRGBA.Green),
+        });
+        public final SubStructArrayField<PointLight> pointLights2 = new SubStructArrayField<PointLight>(12, "pointLights2", new PointLight[] {
+                new PointLight(new Vector3f(3, 9, 7), 91f, ColorRGBA.Green),
+                new PointLight(new Vector3f(3, 10, 7), 90f, ColorRGBA.Blue),
+        });
+
+        public final IntField test13 = new IntField(13, "test13", 555);
+
+    }
+
+    StructStd140BufferObject lightsBO;
+    StructStd140BufferObject lightsBO2;
+    Lights lights = new Lights();
+    int n = 0;
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setEnabled(false);
+
+        lightsBO = new StructStd140BufferObject(lights);
+        lightsBO2 = lightsBO.clone();
+        
+        Material mat = new Material(assetManager, "jme3test/ubo/TestUBO.j3md");
+        mat.setUniformBufferObject("TestStruct1", lightsBO);
+        mat.setUniformBufferObject("TestStruct2", lightsBO2);
+
+        Geometry geo = new Geometry("Test", new Box(1, 1, 1));
+        geo.setMaterial(mat);
+        rootNode.attachChild(geo);
+        geo.setLocalTranslation(0, 0, 1);
+        
+        cam.lookAt(geo.getWorldTranslation(), Vector3f.UNIT_Y);
+    }
+
+    @Override
+    public void update() {
+        super.update();
+        n++;
+        if (n > 10) {
+            lights.test8.setValue(999999);
+            lights.test13.setValue(111);
+            lightsBO2.update(lights);
+        }
+    }
+
+
+    public static void main(String[] args) {
+        AppSettings sett = new AppSettings(true);
+        sett.putBoolean("GraphicsDebug", true);
+        sett.setRenderer(AppSettings.LWJGL_OPENGL32);
+        TestUBO app = new TestUBO();
+        app.setSettings(sett);
+        app.setPauseOnLostFocus(false);
+        app.start();
+    }
+
+}
+

+ 107 - 0
jme3-examples/src/main/resources/jme3test/ubo/TestUBO.frag

@@ -0,0 +1,107 @@
+
+struct PointLight {
+    vec3 position;
+    float radius;
+    vec4 color;
+};
+
+struct DirectLight {
+    vec3 direction;
+    vec4 color;
+};
+
+struct TestStruct{
+    int nDirectLights;
+    DirectLight test1;
+    int test2;
+    PointLight test3;
+    int test4;
+    mat3 test5;
+    int test6;
+    mat4 test7;
+    int test8;
+    DirectLight directLights[1];
+    int nPointLights;
+    PointLight pointLights[2];
+    PointLight pointLights2[2];
+    int test13;
+};
+
+layout (std140) uniform m_TestStruct1 {
+    TestStruct testStruct1;
+};
+
+layout (std140) uniform m_TestStruct2 {
+    TestStruct testStruct2;
+};
+
+out vec4 outFragColor;
+
+void main(){
+    if(
+        testStruct1.nDirectLights==1
+        &&testStruct1.test1.direction==vec3(0,1,0)
+        &&testStruct1.test1.color==vec4(0,0,1,1)
+        &&testStruct1.test2==111
+        &&testStruct1.test3.position==vec3(7.,9.,7.)    
+        &&testStruct1.test3.radius==99.
+        &&testStruct1.test3.color==vec4(1.,0.,0.,1.)
+        &&testStruct1.test4==222        
+        &&testStruct1.test5==transpose(mat3(1,2,3,4,5,6,7,8,9) )               
+        &&testStruct1.test6==333
+        &&testStruct1.test7==transpose(mat4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
+        &&testStruct1.test8==444
+        &&testStruct1.directLights[0].direction==vec3(0.,0.,1.)
+        &&testStruct1.directLights[0].color==vec4(0.,1.,0.,1.)        
+        &&testStruct1.nPointLights==2
+        &&testStruct1.pointLights[0].position==vec3(5.,9.,7.)
+        &&testStruct1.pointLights[0].radius==9.
+        &&testStruct1.pointLights[0].color==vec4(1.,0.,0.,1.)        
+        &&testStruct1.pointLights[1].position==vec3(5.,10.,7.)    
+        &&testStruct1.pointLights[1].radius==8.
+        &&testStruct1.pointLights[1].color==vec4(0.,1.,0.,1.)   
+        &&testStruct1.pointLights2[0].position==vec3(3.,9.,7.)
+        &&testStruct1.pointLights2[0].radius==91.
+        &&testStruct1.pointLights2[0].color==vec4(0.,1.,0.,1.)   
+        &&testStruct1.pointLights2[1].position==vec3(3.,10.,7.)
+        &&testStruct1.pointLights2[1].radius==90.
+        &&testStruct1.pointLights2[1].color==vec4(0.,0.,1.,1.)
+        &&testStruct1.test13==555
+
+        &&
+
+        testStruct2.nDirectLights==1
+        &&testStruct2.test1.direction==vec3(0,1,0)
+        &&testStruct2.test1.color==vec4(0,0,1,1)
+        &&testStruct2.test2==111
+        &&testStruct2.test3.position==vec3(7.,9.,7.)    
+        &&testStruct2.test3.radius==99.
+        &&testStruct2.test3.color==vec4(1.,0.,0.,1.)
+        &&testStruct2.test4==222        
+        &&testStruct2.test5==transpose(mat3(1,2,3,4,5,6,7,8,9) )               
+        &&testStruct2.test6==333
+        &&testStruct2.test7==transpose(mat4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
+        &&testStruct2.test8==999999
+        &&testStruct2.directLights[0].direction==vec3(0.,0.,1.)
+        &&testStruct2.directLights[0].color==vec4(0.,1.,0.,1.)        
+        &&testStruct2.nPointLights==2
+        &&testStruct2.pointLights[0].position==vec3(5.,9.,7.)
+        &&testStruct2.pointLights[0].radius==9.
+        &&testStruct2.pointLights[0].color==vec4(1.,0.,0.,1.)        
+        &&testStruct2.pointLights[1].position==vec3(5.,10.,7.)    
+        &&testStruct2.pointLights[1].radius==8.
+        &&testStruct2.pointLights[1].color==vec4(0.,1.,0.,1.)   
+        &&testStruct2.pointLights2[0].position==vec3(3.,9.,7.)
+        &&testStruct2.pointLights2[0].radius==91.
+        &&testStruct2.pointLights2[0].color==vec4(0.,1.,0.,1.)   
+        &&testStruct2.pointLights2[1].position==vec3(3.,10.,7.)
+        &&testStruct2.pointLights2[1].radius==90.
+        &&testStruct2.pointLights2[1].color==vec4(0.,0.,1.,1.)
+        &&testStruct2.test13==111
+
+    ){
+        outFragColor=vec4(0,1,0,1);
+    }else{
+        outFragColor=vec4(1,0,0,1);
+    }
+}

+ 16 - 0
jme3-examples/src/main/resources/jme3test/ubo/TestUBO.j3md

@@ -0,0 +1,16 @@
+
+MaterialDef TestUBO {
+    MaterialParameters {
+        UniformBufferObject TestStruct1
+        UniformBufferObject TestStruct2
+    }
+
+    Technique {
+        VertexShader GLSL150 : jme3test/ubo/TestUBO.vert
+        FragmentShader GLSL150 : jme3test/ubo/TestUBO.frag
+
+        WorldParameters {
+            ViewProjectionMatrix
+        }
+    }
+}

+ 7 - 0
jme3-examples/src/main/resources/jme3test/ubo/TestUBO.vert

@@ -0,0 +1,7 @@
+
+in vec3 inPosition;
+uniform mat4 g_ViewProjectionMatrix;
+void main(){
+    vec4 modelSpacePos = vec4(inPosition, 1.0);
+    gl_Position = g_ViewProjectionMatrix*modelSpacePos;
+}