/** * Copyright (c) 2012, Matt DesLauriers All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions 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 the Matt DesLauriers nor the names * of his contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package mdesl.graphics; import static org.lwjgl.opengl.GL11.GL_CLAMP; import static org.lwjgl.opengl.GL11.GL_LINEAR; import static org.lwjgl.opengl.GL11.GL_LINEAR_MIPMAP_LINEAR; import static org.lwjgl.opengl.GL11.GL_LINEAR_MIPMAP_NEAREST; import static org.lwjgl.opengl.GL11.GL_NEAREST; import static org.lwjgl.opengl.GL11.GL_NEAREST_MIPMAP_LINEAR; import static org.lwjgl.opengl.GL11.GL_NEAREST_MIPMAP_NEAREST; import static org.lwjgl.opengl.GL11.GL_PACK_ALIGNMENT; import static org.lwjgl.opengl.GL11.GL_REPEAT; import static org.lwjgl.opengl.GL11.GL_RGBA; import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER; import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER; import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S; import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T; import static org.lwjgl.opengl.GL11.GL_UNPACK_ALIGNMENT; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; import static org.lwjgl.opengl.GL11.glBindTexture; import static org.lwjgl.opengl.GL11.glDeleteTextures; import static org.lwjgl.opengl.GL11.glEnable; import static org.lwjgl.opengl.GL11.glGenTextures; import static org.lwjgl.opengl.GL11.glPixelStorei; import static org.lwjgl.opengl.GL11.glTexImage2D; import static org.lwjgl.opengl.GL11.glTexParameteri; import static org.lwjgl.opengl.GL11.glTexSubImage2D; import static org.lwjgl.opengl.GL12.GL_CLAMP_TO_EDGE; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.ByteBuffer; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.EXTFramebufferObject; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GLContext; import de.matthiasmann.twl.utils.PNGDecoder; /** This is a minimal implementation of an OpenGL texture loader. A more complete * implementation would support multiple filetypes (JPEG, BMP, TGA, etc), allow * for parameters such as width/height to be changed (by uploading new texture * data), allow for different internal formats, async loading, different targets * (i.e. for GL_TEXTURE_3D or array textures), mipmaps, compression, etc. * * @author davedes */ public class Texture implements ITexture { protected int id; protected int width; protected int height; public static int toPowerOfTwo(int n) { return 1 << (32 - Integer.numberOfLeadingZeros(n-1)); } public static boolean isPowerOfTwo(int n) { return (n & -n) == n; } public static boolean isNPOTSupported() { return GLContext.getCapabilities().GL_ARB_texture_non_power_of_two; } // Some filters, included here for convenience public static final int LINEAR = GL_LINEAR; public static final int NEAREST = GL_NEAREST; public static final int LINEAR_MIPMAP_LINEAR = GL_LINEAR_MIPMAP_LINEAR; public static final int LINEAR_MIPMAP_NEAREST = GL_LINEAR_MIPMAP_NEAREST; public static final int NEAREST_MIPMAP_NEAREST = GL_NEAREST_MIPMAP_NEAREST; public static final int NEAREST_MIPMAP_LINEAR = GL_NEAREST_MIPMAP_LINEAR; // Some wrap modes, included here for convenience public static final int CLAMP = GL_CLAMP; public static final int CLAMP_TO_EDGE = GL_CLAMP_TO_EDGE; public static final int REPEAT = GL_REPEAT; public static final int DEFAULT_FILTER = NEAREST; public static final int DEFAULT_WRAP = REPEAT; protected Texture() { //does nothing... for subclasses } /** Creates an empty OpenGL texture with the given width and height, where * each pixel is transparent black (0, 0, 0, 0) and the wrap mode is * CLAMP_TO_EDGE and the filter is NEAREST. * * @param width the width of the texture * @param height the height of the texture */ public Texture(int width, int height) { this(width, height, DEFAULT_FILTER); } /** Creates an empty OpenGL texture with the given width and height, where * each pixel is transparent black (0, 0, 0, 0) and the wrap mode is * CLAMP_TO_EDGE. * * @param width the width of the texture * @param height the height of the texture * @param filter the filter to use */ public Texture(int width, int height, int filter) { this(width, height, filter, DEFAULT_WRAP); } /** Creates an empty OpenGL texture with the given width and height, where * each pixel is transparent black (0, 0, 0, 0). * * @param width the width of the texture * @param height the height of the texture * @param minFilter the minification filter to use * @param magFilter the magnification filter to use * @param wrap the wrap mode to use * @param genMipmaps - whether to generate mipmaps, which requires * GL_EXT_framebuffer_object (or GL3+) */ public Texture(int width, int height, int filter, int wrap) { glEnable(getTarget()); id = glGenTextures(); this.width = width; this.height = height; bind(); setFilter(filter); setWrap(wrap); ByteBuffer buf = BufferUtils.createByteBuffer(width * height * 4); upload(GL_RGBA, buf); } public Texture(URL pngRef) throws IOException { this(pngRef, DEFAULT_FILTER); } public Texture(URL pngRef, int filter) throws IOException { this(pngRef, filter, DEFAULT_WRAP); } public Texture(URL pngRef, int filter, int wrap) throws IOException { this(pngRef, filter, filter, wrap, false); } public Texture(URL pngRef, int filter, boolean genMipmap) throws IOException { this(pngRef, filter, filter, DEFAULT_WRAP, genMipmap); } public Texture(URL pngRef, int minFilter, int magFilter, int wrap, boolean genMipmap) throws IOException { //TODO: npot check InputStream input = null; try { input = pngRef.openStream(); PNGDecoder dec = new PNGDecoder(input); width = dec.getWidth(); height = dec.getHeight(); ByteBuffer buf = BufferUtils.createByteBuffer(4 * width * height); dec.decode(buf, width * 4, PNGDecoder.Format.RGBA); buf.flip(); glEnable(getTarget()); id = glGenTextures(); bind(); setFilter(minFilter, magFilter); setWrap(wrap); upload(GL_RGBA, buf); //use EXT since we are targeting 2.0+ if (genMipmap) { EXTFramebufferObject.glGenerateMipmapEXT(getTarget()); } } finally { if (input != null) { try { input.close(); } catch (IOException e) { } } } } public int getTarget() { return GL_TEXTURE_2D; } public int getID() { return id; } protected void setUnpackAlignment() { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1); } /** Uploads image data with the dimensions of this Texture. * * @param dataFormat the format, e.g. GL_RGBA * @param data the byte data */ public void upload(int dataFormat, ByteBuffer data) { bind(); setUnpackAlignment(); glTexImage2D(getTarget(), 0, GL_RGBA, width, height, 0, dataFormat, GL_UNSIGNED_BYTE, data); } /** Uploads a sub-image within this texture. * * @param x the destination x offset * @param y the destination y offset, with lower-left origin * @param width the width of the sub image data * @param height the height of the sub image data * @param dataFormat the format of the sub image data, e.g. GL_RGBA * @param data the sub image data */ public void upload(int x, int y, int width, int height, int dataFormat, ByteBuffer data) { bind(); setUnpackAlignment(); glTexSubImage2D(getTarget(), 0, x, y, width, height, dataFormat, GL_UNSIGNED_BYTE, data); } public void setFilter(int filter) { setFilter(filter, filter); } public void setFilter(int minFilter, int magFilter) { bind(); glTexParameteri(getTarget(), GL_TEXTURE_MIN_FILTER, minFilter); glTexParameteri(getTarget(), GL_TEXTURE_MAG_FILTER, magFilter); } public void setWrap(int wrap) { bind(); glTexParameteri(getTarget(), GL_TEXTURE_WRAP_S, wrap); glTexParameteri(getTarget(), GL_TEXTURE_WRAP_T, wrap); } public void bind() { if (!valid()) throw new IllegalStateException("trying to bind a texture that was disposed"); glBindTexture(getTarget(), id); } public void dispose() { if (valid()) { glDeleteTextures(id); id = 0; } } /** * Returns true if this texture is valid, aka it has not been disposed. * @return true if id!=0 */ public boolean valid() { return id!=0; } public int getWidth() { return width; } public int getHeight() { return height; } /** Returns this object; used for abstraction with SpriteBatch. * @return this texture object */ public Texture getTexture() { return this; } @Override public float getU() { return 0f; } @Override public float getV() { return 0f; } @Override public float getU2() { return 1f; } @Override public float getV2() { return 1f; } }