From 5bfc5b2c1318c2a20b81b9baf5d957dd9532a3b9 Mon Sep 17 00:00:00 2001 From: shadowislord Date: Fri, 23 Jan 2015 22:34:49 -0500 Subject: [PATCH] Renderer texture handling changes * Relax NPOT texture restrictions on OpenGL ES 2: allow non mip-mapped, non repeating NPOT textures - mainly used for GUI elements * Fix various texture array issues: - compressed textures were causing a GL error - the array size was always set to 1 instead of the actual number of images in the array --- .../src/main/java/com/jme3/renderer/Caps.java | 17 +++- .../jme3/renderer/opengl/GLImageFormat.java | 18 +++-- .../jme3/renderer/opengl/GLImageFormats.java | 32 ++++---- .../com/jme3/renderer/opengl/GLRenderer.java | 77 +++++++++++++++++-- .../com/jme3/renderer/opengl/TextureUtil.java | 20 ++--- 5 files changed, 124 insertions(+), 40 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index e87b1d433..5ff564136 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -301,7 +301,22 @@ public enum Caps { /** * Supports 32-bit index buffers. */ - IntegerIndexBuffer; + IntegerIndexBuffer, + + /** + * Partial support for non-power-of-2 textures, typically found + * on OpenGL ES 2 devices. + *

+ * Use of NPOT textures is allowed iff: + *

+ */ + PartialNonPowerOfTwoTextures; /** * Returns true if given the renderer capabilities, the texture diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java index 2fa4c554f..71d95f59b 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java @@ -44,7 +44,7 @@ public final class GLImageFormat { public final boolean compressed; /** - * Constructor for uncompressed formats. + * Constructor for formats. * * @param internalFormat OpenGL internal format * @param format OpenGL format @@ -58,14 +58,16 @@ public final class GLImageFormat { } /** - * Constructor for compressed formats. + * Constructor for formats. * - * @param compressedFormat OpenGL compressed internal format + * @param internalFormat OpenGL internal format + * @param format OpenGL format + * @param dataType OpenGL datatype */ - public GLImageFormat(int compressedFormat) { - this.internalFormat = compressedFormat; - this.format = -1; - this.dataType = -1; - this.compressed = true; + public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) { + this.internalFormat = internalFormat; + this.format = format; + this.dataType = dataType; + this.compressed = compressed; } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java index 649d15876..423ae909e 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java @@ -61,14 +61,18 @@ public final class GLImageFormats { } private static void formatComp(GLImageFormat[][] formatToGL, Image.Format format, - int glCompressedFormat){ - formatToGL[0][format.ordinal()] = new GLImageFormat(glCompressedFormat); + int glCompressedFormat, + int glFormat, + int glDataType){ + formatToGL[0][format.ordinal()] = new GLImageFormat(glCompressedFormat, glFormat, glDataType, true); } private static void formatCompSrgb(GLImageFormat[][] formatToGL, Image.Format format, - int glCompressedFormat) + int glCompressedFormat, + int glFormat, + int glDataType) { - formatToGL[1][format.ordinal()] = new GLImageFormat(glCompressedFormat); + formatToGL[1][format.ordinal()] = new GLImageFormat(glCompressedFormat, glFormat, glDataType, true); } /** @@ -112,10 +116,10 @@ public final class GLImageFormats { formatSrgb(formatToGL, Format.BGRA8, GLExt.GL_SRGB8_ALPHA8_EXT, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE); if (caps.contains(Caps.TextureCompressionS3TC)) { - formatCompSrgb(formatToGL, Format.DXT1, GLExt.GL_COMPRESSED_SRGB_S3TC_DXT1_EXT); - formatCompSrgb(formatToGL, Format.DXT1A, GLExt.GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT); - formatCompSrgb(formatToGL, Format.DXT3, GLExt.GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT); - formatCompSrgb(formatToGL, Format.DXT5, GLExt.GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT); + formatCompSrgb(formatToGL, Format.DXT1, GLExt.GL_COMPRESSED_SRGB_S3TC_DXT1_EXT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); + formatCompSrgb(formatToGL, Format.DXT1A, GLExt.GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); + formatCompSrgb(formatToGL, Format.DXT3, GLExt.GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); + formatCompSrgb(formatToGL, Format.DXT5, GLExt.GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); } } } else if (caps.contains(Caps.Rgba8)) { @@ -179,16 +183,16 @@ public final class GLImageFormats { // Compressed formats if (caps.contains(Caps.TextureCompressionS3TC)) { - formatComp(formatToGL, Format.DXT1, GLExt.GL_COMPRESSED_RGB_S3TC_DXT1_EXT); - formatComp(formatToGL, Format.DXT1A, GLExt.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT); - formatComp(formatToGL, Format.DXT3, GLExt.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT); - formatComp(formatToGL, Format.DXT5, GLExt.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT); + formatComp(formatToGL, Format.DXT1, GLExt.GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); + formatComp(formatToGL, Format.DXT1A, GLExt.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); + formatComp(formatToGL, Format.DXT3, GLExt.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); + formatComp(formatToGL, Format.DXT5, GLExt.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); } if (caps.contains(Caps.TextureCompressionETC2)) { - formatComp(formatToGL, Format.ETC1, GLExt.GL_COMPRESSED_RGB8_ETC2); + formatComp(formatToGL, Format.ETC1, GLExt.GL_COMPRESSED_RGB8_ETC2, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); } else if (caps.contains(Caps.TextureCompressionETC1)) { - formatComp(formatToGL, Format.ETC1, GLExt.GL_ETC1_RGB8_OES); + formatComp(formatToGL, Format.ETC1, GLExt.GL_ETC1_RGB8_OES, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); } return formatToGL; diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 6c6ccd19f..e9029ec21 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -31,7 +31,6 @@ */ package com.jme3.renderer.opengl; -import com.jme3.light.LightList; import com.jme3.material.RenderState; import com.jme3.material.RenderState.StencilOperation; import com.jme3.material.RenderState.TestFunction; @@ -116,7 +115,7 @@ public class GLRenderer implements Renderer { this.gl3 = gl instanceof GL3 ? (GL3)gl : null; this.glfbo = glfbo; this.glext = glfbo instanceof GLExt ? (GLExt)glfbo : null; - this.texUtil = new TextureUtil(gl, gl2, glext); + this.texUtil = new TextureUtil(gl, gl2, glext, context); } @Override @@ -359,6 +358,11 @@ public class GLRenderer implements Renderer { + "support non-power-of-2 textures. " + "Some features might not work."); } + + if (caps.contains(Caps.OpenGLES20)) { + // OpenGL ES 2 has some limited support for NPOT textures + caps.add(Caps.PartialNonPowerOfTwoTextures); + } if (hasExtension("GL_EXT_texture_array") || caps.contains(Caps.OpenGL30)) { caps.add(Caps.TextureArray); @@ -454,6 +458,9 @@ public class GLRenderer implements Renderer { @SuppressWarnings("fallthrough") public void initialize() { loadCapabilities(); + + // Initialize default state.. + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); } public void invalidateState() { @@ -1383,6 +1390,9 @@ public class GLRenderer implements Renderer { Texture tex = rb.getTexture(); Image image = tex.getImage(); if (image.isUpdateNeeded()) { + // Check NPOT requirements + checkNonPowerOfTwo(tex); + updateTexImageData(image, tex.getType(), 0); // NOTE: For depth textures, sets nearest/no-mips mode @@ -1843,6 +1853,61 @@ public class GLRenderer implements Renderer { } } + /** + * Validates if a potentially NPOT texture is supported by the hardware. + *

+ * 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. + * + * @param tex The texture to validate. + * @throws RendererException If the texture is not supported by the hardware + */ + private void checkNonPowerOfTwo(Texture tex) { + if (!tex.getImage().isNPOT()) { + // Texture is power-of-2, safe to use. + return; + } + + if (caps.contains(Caps.NonPowerOfTwoTextures)) { + // Texture is NPOT but it is supported by video hardware. + return; + } + + // Maybe we have some / partial support for NPOT? + if (!caps.contains(Caps.PartialNonPowerOfTwoTextures)) { + // Cannot use any type of NPOT texture (uncommon) + throw new RendererException("non-power-of-2 textures are not " + + "supported by the video hardware"); + } + + // Partial NPOT supported.. + if (tex.getMinFilter().usesMipMapLevels()) { + throw new RendererException("non-power-of-2 textures with mip-maps " + + "are not supported by the video hardware"); + } + + switch (tex.getType()) { + case CubeMap: + case ThreeDimensional: + if (tex.getWrap(WrapAxis.R) != Texture.WrapMode.EdgeClamp) { + throw new RendererException("repeating non-power-of-2 textures " + + "are not supported by the video hardware"); + } + // fallthrough intentional!!! + case TwoDimensionalArray: + case TwoDimensional: + if (tex.getWrap(WrapAxis.S) != Texture.WrapMode.EdgeClamp + || tex.getWrap(WrapAxis.T) != Texture.WrapMode.EdgeClamp) { + throw new RendererException("repeating non-power-of-2 textures " + + "are not supported by the video hardware"); + } + break; + default: + throw new UnsupportedOperationException("unrecongized texture type"); + } + } + /** * Uploads the given image to the GL driver. * @@ -1905,11 +1970,6 @@ public class GLRenderer implements Renderer { } } - // Yes, some OpenGL2 cards (GeForce 5) still dont support NPOT. - if (!caps.contains(Caps.NonPowerOfTwoTextures) && img.isNPOT()) { - throw new RendererException("non-power-of-2 framebuffer textures are not supported by the video hardware"); - } - // Check if graphics card doesn't support multisample textures if (!caps.contains(Caps.TextureMultisample)) { if (img.getMultiSamples() > 1) { @@ -1984,6 +2044,9 @@ public class GLRenderer implements Renderer { public void setTexture(int unit, Texture tex) { Image image = tex.getImage(); if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { + // Check NPOT requirements + checkNonPowerOfTwo(tex); + updateTexImageData(image, tex.getType(), unit); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java index 0d220f6a5..e0cd11e20 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java @@ -32,6 +32,7 @@ package com.jme3.renderer.opengl; import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderContext; import com.jme3.renderer.RendererException; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; @@ -53,12 +54,14 @@ final class TextureUtil { private final GL gl; private final GL2 gl2; private final GLExt glext; + private final RenderContext context; private GLImageFormat[][] formats; - public TextureUtil(GL gl, GL2 gl2, GLExt glext) { + public TextureUtil(GL gl, GL2 gl2, GLExt glext, RenderContext context) { this.gl = gl; this.gl2 = gl2; this.glext = glext; + this.context = context; } public void initialize(EnumSet caps) { @@ -210,24 +213,21 @@ final class TextureUtil { Image.Format jmeFormat = image.getFormat(); GLImageFormat oglFormat = getImageFormatWithError(jmeFormat, getSrgbFormat); - ByteBuffer data; + ByteBuffer data = null; int sliceCount = 1; - if (index >= 0 && image.getData() != null && image.getData().size() > 0) { + + if (index >= 0) { data = image.getData(index); + } + + if (image.getData() != null && image.getData().size() > 0) { sliceCount = image.getData().size(); - } else { - data = null; } 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