Переглянути джерело

Merge branch 'renderer-improvements' into experimental

Kirill Vainer 9 роки тому
батько
коміт
ff6b1be725

+ 5 - 0
jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java

@@ -138,6 +138,11 @@ class BitmapTextPage extends Geometry {
         Mesh m = getMesh();
         int vertCount = pageQuads.size() * 4;
         int triCount = pageQuads.size() * 2;
+        
+        if (vertCount > m.getVertexCount() ||
+            triCount  > m.getTriangleCount()) {
+            m.setUpdateNeeded();
+        }
 
         VertexBuffer pb = m.getBuffer(Type.Position);
         VertexBuffer tb = m.getBuffer(Type.TexCoord);

+ 6 - 0
jme3-core/src/main/java/com/jme3/renderer/Renderer.java

@@ -283,6 +283,12 @@ public interface Renderer {
      *                     the per-instance attributes.
      */
     public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData);
+    
+    /**
+     * Delete a Mesh (or Vertex Array Object in GL terms) from the GPU.
+     * @param mesh The mesh to delete.
+     */
+    public void deleteMesh(Mesh mesh);
 
     /**
      * Resets all previously used {@link NativeObject Native Objects} on this Renderer.

+ 288 - 174
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -149,7 +149,7 @@ public final class GLRenderer implements Renderer {
             int major = Integer.parseInt(m.group(1));
             int minor = Integer.parseInt(m.group(2));
             if (minor >= 10 && minor % 10 == 0) {
-                // some versions can look like "1.30" instead of "1.3". 
+                // some versions can look like "1.30" instead of "1.3".
                 // make sure to correct for this
                 minor /= 10;
             }
@@ -379,7 +379,7 @@ public final class GLRenderer implements Renderer {
             limits.put(Limits.TextureAnisotropy, getInteger(GLExt.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT));
         }
 
-        if (hasExtension("GL_EXT_framebuffer_object") 
+        if (hasExtension("GL_EXT_framebuffer_object")
                 || caps.contains(Caps.OpenGL30)
                 || caps.contains(Caps.OpenGLES20)) {
             caps.add(Caps.FrameBuffer);
@@ -474,7 +474,7 @@ public final class GLRenderer implements Renderer {
             {
                 sb.append("\t").append(cap.toString()).append("\n");
             }
-            
+
             sb.append("\nHardware limits: \n");
             for (Limits limit : Limits.values()) {
                 Integer value = limits.get(limit);
@@ -484,7 +484,7 @@ public final class GLRenderer implements Renderer {
                 sb.append("\t").append(limit.name()).append(" = ")
                   .append(value).append("\n");
             }
-            
+
             logger.log(Level.INFO, sb.toString());
         }
 
@@ -517,7 +517,7 @@ public final class GLRenderer implements Renderer {
 
         // Initialize default state..
         gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
-        
+
         if (caps.contains(Caps.SeamlessCubemap)) {
             // Enable this globally. Should be OK.
             gl.glEnable(GLExt.GL_TEXTURE_CUBE_MAP_SEAMLESS);
@@ -525,9 +525,9 @@ public final class GLRenderer implements Renderer {
 
         if (caps.contains(Caps.CoreProfile)) {
             // Core Profile requires VAO to be bound.
-            gl3.glGenVertexArrays(intBuf16);
-            int vaoId = intBuf16.get(0);
-            gl3.glBindVertexArray(vaoId);
+//            gl3.glGenVertexArrays(intBuf16);
+//            int vaoId = intBuf16.get(0);
+//            gl3.glBindVertexArray(vaoId);
         }
         if (gl2 != null) {
             gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
@@ -637,7 +637,7 @@ public final class GLRenderer implements Renderer {
             gl.glDepthFunc(convertTestFunction(state.getDepthFunc()));
             context.depthFunc = state.getDepthFunc();
         }
-        
+
         if (state.isDepthWrite() && !context.depthWriteEnabled) {
             gl.glDepthMask(true);
             context.depthWriteEnabled = true;
@@ -971,12 +971,12 @@ public final class GLRenderer implements Renderer {
                 gl.glUniform1i(loc, b.booleanValue() ? GL.GL_TRUE : GL.GL_FALSE);
                 break;
             case Matrix3:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 assert fb.remaining() == 9;
                 gl.glUniformMatrix3(loc, false, fb);
                 break;
             case Matrix4:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 assert fb.remaining() == 16;
                 gl.glUniformMatrix4(loc, false, fb);
                 break;
@@ -985,23 +985,23 @@ public final class GLRenderer implements Renderer {
                 gl.glUniform1(loc, ib);
                 break;
             case FloatArray:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniform1(loc, fb);
                 break;
             case Vector2Array:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniform2(loc, fb);
                 break;
             case Vector3Array:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniform3(loc, fb);
                 break;
             case Vector4Array:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniform4(loc, fb);
                 break;
             case Matrix4Array:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniformMatrix4(loc, false, fb);
                 break;
             case Int:
@@ -1086,7 +1086,7 @@ public final class GLRenderer implements Renderer {
                 if (gles2) {
                     // request GLSL ES (1.00) when compiling under GLES2.
                     stringBuf.append("#version 100\n");
-                    
+
                     if (source.getType() == ShaderType.Fragment) {
                         // GLES2 requires precision qualifier.
                         stringBuf.append("precision mediump float;\n");
@@ -1473,7 +1473,7 @@ public final class GLRenderer implements Renderer {
                     rb.getId());
         }
     }
-    
+
     private void bindFrameBuffer(FrameBuffer fb) {
         if (fb == null) {
             if (context.boundFBO != 0) {
@@ -1511,12 +1511,12 @@ public final class GLRenderer implements Renderer {
         }
 
         bindFrameBuffer(fb);
-        
+
         for (int i = 0; i < fb.getNumColorBuffers(); i++) {
             FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
             updateFrameBufferAttachment(fb, colorBuf);
         }
-        
+
         FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();
         if (depthBuf != null) {
             updateFrameBufferAttachment(fb, depthBuf);
@@ -1562,11 +1562,11 @@ public final class GLRenderer implements Renderer {
         if (gl2 == null) {
             return;
         }
-        
+
         final int NONE    = -2;
         final int INITIAL = -1;
         final int MRT_OFF = 100;
-        
+
         if (fb == null) {
             // Set Read/Draw buffers to initial value.
             if (context.boundDrawBuf != INITIAL) {
@@ -1630,9 +1630,9 @@ public final class GLRenderer implements Renderer {
                 }
             }
         }
-        
+
     }
-    
+
     public void setFrameBuffer(FrameBuffer fb) {
         if (fb == null && mainFbOverride != null) {
             fb = mainFbOverride;
@@ -1858,7 +1858,7 @@ public final class GLRenderer implements Renderer {
         if (image != null) {
             haveMips = image.isGeneratedMipmapsRequired() || image.hasMipmaps();
         }
-        
+
         LastTextureState curState = image.getLastTextureState();
 
         if (curState.magFilter != tex.getMagFilter()) {
@@ -1921,7 +1921,7 @@ public final class GLRenderer implements Renderer {
             }
             curState.shadowCompareMode = texCompareMode;
         }
-        
+
         // If at this point we didn't bind the texture, bind it now
         bindTextureOnly(target, image, unit);
     }
@@ -1929,7 +1929,7 @@ public final class GLRenderer implements Renderer {
     /**
      * Validates if a potentially NPOT texture is supported by the hardware.
      * <p>
-     * Textures with power-of-2 dimensions are supported on all hardware, however 
+     * Textures with power-of-2 dimensions are supported on all hardware, however
      * non-power-of-2 textures may or may not be supported depending on which
      * texturing features are used.
      *
@@ -1984,7 +1984,7 @@ public final class GLRenderer implements Renderer {
     /**
      * Ensures that the texture is bound to the given unit
      * and that the unit is currently active (for modification).
-     * 
+     *
      * @param target The texture target, one of GL_TEXTURE_***
      * @param img The image texture to bind
      * @param unit At what unit to bind the texture.
@@ -2002,11 +2002,11 @@ public final class GLRenderer implements Renderer {
             statistics.onTextureUse(img, false);
         }
     }
-    
+
     /**
      * Ensures that the texture is bound to the given unit,
      * but does not care if the unit is active (for rendering).
-     * 
+     *
      * @param target The texture target, one of GL_TEXTURE_***
      * @param img The image texture to bind
      * @param unit At what unit to bind the texture.
@@ -2024,7 +2024,7 @@ public final class GLRenderer implements Renderer {
             statistics.onTextureUse(img, false);
         }
     }
-    
+
     /**
      * Uploads the given image to the GL driver.
      *
@@ -2048,6 +2048,7 @@ public final class GLRenderer implements Renderer {
 
         // bind texture
         int target = convertTextureType(type, img.getMultiSamples(), -1);
+
         bindTextureAndUnit(target, img, unit);
 
         if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
@@ -2062,7 +2063,7 @@ public final class GLRenderer implements Renderer {
                 // We'll generate mipmaps via glGenerateMipmapEXT (see below)
             }
         } else if (img.hasMipmaps()) {
-            // Image already has mipmaps, set the max level based on the 
+            // Image already has mipmaps, set the max level based on the
             // number of mipmaps we have.
             gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
         } else {
@@ -2334,32 +2335,37 @@ public final class GLRenderer implements Renderer {
         context.attribIndexList.copyNewToOld();
     }
 
-    public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) {
-        if (vb.getBufferType() == VertexBuffer.Type.Index) {
-            throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
-        }
-
-        if (context.boundShaderProgram <= 0) {
-            throw new IllegalStateException("Cannot render mesh without shader bound");
-        }
-
-        Attribute attrib = context.boundShader.getAttribute(vb.getBufferType());
+    private int updateAttributeLocation(Shader shader, VertexBuffer.Type attribType) {
+        Attribute attrib = shader.getAttribute(attribType);
         int loc = attrib.getLocation();
         if (loc == -1) {
-            return; // not defined
+            return -1; // not defined
         }
         if (loc == -2) {
-            loc = gl.glGetAttribLocation(context.boundShaderProgram, "in" + vb.getBufferType().name());
+            loc = gl.glGetAttribLocation(context.boundShaderProgram, "in" + attribType.name());
 
             // not really the name of it in the shader (inPosition) but
             // the internal name of the enum (Position).
             if (loc < 0) {
                 attrib.setLocation(-1);
-                return; // not available in shader.
+                return -1; // not available in shader.
             } else {
                 attrib.setLocation(loc);
             }
         }
+        return loc;
+    }
+
+    public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) {
+        if (vb.getBufferType() == VertexBuffer.Type.Index) {
+            throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
+        }
+
+        Shader shader = context.boundShader;
+        int location = updateAttributeLocation(shader, vb.getBufferType());
+        if (location == -1) {
+            return;
+        }
 
         if (vb.isInstanced()) {
             if (!caps.contains(Caps.MeshInstancing)) {
@@ -2383,11 +2389,11 @@ public final class GLRenderer implements Renderer {
 
         VertexBuffer[] attribs = context.boundAttribs;
         for (int i = 0; i < slotsRequired; i++) {
-            if (!context.attribIndexList.moveToNew(loc + i)) {
-                gl.glEnableVertexAttribArray(loc + i);
+            if (!context.attribIndexList.moveToNew(location + i)) {
+                gl.glEnableVertexAttribArray(location + i);
             }
         }
-        if (attribs[loc] != vb) {
+        if (attribs[location] != vb) {
             // NOTE: Use id from interleaved buffer if specified
             int bufId = idb != null ? idb.getId() : vb.getId();
             assert bufId != -1;
@@ -2400,12 +2406,12 @@ public final class GLRenderer implements Renderer {
             }
 
             if (slotsRequired == 1) {
-                gl.glVertexAttribPointer(loc,
-                        vb.getNumComponents(),
-                        convertFormat(vb.getFormat()),
-                        vb.isNormalized(),
-                        vb.getStride(),
-                        vb.getOffset());
+                gl.glVertexAttribPointer(location,
+                                         vb.getNumComponents(),
+                                         convertFormat(vb.getFormat()),
+                                         vb.isNormalized(),
+                                         vb.getStride(),
+                                         vb.getOffset());
             } else {
                 for (int i = 0; i < slotsRequired; i++) {
                     // The pointer maps the next 4 floats in the slot.
@@ -2416,17 +2422,17 @@ public final class GLRenderer implements Renderer {
                     // P4: ____________XXXX____________XXXX
                     // stride = 4 bytes in float * 4 floats in slot * num slots
                     // offset = 4 bytes in float * 4 floats in slot * slot index
-                    gl.glVertexAttribPointer(loc + i,
-                            4,
-                            convertFormat(vb.getFormat()),
-                            vb.isNormalized(),
-                            4 * 4 * slotsRequired,
-                            4 * 4 * i);
+                    gl.glVertexAttribPointer(location + i,
+                                             4,
+                                             convertFormat(vb.getFormat()),
+                                             vb.isNormalized(),
+                                             4 * 4 * slotsRequired,
+                                             4 * 4 * i);
                 }
             }
 
             for (int i = 0; i < slotsRequired; i++) {
-                int slot = loc + i;
+                int slot = location + i;
                 if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
                     // non-instanced -> instanced
                     glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan());
@@ -2439,6 +2445,92 @@ public final class GLRenderer implements Renderer {
         }
     }
 
+    /**
+     * Set VBO on VAO. Assumes a brand new mesh or modified mesh with new buffer.
+     *
+     * @param vb
+     * @param idb
+     */
+    public void setVertexAttribVAO(VertexBuffer vb, VertexBuffer idb) {
+        if (vb.getBufferType() == VertexBuffer.Type.Index) {
+            throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
+        }
+
+        Shader shader = context.boundShader;
+        int location = updateAttributeLocation(shader, vb.getBufferType());
+        if (location == -1) {
+            return;
+        }
+
+        if (vb.isInstanced()) {
+            if (!caps.contains(Caps.MeshInstancing)) {
+                throw new RendererException("Instancing is required, "
+                        + "but not supported by the "
+                        + "graphics hardware");
+            }
+        }
+        int slotsRequired = 1;
+        if (vb.getNumComponents() > 4) {
+            if (vb.getNumComponents() % 4 != 0) {
+                throw new RendererException("Number of components in multi-slot "
+                        + "buffers must be divisible by 4");
+            }
+            slotsRequired = vb.getNumComponents() / 4;
+        }
+
+        if (vb.isUpdateNeeded() && idb == null) {
+            updateBufferData(vb);
+        }
+
+        for (int i = 0; i < slotsRequired; i++) {
+            gl.glEnableVertexAttribArray(location + i);
+        }
+
+        // NOTE: Use id from interleaved buffer if specified
+        int bufId = idb != null ? idb.getId() : vb.getId();
+        assert bufId != -1;
+        if (context.boundArrayVBO != bufId) {
+            gl.glBindBuffer(GL.GL_ARRAY_BUFFER, bufId);
+            context.boundArrayVBO = bufId;
+            //statistics.onVertexBufferUse(vb, true);
+        } else {
+            //statistics.onVertexBufferUse(vb, false);
+        }
+
+        if (slotsRequired == 1) {
+            gl.glVertexAttribPointer(location,
+                                     vb.getNumComponents(),
+                                     convertFormat(vb.getFormat()),
+                                     vb.isNormalized(),
+                                     vb.getStride(),
+                                     vb.getOffset());
+        } else {
+            for (int i = 0; i < slotsRequired; i++) {
+                // The pointer maps the next 4 floats in the slot.
+                // E.g.
+                // P1: XXXX____________XXXX____________
+                // P2: ____XXXX____________XXXX________
+                // P3: ________XXXX____________XXXX____
+                // P4: ____________XXXX____________XXXX
+                // stride = 4 bytes in float * 4 floats in slot * num slots
+                // offset = 4 bytes in float * 4 floats in slot * slot index
+                gl.glVertexAttribPointer(location + i,
+                                         4,
+                                         convertFormat(vb.getFormat()),
+                                         vb.isNormalized(),
+                                         4 * 4 * slotsRequired,
+                                         4 * 4 * i);
+            }
+        }
+
+        for (int i = 0; i < slotsRequired; i++) {
+            int slot = location + i;
+            if (vb.isInstanced()) {
+                glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan());
+            }
+        }
+    }
+
     public void setVertexAttrib(VertexBuffer vb) {
         setVertexAttrib(vb, null);
     }
