/* * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution 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 Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. */ package com.jogamp.opengl.util.texture; import java.nio.*; import java.security.*; import javax.media.opengl.*; import javax.media.opengl.glu.*; import javax.media.nativewindow.NativeWindowFactory; import jogamp.opengl.*; import com.jogamp.opengl.util.texture.spi.*; /** * Represents an OpenGL texture object. Contains convenience routines * for enabling/disabling OpenGL texture state, binding this texture, * and computing texture coordinates for both the entire image as well * as a sub-image. * *

Non-power-of-two restrictions *
When creating an OpenGL texture object, the Texture class will * attempt to leverage the GL_ARB_texture_non_power_of_two * and GL_ARB_texture_rectangle * extensions (in that order) whenever possible. If neither extension * is available, the Texture class will simply upload a non-pow2-sized * image into a standard pow2-sized texture (without any special * scaling). Since the choice of extension (or whether one is used at * all) depends on the user's machine configuration, developers are * recommended to use {@link #getImageTexCoords} and {@link * #getSubImageTexCoords}, as those methods will calculate the * appropriate texture coordinates for the situation. * *

One caveat in this approach is that certain texture wrap modes * (e.g. GL_REPEAT) are not legal when the GL_ARB_texture_rectangle * extension is in use. Another issue to be aware of is that in the * default pow2 scenario, if the original image does not have pow2 * dimensions, then wrapping may not work as one might expect since * the image does not extend to the edges of the pow2 texture. If * texture wrapping is important, it is recommended to use only * pow2-sized images with the Texture class. * *

Performance Tips *
For best performance, try to avoid calling {@link #enable} / * {@link #bind} / {@link #disable} any more than necessary. For * example, applications using many Texture objects in the same scene * may want to reduce the number of calls to both {@link #enable} and * {@link #disable}. To do this it is necessary to call {@link * #getTarget} to make sure the OpenGL texture target is the same for * all of the Texture objects in use; non-power-of-two textures using * the GL_ARB_texture_rectangle extension use a different target than * power-of-two textures using the GL_TEXTURE_2D target. Note that * when switching between textures it is necessary to call {@link * #bind}, but when drawing many triangles all using the same texture, * for best performance only one call to {@link #bind} should be made. * *

Alpha premultiplication and blending *
The mathematically correct way to perform blending in OpenGL * (with the SrcOver "source over destination" mode, or any other * Porter-Duff rule) is to use "premultiplied color components", which * means the R/G/ B color components have already been multiplied by * the alpha value. To make things easier for developers, the Texture * class will automatically convert non-premultiplied image data into * premultiplied data when storing it into an OpenGL texture. As a * result, it is important to use the correct blending function; for * example, the SrcOver rule is expressed as:

    gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
* Also, when using a texture function like GL_MODULATE where * the current color plays a role, it is important to remember to make * sure that the color is specified in a premultiplied form, for * example:
    float a = ...;
    float r = r * a;
    float g = g * a;
    float b = b * a;
    gl.glColor4f(r, g, b, a);
* * For reference, here is a list of the Porter-Duff compositing rules * and the associated OpenGL blend functions (source and destination * factors) to use in the face of premultiplied alpha: *
Rule Source Dest
Clear GL_ZERO GL_ZERO
Src GL_ONE GL_ZERO
SrcOver GL_ONE GL_ONE_MINUS_SRC_ALPHA
DstOver GL_ONE_MINUS_DST_ALPHA GL_ONE
SrcIn GL_DST_ALPHA GL_ZERO
DstIn GL_ZERO GL_SRC_ALPHA
SrcOut GL_ONE_MINUS_DST_ALPHA GL_ZERO
DstOut GL_ZERO GL_ONE_MINUS_SRC_ALPHA
Dst GL_ZERO GL_ONE
SrcAtop GL_DST_ALPHA GL_ONE_MINUS_SRC_ALPHA
DstAtop GL_ONE_MINUS_DST_ALPHA GL_SRC_ALPHA
AlphaXor GL_ONE_MINUS_DST_ALPHA GL_ONE_MINUS_SRC_ALPHA
* * @author Chris Campbell * @author Kenneth Russell */ public class Texture { /** The GL target type. */ private int target; /** The GL texture ID. */ private int texID; /** The width of the texture. */ private int texWidth; /** The height of the texture. */ private int texHeight; /** The width of the image. */ private int imgWidth; /** The height of the image. */ private int imgHeight; /** The original aspect ratio of the image, before any rescaling that might have occurred due to using the GLU mipmap routines. */ private float aspectRatio; /** Indicates whether the TextureData requires a vertical flip of the texture coords. */ private boolean mustFlipVertically; /** Indicates whether we're using automatic mipmap generation support (GL_GENERATE_MIPMAP). */ private boolean usingAutoMipmapGeneration; /** The texture coordinates corresponding to the entire image. */ private TextureCoords coords; /** An estimate of the amount of texture memory this texture consumes. */ private int estimatedMemorySize; private static final AccessControlContext localACC = AccessController.getContext(); private static final boolean DEBUG = Debug.debug("Texture"); private static final boolean VERBOSE = Debug.verbose(); // For testing alternate code paths on more capable hardware private static final boolean disableNPOT = Debug.isPropertyDefined("jogl.texture.nonpot", true, localACC); private static final boolean disableTexRect = Debug.isPropertyDefined("jogl.texture.notexrect", true, localACC); public Texture(GL gl, TextureData data) throws GLException { texID = 0; updateImage(gl, data); } // Constructor for use when creating e.g. cube maps, where there is // no initial texture data public Texture(int target) { texID = 0; this.target = target; } // Package-private constructor for creating a texture object which wraps // an existing texture ID from another package Texture(int textureID, int target, int texWidth, int texHeight, int imgWidth, int imgHeight, boolean mustFlipVertically) { this.texID = textureID; this.target = target; this.mustFlipVertically = mustFlipVertically; this.texWidth = texWidth; this.texHeight = texHeight; setImageSize(imgWidth, imgHeight, target); } /** * Enables this texture's target (e.g., GL_TEXTURE_2D) in the * given GL context's state. This method is a shorthand equivalent * of the following OpenGL code:
     gl.glEnable(texture.getTarget());
     
* * See the performance tips above for hints * on how to maximize performance when using many Texture objects. * * @throws GLException if no OpenGL context was current or if any * OpenGL-related errors occurred */ public void enable(GL gl) throws GLException { gl.glEnable(target); } /** * Disables this texture's target (e.g., GL_TEXTURE_2D) in the * given GL state. This method is a shorthand equivalent * of the following OpenGL code:
     gl.glDisable(texture.getTarget());
     
* * See the performance tips above for hints * on how to maximize performance when using many Texture objects. * @param gl TODO * * @throws GLException if no OpenGL context was current or if any * OpenGL-related errors occurred */ public void disable(GL gl) throws GLException { gl.glDisable(target); } /** * Binds this texture to the given GL context. This method is a * shorthand equivalent of the following OpenGL code:
     gl.glBindTexture(texture.getTarget(), texture.getTextureObject());
     
* * See the performance tips above for hints * on how to maximize performance when using many Texture objects. * @param gl TODO * * @throws GLException if no OpenGL context was current or if any * OpenGL-related errors occurred */ public void bind(GL gl) throws GLException { validateTexID(gl, true); gl.glBindTexture(target, texID); } /** * @deprecated use {@link #destroy(GL)} */ public final void dispose(GL gl) throws GLException { destroy(gl); } /** * Destroys the native resources used by this texture object. * * @throws GLException if any OpenGL-related errors occurred */ public void destroy(GL gl) throws GLException { if(0updateImage. */ private void setImageSize(int width, int height, int target) { imgWidth = width; imgHeight = height; if (target == GL2.GL_TEXTURE_RECTANGLE_ARB) { if (mustFlipVertically) { coords = new TextureCoords(0, imgHeight, imgWidth, 0); } else { coords = new TextureCoords(0, 0, imgWidth, imgHeight); } } else { if (mustFlipVertically) { coords = new TextureCoords(0, (float) imgHeight / (float) texHeight, (float) imgWidth / (float) texWidth, 0); } else { coords = new TextureCoords(0, 0, (float) imgWidth / (float) texWidth, (float) imgHeight / (float) texHeight); } } } private void updateSubImageImpl(GL gl, TextureData data, int newTarget, int mipmapLevel, int dstx, int dsty, int srcx, int srcy, int width, int height) throws GLException { data.setHaveEXTABGR(gl.isExtensionAvailable("GL_EXT_abgr")); data.setHaveGL12(gl.isExtensionAvailable("GL_VERSION_1_2")); Buffer buffer = data.getBuffer(); if (buffer == null && data.getMipmapData() == null) { // Assume user just wanted to get the Texture object allocated return; } int rowlen = data.getRowLength(); int dataWidth = data.getWidth(); int dataHeight = data.getHeight(); if (data.getMipmapData() != null) { // Compute the width, height and row length at the specified mipmap level // Note we do not support specification of the row length for // mipmapped textures at this point for (int i = 0; i < mipmapLevel; i++) { width = Math.max(width / 2, 1); height = Math.max(height / 2, 1); dataWidth = Math.max(dataWidth / 2, 1); dataHeight = Math.max(dataHeight / 2, 1); } rowlen = 0; buffer = data.getMipmapData()[mipmapLevel]; } // Clip incoming rectangles to what is available both on this // texture and in the incoming TextureData if (srcx < 0) { width += srcx; srcx = 0; } if (srcy < 0) { height += srcy; srcy = 0; } // NOTE: not sure whether the following two are the correct thing to do if (dstx < 0) { width += dstx; dstx = 0; } if (dsty < 0) { height += dsty; dsty = 0; } if (srcx + width > dataWidth) { width = dataWidth - srcx; } if (srcy + height > dataHeight) { height = dataHeight - srcy; } if (dstx + width > texWidth) { width = texWidth - dstx; } if (dsty + height > texHeight) { height = texHeight - dsty; } checkCompressedTextureExtensions(gl, data); if (data.isDataCompressed()) { gl.glCompressedTexSubImage2D(newTarget, mipmapLevel, dstx, dsty, width, height, data.getInternalFormat(), buffer.remaining(), buffer); } else { int[] align = { 0 }; int[] rowLength = { 0 }; int[] skipRows = { 0 }; int[] skipPixels = { 0 }; gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, align, 0); // save alignment if(gl.isGL2GL3()) { gl.glGetIntegerv(GL2GL3.GL_UNPACK_ROW_LENGTH, rowLength, 0); // save row length gl.glGetIntegerv(GL2GL3.GL_UNPACK_SKIP_ROWS, skipRows, 0); // save skipped rows gl.glGetIntegerv(GL2GL3.GL_UNPACK_SKIP_PIXELS, skipPixels, 0); // save skipped pixels } gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, data.getAlignment()); if (DEBUG && VERBOSE) { System.out.println("Row length = " + rowlen); System.out.println("skip pixels = " + srcx); System.out.println("skip rows = " + srcy); System.out.println("dstx = " + dstx); System.out.println("dsty = " + dsty); System.out.println("width = " + width); System.out.println("height = " + height); } if(gl.isGL2GL3()) { gl.glPixelStorei(GL2GL3.GL_UNPACK_ROW_LENGTH, rowlen); gl.glPixelStorei(GL2GL3.GL_UNPACK_SKIP_ROWS, srcy); gl.glPixelStorei(GL2GL3.GL_UNPACK_SKIP_PIXELS, srcx); } else { if ( rowlen!=0 && rowlen!=width && srcy!=0 && srcx!=0 ) { throw new GLException("rowlen and/or x/y offset only available for GL2"); } } gl.glTexSubImage2D(newTarget, mipmapLevel, dstx, dsty, width, height, data.getPixelFormat(), data.getPixelType(), buffer); gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore alignment if(gl.isGL2GL3()) { gl.glPixelStorei(GL2GL3.GL_UNPACK_ROW_LENGTH, rowLength[0]); // restore row length gl.glPixelStorei(GL2GL3.GL_UNPACK_SKIP_ROWS, skipRows[0]); // restore skipped rows gl.glPixelStorei(GL2GL3.GL_UNPACK_SKIP_PIXELS, skipPixels[0]); // restore skipped pixels } } } private void checkCompressedTextureExtensions(GL gl, TextureData data) { if (data.isDataCompressed()) { switch (data.getInternalFormat()) { case GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT: case GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: if (!gl.isExtensionAvailable("GL_EXT_texture_compression_s3tc") && !gl.isExtensionAvailable("GL_NV_texture_compression_vtc")) { throw new GLException("DXTn compressed textures not supported by this graphics card"); } break; default: // FI1027GXME: should test availability of more texture // compression extensions here break; } } } private boolean validateTexID(GL gl, boolean throwException) { if( 0 >= texID ) { if( null != gl ) { int[] tmp = new int[1]; gl.glGenTextures(1, tmp, 0); texID = tmp[0]; if ( 0 >= texID && throwException ) { throw new GLException("Create texture ID invalid: texID "+texID+", glerr 0x"+Integer.toHexString(gl.glGetError())); } } else if ( throwException ) { throw new GLException("No GL context given, can't create texture ID"); } } return 0 < texID; } // Helper routines for disabling certain codepaths private static boolean haveNPOT(GL gl) { return (!disableNPOT && ( gl.isGLES2() || gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two") ) ); } private static boolean haveTexRect(GL gl) { return (!disableTexRect && TextureIO.isTexRectEnabled() && gl.isExtensionAvailable("GL_ARB_texture_rectangle")); } private static boolean preferTexRect(GL gl) { // Prefer GL_ARB_texture_rectangle on ATI hardware on Mac OS X // due to software fallbacks if (NativeWindowFactory.TYPE_MACOSX.equals(NativeWindowFactory.getNativeWindowType(false))) { String vendor = gl.glGetString(GL.GL_VENDOR); if (vendor != null && vendor.startsWith("ATI")) { return true; } } return false; } }