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:
+ *
+ * - The {@link Texture.WrapMode} is set to
+ * {@link Texture.WrapMode#EdgeClamp}.
+ * - Mip-mapping is not used, meaning {@link Texture.MinFilter} is set to
+ * {@link Texture.MinFilter#BilinearNoMipMaps} or
+ * {@link Texture.MinFilter#NearestNoMipMaps}
+ *
+ */
+ 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