/*********************************************************************** * mt4j Copyright (c) 2008 - 2010 Christopher Ruff, Fraunhofer-Gesellschaft All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***********************************************************************/ package org.mt4j.util.opengl; import java.nio.IntBuffer; import javax.media.opengl.GL; import javax.media.opengl.glu.GLU; import org.mt4j.MTApplication; import org.mt4j.util.math.Tools3D; import org.mt4j.util.math.ToolsMath; import processing.core.PApplet; import processing.core.PConstants; import processing.core.PImage; import processing.opengl.PGraphicsOpenGL; import com.sun.opengl.util.BufferUtil; /** * This class can only be used in combination with a OpenGL renderer. * It holds a texture which can be used by processing and OpenGL. It allows to load, configure and update the OpenGL texture object as well * as Processing's PImage superclass. * If the texture isnt neeeded anymore, the destroy() method has to be called. * * @author Christopher Ruff */ public class GLTexture extends PImage { public enum WRAP_MODE{ REPEAT(GL.GL_REPEAT), CLAMP(GL.GL_CLAMP), CLAMP_TO_EDGE(GL.GL_CLAMP_TO_EDGE), CLAMP_TO_BORDER(GL.GL_CLAMP_TO_BORDER); private int glConstant; private WRAP_MODE(int glConstant) { this.glConstant = glConstant; } public int getGLConstant(){ return glConstant; } } public enum SHRINKAGE_FILTER{ /** * Nearest neighbor interpolation is the fastest and crudest filtering * method - it simply uses the color of the texel closest to the pixel * center for the pixel color. While fast, this results in aliasing and * shimmering during minification. (GL equivalent: GL_NEAREST) */ NearestNeighborNoMipMaps(GL.GL_NEAREST, false), /** * In this method the four nearest texels to the pixel center are * sampled (at texture level 0), and their colors are combined by * weighted averages. Though smoother, without mipmaps it suffers the * same aliasing and shimmering problems as nearest * NearestNeighborNoMipMaps. (GL equivalent: GL_LINEAR) */ BilinearNoMipMaps(GL.GL_LINEAR, false), /** * Same as NearestNeighborNoMipMaps except that instead of using samples * from texture level 0, the closest mipmap level is chosen based on * distance. This reduces the aliasing and shimmering significantly, but * does not help with blockiness. (GL equivalent: GL_NEAREST_MIPMAP_NEAREST) */ NearestNeighborNearestMipMap(GL.GL_NEAREST_MIPMAP_NEAREST, true), /** * Same as BilinearNoMipMaps except that instead of using samples from * texture level 0, the closest mipmap level is chosen based on * distance. By using mipmapping we avoid the aliasing and shimmering * problems of BilinearNoMipMaps. (GL equivalent: GL_LINEAR_MIPMAP_NEAREST) */ BilinearNearestMipMap(GL.GL_LINEAR_MIPMAP_NEAREST, true), /** * Similar to NearestNeighborNoMipMaps except that instead of using * samples from texture level 0, a sample is chosen from each of the * closest (by distance) two mipmap levels. A weighted average of these * two samples is returned. (GL equivalent: GL_NEAREST_MIPMAP_LINEAR) */ NearestNeighborLinearMipMap(GL.GL_NEAREST_MIPMAP_LINEAR, true), /** * Trilinear filtering is a remedy to a common artifact seen in * mipmapped bilinearly filtered images: an abrupt and very noticeable * change in quality at boundaries where the renderer switches from one * mipmap level to the next. Trilinear filtering solves this by doing a * texture lookup and bilinear filtering on the two closest mipmap * levels (one higher and one lower quality), and then linearly * interpolating the results. This results in a smooth degradation of * texture quality as distance from the viewer increases, rather than a * series of sudden drops. Of course, closer than Level 0 there is only * one mipmap level available, and the algorithm reverts to bilinear * filtering (GL equivalent: GL_LINEAR_MIPMAP_LINEAR) */ Trilinear(GL.GL_LINEAR_MIPMAP_LINEAR, true); private boolean usesMipMapLevels; private int glConstant; private SHRINKAGE_FILTER(int glConstant, boolean usesMipMapLevels) { this.usesMipMapLevels = usesMipMapLevels; this.glConstant = glConstant; } public int getGLConstant(){ return glConstant; } public boolean usesMipMapLevels() { return usesMipMapLevels; } } public enum EXPANSION_FILTER{ /** * Nearest neighbor interpolation is the fastest and crudest filtering * mode - it simply uses the color of the texel closest to the pixel * center for the pixel color. While fast, this results in texture * 'blockiness' during magnification. (GL equivalent: GL_NEAREST) */ NearestNeighbor(GL.GL_NEAREST), /** * In this mode the four nearest texels to the pixel center are sampled * (at the closest mipmap level), and their colors are combined by * weighted average according to distance. This removes the 'blockiness' * seen during magnification, as there is now a smooth gradient of color * change from one texel to the next, instead of an abrupt jump as the * pixel center crosses the texel boundary. (GL equivalent: GL_LINEAR) */ Bilinear(GL.GL_LINEAR); private int glConstant; private EXPANSION_FILTER(int glConstant) { this.glConstant = glConstant; } public int getGLConstant(){ return glConstant; } } public enum TEXTURE_TARGET{ TEXTURE_1D(GL.GL_TEXTURE_1D), TEXTURE_2D(GL.GL_TEXTURE_2D), RECTANGULAR(GL.GL_TEXTURE_RECTANGLE_ARB); private int glConstant; private TEXTURE_TARGET(int glConstant) { this.glConstant = glConstant; } public int getGLConstant(){ return glConstant; } } /* //FIXME obsolete? how to deal with the PImage.format and the glFormat?? public enum INTERNAL_FORMAT{ // RGB(GL.GL_RGB), RGBA(GL.GL_RGBA) // ,BGRA(GL.GL_BGRA) ; private int glConstant; private INTERNAL_FORMAT(int glConstant) { this.glConstant = glConstant; } public int getGLConstant(){ return glConstant; } } */ /* //FIXME use instead of hardcoded GL_UNSIGNED_BYTE? public enum GL_TYPE{ INTEGER(GL.GL_INT), UNSIGNED_BYTE(GL.GL_UNSIGNED_BYTE); private int glConstant; private GL_TYPE(int glConstant) { this.glConstant = glConstant; } public int getGLConstant(){ return glConstant; } } */ private PApplet app; private PGraphicsOpenGL pgl; private GL gl; protected boolean fboSupported; private boolean glTextureInitialized; protected int[] glTextureID = { 0 } ; private GLTextureSettings glTextureSettings; private int internalFormat; private boolean forcedRectMipMaps = false; // private int width; // private int height; //FIXME too many isPowerOfTwo checks (see space3d example texture creation) //TODO implement PBO texture upload //TODO initialize so that only the GLTexture object is initialized or nothing //TODO if shape useOpenGL/useProcessing changes check if PImage or OpenGL texture object is initialized and do if it isnt - on demand! //TODO need constructor that doesent init super so that when we create fbos at runtime we dont have toallocate huge pixel array! //TODO or mannualy set the fbo texture's width/height settings before //TODO mark the constructors which may only be used in the OpenGL/MT4j/Processing Thread /** * Instantiates a new gL texture. * * @param parent the parent */ public GLTexture(PApplet parent){ // this(parent, 2, 2, new GLTextureSettings()); //ORG this(parent, new GLTextureSettings()); } /** * Instantiates a new gL texture. * * @param parent the parent * @param settings the settings */ public GLTexture(PApplet parent, GLTextureSettings settings){ super(0, 0, ARGB); this.glTextureInitialized = false; // this.pImageUpToDate = false; this.glTextureSettings = settings; this.app = parent; this.parent = parent; pgl = (PGraphicsOpenGL)parent.g; gl = pgl.gl; } /** * Instantiates a empty texture of the specified dimensions, using default GLTextureSettings. * Image data can be uploaded by calling setTexture(), setGLTexture //TODO setPImageTexture * * @param parent the parent * @param width the width * @param height the height */ public GLTexture(PApplet parent, int width, int height){ this(parent, width, height, new GLTextureSettings()); } //TODO maybe make same constructor but with boolean initialzeGLTexture = true/false!? //-> see swingtexrenderer -> need to init with dimensions but not init gltex /** * Instantiates a new gL texture. * * @param parent the parent * @param width the width * @param height the height * @param settings the settings */ public GLTexture(PApplet parent, int width, int height, GLTextureSettings settings){ // this(parent, width, height, new GLTextureSettings()); super(width, height, ARGB); //FIXME original! // super(2,2,ARGB); this.glTextureInitialized = false; // this.pImageUpToDate = false; this.glTextureSettings = settings; //FIXME test - set target to RECTANGULAR_ARB if loaded image is NPOT if ( !(ToolsMath.isPowerOfTwo(width) && ToolsMath.isPowerOfTwo(height)) ){ this.glTextureSettings.target = TEXTURE_TARGET.RECTANGULAR; } this.app = parent; this.parent = parent; pgl = (PGraphicsOpenGL)parent.g; gl = pgl.gl; // setTextureParams(params); // if (initGLTextureObject){ // initTexture(width, height); // } // if (app.isRenderThreadCurrent()){ //FIXME really allow to delay this? what if other methods are invoked afterwards which depend on this being called? setupGLTexture(width, height); // }else{ // app.invokeLater(new Runnable() { // public void run() { // setupGLTexture(MTTexture.this.width, MTTexture.this.height); //FIXME check for initTexture calls in mt4j and remove them! // } // }); // } } /** * Instantiates a new gL texture. * * @param parent the parent * @param fileName the file name */ public GLTexture(PApplet parent, String fileName){ this(parent, fileName, new GLTextureSettings()); } /** * Instantiates a new gL texture. * * @param parent the parent * @param fileName the file name * @param settings the settings */ public GLTexture(PApplet parent, String fileName, GLTextureSettings settings){ super(2, 2, ARGB); //will get correct dimensions later at setTexture(PImage img, GLTextureSettings settings) -> init(..) call this.glTextureInitialized = false; // this.pImageUpToDate = false; this.app = parent; this.parent = parent; pgl = (PGraphicsOpenGL)app.g; gl = pgl.gl; this.glTextureSettings = settings; this.loadTexture(fileName, this.glTextureSettings); } /** * Instantiates a new gL texture. * * @param parent the parent * @param pImage the image */ public GLTexture(PApplet parent, PImage pImage){ this(parent, pImage, new GLTextureSettings()); } /** * Instantiates a new texture using the specified settings and image data. * * <br><b>NOTE: </b>This will make this texture share the specified PImage's pixel * array. So changes to the original PImage may change this texture. * * @param parent the parent * @param pImage the image * @param settings the settings */ public GLTexture(PApplet parent, PImage pImage, GLTextureSettings settings){ this(parent, pImage.width, pImage.height, settings); if (pImage.pixels == null || pImage.pixels.length == 0){ pImage.loadPixels(); } this.pixels = pImage.pixels; //Dont copy the pixels for performance this.width = pImage.width; this.height = pImage.height; // this.loadPixels(); //FIXME neccessary? if we assigned the pixel array it should be loaded already! updateGLTextureFromPImage(); //TODO invokelater if not gl thread updatePixels(); } // private void init(int width, int height){ // if (this.glTextureSettings == null){ // this.init(width, height, new GLTextureSettings()); // }else{ // this.init(width, height, this.glTextureSettings); // } // } /** * Initializes the empty PImage AND the OpenGL texture with the given dimension. * This can be used to change the texture dimension and reload a different texture into it. * * @param width the width * @param height the height * @param texSettings the tex settings */ private void init(int width, int height, GLTextureSettings texSettings){ // super.init(1, 1, ARGB); super.init(width, height, ARGB); // pImageUpToDate = false; this.glTextureSettings = texSettings; boolean POT = (ToolsMath.isPowerOfTwo(width) && ToolsMath.isPowerOfTwo(height)) ; if (POT){ this.glTextureSettings.target = TEXTURE_TARGET.TEXTURE_2D; }else{ this.glTextureSettings.target = TEXTURE_TARGET.RECTANGULAR; } //FIXME TEST -> only init GL texture if in opengl thread! if (app instanceof MTApplication && ((MTApplication)app).isRenderThreadCurrent()) { setupGLTexture(width, height); } } // protected void apply(GLTextureSettings settings){ //TODO // if (this.glTextureSettings == null || !this.glTextureSettings.equals(settings)){ // // } // } /** * Creates and sets up an empty OpenGL texture object with the specified dimensions and * this texture's GLTextureSettings. * <br><b>NOTE: </b> * * @param width the width * @param height the height */ public void setupGLTexture(int width, int height){ //TODO make private/protected if (this.glTextureID[0] != 0) destroy(); //FIXME if check done here, we can remove the check elsewhere? boolean POT = (ToolsMath.isPowerOfTwo(width) && ToolsMath.isPowerOfTwo(height)); if (this.glTextureSettings.target != TEXTURE_TARGET.RECTANGULAR && !POT){ this.glTextureSettings.target = TEXTURE_TARGET.RECTANGULAR; } //FIXME TEST gluBuild2DMimaps with NPOT TEXTURE -> stretches the images to POT -> we can use normal TEXTURE2D target then if (this.glTextureSettings.target == TEXTURE_TARGET.RECTANGULAR && this.glTextureSettings.shrinkFilter.usesMipMapLevels()){ System.err.println("INFO: A non-power-of-two dimension texture should ideally not be used with Mip Map minification filter. -> Result can be blurred/streched." ); this.glTextureSettings.target = TEXTURE_TARGET.TEXTURE_2D; this.forcedRectMipMaps = true; } //check if fbo and thus the glGenerateMipmapEXT(GL_TEXTURE_2D); this.fboSupported = GLFBO.isSupported(app); // Target (GL_TEXTURE1D, GL_TEXTURE2D, GL_RECTANGLE_ARB ..) int textureTarget = glTextureSettings.target.getGLConstant(); //GL_REPEAT with a GL_RECTANGLE_ARB texture target are not supported! => use GL_CLAMP then. if (glTextureSettings.target == TEXTURE_TARGET.RECTANGULAR){ //FIXME gluBuildmipmaps staucht NPOT texture auf pot zusammen? //BEi clamp komischer wasser fbo error if (glTextureSettings.wrappingHorizontal == WRAP_MODE.REPEAT){ glTextureSettings.wrappingHorizontal = WRAP_MODE.CLAMP_TO_EDGE; // this.wrap_s = GL.GL_CLAMP; } if (glTextureSettings.wrappingVertical == WRAP_MODE.REPEAT){ glTextureSettings.wrappingVertical = WRAP_MODE.CLAMP_TO_EDGE; // this.wrap_t = GL.GL_CLAMP; } //NPOT texture dont support mipmaps! if (glTextureSettings.shrinkFilter.usesMipMapLevels()){ this.glTextureSettings.shrinkFilter = SHRINKAGE_FILTER.BilinearNoMipMaps; } } // Wrapping int wrap_s = glTextureSettings.wrappingHorizontal.getGLConstant(); int wrap_t = glTextureSettings.wrappingVertical.getGLConstant(); //Filtering int minFilter = glTextureSettings.shrinkFilter.getGLConstant(); int magFilter = glTextureSettings.expansionFilter.getGLConstant(); // Texture internal format switch (this.format) { case PConstants.RGB: this.internalFormat = GL.GL_RGB; break; case PConstants.ARGB: this.internalFormat = GL.GL_RGBA; break; default: this.internalFormat = GL.GL_RGBA; break; } //Create the texture object gl.glGenTextures(1, glTextureID, 0); //Bind the texture gl.glBindTexture(textureTarget, glTextureID[0]); //SET texture mag/min FILTER mode gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MIN_FILTER, minFilter); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, magFilter); //Set texture wrapping mode gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_S, wrap_s); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_T, wrap_t); switch (glTextureSettings.target) { case TEXTURE_1D: gl.glTexImage1D(textureTarget, 0, internalFormat, width, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, null); break; default: gl.glTexImage2D(textureTarget, 0, internalFormat, width, height, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, null); //FIXME always use GL_RGBA as glformat?? // gl.glTexImage2D(textureTarget, 0, internalFormat, width, height, 0, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, null);//ORIGINAL break; } gl.glBindTexture(textureTarget, 0); this.glTextureInitialized = true; } /** * Loads the image from the specifed file and the specified settings. * Then this PImage AND an OpenGL texture object are set up with the image data. * Re-initializes this texture if the old dimensions dont match the new image. * * @param filename the filename * @param settings the settings */ public void loadTexture(String filename, GLTextureSettings settings){ PImage img = app.loadImage(filename); this.glTextureSettings = settings; if (!(ToolsMath.isPowerOfTwo(img.width) && ToolsMath.isPowerOfTwo(img.height)) ){ this.glTextureSettings.target = TEXTURE_TARGET.RECTANGULAR; } this.loadTexture(img, this.glTextureSettings); } /** * Sets this texture to the data of the specified PImage. * Re-sets this texture's PImage data AND OpenGL texture object data. * <br><b>NOTE: </b>This will make this texture share the specified PImage's pixel * array. So changes to the original PImage may change this texture. * * @param img the img * @param settings the settings */ public void loadTexture(PImage img, GLTextureSettings settings) { img.loadPixels(); this.glTextureSettings = settings; if (!(ToolsMath.isPowerOfTwo(img.width) && ToolsMath.isPowerOfTwo(img.height)) ){ this.glTextureSettings.target = TEXTURE_TARGET.RECTANGULAR; } if ((img.width != this.width) || (img.height != this.height) || glTextureID[0] == 0) { this.init(img.width, img.height, settings); } // PApplet.arrayCopy(img.pixels, pixels); //TODO use same pixel array, avoid copy? this.pixels = img.pixels; this.updateGLTextureFromPImage(); this.updatePixels(); } /** * Sets this texture to the data of the specified image. * Re-sets ONLY this texture's OpenGL texture object data! * <br><b>NOTE: </b> This texture object should then only be rendered directly by OpenGL (not Processing) * <br>To also update the PImage's pixel data used by Processing, use <code>loadPImageTexture(...)</code> or * <code>updatePImageFromGLTexture()</code> * * @param img the new gL texture */ public void loadGLTexture(PImage img) { if (!Tools3D.isPowerOfTwoDimension(img)){ this.glTextureSettings.target = TEXTURE_TARGET.RECTANGULAR; } if ((img.width != width) || (img.height != height) ) { init(img.width, img.height, this.glTextureSettings); } this.updateGLTexture(img.pixels); } /** * Sets this texture's PImage pixel data to the data of the specified PImage. * Re-sets only this texture's PImage data, not the OpenGL texture object! * <br><b>NOTE: </b> This texture object should then only be rendered by Processing and not directly by OpenGL! * <br>To also update the OpenGL texture object, use <code>loadGLTexture(...)</code>or * <code>updateGLTextureFromPImage()</code> * * @param img the new p image texture */ public void loadPImageTexture(PImage img){ img.loadPixels(); this.format = img.format; if ((img.width != width) || (img.height != height)){ // System.out.println("loadPImageTexture ..dimensions are different from former texture!"); this.init(img.width, img.height, this.glTextureSettings); // original // super.init(img.width, img.height, img.format); } // PApplet.arrayCopy(img.pixels, pixels); //TODO use same pixel array, avoid copy? this.pixels = img.pixels; this.updatePixels(); } /** * Updates only the OpenGL texture object with the data from the specified image data array. * <br><b>NOTE:</b> The data has to match this texture's width/height dimensions! If it doesen't - * call <code>texture.init(newWidth, newHeight)</code> first! * * @param intArray the int array */ public void updateGLTexture(int[] intArray){ this.updateGLTexture(IntBuffer.wrap(intArray)); } /** * Updates only the OpenGL texture object with the data from the specified image data buffer. * <br><b>NOTE:</b> The data has to match this texture's width/height dimensions! If it doesen't - * call <code>texture.init(newWidth, newHeight)</code> first! * * @param buffer the buffer */ public void updateGLTexture(IntBuffer buffer){ if (this.glTextureID[0] == 0 || !this.glTextureInitialized){ setupGLTexture(this.width, this.height); System.out.println("calling setupGLTexture()" + " in " + "updateGLTexture() since texture wasnt initialized!" ); } // int glFormat = glTextureSettings.glType.getGLConstant(); int glFormat = GL.GL_BGRA; //FIXME DONT HARDCODE!? int type = GL.GL_UNSIGNED_BYTE; //FIXME DONT HARDCODE!? int textureTarget = glTextureSettings.target.getGLConstant(); // int internalFormat = glTextureSettings.textureInternalFormat.getGLConstant(); //FIXME TEST gluBuild2DMimaps with NPOT TEXTURE -> stretches the images to POT -> we can use normal TEXTURE2D target then if (this.glTextureSettings.target == TEXTURE_TARGET.RECTANGULAR && this.glTextureSettings.shrinkFilter.usesMipMapLevels()){ this.glTextureSettings.target = TEXTURE_TARGET.TEXTURE_2D; this.forcedRectMipMaps = true; } //NPOT texture targets dont support mipmaps! if (glTextureSettings.target == TEXTURE_TARGET.RECTANGULAR){ if (glTextureSettings.shrinkFilter.usesMipMapLevels()){ this.glTextureSettings.shrinkFilter = SHRINKAGE_FILTER.BilinearNoMipMaps; } } switch (this.format) { case PConstants.RGB: this.internalFormat = GL.GL_RGB; break; case PConstants.ARGB: this.internalFormat = GL.GL_RGBA; break; default: this.internalFormat = GL.GL_RGBA; break; } gl.glBindTexture(textureTarget, this.glTextureID[0]); switch (glTextureSettings.target) { case TEXTURE_1D: if (glFormat == GL.GL_BGRA){ glFormat = GL.GL_RGBA; } gl.glTexSubImage1D(textureTarget, 0, 0, this.width, glFormat, type, buffer); break; case TEXTURE_2D: case RECTANGULAR: default: //MipMapping wont work with RECTANGLE_ARB TARGET ! if (glTextureSettings.shrinkFilter.usesMipMapLevels() && this.glTextureSettings.target != TEXTURE_TARGET.RECTANGULAR ){ //deprectated in opengl 3.0 -will always create mipmaps automatically if lvl 0 changes // gl.glTexParameteri( textureTarget, GL.GL_GENERATE_MIPMAP, GL.GL_TRUE ); if (this.forcedRectMipMaps){ //Resizes NPOT textures to POT GLU glu = ((PGraphicsOpenGL)this.parent.g).glu; glu.gluBuild2DMipmaps(textureTarget, internalFormat, this.width, this.height, glFormat, type, buffer); }else{ if (this.fboSupported){ //Naive check if glGenerateMipmapEXT command is supported gl.glTexSubImage2D(textureTarget, 0, 0, 0, this.width, this.height, glFormat, type, buffer); gl.glGenerateMipmapEXT(textureTarget); //newer OpenGL 3.x method of creating mip maps //TODO problems on ATI? use gl.glEnable(textureTarget) first? }else{ //Old school software method, will resize a NPOT texture to a POT texture GLU glu = ((PGraphicsOpenGL)this.parent.g).glu; glu.gluBuild2DMipmaps(textureTarget, internalFormat, this.width, this.height, glFormat, type, buffer); } } } else{ gl.glTexSubImage2D(textureTarget, 0, 0, 0, width, height, glFormat, type, buffer); //ORG // gl.glTexSubImage2D(textureTarget, 0, 0, 0, width, height, GL.GL_RGB, type, buffer); } break; } gl.glBindTexture(textureTarget, 0); } /** * Updates the OpenGL texture object with the data from this PImage.pixels pixel * array. This method should be called if the pixel data has changed and the change * has to be reflected in the OpenGL texture object (probably because direct OpenGL texture rendering is used) * <b>NOTE:</b>The PImage pixel data dimensions have to match the OpenGL texture dimension! If not, use loadTexture() */ public void updateGLTextureFromPImage(){ updateGLTexture(this.pixels); } /** * Updates the PImage pixel data from the texture's OpenGL texture object. * This method should be called if the OpenGL texture was changed and the change * has to be reflected in the PImage used by Processing * (probably because Processings rendering pipeling is used instead of direct OpenGL) * */ public void updatePImageFromGLTexture(){ IntBuffer buff = BufferUtil.newIntBuffer(this.width * this.height); int textureTarget = this.glTextureSettings.target.getGLConstant(); gl.glBindTexture(textureTarget, this.glTextureID[0]); gl.glGetTexImage(textureTarget, 0, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, buff); gl.glBindTexture(textureTarget, 0); buff.get(pixels); } /** * Deletes the opengl texture object. */ public void destroy(){ if (this.glTextureID[0] != 0){ gl.glDeleteTextures(1, this.glTextureID, 0); this.glTextureID[0] = 0; } // releasePBO(); //FIXME implement } /** * Sets the texture wrap mode. * * @param wrappingHorizontal the wrapping horizontal * @param wrappingVertical the wrapping vertical */ public void setWrapMode(WRAP_MODE wrappingHorizontal, WRAP_MODE wrappingVertical){ this.glTextureSettings.wrappingHorizontal = wrappingHorizontal; this.glTextureSettings.wrappingVertical = wrappingVertical; if (this.isGLTexObjectInitialized()){ gl.glBindTexture(this.getTextureTarget(), this.getTextureID()); gl.glTexParameteri(this.getTextureTarget(), GL.GL_TEXTURE_WRAP_S, this.glTextureSettings.wrappingHorizontal.getGLConstant()); gl.glTexParameteri(this.getTextureTarget(), GL.GL_TEXTURE_WRAP_T, this.glTextureSettings.wrappingVertical.getGLConstant()); gl.glBindTexture(this.getTextureTarget(), 0); } } public WRAP_MODE getWrappingHorizontal(){ return this.glTextureSettings.wrappingHorizontal; } public WRAP_MODE getWrappingVertical(){ return this.glTextureSettings.wrappingVertical; } /** * Sets the texture filtes. * * @param minFilter the min filter * @param magFilter the mag filter */ public void setFilter(SHRINKAGE_FILTER minFilter, EXPANSION_FILTER magFilter){ if (this.forcedRectMipMaps){ //Because current target is TEXTURE_2D although it was a NPOT texture which was rescaled using glubuild2dmipmaps //and if another filter is chosen that doesent use mip maps and an updating method is called it would choose a RECTANGULAR target = conflict System.err.println("INFO: Changing the texture filter for NPOT texture in combination with MipMapping isnt allowed atm."); } boolean usedMipMapPreviously = this.glTextureSettings.shrinkFilter.usesMipMapLevels(); this.glTextureSettings.shrinkFilter = minFilter; this.glTextureSettings.expansionFilter = magFilter; if (this.isGLTexObjectInitialized()){ gl.glBindTexture(this.getTextureTarget(), this.getTextureID()); gl.glTexParameteri(this.getTextureTarget(), GL.GL_TEXTURE_MIN_FILTER, this.glTextureSettings.shrinkFilter.getGLConstant()); gl.glTexParameteri(this.getTextureTarget(), GL.GL_TEXTURE_MAG_FILTER, this.glTextureSettings.expansionFilter.getGLConstant()); gl.glBindTexture(this.getTextureTarget(), 0); } //FIXME pixels may be empty/not current - just create mipmaps with gl code ourselves!! if (!usedMipMapPreviously && this.glTextureSettings.shrinkFilter.usesMipMapLevels()){ this.updateGLTexture(this.pixels); } } public SHRINKAGE_FILTER getShrinkageFilter(){ return this.glTextureSettings.shrinkFilter; } public EXPANSION_FILTER getExpansionFilter(){ return this.glTextureSettings.expansionFilter; } /** * Gets the OpenGL texture id. * * @return the texture id */ public int getTextureID(){ return this.glTextureID[0]; } public int getTextureTarget(){ return this.glTextureSettings.target.getGLConstant(); } public TEXTURE_TARGET getTextureTargetEnum(){ return this.glTextureSettings.target; } public boolean isGLTexObjectInitialized(){ return this.glTextureInitialized; } /* private int[] toRGBA(int[] intArray, int arrayFormat) { int t = 0; int p = 0; int twidth = width; int[] tIntArray = new int[width * height]; if (PGraphicsOpenGL.BIG_ENDIAN) { switch (arrayFormat) { case ALPHA: for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { tIntArray[t++] = 0xFFFFFF00 | intArray[p++]; } t += twidth - width; } break; case RGB: for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int pixel = intArray[p++]; tIntArray[t++] = (pixel << 8) | 0xff; } t += twidth - width; } break; case ARGB: for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int pixel = intArray[p++]; tIntArray[t++] = (pixel << 8) | ((pixel >>4) & 0xff); } t += twidth - width; } break; } } else { // LITTLE_ENDIAN // ARGB native, and RGBA opengl means ABGR on windows // for the most part just need to swap two components here // the sun.cpu.endian here might be "false", oddly enough.. // (that's why just using an "else", rather than check for "little") switch (arrayFormat) { case ALPHA: for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { tIntArray[t++] = (intArray[p++] <<4) | 0x00FFFFFF; } t += twidth - width; } break; case RGB: for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int pixel = intArray[p++]; // needs to be ABGR, stored in memory xRGB // so R and B must be swapped, and the x just made FF tIntArray[t++] = 0xff000000 | // force opacity for good measure ((pixel & 0xFF) <<6) | ((pixel & 0xFF0000) >>6) | (pixel & 0x0000FF00); } t += twidth - width; } break; case ARGB: for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int pixel = intArray[p++]; // needs to be ABGR stored in memory ARGB // so R and B must be swapped, A and G just brought back in tIntArray[t++] = ((pixel & 0xFF) <<6) | ((pixel & 0xFF0000) >>6) | (pixel & 0xFF00FF00); } t += twidth - width; } break; } } return tIntArray; } private int[] toARGB(int[] intArray) { int t = 0; int p = 0; int twidth = width; int[] tIntArray = new int[width * height]; if (PGraphicsOpenGL.BIG_ENDIAN) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int pixel = intArray[p++]; tIntArray[t++] = (pixel >> 8) | ((pixel <<4) & 0xff); } t += twidth - width; } } else { // LITTLE_ENDIAN // ARGB native, and RGBA opengl means ABGR on windows // for the most part just need to swap two components here // the sun.cpu.endian here might be "false", oddly enough.. // (that's why just using an "else", rather than check for "little") for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int pixel = intArray[p++]; // needs to be ARGB stored in memory ABGR (RGBA = ABGR -> ARGB) // so R and B must be swapped, A and G just brought back in tIntArray[t++] = ((pixel & 0xFF) <<6) | ((pixel & 0xFF0000) >>6) | (pixel & 0xFF00FF00); } t += twidth - width; } } return tIntArray; } */ /* if (tpixels == null) { twidth = width2; theight = height2; tpixels = new int[twidth * theight]; tbuffer = BufferUtil.newIntBuffer(twidth * theight); } // copy image data into the texture int p = 0; int t = 0; if (BIG_ENDIAN) { switch (source.format) { case ALPHA: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { tpixels[t++] = 0xFFFFFF00 | source.pixels[p++]; } t += twidth - source.width; } break; case RGB: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { int pixel = source.pixels[p++]; tpixels[t++] = (pixel << 8) | 0xff; } t += twidth - source.width; } break; case ARGB: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { int pixel = source.pixels[p++]; tpixels[t++] = (pixel << 8) | ((pixel >> 24) & 0xff); } t += twidth - source.width; } break; } } else { // LITTLE_ENDIAN // ARGB native, and RGBA opengl means ABGR on windows // for the most part just need to swap two components here // the sun.cpu.endian here might be "false", oddly enough.. // (that's why just using an "else", rather than check for "little") switch (source.format) { case ALPHA: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { tpixels[t++] = (source.pixels[p++] << 24) | 0x00FFFFFF; } t += twidth - source.width; } break; case RGB: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { int pixel = source.pixels[p++]; // needs to be ABGR, stored in memory xRGB // so R and B must be swapped, and the x just made FF tpixels[t++] = 0xff000000 | // force opacity for good measure ((pixel & 0xFF) << 16) | ((pixel & 0xFF0000) >> 16) | (pixel & 0x0000FF00); } t += twidth - source.width; } break; case ARGB: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { int pixel = source.pixels[p++]; // needs to be ABGR stored in memory ARGB // so R and B must be swapped, A and G just brought back in tpixels[t++] = ((pixel & 0xFF) << 16) | ((pixel & 0xFF0000) >> 16) | (pixel & 0xFF00FF00); } t += twidth - source.width; } break; } } tbuffer.put(tpixels); tbuffer.rewind(); */ public boolean isGLTextureInitialized(){ return this.glTextureInitialized && this.glTextureID[0] != 0; } /* However, non-power-of-two sized textures have limitations that do not apply to power-of-two sized textures. NPOTS textures may not use mipmap filtering; POTS textures support both mipmapped and non-mipmapped filtering. NPOTS textures support only the GL_CLAMP, GL_CLAMP_TO_EDGE, and GL_CLAMP_TO_BORDER wrap modes; POTS textures support GL_CLAMP_TO_EDGE, GL_REPEAT, GL_CLAMP, GL_MIRRORED_REPEAT, and GL_CLAMP_TO_BORDER (and GL_MIRROR_CLAMP_ATI and GL_MIRROR_CLAMP_TO_EDGE_ATI if ATI_texture_mirror_once is supported) . NPOTS textures do not support an optional 1-texel border; POTS textures do support an optional 1-texel border. */ @Override protected void finalize() throws Throwable { //System.out.println("Finalizing GLTEXTURE - " + this); if (this.app instanceof MTApplication) { MTApplication mtApp = (MTApplication) this.app; mtApp.invokeLater(new Runnable() { public void run() { destroy(); } }); }else{ //TODO use registerPre()? //is the object even valid after finalize() is called?? } super.finalize(); } }