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
experimental
shadowislord 10 years ago
parent fb6fb73239
commit 5bfc5b2c13
  1. 17
      jme3-core/src/main/java/com/jme3/renderer/Caps.java
  2. 18
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormat.java
  3. 32
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
  4. 77
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  5. 20
      jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java

@ -301,7 +301,22 @@ public enum Caps {
/** /**
* Supports 32-bit index buffers. * Supports 32-bit index buffers.
*/ */
IntegerIndexBuffer; IntegerIndexBuffer,
/**
* Partial support for non-power-of-2 textures, typically found
* on OpenGL ES 2 devices.
* <p>
* Use of NPOT textures is allowed iff:
* <ul>
* <li>The {@link Texture.WrapMode} is set to
* {@link Texture.WrapMode#EdgeClamp}.</li>
* <li>Mip-mapping is not used, meaning {@link Texture.MinFilter} is set to
* {@link Texture.MinFilter#BilinearNoMipMaps} or
* {@link Texture.MinFilter#NearestNoMipMaps}</li>
* </ul>
*/
PartialNonPowerOfTwoTextures;
/** /**
* Returns true if given the renderer capabilities, the texture * Returns true if given the renderer capabilities, the texture

@ -44,7 +44,7 @@ public final class GLImageFormat {
public final boolean compressed; public final boolean compressed;
/** /**
* Constructor for uncompressed formats. * Constructor for formats.
* *
* @param internalFormat OpenGL internal format * @param internalFormat OpenGL internal format
* @param format OpenGL 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) { public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) {
this.internalFormat = compressedFormat; this.internalFormat = internalFormat;
this.format = -1; this.format = format;
this.dataType = -1; this.dataType = dataType;
this.compressed = true; this.compressed = compressed;
} }
} }

@ -61,14 +61,18 @@ public final class GLImageFormats {
} }
private static void formatComp(GLImageFormat[][] formatToGL, Image.Format format, private static void formatComp(GLImageFormat[][] formatToGL, Image.Format format,
int glCompressedFormat){ int glCompressedFormat,
formatToGL[0][format.ordinal()] = new GLImageFormat(glCompressedFormat); int glFormat,
int glDataType){
formatToGL[0][format.ordinal()] = new GLImageFormat(glCompressedFormat, glFormat, glDataType, true);
} }
private static void formatCompSrgb(GLImageFormat[][] formatToGL, Image.Format format, 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); formatSrgb(formatToGL, Format.BGRA8, GLExt.GL_SRGB8_ALPHA8_EXT, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE);
if (caps.contains(Caps.TextureCompressionS3TC)) { if (caps.contains(Caps.TextureCompressionS3TC)) {
formatCompSrgb(formatToGL, Format.DXT1, GLExt.GL_COMPRESSED_SRGB_S3TC_DXT1_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); 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); 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); 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)) { } else if (caps.contains(Caps.Rgba8)) {
@ -179,16 +183,16 @@ public final class GLImageFormats {
// Compressed formats // Compressed formats
if (caps.contains(Caps.TextureCompressionS3TC)) { if (caps.contains(Caps.TextureCompressionS3TC)) {
formatComp(formatToGL, Format.DXT1, GLExt.GL_COMPRESSED_RGB_S3TC_DXT1_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); 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); 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); formatComp(formatToGL, Format.DXT5, GLExt.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
} }
if (caps.contains(Caps.TextureCompressionETC2)) { 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)) { } 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; return formatToGL;

@ -31,7 +31,6 @@
*/ */
package com.jme3.renderer.opengl; package com.jme3.renderer.opengl;
import com.jme3.light.LightList;
import com.jme3.material.RenderState; import com.jme3.material.RenderState;
import com.jme3.material.RenderState.StencilOperation; import com.jme3.material.RenderState.StencilOperation;
import com.jme3.material.RenderState.TestFunction; import com.jme3.material.RenderState.TestFunction;
@ -116,7 +115,7 @@ public class GLRenderer implements Renderer {
this.gl3 = gl instanceof GL3 ? (GL3)gl : null; this.gl3 = gl instanceof GL3 ? (GL3)gl : null;
this.glfbo = glfbo; this.glfbo = glfbo;
this.glext = glfbo instanceof GLExt ? (GLExt)glfbo : null; this.glext = glfbo instanceof GLExt ? (GLExt)glfbo : null;
this.texUtil = new TextureUtil(gl, gl2, glext); this.texUtil = new TextureUtil(gl, gl2, glext, context);
} }
@Override @Override
@ -360,6 +359,11 @@ public class GLRenderer implements Renderer {
+ "Some features might not work."); + "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)) { if (hasExtension("GL_EXT_texture_array") || caps.contains(Caps.OpenGL30)) {
caps.add(Caps.TextureArray); caps.add(Caps.TextureArray);
} }
@ -454,6 +458,9 @@ public class GLRenderer implements Renderer {
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
public void initialize() { public void initialize() {
loadCapabilities(); loadCapabilities();
// Initialize default state..
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
} }
public void invalidateState() { public void invalidateState() {
@ -1383,6 +1390,9 @@ public class GLRenderer implements Renderer {
Texture tex = rb.getTexture(); Texture tex = rb.getTexture();
Image image = tex.getImage(); Image image = tex.getImage();
if (image.isUpdateNeeded()) { if (image.isUpdateNeeded()) {
// Check NPOT requirements
checkNonPowerOfTwo(tex);
updateTexImageData(image, tex.getType(), 0); updateTexImageData(image, tex.getType(), 0);
// NOTE: For depth textures, sets nearest/no-mips mode // 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.
* <p>
* 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. * 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 // Check if graphics card doesn't support multisample textures
if (!caps.contains(Caps.TextureMultisample)) { if (!caps.contains(Caps.TextureMultisample)) {
if (img.getMultiSamples() > 1) { if (img.getMultiSamples() > 1) {
@ -1984,6 +2044,9 @@ public class GLRenderer implements Renderer {
public void setTexture(int unit, Texture tex) { public void setTexture(int unit, Texture tex) {
Image image = tex.getImage(); Image image = tex.getImage();
if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) {
// Check NPOT requirements
checkNonPowerOfTwo(tex);
updateTexImageData(image, tex.getType(), unit); updateTexImageData(image, tex.getType(), unit);
} }

@ -32,6 +32,7 @@
package com.jme3.renderer.opengl; package com.jme3.renderer.opengl;
import com.jme3.renderer.Caps; import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderContext;
import com.jme3.renderer.RendererException; import com.jme3.renderer.RendererException;
import com.jme3.texture.Image; import com.jme3.texture.Image;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
@ -53,12 +54,14 @@ final class TextureUtil {
private final GL gl; private final GL gl;
private final GL2 gl2; private final GL2 gl2;
private final GLExt glext; private final GLExt glext;
private final RenderContext context;
private GLImageFormat[][] formats; 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.gl = gl;
this.gl2 = gl2; this.gl2 = gl2;
this.glext = glext; this.glext = glext;
this.context = context;
} }
public void initialize(EnumSet<Caps> caps) { public void initialize(EnumSet<Caps> caps) {
@ -210,24 +213,21 @@ final class TextureUtil {
Image.Format jmeFormat = image.getFormat(); Image.Format jmeFormat = image.getFormat();
GLImageFormat oglFormat = getImageFormatWithError(jmeFormat, getSrgbFormat); GLImageFormat oglFormat = getImageFormatWithError(jmeFormat, getSrgbFormat);
ByteBuffer data; ByteBuffer data = null;
int sliceCount = 1; int sliceCount = 1;
if (index >= 0 && image.getData() != null && image.getData().size() > 0) {
if (index >= 0) {
data = image.getData(index); data = image.getData(index);
}
if (image.getData() != null && image.getData().size() > 0) {
sliceCount = image.getData().size(); sliceCount = image.getData().size();
} else {
data = null;
} }
int width = image.getWidth(); int width = image.getWidth();
int height = image.getHeight(); int height = image.getHeight();
int depth = image.getDepth(); int depth = image.getDepth();
if (data != null) {
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
}
int[] mipSizes = image.getMipMapSizes(); int[] mipSizes = image.getMipMapSizes();
int pos = 0; int pos = 0;
// TODO: Remove unneccessary allocation // TODO: Remove unneccessary allocation

Loading…
Cancel
Save