فهرست منبع

Ports all changes done on the official renderer during several months to the JOGL renderer (I thank a lot the lazy developers)

Julien Gouesse 11 سال پیش
والد
کامیت
2bca84a43d
1فایلهای تغییر یافته به همراه255 افزوده شده و 141 حذف شده
  1. 255 141
      jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java

+ 255 - 141
jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java

@@ -33,27 +33,20 @@ package com.jme3.renderer.jogl;
 
 import com.jme3.light.LightList;
 import com.jme3.material.RenderState;
-import com.jme3.math.ColorRGBA;
-import com.jme3.math.FastMath;
-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;
-import com.jme3.renderer.Caps;
-import com.jme3.renderer.IDList;
-import com.jme3.renderer.RenderContext;
-import com.jme3.renderer.Renderer;
-import com.jme3.renderer.RendererException;
-import com.jme3.renderer.Statistics;
+import com.jme3.material.RenderState.StencilOperation;
+import com.jme3.material.RenderState.TestFunction;
+import com.jme3.math.*;
+import com.jme3.renderer.*;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.Mesh.Mode;
 import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
 import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.VertexBuffer.Usage;
 import com.jme3.shader.Attribute;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Shader.ShaderSource;
+import com.jme3.shader.Shader.ShaderType;
 import com.jme3.shader.Uniform;
 import com.jme3.texture.FrameBuffer;
 import com.jme3.texture.FrameBuffer.RenderBuffer;
@@ -63,7 +56,16 @@ import com.jme3.texture.Texture.WrapAxis;
 import com.jme3.util.BufferUtils;
 import com.jme3.util.ListMap;
 import com.jme3.util.NativeObjectManager;
-import com.jme3.util.SafeArrayList;
+
+import java.nio.*;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import jme3tools.converters.MipMapGenerator;
+import jme3tools.shader.ShaderDebug;
+
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.FloatBuffer;
@@ -72,16 +74,16 @@ import java.util.EnumSet;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+
 import javax.media.nativewindow.NativeWindowFactory;
 import javax.media.opengl.GL;
 import javax.media.opengl.GL2;
 import javax.media.opengl.GL2ES1;
 import javax.media.opengl.GL2ES2;
+import javax.media.opengl.GL2ES3;
 import javax.media.opengl.GL2GL3;
 import javax.media.opengl.GL3;
 import javax.media.opengl.GLContext;