@@ -2488,57 +2580,20 @@ public final class GLRenderer implements Renderer {
         }
 
         int vertCount = mesh.getVertexCount();
-        if (mesh.getMode() == Mode.Hybrid) {
-            int[] modeStart = mesh.getModeStart();
-            int[] elementLengths = mesh.getElementLengths();
-
-            int elMode = convertElementMode(Mode.Triangles);
-            int fmt = convertFormat(indexBuf.getFormat());
-            int elSize = indexBuf.getFormat().getComponentSize();
-            int listStart = modeStart[0];
-            int stripStart = modeStart[1];
-            int fanStart = modeStart[2];
-            int curOffset = 0;
-            for (int i = 0; i < elementLengths.length; i++) {
-                if (i == stripStart) {
-                    elMode = convertElementMode(Mode.TriangleStrip);
-                } else if (i == fanStart) {
-                    elMode = convertElementMode(Mode.TriangleFan);
-                }
-                int elementLength = elementLengths[i];
-
-                if (count > 1) {
-                    glext.glDrawElementsInstancedARB(elMode,
-                            elementLength,
-                            fmt,
-                            curOffset,
-                            count);
-                } else {
-                    gl.glDrawRangeElements(elMode,
-                            0,
-                            vertCount,
-                            elementLength,
-                            fmt,
-                            curOffset);
-                }
-
-                curOffset += elementLength * elSize;
-            }
+        boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
+        if (useInstancing) {
+            glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
+                    indexBuf.getData().limit(),
+                    convertFormat(indexBuf.getFormat()),
+                    0,
+                    count);
         } else {
-            if (count > 1) {
-                glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
-                        indexBuf.getData().limit(),
-                        convertFormat(indexBuf.getFormat()),
-                        0,
-                        count);
-            } else {
-                gl.glDrawRangeElements(convertElementMode(mesh.getMode()),
-                        0,
-                        vertCount,
-                        indexBuf.getData().limit(),
-                        convertFormat(indexBuf.getFormat()),
-                        0);
-            }
+            gl.glDrawRangeElements(convertElementMode(mesh.getMode()),
+                    0,
+                    vertCount,
+                    indexBuf.getData().limit(),
+                    convertFormat(indexBuf.getFormat()),
+                    0);
         }
     }
 
