/* * 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.sun.opengl.utils; import java.awt.geom.*; import javax.media.opengl.*; import com.sun.opengl.impl.*; /** * 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. * *
REMIND: document GL_TEXTURE_2D/GL_TEXTURE_RECTANGLE_ARB issues... *
REMIND: translucent images will have premultiplied comps by default... * * @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; /** Indicates whether the TextureData requires a vertical flip of the texture coords. */ private boolean mustFlipVertically; /** The texture coordinate corresponding to the lower left corner of the texture when properly oriented. */ private Point2D lowerLeftTexCoord = new Point2D.Float(); /** The texture coordinate corresponding to the upper right corner of the texture when properly oriented. */ private Point2D upperRightTexCoord = new Point2D.Float(); private static final boolean DEBUG = Debug.debug("Texture"); // For now make Texture constructor package-private to limit the // number of public APIs we commit to Texture(TextureData data) throws GLException { GL gl = getCurrentGL(); imgWidth = data.getWidth(); imgHeight = data.getHeight(); mustFlipVertically = data.getMustFlipVertically(); if ((isPowerOfTwo(imgWidth) && isPowerOfTwo(imgHeight)) || gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two")) { if (DEBUG) { if (isPowerOfTwo(imgWidth) && isPowerOfTwo(imgHeight)) { System.err.println("Power-of-two texture"); } else { System.err.println("Using GL_ARB_texture_non_power_of_two"); } } texWidth = imgWidth; texHeight = imgHeight; target = GL.GL_TEXTURE_2D; } else if (gl.isExtensionAvailable("GL_ARB_texture_rectangle")) { if (DEBUG) { System.err.println("Using GL_ARB_texture_rectangle"); } texWidth = imgWidth; texHeight = imgHeight; target = GL.GL_TEXTURE_RECTANGLE_ARB; } else { if (DEBUG) { System.err.println("Expanding texture to power-of-two dimensions"); } if (data.getBorder() != 0) { throw new RuntimeException("Scaling up a non-power-of-two texture which has a border won't work"); } texWidth = nextPowerOfTwo(imgWidth); texHeight = nextPowerOfTwo(imgHeight); target = GL.GL_TEXTURE_2D; } // REMIND: let the user specify these, optionally int minFilter = GL.GL_LINEAR; int magFilter = GL.GL_LINEAR; int wrapMode = GL.GL_CLAMP_TO_EDGE; texID = createTextureID(gl); setImageSize(imgWidth, imgHeight); gl.glBindTexture(target, texID); gl.glTexImage2D(target, 0, data.getInternalFormat(), texWidth, texHeight, data.getBorder(), data.getPixelFormat(), data.getPixelType(), null); // REMIND: figure out what to do for GL_TEXTURE_RECTANGLE_ARB if (target == GL.GL_TEXTURE_2D) { gl.glTexParameteri(target, GL.GL_TEXTURE_MIN_FILTER, minFilter); gl.glTexParameteri(target, GL.GL_TEXTURE_MAG_FILTER, magFilter); gl.glTexParameteri(target, GL.GL_TEXTURE_WRAP_S, wrapMode); gl.glTexParameteri(target, GL.GL_TEXTURE_WRAP_T, wrapMode); } updateSubImage(data, 0, 0); } /** * Enables this texture's target (e.g., GL_TEXTURE_2D) in the * current GL context's state. * * @throws GLException if no OpenGL context was current or if any * OpenGL-related errors occurred */ public void enable() throws GLException { getCurrentGL().glEnable(target); } /** * Disables this texture's target (e.g., GL_TEXTURE_2D) in the * current GL context's state. * * @throws GLException if no OpenGL context was current or if any * OpenGL-related errors occurred */ public void disable() throws GLException { getCurrentGL().glDisable(target); } /** * Binds this texture to the current GL context. * * @throws GLException if no OpenGL context was current or if any * OpenGL-related errors occurred */ public void bind() throws GLException { getCurrentGL().glBindTexture(target, texID); } /** * Disposes the native resources used by this texture object. * * @throws GLException if no OpenGL context was current or if any * OpenGL-related errors occurred */ public void dispose() throws GLException { getCurrentGL().glDeleteTextures(1, new int[] {texID}, 0); } /** * Returns the OpenGL "target" of this texture. * * @return the OpenGL target of this texture * @see javax.media.opengl.GL#GL_TEXTURE_2D * @see javax.media.opengl.GL#GL_TEXTURE_RECTANGLE_ARB */ public int getTarget() { return target; } /** * Returns the width of the texture. Note that the texture width will * be greater than or equal to the width of the image contained within. * * @return the width of the texture */ public int getWidth() { return texWidth; } /** * Returns the height of the texture. Note that the texture height will * be greater than or equal to the height of the image contained within. * * @return the height of the texture */ public int getHeight() { return texHeight; } /** * Returns the width of the image contained within this texture. * * @return the width of the image */ public int getImageWidth() { return imgWidth; } /** * Returns the height of the image contained within this texture. * * @return the height of the image */ public int getImageHeight() { return imgHeight; } /** * Returns the texture coordinate corresponding to the lower-left * point of this texture when oriented properly. If the TextureData * indicated that the texture coordinates must be flipped * vertically, the returned Point will take that into account. * * @return the texture coordinate of the lower-left point of the * texture */ public Point2D getImageLowerLeftTexCoord() { return (Point2D) lowerLeftTexCoord.clone(); } /** * Returns the texture coordinate corresponding to the upper-right * point of this texture when oriented properly. If the TextureData * indicated that the texture coordinates must be flipped * vertically, the returned Point will take that into account. * * @return the texture coordinates of the upper-right point of the * texture */ public Point2D getImageUpperRightTexCoord() { return (Point2D) upperRightTexCoord.clone(); } /** * Returns the texture coordinate corresponding to the lower-left * point of the specified sub-image of this texture when oriented * properly. If the TextureData indicated that the texture * coordinates must be flipped vertically, the returned Point will * take that into account. * * @return the texture coordinate of the lower-left point of the * texture */ public Point2D getSubImageLowerLeftTexCoord(int x1, int y1, int x2, int y2) { if (target == GL.GL_TEXTURE_RECTANGLE_ARB) { if (mustFlipVertically) { return new Point2D.Float(x1, texHeight - y1); } else { return new Point2D.Float(x1, y1); } } else { float tx = (float)x1 / (float)texWidth; float ty = (float)y1 / (float)texHeight; if (mustFlipVertically) { return new Point2D.Float(tx, 1.0f - ty); } else { return new Point2D.Float(tx, ty); } } } /** * Returns the texture coordinate corresponding to the upper-right * point of the specified sub-image of this texture when oriented * properly. If the TextureData indicated that the texture * coordinates must be flipped vertically, the returned Point will * take that into account. * * @return the texture coordinate of the upper-right point of the * texture */ public Point2D getSubImageUpperRightTexCoord(int x1, int y1, int x2, int y2) { if (target == GL.GL_TEXTURE_RECTANGLE_ARB) { if (mustFlipVertically) { return new Point2D.Float(x2, texHeight - y2); } else { return new Point2D.Float(x2, y2); } } else { float tx = (float)x2 / (float)texWidth; float ty = (float)y2 / (float)texHeight; if (mustFlipVertically) { return new Point2D.Float(tx, 1.0f - ty); } else { return new Point2D.Float(tx, ty); } } } /** * Updates a subregion of the content area of this texture using the * data in the given image. * * @param data the image data to be uploaded to this texture * @param x the x offset (in pixels) relative to the lower-left corner * of this texture * @param y the y offset (in pixels) relative to the lower-left corner * of this texture * * @throws GLException if no OpenGL context was current or if any * OpenGL-related errors occurred */ public void updateSubImage(TextureData data, int x, int y) throws GLException { GL gl = getCurrentGL(); bind(); if (data.isDataCompressed()) { // FIXME: should test availability of appropriate texture // compression extension here gl.glCompressedTexSubImage2D(target, data.getMipmapLevel(), x, y, data.getWidth(), data.getHeight(), data.getInternalFormat(), data.getBuffer().remaining(), data.getBuffer()); } else { int[] align = new int[1]; gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, align, 0); // save alignment gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, data.getAlignment()); gl.glTexSubImage2D(target, data.getMipmapLevel(), x, y, data.getWidth(), data.getHeight(), data.getPixelFormat(), data.getPixelType(), data.getBuffer()); gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore align } } //---------------------------------------------------------------------- // Internals only below this point // /** * Returns the current GL object. Throws GLException if no OpenGL * context was current. */ private static GL getCurrentGL() throws GLException { GLContext context = GLContext.getCurrent(); if (context == null) { throw new GLException("No OpenGL context current on current thread"); } return context.getGL(); } /** * Returns true if the given value is a power of two. * * @return true if the given value is a power of two, false otherwise */ private static boolean isPowerOfTwo(int val) { return ((val & (val - 1)) == 0); } /** * Returns the nearest power of two that is larger than the given value. * If the given value is already a power of two, this method will simply * return that value. * * @param val the value * @return the next power of two */ private static int nextPowerOfTwo(int val) { int ret = 1; while (ret < val) { ret <<= 1; } return ret; } /** * Updates the actual image dimensions; usually only called from * updateImage. */ private void setImageSize(int width, int height) { imgWidth = width; imgHeight = height; if (target == GL.GL_TEXTURE_RECTANGLE_ARB) { if (mustFlipVertically) { lowerLeftTexCoord = new Point2D.Float(0, imgHeight); upperRightTexCoord = new Point2D.Float(imgWidth, 0); } else { lowerLeftTexCoord = new Point2D.Float(0, 0); upperRightTexCoord = new Point2D.Float(imgWidth, imgHeight); } } else { if (mustFlipVertically) { lowerLeftTexCoord = new Point2D.Float(0, (float) imgHeight / (float) texHeight); upperRightTexCoord = new Point2D.Float((float) imgWidth / (float) texWidth, 0); } else { lowerLeftTexCoord = new Point2D.Float(0, 0); upperRightTexCoord = new Point2D.Float((float) imgWidth / (float) texWidth, (float) imgHeight / (float) texHeight); } } } /** * Creates a new texture ID. * * @param gl the GL object associated with the current OpenGL context * @return a new texture ID */ private static int createTextureID(GL gl) { int[] tmp = new int[1]; gl.glGenTextures(1, tmp, 0); return tmp[0]; } }