-import jme3tools.converters.MipMapGenerator;
-import jme3tools.shader.ShaderDebug;
 
 public class JoglRenderer implements Renderer {
 
@@ -136,11 +138,13 @@ public class JoglRenderer implements Renderer {
         nameBuf.rewind();
     }
 
-    public Statistics getStatistics() {
+    @Override
+	public Statistics getStatistics() {
         return statistics;
     }
     
-    public EnumSet<Caps> getCaps() {
+    @Override
+	public EnumSet<Caps> getCaps() {
         return caps;
     }
 
@@ -302,7 +306,7 @@ public class JoglRenderer implements Renderer {
             caps.add(Caps.PackedDepthStencilBuffer);
         }
 
-        if (gl.isExtensionAvailable("GL_ARB_draw_instanced")) {
+        if (gl.isExtensionAvailable("GL_ARB_draw_instanced") || gl.isExtensionAvailable("GL_ARB_instanced_arrays")) {
             caps.add(Caps.MeshInstancing);
         }
 
@@ -335,12 +339,13 @@ public class JoglRenderer implements Renderer {
 
         boolean latc = gl.isExtensionAvailable("GL_EXT_texture_compression_latc");
         //FIXME ignore atdc?
-        boolean atdc = gl.isExtensionAvailable("GL_ATI_texture_compression_3dc");
-        if (latc || atdc) {
+        //boolean atdc = gl.isExtensionAvailable("GL_ATI_texture_compression_3dc");
+        if (latc /*|| atdc*/) {
             caps.add(Caps.TextureCompressionLATC);
         }
 
-        if (gl.isExtensionAvailable("GL_EXT_packed_float")) {
+        if (gl.isExtensionAvailable("GL_EXT_packed_float") || gl.isExtensionAvailable("GL_VERSION_3_0")) {
+        	// This format is part of the OGL3 specification
             caps.add(Caps.PackedFloatColorBuffer);
             if (gl.isExtensionAvailable("GL_ARB_half_float_pixel")) {
                 // because textures are usually uploaded as RGB16F
@@ -349,11 +354,11 @@ public class JoglRenderer implements Renderer {
             }
         }
 
-        if (gl.isExtensionAvailable("GL_EXT_texture_array")) {
+        if (gl.isExtensionAvailable("GL_EXT_texture_array") || gl.isExtensionAvailable("GL_VERSION_3_0")) {
             caps.add(Caps.TextureArray);
         }
 
-        if (gl.isExtensionAvailable("GL_EXT_texture_shared_exponent")) {
+        if (gl.isExtensionAvailable("GL_EXT_texture_shared_exponent") || gl.isExtensionAvailable("GL_VERSION_3_0")) {
             caps.add(Caps.SharedExponentTexture);
         }
 
@@ -417,14 +422,15 @@ public class JoglRenderer implements Renderer {
         }
         
         //supports sRGB pipeline
-        if (gl.isExtensionAvailable("GL_ARB_framebuffer_sRGB") && gl.isExtensionAvailable("GL_EXT_texture_sRGB")){
+        if ((gl.isExtensionAvailable("GL_ARB_framebuffer_sRGB") && gl.isExtensionAvailable("GL_EXT_texture_sRGB")) || gl.isExtensionAvailable("GL_VERSION_3_0")){
             caps.add(Caps.Srgb);
         }
         
         logger.log(Level.FINE, "Caps: {0}", caps);
     }
     
-    public void invalidateState() {
+    @Override
+	public void invalidateState() {
         context.reset();
         boundShader = null;
         lastFb = null;
@@ -436,35 +442,39 @@ public class JoglRenderer implements Renderer {
         initialReadBuf = intBuf1.get(0);
     }
     
-    public void resetGLObjects() {
+    @Override
+	public void resetGLObjects() {
         logger.log(Level.FINE, "Reseting objects and invalidating state");
         objManager.resetObjects();
         statistics.clearMemory();
         invalidateState();
     }
     
-    public void cleanup() {
+    @Override
+	public void cleanup() {
         logger.log(Level.FINE, "Deleting objects and invalidating state");
         objManager.deleteAllObjects(this);
         statistics.clearMemory();
         invalidateState();
     }
     
-    private void checkCap(Caps cap) {
-        if (!caps.contains(cap)) {
-            throw new UnsupportedOperationException("Required capability missing: " + cap.name());
-        }
-    }
+//    private void checkCap(Caps cap) {
+//        if (!caps.contains(cap)) {
+//            throw new UnsupportedOperationException("Required capability missing: " + cap.name());
+//        }
+//    }
     
     /*********************************************************************\
     |* Render State                                                      *|
     \*********************************************************************/
-    public void setDepthRange(float start, float end) {
+    @Override
+	public void setDepthRange(float start, float end) {
         GL gl = GLContext.getCurrentGL();
         gl.glDepthRange(start, end);
     }
     
-    public void clearBuffers(boolean color, boolean depth, boolean stencil) {
+    @Override
+	public void clearBuffers(boolean color, boolean depth, boolean stencil) {
         GL gl = GLContext.getCurrentGL();
         int bits = 0;
         if (color) {
@@ -495,12 +505,14 @@ public class JoglRenderer implements Renderer {
         }
     }
 
-    public void setBackgroundColor(ColorRGBA color) {
+    @Override
+	public void setBackgroundColor(ColorRGBA color) {
         GL gl = GLContext.getCurrentGL();
         gl.glClearColor(color.r, color.g, color.b, color.a);
     }
 
-    public void setAlphaToCoverage(boolean value) {
+    @Override
+	public void setAlphaToCoverage(boolean value) {
         if (caps.contains(Caps.Multisample)) {
             GL gl = GLContext.getCurrentGL();
             if (value) {
@@ -511,7 +523,8 @@ public class JoglRenderer implements Renderer {
         }
     }
 
-    public void applyRenderState(RenderState state) {
+    @Override
+	public void applyRenderState(RenderState state) {
         GL gl = GLContext.getCurrentGL();
         if (state.isWireframe() && !context.wireframe) {
             if (gl.isGL2GL3()) {
@@ -537,22 +550,23 @@ public class JoglRenderer implements Renderer {
             gl.glDepthFunc(convertTestFunction(state.getDepthFunc()));
             context.depthFunc = state.getDepthFunc();
         }
-
-        if (state.isAlphaTest() && context.alphaTestFallOff == 0) {
-            gl.glEnable(GL2ES1.GL_ALPHA_TEST);
-            if (gl.isGL2ES1()) {
-                gl.getGL2ES1().glAlphaFunc(convertTestFunction(context.alphaFunc), state.getAlphaFallOff());               
-            }
-            context.alphaTestFallOff = state.getAlphaFallOff();
-        } else if (!state.isAlphaTest() && context.alphaTestFallOff != 0) {
-            if (gl.isGL2ES1()) {
+        if (gl.isGL2ES1()) {
+            if (state.isAlphaTest() && !context.alphaTestEnabled) {
+            	gl.glEnable(GL2ES1.GL_ALPHA_TEST);
+                gl.getGL2ES1().glAlphaFunc(convertTestFunction(context.alphaFunc), context.alphaTestFallOff);
+                context.alphaTestEnabled = true;           
+            } else if (!state.isAlphaTest() && context.alphaTestEnabled) {
                 gl.glDisable(GL2ES1.GL_ALPHA_TEST);
+                context.alphaTestEnabled = false;
+            }
+            if (state.getAlphaFallOff() != context.alphaTestFallOff) {
+            	gl.getGL2ES1().glAlphaFunc(convertTestFunction(context.alphaFunc), context.alphaTestFallOff);   
+                context.alphaTestFallOff = state.getAlphaFallOff();
+            }         
+            if (state.getAlphaFunc() != context.alphaFunc) {
+            	gl.getGL2ES1().glAlphaFunc(convertTestFunction(state.getAlphaFunc()), context.alphaTestFallOff);
+                context.alphaFunc = state.getAlphaFunc();
             }
-            context.alphaTestFallOff = 0;
-        }
-        if (state.getAlphaFunc() != context.alphaFunc && gl.isGL2ES1()) {
-            gl.getGL2ES1().glAlphaFunc(convertTestFunction(context.alphaFunc), state.getAlphaFallOff());  
-            context.alphaFunc = state.getAlphaFunc();
         }
 
         if (state.isDepthWrite() && !context.depthWriteEnabled) {
@@ -783,7 +797,8 @@ public class JoglRenderer implements Renderer {
     /*********************************************************************\
     |* Camera and World transforms                                       *|
     \*********************************************************************/
-    public void setViewPort(int x, int y, int w, int h) {
+    @Override
+	public void setViewPort(int x, int y, int w, int h) {
         if (x != vpX || vpY != y || vpW != w || vpH != h) {
             GL gl = GLContext.getCurrentGL();
             gl.glViewport(x, y, w, h);
@@ -794,7 +809,8 @@ public class JoglRenderer implements Renderer {
         }
     }
 
-    public void setClipRect(int x, int y, int width, int height) {
+    @Override
+	public void setClipRect(int x, int y, int width, int height) {
         GL gl = GLContext.getCurrentGL();
         if (!context.clipRectEnabled) {
             gl.glEnable(GL.GL_SCISSOR_TEST);
@@ -809,7 +825,8 @@ public class JoglRenderer implements Renderer {
         }
     }
 
-    public void clearClipRect() {
+    @Override
+	public void clearClipRect() {
         if (context.clipRectEnabled) {
             GL gl = GLContext.getCurrentGL();
             gl.glDisable(GL.GL_SCISSOR_TEST);
@@ -822,14 +839,17 @@ public class JoglRenderer implements Renderer {
         }
     }
 
-    public void onFrame() {
+    @Override
+	public void onFrame() {
         objManager.deleteUnused(this);
     }
     
-    public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) {
+    @Override
+	public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) {
     }
 
-    public void setWorldMatrix(Matrix4f worldMatrix) {
+    @Override
+	public void setWorldMatrix(Matrix4f worldMatrix) {
     }
 
     /*********************************************************************\
@@ -990,7 +1010,8 @@ public class JoglRenderer implements Renderer {
      * (Non-javadoc)
      * Only used for fixed-function. Ignored.
      */
-    public void setLighting(LightList list) {
+    @Override
+	public void setLighting(LightList list) {
     }
     
     public int convertShaderType(Shader.ShaderType type) {
@@ -1000,7 +1021,7 @@ public class JoglRenderer implements Renderer {
             case Vertex:
                 return GL2ES2.GL_VERTEX_SHADER;
 //            case Geometry:
-//                return ARBGeometryShader4.GL_GEOMETRY_SHADER_ARB;
+//                return GL3.GL_GEOMETRY_SHADER_ARB;
             default:
                 throw new UnsupportedOperationException("Unrecognized shader type.");
         }
@@ -1011,7 +1032,14 @@ public class JoglRenderer implements Renderer {
         GL gl = GLContext.getCurrentGL();
         if (id == -1) {
             // Create id
-            id = gl.getGL2ES2().glCreateShader(convertShaderType(source.getType()));
+//        	if (gl.isGL2ES2()) {
+        		id = gl.getGL2ES2().glCreateShader(convertShaderType(source.getType()));
+//        	}
+//        	else {
+//        		if (gl.isGL2()) {
+//        			id = gl.getGL2().glCreateShaderObjectARB(convertShaderType(source.getType()));
+//        		}
+//        	}
             if (id <= 0) {
                 throw new RendererException("Invalid ID received when trying to create shader.");
             }
@@ -1118,7 +1146,7 @@ public class JoglRenderer implements Renderer {
             gl.getGL2ES2().glAttachShader(id, source.getId());
         }
 
-        if (caps.contains(Caps.OpenGL30) && gl.isGL2GL3()) {
+        if (gl.isGL2GL3() && gl.isExtensionAvailable("GL_EXT_gpu_shader4")) {
             // Check if GLSL version is 1.5 for shader
             gl.getGL2GL3().glBindFragDataLocation(id, 0, "outFragColor");
             // For MRT
@@ -1167,14 +1195,15 @@ public class JoglRenderer implements Renderer {
             }
         } else {
             if (infoLog != null) {
-                throw new RendererException("Shader link failure, shader:" + shader + "\n" + infoLog);
+                throw new RendererException("Shader failed to link, shader:" + shader + "\n" + infoLog);
             } else {
-                throw new RendererException("Shader link failure, shader:" + shader + " info: <not provided>");
+                throw new RendererException("Shader failed to link, shader:" + shader + "\ninfo: <not provided>");
             }
         }
     }
 
-    public void setShader(Shader shader) {
+    @Override
+	public void setShader(Shader shader) {
         if (shader == null) {
             throw new IllegalArgumentException("Shader cannot be null");
         } else {
@@ -1192,7 +1221,8 @@ public class JoglRenderer implements Renderer {
         }
     }
 
-    public void deleteShaderSource(ShaderSource source) {
+    @Override
+	public void deleteShaderSource(ShaderSource source) {
         if (source.getId() < 0) {
             logger.warning("Shader source is not uploaded to GPU, cannot delete.");
             return;
@@ -1203,7 +1233,8 @@ public class JoglRenderer implements Renderer {
         source.resetObject();
     }
 
-    public void deleteShader(Shader shader) {
+    @Override
+	public void deleteShader(Shader shader) {
         if (shader.getId() == -1) {
             logger.warning("Shader is not uploaded to GPU, cannot delete.");
             return;
@@ -1225,22 +1256,24 @@ public class JoglRenderer implements Renderer {
     /*********************************************************************\
     |* Framebuffers                                                      *|
     \*********************************************************************/
-    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) {
+    @Override
+	public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) {
         copyFrameBuffer(src, dst, true);
     }
 
-    public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) {
+    @Override
+	public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) {
         GL gl = GLContext.getCurrentGL();
         if (gl.isExtensionAvailable("GL_EXT_framebuffer_blit") && gl.isGL2GL3()) {
             int srcX0 = 0;
             int srcY0 = 0;
-            int srcX1/* = 0*/;
-            int srcY1/* = 0*/;
+            int srcX1;
+            int srcY1;
 
             int dstX0 = 0;
             int dstY0 = 0;
-            int dstX1/* = 0*/;
-            int dstY1/* = 0*/;
+            int dstX1;
+            int dstY1;
 
             int prevFBO = context.boundFBO;
 
@@ -1472,8 +1505,7 @@ public class JoglRenderer implements Renderer {
         if (attachmentSlot == -100) {
             return GL.GL_DEPTH_ATTACHMENT;
         } else if (attachmentSlot < 0 || attachmentSlot >= 16) {
-            throw new UnsupportedOperationException("Invalid FBO attachment slot: "
-                    + attachmentSlot);
+            throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot);
         }
 
         return GL.GL_COLOR_ATTACHMENT0 + attachmentSlot;
@@ -1492,9 +1524,11 @@ public class JoglRenderer implements Renderer {
             setupTextureParams(tex);
         }
 
-        gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, convertAttachmentSlot(rb.getSlot()),
+        gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, 
+        		convertAttachmentSlot(rb.getSlot()),
                 convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()),
-                image.getId(), 0);
+                image.getId(), 
+                0);
     }
     
     public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb) {
@@ -1568,11 +1602,19 @@ public class JoglRenderer implements Renderer {
         return samplePositions;
     }
 
-    public void setMainFrameBufferOverride(FrameBuffer fb) {
+    @Override
+	public void setMainFrameBufferOverride(FrameBuffer fb) {
         mainFbOverride = fb;
     }
     
-    public void setFrameBuffer(FrameBuffer fb) {
+    @Override
+	public void setFrameBuffer(FrameBuffer fb) {
+    	GL gl = GLContext.getCurrentGL();
+    	if (!gl.isExtensionAvailable("GL_EXT_framebuffer_object")) {
+            throw new RendererException("Framebuffer objects are not supported" +
+                                        " by the video hardware");
+        }
+    	
         if (fb == null && mainFbOverride != null) {
             fb = mainFbOverride;
         }
@@ -1582,8 +1624,7 @@ public class JoglRenderer implements Renderer {
                 return;
             }
         }
-
-        GL gl = GLContext.getCurrentGL();
+        
         // generate mipmaps for last FB if needed
         if (lastFb != null) {
             for (int i = 0; i < lastFb.getNumColorBuffers(); i++) {
@@ -1711,7 +1752,8 @@ public class JoglRenderer implements Renderer {
         }
     }
 
-    public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
+    @Override
+	public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
         GL gl = GLContext.getCurrentGL();
         if (fb != null) {
             RenderBuffer rb = fb.getColorBuffer();
@@ -1740,7 +1782,8 @@ public class JoglRenderer implements Renderer {
         gl.glDeleteRenderbuffers(1, intBuf1);
     }
     
-    public void deleteFrameBuffer(FrameBuffer fb) {
+    @Override
+	public void deleteFrameBuffer(FrameBuffer fb) {
         if (fb.getId() != -1) {
             GL gl = GLContext.getCurrentGL();
             if (context.boundFBO == fb.getId()) {
@@ -1767,6 +1810,12 @@ public class JoglRenderer implements Renderer {
     |* Textures                                                          *|
     \*********************************************************************/
     private int convertTextureType(Texture.Type type, int samples, int face) {
+    	GL gl = GLContext.getCurrentGL();
+    	if (samples > 1 && !gl.isExtensionAvailable("GL_ARB_texture_multisample")) {
+            throw new RendererException("Multisample textures are not supported" + 
+                                        " by the video hardware.");
+        }
+    	
         switch (type) {
             case TwoDimensional:
                 if (samples > 1) {
@@ -1830,7 +1879,7 @@ public class JoglRenderer implements Renderer {
             case BorderClamp:
                 return GL2GL3.GL_CLAMP_TO_BORDER;
             case Clamp:
-                return GL2.GL_CLAMP;
+            	// Falldown intentional.
             case EdgeClamp:
                 return GL.GL_CLAMP_TO_EDGE;
             case Repeat:
@@ -1904,8 +1953,7 @@ public class JoglRenderer implements Renderer {
      *
      * @param img The image to upload
      * @param type How the data in the image argument should be interpreted.
-     * @param unit The texture slot to be used to upload the image, not
-     * important
+     * @param unit The texture slot to be used to upload the image, not important
      */
     public void updateTexImageData(Image img, Texture.Type type, int unit) {
         int texId = img.getId();
@@ -1934,22 +1982,28 @@ public class JoglRenderer implements Renderer {
         }
 
         if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
-            // No pregenerated mips available,
-            // generate from base level if required
+        	// Image does not have mipmaps, but they are required.
+            // Generate from base level.
+        	
             if (!gl.isExtensionAvailable("GL_VERSION_3_0")) {
                 if (gl.isGL2ES1()) {
                     gl.glTexParameteri(target, GL2ES1.GL_GENERATE_MIPMAP, GL.GL_TRUE);
+                    img.setMipmapsGenerated(true);
                 }
-                img.setMipmapsGenerated(true);
+            } else {
+                // For OpenGL3 and up.
+                // We'll generate mipmaps via glGenerateMipmapEXT (see below)
             }
-        } else {
-            // Image already has mipmaps or no mipmap generation desired.
-//          glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0 );
-            if (img.getMipMapSizes() != null) {
-                if (gl.isGL2GL3()) {
-                    gl.glTexParameteri(target, GL2GL3.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
-                }
+        } else if (img.hasMipmaps()) {
+            // Image already has mipmaps, set the max level based on the 
+            // number of mipmaps we have.
+            if (gl.isGL2GL3()) {
+                gl.glTexParameteri(target, GL2ES3.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
             }
+        } else {
+            // Image does not have mipmaps and they are not required.
+            // Specify that that the texture has no mipmaps.
+        	gl.glTexParameteri(target, GL2ES3.GL_TEXTURE_MAX_LEVEL, 0);
         }
 
         int imageSamples = img.getMultiSamples();
@@ -1977,17 +2031,6 @@ public class JoglRenderer implements Renderer {
             }
         }
         
-        if (target == GL.GL_TEXTURE_CUBE_MAP) {
-            // Check max texture size before upload
-            if (img.getWidth() > maxCubeTexSize || img.getHeight() > maxCubeTexSize) {
-                throw new RendererException("Cannot upload cubemap " + img + ". The maximum supported cubemap resolution is " + maxCubeTexSize);
-            }
-        } else {
-            if (img.getWidth() > maxTexSize || img.getHeight() > maxTexSize) {
-                throw new RendererException("Cannot upload texture " + img + ". The maximum supported texture resolution is " + maxTexSize);
-            }
-        }
-        
         if (target == GL.GL_TEXTURE_CUBE_MAP) {
             List<ByteBuffer> data = img.getData();
             if (data.size() != 6) {
@@ -1999,9 +2042,15 @@ public class JoglRenderer implements Renderer {
                 TextureUtil.uploadTexture(img, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, linearizeSrgbImages);
             }
         } else if (target == GL.GL_TEXTURE_2D_ARRAY) {
+            if (!caps.contains(Caps.TextureArray)) {
+                throw new RendererException("Texture arrays not supported by graphics hardware");
+            }
+            
             List<ByteBuffer> data = img.getData();
+            
             // -1 index specifies prepare data for 2D Array
             TextureUtil.uploadTexture(img, target, -1, 0, linearizeSrgbImages);
+            
             for (int i = 0; i < data.size(); i++) {
                 // upload each slice of 2D array in turn
                 // this time with the appropriate index
@@ -2028,7 +2077,8 @@ public class JoglRenderer implements Renderer {
         img.clearUpdateNeeded();
     }
 
-    public void setTexture(int unit, Texture tex) {
+    @Override
+	public void setTexture(int unit, Texture tex) {
         GL gl = GLContext.getCurrentGL();
         Image image = tex.getImage();
         if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) {
@@ -2065,7 +2115,8 @@ public class JoglRenderer implements Renderer {
         setupTextureParams(tex);
     }
 
-    public void modifyTexture(Texture tex, Image pixels, int x, int y) {
+    @Override
+	public void modifyTexture(Texture tex, Image pixels, int x, int y) {
       setTexture(0, tex);
       TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), -1), 0, x, y, linearizeSrgbImages);
     }
@@ -2087,7 +2138,8 @@ public class JoglRenderer implements Renderer {
          context.textureIndexList.copyNewToOld();*/
     }
 
-    public void deleteImage(Image image) {
+    @Override
+	public void deleteImage(Image image) {
         int texId = image.getId();
         if (texId != -1) {
             intBuf1.put(0, texId);
@@ -2130,9 +2182,6 @@ public class JoglRenderer implements Renderer {
                 return GL2ES2.GL_INT;
             case UnsignedInt:
                 return GL.GL_UNSIGNED_INT;
-//            case Half:
-//                return NVHalfFloat.GL_HALF_FLOAT_NV;
-//                return ARBHalfFloatVertex.GL_HALF_FLOAT;
             case Float:
                 return GL.GL_FLOAT;
             case Double:
@@ -2143,7 +2192,8 @@ public class JoglRenderer implements Renderer {
         }
     }
 
-    public void updateBufferData(VertexBuffer vb) {
+    @Override
+	public void updateBufferData(VertexBuffer vb) {
         GL gl = GLContext.getCurrentGL();
         int bufId = vb.getId();
         boolean created = false;
@@ -2195,7 +2245,8 @@ public class JoglRenderer implements Renderer {
         vb.clearUpdateNeeded();
     }
 
-    public void deleteBuffer(VertexBuffer vb) {
+    @Override
+	public void deleteBuffer(VertexBuffer vb) {
         GL gl = GLContext.getCurrentGL();
         int bufId = vb.getId();
         if (bufId != -1) {
@@ -2210,11 +2261,14 @@ public class JoglRenderer implements Renderer {
     }
     
     public void clearVertexAttribs() {
+    	GL gl = GLContext.getCurrentGL();
         IDList attribList = context.attribIndexList;
         for (int i = 0; i < attribList.oldLen; i++) {
             int idx = attribList.oldList[i];
-            GL gl = GLContext.getCurrentGL();
             gl.getGL2ES2().glDisableVertexAttribArray(idx);
+            if (context.boundAttribs[idx].isInstanced() && gl.isGL3ES3()) {
+                gl.getGL3ES3().glVertexAttribDivisor(idx, 0);
+            }
             context.boundAttribs[idx] = null;
         }
         context.attribIndexList.copyNewToOld();
@@ -2248,15 +2302,34 @@ public class JoglRenderer implements Renderer {
                     attrib.setLocation(loc);
                 }
             }
+            
+            if (vb.isInstanced()) {
+                if (!gl.isExtensionAvailable("GL_ARB_instanced_arrays")
+                 || !gl.isExtensionAvailable("GL_ARB_draw_instanced")) {
+                    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);
             }
 
             VertexBuffer[] attribs = context.boundAttribs;
-            if (!context.attribIndexList.moveToNew(loc)) {
-                gl.getGL2ES2().glEnableVertexAttribArray(loc);
-                //System.out.println("Enabled ATTRIB IDX: "+loc);
+            for (int i = 0; i < slotsRequired; i++) {
+                if (!context.attribIndexList.moveToNew(loc + i)) {
+                    gl.getGL2ES2().glEnableVertexAttribArray(loc + i);
+                    //System.out.println("Enabled ATTRIB IDX: "+loc + i);
+                }
             }
             if (attribs[loc] != vb) {
                 // NOTE: Use id from interleaved buffer if specified
@@ -2270,14 +2343,43 @@ public class JoglRenderer implements Renderer {
                     //statistics.onVertexBufferUse(vb, false);
                 }
 
-                gl.getGL2ES2().glVertexAttribPointer(loc,
+                if (slotsRequired == 1) {
+                    gl.getGL2ES2().glVertexAttribPointer(loc,
                         vb.getNumComponents(),
                         convertFormat(vb.getFormat()),
                         vb.isNormalized(),
                         vb.getStride(),
                         vb.getOffset());
-
-                attribs[loc] = vb;
+                } 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.getGL2ES2().glVertexAttribPointer(loc + i,
+                                4,
+                                convertFormat(vb.getFormat()),
+                                vb.isNormalized(),
+                                4 * 4 * slotsRequired,
+                                4 * 4 * i);
+                    }
+                }
+                
+                for (int i = 0; i < slotsRequired; i++) {
+                    int slot = loc + i;
+                    if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
+                        // non-instanced -> instanced
+                        gl.getGL3ES3().glVertexAttribDivisor(slot, vb.getInstanceSpan());
+                    } else if (!vb.isInstanced() && attribs[slot] != null && attribs[slot].isInstanced()) {
+                        // instanced -> non-instanced
+                    	gl.getGL3ES3().glVertexAttribDivisor(slot, 0);
+                    }
+                    attribs[slot] = vb;
+                }
             }
         } else {
             throw new IllegalStateException("Cannot render mesh without shader bound");
@@ -2289,14 +2391,15 @@ public class JoglRenderer implements Renderer {
     }
     
     public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
+    	boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
         GL gl = GLContext.getCurrentGL();
-        if (count > 1) {
-            if (gl.isGL2GL3()) {
+        if (useInstancing) {
+        	if (gl.isGL2GL3()) {
                 gl.getGL2GL3().glDrawArraysInstanced(convertElementMode(mode), 0,
                     vertCount, count);
             }
         } else {
-            gl.glDrawArrays(convertElementMode(mode), 0, vertCount);
+        	gl.glDrawArrays(convertElementMode(mode), 0, vertCount);
         }
     }
     
@@ -2373,7 +2476,6 @@ public class JoglRenderer implements Renderer {
                     }
                 }
 
-                //FIXME check whether elSize is required
                 curOffset += elementLength * elSize;
             }
         } else {
@@ -2432,7 +2534,7 @@ public class JoglRenderer implements Renderer {
         }
     }
 
-    public void updateVertexArray(Mesh mesh) {
+    public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) {
         int id = mesh.getId();
         GL gl = GLContext.getCurrentGL();
 
@@ -2456,6 +2558,10 @@ public class JoglRenderer implements Renderer {
         if (interleavedData != null && interleavedData.isUpdateNeeded()) {
             updateBufferData(interleavedData);
         }
+        
+        if (instanceData != null) {
+            setVertexAttrib(instanceData, null);
+        }
 
         for (VertexBuffer vb : mesh.getBufferList().getArray()) {
             if (vb.getBufferType() == Type.InterleavedData
@@ -2474,9 +2580,9 @@ public class JoglRenderer implements Renderer {
         }
     }
     
-    private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
+    private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) {
         if (mesh.getId() == -1) {
-            updateVertexArray(mesh);
+            updateVertexArray(mesh, instanceData);
         } else {
             // TODO: Check if it was updated
         }
@@ -2505,25 +2611,30 @@ public class JoglRenderer implements Renderer {
         clearTextureUnits();
     }
     
-    private void renderMeshDefault(Mesh mesh, int lod, int count) {
-        VertexBuffer indices;
-
+    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);
+    
         VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
         if (interleavedData != null && interleavedData.isUpdateNeeded()) {
             updateBufferData(interleavedData);
         }
 
-//        IntMap<VertexBuffer> buffers = mesh.getBuffers();
-        SafeArrayList<VertexBuffer> buffersList = mesh.getBufferList();
-
+        VertexBuffer indices;
         if (mesh.getNumLodLevels() > 0) {
             indices = mesh.getLodLevel(lod);
         } else {
             indices = mesh.getBuffer(Type.Index);
         }
 
-//        for (Entry<VertexBuffer> entry : buffers) {
-//             VertexBuffer vb = entry.getValue();
+        if (instanceData != null) {
+            for (VertexBuffer vb : instanceData) {
+                setVertexAttrib(vb, null);
+            }
+        }
+        
         for (VertexBuffer vb : mesh.getBufferList().getArray()) {
             if (vb.getBufferType() == Type.InterleavedData
                     || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
@@ -2549,7 +2660,8 @@ public class JoglRenderer implements Renderer {
         clearTextureUnits();
     }
 
-    public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
+    @Override
+	public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
         if (mesh.getVertexCount() == 0) {
             return;
         }
@@ -2587,11 +2699,12 @@ public class JoglRenderer implements Renderer {
 //        if (gl.isExtensionAvailable("GL_ARB_vertex_array_object")){
 //            renderMeshVertexArray(mesh, lod, count);
 //        }else{
-        renderMeshDefault(mesh, lod, count);
+        renderMeshDefault(mesh, lod, count, instanceData);
 //        }
     }
 
-    public void setMainFrameBufferSrgb(boolean srgb) {
+    @Override
+	public void setMainFrameBufferSrgb(boolean srgb) {
         //Gamma correction
         if(srgb && caps.contains(Caps.Srgb)){
             GLContext.getCurrentGL().glEnable(GL3.GL_FRAMEBUFFER_SRGB);
@@ -2602,7 +2715,8 @@ public class JoglRenderer implements Renderer {
     
     }
 
-    public void setLinearizeSrgbImages(boolean linearize) {
+    @Override
+	public void setLinearizeSrgbImages(boolean linearize) {
         linearizeSrgbImages = linearize;
     }
 }