@@ -2568,27 +2623,16 @@ public final class GLRenderer implements Renderer {
         }
     }
 
-    public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) {
-        int id = mesh.getId();
-        if (id == -1) {
-            IntBuffer temp = intBuf1;
-            gl3.glGenVertexArrays(temp);
-            id = temp.get(0);
-            mesh.setId(id);
-        }
-
-        if (context.boundVertexArray != id) {
-            gl3.glBindVertexArray(id);
-            context.boundVertexArray = id;
-        }
-
+    private void setupVertexBuffersLegacy(Mesh mesh, VertexBuffer[] instanceData) {
         VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
         if (interleavedData != null && interleavedData.isUpdateNeeded()) {
             updateBufferData(interleavedData);
         }
 
         if (instanceData != null) {
-            setVertexAttrib(instanceData, null);
+            for (VertexBuffer vb : instanceData) {
+                setVertexAttrib(vb, null);
+            }
         }
 
         for (VertexBuffer vb : mesh.getBufferList().getArray()) {
@@ -2608,80 +2652,132 @@ public final class GLRenderer implements Renderer {
         }
     }
 
-    private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) {
-        if (mesh.getId() == -1) {
-            updateVertexArray(mesh, instanceData);
-        } else {
-            // TODO: Check if it was updated
+    private void setupVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
+        VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
+        if (instanceData != null) {
+            for (VertexBuffer vb : instanceData) {
+                setVertexAttribVAO(vb, null);
+            }
         }
 
-        if (context.boundVertexArray != mesh.getId()) {
-            gl3.glBindVertexArray(mesh.getId());
-            context.boundVertexArray = mesh.getId();
-        }
+        for (VertexBuffer vb : mesh.getBufferList().getArray()) {
+            if (vb.getBufferType() == Type.InterleavedData
+                    || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
+                    || vb.getBufferType() == Type.Index) {
+                continue;
+            }
 
-//        IntMap<VertexBuffer> buffers = mesh.getBuffers();
-        VertexBuffer indices;
-        if (mesh.getNumLodLevels() > 0) {
-            indices = mesh.getLodLevel(lod);
-        } else {
-            indices = mesh.getBuffer(Type.Index);
-        }
-        if (indices != null) {
-            drawTriangleList(indices, mesh, count);
-        } else {
-            drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
+            if (vb.getStride() == 0) {
+                // not interleaved
+                setVertexAttribVAO(vb, null);
+            } else {
+                // interleaved
+                setVertexAttribVAO(vb, interleavedData);
+            }
         }
-        clearVertexAttribs();
-    }
-
-    private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
 
-        // Here while count is still passed in.  Can be removed when/if
-        // the method is collapsed again.  -pspeed        
-        count = Math.max(mesh.getInstanceCount(), count);
+        mesh.clearUpdateNeeded();
+    }
 
+    private void updateVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
         VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
         if (interleavedData != null && interleavedData.isUpdateNeeded()) {
             updateBufferData(interleavedData);
         }
+        if (instanceData != null) {
+            for (VertexBuffer vb : instanceData) {
+                if (vb.isUpdateNeeded()) {
+                    updateBufferData(vb);
+                }
+            }
+        }
+        for (VertexBuffer vb : mesh.getBufferList().getArray()) {
+            if (vb.getBufferType() == Type.InterleavedData
+                    || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
+                    || vb.getBufferType() == Type.Index
+                    || !vb.isUpdateNeeded()
+                    || !context.boundShader.isAttributeDefined(vb.getBufferType())) {
+                continue;
+            }
+            updateBufferData(vb);
+        }
+    }
 
+    private VertexBuffer getIndexBuffer(Mesh mesh, int lod) {
         VertexBuffer indices;
         if (mesh.getNumLodLevels() > 0) {
             indices = mesh.getLodLevel(lod);
         } else {
             indices = mesh.getBuffer(Type.Index);
         }
+        return indices;
+    }
 
-        if (instanceData != null) {
-            for (VertexBuffer vb : instanceData) {
-                setVertexAttrib(vb, null);
-            }
+    private void setVertexArrayObject(Mesh mesh) {
+        int id = mesh.getId();
+
+        if (id == -1) {
+            IntBuffer temp = intBuf1;
+            gl3.glGenVertexArrays(temp);
+            id = temp.get(0);
+            mesh.setId(id);
+
+            objManager.registerObject(mesh);
         }
 
-        for (VertexBuffer vb : mesh.getBufferList().getArray()) {
-            if (vb.getBufferType() == Type.InterleavedData
-                    || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
-                    || vb.getBufferType() == Type.Index) {
-                continue;
+        if (context.boundVertexArray != id) {
+            gl3.glBindVertexArray(id);
+            context.boundVertexArray = id;
+        }
+    }
+
+    private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
+        setVertexArrayObject(mesh);
+
+        // VAO clears current bound VBO automatically
+        context.boundElementArrayVBO = 0;
+        context.boundArrayVBO = 0;
+
+        VertexBuffer indices = getIndexBuffer(mesh, lod);
+        if (mesh.isUpdateNeeded()) {
+            setupVertexBuffers(mesh, instanceData);
+            if (indices != null) {
+                updateBufferData(indices);
+            }
+        } else {
+            updateVertexBuffers(mesh, instanceData);
+            if (indices != null) {
+                // NOTE: context.boundElementArrayVBO gets captured in the VAO.
+                // Make everyone think its already bound.
+                context.boundElementArrayVBO = indices.getId();
             }
+        }
 
-            if (vb.getStride() == 0) {
-                // not interleaved
-                setVertexAttrib(vb);
-            } else {
-                // interleaved
-                setVertexAttrib(vb, interleavedData);
+        if (indices != null) {
+            if (indices.isUpdateNeeded()) {
+                updateBufferData(indices);
             }
+
+            drawTriangleList(indices, mesh, count);
+
+            context.boundElementArrayVBO = 0;
+        } else {
+            drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
         }
+    }
+
+    private void renderMeshLegacy(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
+        setupVertexBuffersLegacy(mesh, instanceData);
+        VertexBuffer indices = getIndexBuffer(mesh, lod);
 
         clearVertexAttribs();
-        
+
         if (indices != null) {
             drawTriangleList(indices, mesh, count);
         } else {
             drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
         }
+
     }
 
     public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
@@ -2701,12 +2797,30 @@ public final class GLRenderer implements Renderer {
         if (gl4 != null && mesh.getMode().equals(Mode.Patch)) {
             gl4.glPatchParameter(mesh.getPatchVertexCount());
         }
+
         statistics.onMeshDrawn(mesh, lod, count);
-//        if (ctxCaps.GL_ARB_vertex_array_object){
-//            renderMeshVertexArray(mesh, lod, count);
-//        }else{
-        renderMeshDefault(mesh, lod, count, instanceData);
-//        }
+
+        // Here while count is still passed in.  Can be removed when/if
+        // the method is collapsed again.  -pspeed
+        count = Math.max(mesh.getInstanceCount(), count);
+
+//         if (caps.contains(Caps.VertexBufferArray)) {
+             renderMeshDefault(mesh, lod, count, instanceData);
+//         } else {
+//            renderMeshLegacy(mesh, lod, count, instanceData);
+//        // }
+    }
+
+    @Override
+    public void deleteMesh(Mesh mesh) {
+        int bufId = mesh.getId();
+        if (bufId != -1) {
+            // delete vertex array object
+            intBuf1.put(0, bufId);
+            intBuf1.position(0).limit(1);
+            gl3.glDeleteVertexArrays(intBuf1);
+            mesh.resetObject();
+        }
     }
 
     public void setMainFrameBufferSrgb(boolean enableSrgb) {

+ 77 - 139
jme3-core/src/main/java/com/jme3/scene/Mesh.java

@@ -42,6 +42,7 @@ import com.jme3.math.Matrix4f;
 import com.jme3.math.Triangle;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector3f;
+import com.jme3.renderer.Renderer;
 import com.jme3.scene.VertexBuffer.Format;
 import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.VertexBuffer.Usage;
@@ -49,6 +50,7 @@ import com.jme3.scene.mesh.*;
 import com.jme3.util.BufferUtils;
 import com.jme3.util.IntMap;
 import com.jme3.util.IntMap.Entry;
+import com.jme3.util.NativeObject;
 import com.jme3.util.SafeArrayList;
 import java.io.IOException;
 import java.nio.*;
@@ -71,7 +73,7 @@ import java.util.ArrayList;
  * 
  * @author Kirill Vainer
  */
-public class Mesh implements Savable, Cloneable {
+public class Mesh extends NativeObject implements Savable {
 
     /**
      * The mode of the Mesh specifies both the type of primitive represented
@@ -127,19 +129,14 @@ public class Mesh implements Savable, Cloneable {
          */
         TriangleFan(false),
         
-        /**
-         * A combination of various triangle modes. It is best to avoid
-         * using this mode as it may not be supported by all renderers.
-         * The {@link Mesh#setModeStart(int[]) mode start points} and
-         * {@link Mesh#setElementLengths(int[]) element lengths} must 
-         * be specified for this mode.
-         */
-        Hybrid(false),
+        Reserved(false),
+        
         /**
          * Used for Tesselation only. Requires to set the number of vertices
          * for each patch (default is 3 for triangle tesselation)
          */
         Patch(true);
+        
         private boolean listMode = false;
         
         private Mode(boolean listMode){
@@ -182,9 +179,6 @@ public class Mesh implements Savable, Cloneable {
     private int patchVertexCount=3; //only used for tesselation
     private int maxNumWeights = -1; // only if using skeletal animation
 
-    private int[] elementLengths;
-    private int[] modeStart;
-
     private Mode mode = Mode.Triangles;
 
     /**
@@ -193,6 +187,10 @@ public class Mesh implements Savable, Cloneable {
     public Mesh(){
     }
 
+    protected Mesh(int id) {
+        super(id);
+    }
+    
     /**
      * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
      * buffers} are shared between this and the clone mesh, the rest
@@ -202,23 +200,12 @@ public class Mesh implements Savable, Cloneable {
      */
     @Override
     public Mesh clone() {
-        try {
-            Mesh clone = (Mesh) super.clone();
-            clone.meshBound = meshBound.clone();
-            clone.collisionTree = collisionTree != null ? collisionTree : null;
-            clone.buffers = buffers.clone();
-            clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
-            clone.vertexArrayID = -1;
-            if (elementLengths != null) {
-                clone.elementLengths = elementLengths.clone();
-            }
-            if (modeStart != null) {
-                clone.modeStart = modeStart.clone();
-            }
-            return clone;
-        } catch (CloneNotSupportedException ex) {
-            throw new AssertionError();
-        }
+        Mesh clone = (Mesh) super.clone();
+        clone.meshBound = meshBound.clone();
+        clone.collisionTree = collisionTree != null ? collisionTree : null;
+        clone.buffers = buffers.clone();
+        clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
+        return clone;
     }
 
     /**
@@ -229,37 +216,30 @@ public class Mesh implements Savable, Cloneable {
      * @return a deep clone of this mesh.
      */
     public Mesh deepClone(){
-        try{
-            Mesh clone = (Mesh) super.clone();
-            clone.meshBound = meshBound != null ? meshBound.clone() : null;
-
-            // TODO: Collision tree cloning
-            //clone.collisionTree = collisionTree != null ? collisionTree : null;
-            clone.collisionTree = null; // it will get re-generated in any case
-
-            clone.buffers = new IntMap<VertexBuffer>();
-            clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
-            for (VertexBuffer vb : buffersList.getArray()){
-                VertexBuffer bufClone = vb.clone();
-                clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
-                clone.buffersList.add(bufClone);
-            }
-            
-            clone.vertexArrayID = -1;
-            clone.vertCount = vertCount;
-            clone.elementCount = elementCount;
-            clone.instanceCount = instanceCount;
-            
-            // although this could change
-            // if the bone weight/index buffers are modified
-            clone.maxNumWeights = maxNumWeights; 
-            
-            clone.elementLengths = elementLengths != null ? elementLengths.clone() : null;
-            clone.modeStart = modeStart != null ? modeStart.clone() : null;
-            return clone;
-        }catch (CloneNotSupportedException ex){
-            throw new AssertionError();
+        Mesh clone = (Mesh) super.clone();
+        clone.meshBound = meshBound != null ? meshBound.clone() : null;
+
+        // TODO: Collision tree cloning
+        //clone.collisionTree = collisionTree != null ? collisionTree : null;
+        clone.collisionTree = null; // it will get re-generated in any case
+
+        clone.buffers = new IntMap<VertexBuffer>();
+        clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+        for (VertexBuffer vb : buffersList.getArray()){
+            VertexBuffer bufClone = vb.clone();
+            clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
+            clone.buffersList.add(bufClone);
         }
+
+        clone.vertCount = vertCount;
+        clone.elementCount = elementCount;
+        clone.instanceCount = instanceCount;
+
+        // although this could change
+        // if the bone weight/index buffers are modified
+        clone.maxNumWeights = maxNumWeights; 
+        
+        return clone;
     }
 
     /**
@@ -373,12 +353,17 @@ public class Mesh implements Savable, Cloneable {
 
             // convert weights on the heap		
             VertexBuffer weights = getBuffer(Type.BoneWeight);		
-            if (!weights.getData().hasArray()) {		
-                FloatBuffer originalWeight = (FloatBuffer) weights.getData();		
-                FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());		
-                originalWeight.clear();		
-                arrayWeight.put(originalWeight);		
-                weights.updateData(arrayWeight);		
+            if (!weights.getData().hasArray()) {
+                if (weights.getFormat() == Format.Float) {
+                    FloatBuffer originalWeight = (FloatBuffer) weights.getData();		
+                    FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());		
+                    originalWeight.clear();		
+                    arrayWeight.put(originalWeight);		
+                    weights.updateData(arrayWeight);		
+                } else {
+                    // UByte to Float conversion
+                    throw new UnsupportedOperationException("Not yet supported");
+                }
             }		
             weights.setUsage(Usage.CpuOnly);
             // position, normal, and tanget buffers to be in "Stream" mode
@@ -475,40 +460,6 @@ public class Mesh implements Savable, Cloneable {
         return lodLevels[lod];
     }
     
-    /**
-     * Get the element lengths for {@link Mode#Hybrid} mesh mode.
-     * 
-     * @return element lengths
-     */
-    public int[] getElementLengths() {
-        return elementLengths;
-    }
-
-    /**
-     * Set the element lengths for {@link Mode#Hybrid} mesh mode.
-     * 
-     * @param elementLengths The element lengths to set
-     */
-    public void setElementLengths(int[] elementLengths) {
-        this.elementLengths = elementLengths;
-    }
-
-    /**
-     * Set the mode start indices for {@link Mode#Hybrid} mesh mode.
-     * 
-     * @return mode start indices
-     */
-    public int[] getModeStart() {
-        return modeStart;
-    }
-
-    /**
-     * Get the mode start indices for {@link Mode#Hybrid} mesh mode.
-     */
-    public void setModeStart(int[] modeStart) {
-        this.modeStart = modeStart;
-    }
-
     /**
      * Returns the mesh mode
      * 
@@ -899,23 +850,6 @@ public class Mesh implements Savable, Cloneable {
         indices[2] = ib.get(vertIndex+2);
     }
 
-    /**
-     * Returns the mesh's VAO ID. Internal use only.
-     */
-    public int getId(){
-        return vertexArrayID;
-    }
-
-    /**
-     * Sets the mesh's VAO ID. Internal use only.
-     */
-    public void setId(int id){
-        if (vertexArrayID != -1)
-            throw new IllegalStateException("ID has already been set.");
-        
-        vertexArrayID = id;
-    }
-
     /**
      * Generates a collision tree for the mesh.
      * Called automatically by {@link #collideWith(com.jme3.collision.Collidable, 
@@ -1109,20 +1043,17 @@ public class Mesh implements Savable, Cloneable {
      * @return A virtual or wrapped index buffer to read the data as a list
      */
     public IndexBuffer getIndicesAsList(){
-        if (mode == Mode.Hybrid)
-            throw new UnsupportedOperationException("Hybrid mode not supported");
-        
         IndexBuffer ib = getIndexBuffer();
-        if (ib != null){
-            if (mode.isListMode()){
+        if (ib != null) {
+            if (mode.isListMode()) {
                 // already in list mode
-                return ib; 
-            }else{
+                return ib;
+            } else {
                 // not in list mode but it does have an index buffer
                 // wrap it so the data is converted to list format
                 return new WrappedIndexBuffer(this);
             }
-        }else{
+        } else {
             // return a virtual index buffer that will supply
             // "fake" indices in list format
             return new VirtualIndexBuffer(vertCount, mode);
@@ -1385,16 +1316,30 @@ public class Mesh implements Savable, Cloneable {
         return patchVertexCount;
     }
 
+    @Override
+    public void resetObject() {
+        id = -1;
+        setUpdateNeeded();
+    }
+
+    @Override
+    public void deleteObject(Object rendererObject) {
+        ((Renderer)rendererObject).deleteMesh(this);
+    }
+
+    @Override
+    public NativeObject createDestructableClone() {
+        return new Mesh(id);
+    }
+
+    @Override
+    public long getUniqueId() {
+        return ((long)OBJTYPE_MESH << 32) | ((long)id);
+    }
+    
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule out = ex.getCapsule(this);
 
-//        HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
-//        for (Entry<VertexBuffer> buf : buffers){
-//            if (buf.getValue() != null)
-//                map.put(buf.getKey()+"a", buf.getValue());
-//        }
-//        out.writeStringSavableMap(map, "buffers", null);
-
         out.write(meshBound, "modelBound", null);
         out.write(vertCount, "vertCount", -1);
         out.write(elementCount, "elementCount", -1);
@@ -1402,8 +1347,6 @@ public class Mesh implements Savable, Cloneable {
         out.write(maxNumWeights, "max_num_weights", -1);
         out.write(mode, "mode", Mode.Triangles);
         out.write(collisionTree, "collisionTree", null);
-        out.write(elementLengths, "elementLengths", null);
-        out.write(modeStart, "modeStart", null);
         out.write(pointSize, "pointSize", 1f);
         
         //Removing HW skinning buffers to not save them
@@ -1439,21 +1382,16 @@ public class Mesh implements Savable, Cloneable {
         instanceCount = in.readInt("instanceCount", -1);
         maxNumWeights = in.readInt("max_num_weights", -1);
         mode = in.readEnum("mode", Mode.class, Mode.Triangles);
-        elementLengths = in.readIntArray("elementLengths", null);
-        modeStart = in.readIntArray("modeStart", null);
         collisionTree = (BIHTree) in.readSavable("collisionTree", null);
-        elementLengths = in.readIntArray("elementLengths", null);
-        modeStart = in.readIntArray("modeStart", null);
         pointSize = in.readFloat("pointSize", 1f);
 
-//        in.readStringSavableMap("buffers", null);
         buffers = (IntMap<VertexBuffer>) in.readIntSavableMap("buffers", null);
         for (Entry<VertexBuffer> entry : buffers){
             buffersList.add(entry.getValue());
         }
         
         //creating hw animation buffers empty so that they are put in the cache
-        if(isAnimated()){
+        if (isAnimated()) {
             VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex);
             hwBoneIndex.setUsage(Usage.CpuOnly);
             setBuffer(hwBoneIndex);

+ 16 - 1
jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java

@@ -524,6 +524,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         this.usage = usage;
     }
 
+    /**
+     * The size of an element in bytes.
+     * 
+     * The number of components multiplied by the size of a component.
+     * 
+     * @return size of an element in bytes.
+     */
+    public int getElementSize() {
+        return componentsLength;
+    }
+
     /**
      * @param normalized Set to true if integer components should be converted
      * from their maximal range into the range 0.0 - 1.0 when converted to
@@ -976,6 +987,10 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
      * of the parameters. The buffer will be of the type specified by
      * {@link Format format} and would be able to contain the given number
      * of elements with the given number of components in each element.
+     * @param format The format of the buffer to create
+     * @param components The number of components (aka dimensions)
+     * @param numElements Capacity of the buffer in number of elements.
+     * @return A buffer satisfying the given requirements.
      */
     public static Buffer createBuffer(Format format, int components, int numElements){
         if (components < 1 || components > 4)
@@ -1010,7 +1025,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
      * @return Deep clone of this buffer
      */
     @Override
-    public VertexBuffer clone(){
+    public VertexBuffer clone() {
         // NOTE: Superclass GLObject automatically creates shallow clone
         // e.g re-use ID.
         VertexBuffer vb = (VertexBuffer) super.clone();

+ 1 - 1
jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java

@@ -81,7 +81,7 @@ public class VirtualIndexBuffer extends IndexBuffer {
             case Triangles:
                 numIndices = numVerts;
                 return;
-            case Hybrid:
+            case Reserved:
                 throw new UnsupportedOperationException();
         }
     }

+ 10 - 0
jme3-core/src/main/java/com/jme3/shader/Shader.java

@@ -303,6 +303,16 @@ public final class Shader extends NativeObject {
         return attrib;
     }
 
+    public boolean isAttributeDefined(VertexBuffer.Type attribType) {
+        int ordinal = attribType.ordinal();
+        Attribute attrib = attribs.get(ordinal);
+        if (attrib == null){
+            return false;
+        } else {
+            return attrib.location != -1 && attrib.location != 2;
+        }
+    }
+    
     public ListMap<String, Uniform> getUniformMap(){
         return uniforms;
     }

+ 57 - 32
jme3-core/src/main/java/com/jme3/shader/Uniform.java

@@ -102,6 +102,10 @@ public class Uniform extends ShaderVariable {
     public Object getValue(){
         return value;
     }
+    
+    public FloatBuffer getMultiData() {
+        return multiData;
+    }
 
     public boolean isSetByCurrentMaterial() {
         return setByCurrentMaterial;
@@ -111,21 +115,6 @@ public class Uniform extends ShaderVariable {
         setByCurrentMaterial = false;
     }
 
-    private static void setVector4(Vector4f vec, Object value) {
-        if (value instanceof ColorRGBA) {
-            ColorRGBA color = (ColorRGBA) value;
-            vec.set(color.r, color.g, color.b, color.a);
-        } else if (value instanceof Quaternion) {
-            Quaternion quat = (Quaternion) value;
-            vec.set(quat.getX(), quat.getY(), quat.getZ(), quat.getW());
-        } else if (value instanceof Vector4f) {
-            Vector4f vec4 = (Vector4f) value;
-            vec.set(vec4);
-        } else{
-            throw new IllegalArgumentException();
-        }
-    }
-    
     public void clearValue(){
         updateNeeded = true;
 
@@ -189,20 +178,36 @@ public class Uniform extends ShaderVariable {
 
         switch (type){
             case Matrix3:
+                if (value.equals(this.value)) {
+                    return;
+                }
                 Matrix3f m3 = (Matrix3f) value;
                 if (multiData == null) {
                     multiData = BufferUtils.createFloatBuffer(9);
                 }
                 m3.fillFloatBuffer(multiData, true);
                 multiData.clear();
+                if (this.value == null) {
+                    this.value = new Matrix3f(m3);
+                } else {
+                    ((Matrix3f)this.value).set(m3);
+                }
                 break;
             case Matrix4:
+                if (value.equals(this.value)) {
+                    return;
+                }
                 Matrix4f m4 = (Matrix4f) value;
                 if (multiData == null) {
                     multiData = BufferUtils.createFloatBuffer(16);
                 }
                 m4.fillFloatBuffer(multiData, true);
                 multiData.clear();
+                if (this.value == null) {
+                    this.value = new Matrix4f(m4);
+                } else {
+                    ((Matrix4f)this.value).copy(m4);
+                }
                 break;
             case IntArray:
                 int[] ia = (int[]) value;
@@ -283,11 +288,32 @@ public class Uniform extends ShaderVariable {
                 }
                 multiData.clear();
                 break;
+            case Vector4:
+                if (value.equals(this.value)) {
+                    return;
+                }
+                if (value instanceof ColorRGBA) {
+                    if (this.value == null) {
+                        this.value = new ColorRGBA();
+                    }
+                    ((ColorRGBA) this.value).set((ColorRGBA) value);
+                } else if (value instanceof Vector4f) {
+                    if (this.value == null) {
+                        this.value = new Vector4f();
+                    }
+                    ((Vector4f) this.value).set((Vector4f) value);
+                } else {
+                    if (this.value == null) {
+                        this.value = new Quaternion();
+                    }
+                    ((Quaternion) this.value).set((Quaternion) value);
+                }
+                break;
                 // Only use check if equals optimization for primitive values
             case Int:
             case Float:
             case Boolean:
-                if (this.value != null && this.value.equals(value)) {
+                if (value.equals(this.value)) {
                     return;
                 }
                 this.value = value;
@@ -297,39 +323,38 @@ public class Uniform extends ShaderVariable {
                 break;
         }
 
-        if (multiData != null) {
-            this.value = multiData;
-        }
+//        if (multiData != null) {
+//            this.value = multiData;
+//        }
 
         varType = type;
         updateNeeded = true;
     }
 
     public void setVector4Length(int length){
-        if (location == -1)
+        if (location == -1) {
             return;
-
-        FloatBuffer fb = (FloatBuffer) value;
-        if (fb == null || fb.capacity() < length * 4) {
-            value = BufferUtils.createFloatBuffer(length * 4);
         }
-
+        
+        multiData = BufferUtils.ensureLargeEnough(multiData, length * 4);
+        value = multiData;
         varType = VarType.Vector4Array;
         updateNeeded = true;
         setByCurrentMaterial = true;
     }
 
     public void setVector4InArray(float x, float y, float z, float w, int index){
-        if (location == -1)
+        if (location == -1) {
             return;
+        }
 
-        if (varType != null && varType != VarType.Vector4Array)
-            throw new IllegalArgumentException("Expected a "+varType.name()+" value!");
+        if (varType != null && varType != VarType.Vector4Array) {
+            throw new IllegalArgumentException("Expected a " + varType.name() + " value!");
+        }
 
-        FloatBuffer fb = (FloatBuffer) value;
-        fb.position(index * 4);
-        fb.put(x).put(y).put(z).put(w);
-        fb.rewind();
+        multiData.position(index * 4);
+        multiData.put(x).put(y).put(z).put(w);
+        multiData.rewind();
         updateNeeded = true;
         setByCurrentMaterial = true;
     }

+ 3 - 0
jme3-core/src/main/java/com/jme3/system/NullRenderer.java

@@ -164,4 +164,7 @@ public class NullRenderer implements Renderer {
     public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {        
     }
 
+    @Override
+    public void deleteMesh(Mesh mesh) {
+    }
 }

+ 2 - 1
jme3-core/src/main/java/com/jme3/util/NativeObject.java

@@ -52,7 +52,8 @@ public abstract class NativeObject implements Cloneable {
                                OBJTYPE_SHADERSOURCE = 5,
                                OBJTYPE_AUDIOBUFFER  = 6,
                                OBJTYPE_AUDIOSTREAM  = 7,
-                               OBJTYPE_FILTER       = 8;
+                               OBJTYPE_FILTER       = 8,
+                               OBJTYPE_MESH         = 9;
     
     /**
      * The object manager to which this NativeObject is registered to.

+ 141 - 0
jme3-examples/src/main/java/jme3test/stress/TestUniqueGeometries.java

@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2009-2015 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 jme3test.stress;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.AppSettings;
+
+// Let's see if we can render 2500 batches in 60 fps.
+// We'll use 50 materials with various combinations of textures and colors
+// to make things wild.
+public class TestUniqueGeometries extends SimpleApplication {
+
+    private Material[] randomMaterials = new Material[50];
+    
+    private String[] textureList = new String[] {
+        "Blender/2.4x/textures/Concrete_Wall.PNG", 
+        "Blender/2.4x/textures/Grass_256.png", 
+        "Blender/2.4x/textures/SandDesert_StartTower.png", 
+        "Blender/2.4x/textures/Tar_Cracked.png", 
+        "Blender/2.4x/textures/WarningStrip.png", 
+        "Blender/2.4x/WoodCrate_lighter.png",
+        "Interface/Logo/Monkey.jpg", 
+        "Interface/Logo/Monkey.png", 
+        "Models/Boat/boat.png", 
+        "Models/Ninja/Ninja.jpg",
+        "Models/Tree/BarkColor.jpg", 
+        "Textures/Terrain/BrickWall/BrickWall.jpg", 
+        "Textures/Terrain/Pond/Pond.jpg", 
+        "Textures/Terrain/Pond/Pond_normal.png", 
+        "Textures/Terrain/Rock/Rock.PNG",
+        "Textures/Terrain/Rock/Rock_normal.png",
+        "Textures/Terrain/Rock2/rock.jpg",
+        "Textures/Terrain/Rocky/RockyNormals.jpg",
+        "Textures/Terrain/Rocky/RockyTexture.jpg",
+        "Textures/Terrain/splat/alpha1.png", 
+        "Textures/Terrain/splat/alpha2.png", 
+        "Textures/Terrain/splat/alphamap.png", 
+        "Textures/Terrain/splat/alphamap2.png", 
+        "Textures/Terrain/splat/dirt.jpg", 
+        "Textures/Terrain/splat/dirt_normal.png", 
+        "Textures/Terrain/splat/fortress512.png", 
+        "Textures/Terrain/splat/grass.jpg", 
+        "Textures/Terrain/splat/grass_normal.jpg",
+        "Textures/Terrain/splat/mountains128.png",
+        "Textures/Terrain/splat/road.jpg",
+        "Textures/Terrain/splat/road_normal.png",
+    };
+    
+    public static void main(String[] args) {
+        TestUniqueGeometries app = new TestUniqueGeometries();
+        AppSettings settings = new AppSettings(true);
+        settings.putBoolean("GraphicsTrace", false);
+        settings.putBoolean("GraphicsTiming", true);
+        app.setSettings(settings);
+        app.start();
+    }
+    
+    private void loadRandomMaterials() {
+        for (int i = 0; i < randomMaterials.length; i++) {
+            Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+            mat.setBoolean("VertexLighting", true);
+            mat.setBoolean("UseMaterialColors", true);
+            mat.setColor("Ambient", ColorRGBA.Black);
+            mat.setColor("Diffuse", ColorRGBA.White);
+            mat.setColor("Specular", ColorRGBA.White);
+            mat.setFloat("Shininess", 32);
+            mat.setTexture("DiffuseMap", assetManager.loadTexture(textureList[i % textureList.length]));
+            randomMaterials[i] = mat;
+        }
+    }
+    
+    @Override
+    public void simpleInitApp() {
+        flyCam.setDragToRotate(true);
+        
+        cam.setLocation(new Vector3f(22.717342f, 18.366547f, 22.043106f));
+        cam.setRotation(new Quaternion(-0.11630201f, 0.8794429f, -0.27703872f, -0.36919326f));
+        
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        rootNode.addLight(dl);
+        
+        flyCam.setMoveSpeed(5);
+        
+        loadRandomMaterials();
+        
+        // Box box = new Box(1,1,1);
+        
+        for (int y = -25; y < 25; y++) {
+            for (int x = -25; x < 25; x++) {
+                Material mat = randomMaterials[0]; // randomMaterials[FastMath.nextRandomInt(0, randomMaterials.length - 1)];
+        
+                Box box = new Box(1,1,1);
+                Geometry boxClone = new Geometry("box", box);
+                boxClone.setMaterial(mat);
+                
+                boxClone.setLocalTranslation(x * .5f, 0, y * .5f);
+                boxClone.setLocalScale(.15f);
+                boxClone.setMaterial(mat);
+                rootNode.attachChild(boxClone);
+            }
+        }
+    }
+}

+ 0 - 529
jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java

@@ -1,529 +0,0 @@
-/*
- * Copyright (c) 2009-2012 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.renderer.jogl;
-
-import com.jme3.renderer.RendererException;
-import com.jme3.texture.Image;
-import com.jme3.texture.Image.Format;
-import com.jme3.texture.image.ColorSpace;
-
-import java.nio.ByteBuffer;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.jogamp.opengl.GL;
-import com.jogamp.opengl.GL2;
-import com.jogamp.opengl.GL2ES2;
-import com.jogamp.opengl.GL2ES3;
-import com.jogamp.opengl.GL2GL3;
-import com.jogamp.opengl.GLContext;
-
-public class TextureUtil {
-    
-    private static boolean abgrToRgbaConversionEnabled = false;
-
-    public static int convertTextureFormat(Format fmt) {
-        switch (fmt) {
-            case Alpha8:
-                return GL.GL_ALPHA;
-            case Luminance8Alpha8:
-                return GL.GL_LUMINANCE_ALPHA;
-            case Luminance8:
-                return GL.GL_LUMINANCE;
-            case BGR8:
-            case RGB8:
-            case RGB565:
-                return GL.GL_RGB;
-            case RGB5A1:
-            case RGBA8:
-                return GL.GL_RGBA;
-            case Depth:
-                return GL2ES2.GL_DEPTH_COMPONENT;
-            default:
-                throw new UnsupportedOperationException("Unrecognized format: " + fmt);
-        }
-    }
-    
-    public static class GLImageFormat {
-        
-        int internalFormat;
-        int format;
-        int dataType;
-        boolean compressed;
-
-        public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) {
-            this.internalFormat = internalFormat;
-            this.format = format;
-            this.dataType = dataType;
-            this.compressed = compressed;
-        }
-    }
-    
-    private static final GLImageFormat[] formatToGL = new GLImageFormat[Format.values().length];
-    
-    private static void setFormat(Format format, int glInternalFormat, int glFormat, int glDataType, boolean glCompressed){
-        formatToGL[format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, glCompressed);
-    }
-    
-    static {
-        // Alpha formats
-        setFormat(Format.Alpha8,  GL2.GL_ALPHA8,  GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE, false);
-        
-        // Luminance formats
-        setFormat(Format.Luminance8,   GL2.GL_LUMINANCE8,  GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false);
-        setFormat(Format.Luminance16F, GL2.GL_LUMINANCE16F, GL.GL_LUMINANCE, GL.GL_HALF_FLOAT, false);
-        setFormat(Format.Luminance32F, GL2.GL_LUMINANCE32F, GL.GL_LUMINANCE, GL.GL_FLOAT, false);
-        
-        // Luminance alpha formats
-        setFormat(Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8,  GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false);
-        setFormat(Format.Luminance16FAlpha16F, GL2.GL_LUMINANCE_ALPHA16F, GL.GL_LUMINANCE_ALPHA, GL.GL_HALF_FLOAT, false);
-        
-        // Depth formats
-        setFormat(Format.Depth,    GL2ES2.GL_DEPTH_COMPONENT,    GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE, false);
-        setFormat(Format.Depth16,  GL.GL_DEPTH_COMPONENT16,  GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT, false);
-        setFormat(Format.Depth24,  GL.GL_DEPTH_COMPONENT24,  GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false);
-        setFormat(Format.Depth32,  GL.GL_DEPTH_COMPONENT32,  GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false);
-        setFormat(Format.Depth32F, GL2GL3.GL_DEPTH_COMPONENT32F, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT,         false);
-        
-        // Depth stencil formats
-        setFormat(Format.Depth24Stencil8, GL.GL_DEPTH24_STENCIL8, GL.GL_DEPTH_STENCIL, GL.GL_UNSIGNED_INT_24_8, false);
-        
-        // RGB formats
-        setFormat(Format.BGR8,       GL.GL_RGB8,  GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false);
-        setFormat(Format.ARGB8,       GL.GL_RGBA8,  GL.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8_REV, false);
-        setFormat(Format.BGRA8,       GL.GL_RGBA8,  GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, false);
-        setFormat(Format.RGB8,       GL.GL_RGB8,  GL.GL_RGB,        GL.GL_UNSIGNED_BYTE, false);
-        setFormat(Format.RGB16F,     GL2ES2.GL_RGB16F, GL.GL_RGB, GL.GL_HALF_FLOAT, false);
-        setFormat(Format.RGB32F,     GL.GL_RGB32F, GL.GL_RGB, GL.GL_FLOAT, false);
-        
-        // Special RGB formats
-        setFormat(Format.RGB111110F, GL2ES3.GL_R11F_G11F_B10F,    GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false);
-        setFormat(Format.RGB9E5,     GL2GL3.GL_RGB9_E5, GL.GL_RGB, GL2GL3.GL_UNSIGNED_INT_5_9_9_9_REV, false);
-        setFormat(Format.RGB16F_to_RGB111110F, GL2ES3.GL_R11F_G11F_B10F,    GL.GL_RGB, GL.GL_HALF_FLOAT, false);
-        setFormat(Format.RGB16F_to_RGB9E5, GL2.GL_RGB9_E5, GL.GL_RGB, GL.GL_HALF_FLOAT, false);
-        
-        // RGBA formats
-        setFormat(Format.ABGR8,   GL.GL_RGBA8,       GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false);
-        setFormat(Format.RGB5A1,    GL.GL_RGB5_A1,   GL.GL_RGBA,        GL.GL_UNSIGNED_SHORT_5_5_5_1, false);
-        setFormat(Format.RGBA8,   GL.GL_RGBA8,       GL.GL_RGBA,        GL.GL_UNSIGNED_BYTE, false);
-        setFormat(Format.RGBA16F, GL2ES2.GL_RGBA16F, GL.GL_RGBA, GL.GL_HALF_FLOAT, false);
-        setFormat(Format.RGBA32F, GL.GL_RGBA32F, GL.GL_RGBA, GL.GL_FLOAT, false);
-        
-        // DXT formats
-        setFormat(Format.DXT1,  GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL.GL_RGB,   GL.GL_UNSIGNED_BYTE, true);
-        setFormat(Format.DXT1A, GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
-        setFormat(Format.DXT3,  GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
-        setFormat(Format.DXT5,  GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
-    }
-    
-     //sRGB formats        
-    private static final GLImageFormat sRGB_RGB8 = new GLImageFormat(GL2.GL_SRGB8,GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false);
-    private static final GLImageFormat sRGB_RGBA8 = new GLImageFormat(GL.GL_SRGB8_ALPHA8,GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false);
-    private static final GLImageFormat sRGB_Luminance8 = new GLImageFormat(GL2.GL_SLUMINANCE8,GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false);
-    private static final GLImageFormat sRGB_LuminanceAlpha8 = new GLImageFormat(GL2.GL_SLUMINANCE8_ALPHA8,GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false);
-    private static final GLImageFormat sRGB_BGR8 = new GLImageFormat(GL2.GL_SRGB8, GL2GL3.GL_BGR,  GL.GL_UNSIGNED_BYTE, false);
-    private static final GLImageFormat sRGB_ABGR8 = new GLImageFormat(GL2.GL_SRGB8_ALPHA8, GL2.GL_ABGR_EXT,  GL.GL_UNSIGNED_BYTE, false);
-
-    //FIXME cannot find GL_COMPRESSED_RGB_S3TC_DXT1,GL_COMPRESSED_RGBA_S3TC_DXT1,GL_COMPRESSED_RGB_S3TC_DXT3,GL_COMPRESSED_RGB_S3TC_DXT5 in JOGL used constants
-    //GL_COMPRESSED_RGB_S3TC_DXT1 = 33776;
-    //GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 33777;
-    //GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 33778;
-    //GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 33779;
-    private static final GLImageFormat sRGB_DXT1 = new GLImageFormat(33776, GL.GL_RGB,   GL.GL_UNSIGNED_BYTE, true);
-    private static final GLImageFormat sRGB_DXT1A = new GLImageFormat( 33777, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
-    private static final GLImageFormat sRGB_DXT3 = new GLImageFormat(33778, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
-    private static final GLImageFormat sRGB_DXT5 = new GLImageFormat( 33779, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
-
-    
-    public static GLImageFormat getImageFormat(Format fmt, boolean isSrgb){
-        GL gl = GLContext.getCurrentGL();
-        switch (fmt){
-            case ABGR8:
-                if (!gl.isExtensionAvailable("GL_EXT_abgr") && !abgrToRgbaConversionEnabled) {
-                    setFormat(Format.ABGR8,   GL.GL_RGBA,        GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false);
-                    abgrToRgbaConversionEnabled = true;
-                }
-                break;
-            case BGR8:
-                if (!gl.isExtensionAvailable("GL_VERSION_1_2") && !gl.isExtensionAvailable("EXT_bgra")){
-                    return null;
-                }
-                break;
-            case DXT1:
-            case DXT1A:
-            case DXT3:
-            case DXT5:
-                if (!gl.isExtensionAvailable("GL_EXT_texture_compression_s3tc")) {
-                    return null;
-                }
-                break;
-            case Depth:
-            case Depth16:
-            case Depth24:
-            case Depth32:
-                if (!gl.isExtensionAvailable("GL_VERSION_1_4") && !gl.isExtensionAvailable("ARB_depth_texture")){
-                    return null;
-                }
-                break;
-            case Depth24Stencil8:
-                if (!gl.isExtensionAvailable("GL_VERSION_3_0")){
-                    return null;
-                }
-                break;
-            case Luminance16F:
-            case Luminance16FAlpha16F:
-            case Luminance32F:
-                if (!gl.isExtensionAvailable("GL_ARB_texture_float")){
-                    return null;
-                }
-                break;
-            case RGB16F:
-            case RGB32F:
-            case RGBA16F:
-            case RGBA32F:
-                if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_ARB_texture_float")){
-                    return null;
-                }
-                break;
-            case Depth32F:
-                if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_NV_depth_buffer_float")){
-                    return null;
-                }
-                break;
-            case RGB9E5:
-            case RGB16F_to_RGB9E5:
-                if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_texture_shared_exponent")){
-                    return null;
-                }
-                break;
-            case RGB111110F:
-            case RGB16F_to_RGB111110F:
-                if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_packed_float")){
-                    return null;
-                }
-                break;
-        }
-        
-        if(isSrgb){
-            return getSrgbFormat(fmt);
-        }
-        return formatToGL[fmt.ordinal()];
-    }
-  
-    public static GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) {
-        GLImageFormat glFmt = getImageFormat(fmt, isSrgb);
-        if (glFmt == null) {
-            throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware.");
-        }
-        return glFmt;
-    }
-    
-    private static GLImageFormat getSrgbFormat(Format fmt){
-        switch (fmt){
-            case RGB8 : return sRGB_RGB8;
-            case RGBA8 : return sRGB_RGBA8;
-            case BGR8 : return sRGB_BGR8;    
-            case ABGR8 : return sRGB_ABGR8;    
-            case Luminance8 : return sRGB_Luminance8;
-            case Luminance8Alpha8 : return sRGB_LuminanceAlpha8;
-            case DXT1 : return sRGB_DXT1;
-            case DXT1A : return sRGB_DXT1A;
-            case DXT3 : return sRGB_DXT3;
-            case DXT5 : return sRGB_DXT5;
-            default : Logger.getLogger(TextureUtil.class.getName()).log(Level.WARNING, "Format {0} has no sRGB equivalent, using linear format.", fmt.toString());
-                return formatToGL[fmt.ordinal()];
-        }
-    }
-
-    public static void uploadTexture(Image image,
-                                     int target,
-                                     int index,
-                                     int border,
-                                     boolean linearizeSrgb){
-        GL gl = GLContext.getCurrentGL();
-        Image.Format fmt = image.getFormat();
-        GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB  && linearizeSrgb);
-
-        ByteBuffer data;
-        if (index >= 0 && image.getData() != null && image.getData().size() > 0){
-            data = image.getData(index);
-        }else{
-            data = null;
-        }
-
-        int width = image.getWidth();
-        int height = image.getHeight();
-        int depth = image.getDepth();
-
-        if (data != null) {
-            if (abgrToRgbaConversionEnabled) {
-                convertABGRtoRGBA(data);
-            }
-            gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
-        }
-
-        int[] mipSizes = image.getMipMapSizes();
-        int pos = 0;
-        // TODO: Remove unneccessary allocation
-        if (mipSizes == null){
-            if (data != null)
-                mipSizes = new int[]{ data.capacity() };
-            else
-                mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 };
-        }
-
-        boolean subtex = false;
-        int samples = image.getMultiSamples();
-
-        for (int i = 0; i < mipSizes.length; i++){
-            int mipWidth =  Math.max(1, width  >> i);
-            int mipHeight = Math.max(1, height >> i);
-            int mipDepth =  Math.max(1, depth  >> i);
-
-            if (data != null){
-                data.position(pos);
-                data.limit(pos + mipSizes[i]);
-            }
-            
-            if (glFmt.compressed && data != null){
-                if (target == GL2ES2.GL_TEXTURE_3D){
-                    gl.getGL2ES2().glCompressedTexImage3D(target,
-                                                i,
-                                                glFmt.internalFormat,
-                                                mipWidth,
-                                                mipHeight,
-                                                mipDepth,
-                                                data.remaining(),
-                                                border,
-                                                data);
-                }else{
-                    //all other targets use 2D: array, cubemap, 2d
-                    gl.glCompressedTexImage2D(target,
-                                                i,
-                                                glFmt.internalFormat,
-                                                mipWidth,
-                                                mipHeight,
-                                                data.remaining(),
-                                                border,
-                                                data);
-                }
-            }else{
-                if (target == GL2ES2.GL_TEXTURE_3D){
-                    gl.getGL2ES2().glTexImage3D(target,
-                                      i,
-                                      glFmt.internalFormat,
-                                      mipWidth,
-                                      mipHeight,
-                                      mipDepth,
-                                      border,
-                                      glFmt.format,
-                                      glFmt.dataType,
-                                      data);
-                }else if (target == GL2ES3.GL_TEXTURE_2D_ARRAY){
-                    // prepare data for 2D array
-                    // or upload slice
-                    if (index == -1){
-                        gl.getGL2ES2().glTexImage3D(target,
-                                          0,
-                                          glFmt.internalFormat,
-                                          mipWidth,
-                                          mipHeight,
-                                          image.getData().size(), //# of slices
-                                          border,
-                                          glFmt.format,
-                                          glFmt.dataType,
-                                          data);
-                    }else{
-                        gl.getGL2ES2().glTexSubImage3D(target,
-                                             i, // level
-                                             0, // xoffset
-                                             0, // yoffset
-                                             index, // zoffset
-                                             width, // width
-                                             height, // height
-                                             1, // depth
-                                             glFmt.format,
-                                             glFmt.dataType,
-                                             data);
-                    }
-                }else{
-                    if (subtex){
-                        if (samples > 1){
-                            throw new IllegalStateException("Cannot update multisample textures");
-                        }
-
-                        gl.glTexSubImage2D(target,
-                                             i,
-                                             0, 0,
-                                             mipWidth, mipHeight,
-                                             glFmt.format,
-                                             glFmt.dataType,
-                                             data);
-                    }else{
-                        if (samples > 1){
-                            if (gl.isGL2GL3()) {
-                                gl.getGL3().glTexImage2DMultisample(target,
-                                        samples,
-                                        glFmt.internalFormat,
-                                        mipWidth,
-                                        mipHeight,
-                                        true);
-                            }
-                        } else {
-                            gl.glTexImage2D(target,
-                                              i,
-                                              glFmt.internalFormat,
-                                              mipWidth,
-                                              mipHeight,
-                                              border,
-                                              glFmt.format,
-                                              glFmt.dataType,
-                                              data);
-                        }
-                    }
-                }
-            }
-            
-            pos += mipSizes[i];
-        }
-    }
-    
-    private static void convertABGRtoRGBA(ByteBuffer buffer) {
-
-        for (int i = 0; i < buffer.capacity(); i++) {
-
-            int a = buffer.get(i++);
-            int b = buffer.get(i++);
-            int g = buffer.get(i++);
-            int r = buffer.get(i);
-
-            buffer.put(i - 3, (byte) r);
-            buffer.put(i - 2, (byte) g);
-            buffer.put(i - 1, (byte) b);
-            buffer.put(i, (byte) a);
-
-        }
-
-    }
-
-    /**
-     * Update the texture currently bound to target at with data from the given Image at position x and y. The parameter
-     * index is used as the zoffset in case a 3d texture or texture 2d array is being updated.
-     *
-     * @param image Image with the source data (this data will be put into the texture)
-     * @param target the target texture
-     * @param index the mipmap level to update
-     * @param x the x position where to put the image in the texture
-     * @param y the y position where to put the image in the texture
-     */
-    public static void uploadSubTexture(
-        Image image,
-        int target,
-        int index,
-        int x,
-        int y,
-        boolean linearizeSrgb) {
-      GL gl = GLContext.getCurrentGL();
-      Image.Format fmt = image.getFormat();
-      GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB  && linearizeSrgb);
-
-      ByteBuffer data = null;
-      if (index >= 0 && image.getData() != null && image.getData().size() > 0) {
-        data = image.getData(index);
-      }
-
-      int width = image.getWidth();
-      int height = image.getHeight();
-      int depth = image.getDepth();
-
-      if (data != null) {
-        gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
-      }
-
-      int[] mipSizes = image.getMipMapSizes();
-      int pos = 0;
-
-      // TODO: Remove unneccessary allocation
-      if (mipSizes == null){
-        if (data != null) {
-          mipSizes = new int[]{ data.capacity() };
-        } else {
-          mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 };
-        }
-      }
-
-      int samples = image.getMultiSamples();
-
-      for (int i = 0; i < mipSizes.length; i++){
-        int mipWidth =  Math.max(1, width  >> i);
-        int mipHeight = Math.max(1, height >> i);
-        int mipDepth =  Math.max(1, depth  >> i);
-
-        if (data != null){
-          data.position(pos);
-          data.limit(pos + mipSizes[i]);
-        }
-
-        // to remove the cumbersome if/then/else stuff below we'll update the pos right here and use continue after each
-        // gl*Image call in an attempt to unclutter things a bit
-        pos += mipSizes[i];
-
-        int glFmtInternal = glFmt.internalFormat;
-        int glFmtFormat = glFmt.format;
-        int glFmtDataType = glFmt.dataType;
-
-        if (glFmt.compressed && data != null){
-          if (target == GL2ES2.GL_TEXTURE_3D){
-            gl.getGL2ES2().glCompressedTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtInternal, data.limit(), data);
-            continue;
-          }
-
-          // all other targets use 2D: array, cubemap, 2d
-          gl.getGL2ES2().glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtInternal, data.limit(), data);
-          continue;
-        }
-
-        if (target == GL2ES2.GL_TEXTURE_3D){
-          gl.getGL2ES2().glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data);
-          continue;
-        }
-
-        if (samples > 1){
-          throw new IllegalStateException("Cannot update multisample textures");
-        }
-
-        gl.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtFormat, glFmtDataType, data);
-        continue;
-      }
-    }
-}

+ 17 - 16
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java

@@ -54,6 +54,7 @@ import com.jme3.system.AppSettings;
 import com.jme3.system.JmeContext;
 import com.jme3.system.NanoTimer;
 import com.jme3.system.NativeLibraryLoader;
+import com.jme3.system.NullRenderer;
 import com.jme3.system.SystemListener;
 import com.jme3.system.Timer;
 
@@ -69,9 +70,9 @@ import com.jogamp.opengl.GLContext;
 public abstract class JoglContext implements JmeContext {
 
     private static final Logger logger = Logger.getLogger(JoglContext.class.getName());
-    
+
     protected static final String THREAD_NAME = "jME3 Main";
-    
+
     protected AtomicBoolean created = new AtomicBoolean(false);
     protected AtomicBoolean renderable = new AtomicBoolean(false);
     protected final Object createdLock = new Object();
@@ -91,7 +92,7 @@ public abstract class JoglContext implements JmeContext {
             NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
         }
     }
-    
+
     @Override
 	public void setSystemListener(SystemListener listener){
         this.listener = listener;
@@ -101,7 +102,7 @@ public abstract class JoglContext implements JmeContext {
 	public void setSettings(AppSettings settings) {
         this.settings.copyFrom(settings);
     }
-    
+
     @Override
 	public boolean isRenderable(){
         return renderable.get();
@@ -160,48 +161,48 @@ public abstract class JoglContext implements JmeContext {
             }
         }
     }
-    
+
     protected void initContextFirstTime(){
         if (GLContext.getCurrent().getGLVersionNumber().getMajor() < 2) {
-            throw new RendererException("OpenGL 2.0 or higher is " + 
+            throw new RendererException("OpenGL 2.0 or higher is " +
                                         "required for jMonkeyEngine");
         }
-        
+
         if (settings.getRenderer().startsWith("JOGL")) {
         	com.jme3.renderer.opengl.GL gl = new JoglGL();
         	GLExt glext = new JoglGLExt();
         	GLFbo glfbo = new JoglGLFbo();
-            
+
             if (settings.getBoolean("GraphicsDebug")) {
                 gl    = new GLDebugDesktop(gl, glext, glfbo);
                 glext = (GLExt) gl;
                 glfbo = (GLFbo) gl;
             }
-            
+
             if (settings.getBoolean("GraphicsTiming")) {
                 GLTimingState timingState = new GLTimingState();
                 gl    = (com.jme3.renderer.opengl.GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class);
                 glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class);
                 glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class);
             }
-                  
+
             if (settings.getBoolean("GraphicsTrace")) {
                 gl    = (com.jme3.renderer.opengl.GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class);
                 glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class);
                 glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class);
             }
-            
+
             renderer = new GLRenderer(gl, glext, glfbo);
             renderer.initialize();
         } else {
             throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer());
         }
-        
+
         if (GLContext.getCurrentGL().isExtensionAvailable("GL_ARB_debug_output") && settings.getBoolean("GraphicsDebug")) {
         	GLContext.getCurrent().enableGLDebugMessage(true);
         	GLContext.getCurrent().addGLDebugListener(new JoglGLDebugOutputHandler());
         }
-        
+
         renderer.setMainFrameBufferSrgb(settings.getGammaCorrection());
         renderer.setLinearizeSrgbImages(settings.getGammaCorrection());
 
@@ -241,7 +242,7 @@ public abstract class JoglContext implements JmeContext {
             createdLock.notifyAll();
         }
     }
-    
+
     protected int determineMaxSamples(int requestedSamples) {
         GL gl = GLContext.getCurrentGL();
         if (gl.hasFullFBOSupport()) {
@@ -257,7 +258,7 @@ public abstract class JoglContext implements JmeContext {
             }
         }
     }
-    
+
     protected int getNumSamplesToUse() {
         int samples = 0;
         if (settings.getSamples() > 1){
@@ -268,7 +269,7 @@ public abstract class JoglContext implements JmeContext {
                         "Couldn''t satisfy antialiasing samples requirement: x{0}. "
                         + "Video hardware only supports: x{1}",
                         new Object[]{samples, supportedSamples});
-                
+
                 samples = supportedSamples;
             }
         }