/*
* 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;
}
}