package electrosphere.renderer.texture; import electrosphere.engine.Globals; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.OpenGLState; import electrosphere.renderer.RenderingEngine; import electrosphere.util.FileUtils; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.IOException; import java.nio.ByteBuffer; import javax.imageio.ImageIO; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL40; import org.lwjgl.system.MemoryUtil; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL12.GL_CLAMP_TO_EDGE; import static org.lwjgl.opengl.GL13.GL_TEXTURE0; import static org.lwjgl.opengl.GL14.*; import static org.lwjgl.opengl.GL30.*; /** * A opengl in texture */ public class Texture { //the pointer for the texture int texturePointer = -1; //the width of the texture int width = -1; //the height of the texture int height = -1; //whether the texture has transparency or not boolean hasTransparency; //the path to the texture String path = ""; //the border color float[] borderColor = null; //the min and max filter int minFilter = -1; int maxFilter = -1; //the pixel format (ie RGB, ARGB, etc) int pixelFormat = -1; //the data type of a single component of a pixel (IE UNSIGNED_INT, BYTE, etc) int datatype = -1; /** * Constructs a texture from a pointer * @param pointer the pointer */ public Texture(int pointer){ this.texturePointer = pointer; } /** * Creates a texture with a new opengl texture object */ public Texture(){ this.texturePointer = glGenTextures(); } /** * Creates an in engine texture object from a java bufferedimage object * @param bufferedImage The java bufferedimage object */ public Texture(OpenGLState openGlState, BufferedImage bufferedImage){ this.texturePointer = glGenTextures(); //bind the new texture openGlState.glBindTexture(GL_TEXTURE_2D, texturePointer); //how are we gonna wrap the texture?? this.setWrap(openGlState, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); this.setWrap(openGlState, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); //set the border color to black this.setBorderColor(openGlState, new float[]{ 0.0f, 0.0f, 0.0f, 1.0f }); //set magnification and minification operation sampling strategies this.setMinFilter(openGlState, GL_LINEAR); this.setMagFilter(openGlState, GL_LINEAR); //load the image here ByteBuffer data; width = 1; height = 1; BufferedImage image_data = bufferedImage; if ( image_data.getType() == BufferedImage.TYPE_3BYTE_BGR || image_data.getType() == BufferedImage.TYPE_INT_RGB ){ hasTransparency = false; } else if( image_data.getType() == BufferedImage.TYPE_4BYTE_ABGR || image_data.getType() == BufferedImage.TYPE_INT_ARGB ){ hasTransparency = true; } width = image_data.getWidth(); height = image_data.getHeight(); if(hasTransparency){ data = BufferUtils.createByteBuffer(width * height * 4); } else { data = BufferUtils.createByteBuffer(width * height * 3); } for(int y = height - 1; y > -1; y--){ for(int x = 0; x < width; x++){ Color temp = new Color(image_data.getRGB(x, y), true); data.put((byte)temp.getRed()); data.put((byte)temp.getGreen()); data.put((byte)temp.getBlue()); if(hasTransparency){ data.put((byte)temp.getAlpha()); } } } data.flip(); //call if width != height so opengl figures out how to unpack it properly if(width != height){ glPixelStorei(GL_UNPACK_ALIGNMENT, 1); } //buffer the texture information if(hasTransparency){ this.pixelFormat = GL_RGBA; this.datatype = GL_UNSIGNED_BYTE; this.glTexImage2D(openGlState, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); } else { this.pixelFormat = GL_RGB; this.datatype = GL_UNSIGNED_BYTE; this.glTexImage2D(openGlState, width, height, GL_RGB, GL_UNSIGNED_BYTE, data); } glGenerateMipmap(GL_TEXTURE_2D); //check build status String errorMessage = RenderingEngine.getErrorInEnglish(Globals.renderingEngine.getError()); if(errorMessage != null){ LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Texture Constructor[from bufferedimage]: " + errorMessage)); } openGlState.glBindTexture(GL_TEXTURE_2D, 0); } /** * Creates a texture from an existing file * @param path The path to the image file */ public Texture(OpenGLState openGlState, String path){ LoggerInterface.loggerRenderer.DEBUG("Create texture " + path); this.path = path; if(!Globals.HEADLESS){ LoggerInterface.loggerRenderer.DEBUG("Setup texture object"); //generate the texture object on gpu this.texturePointer = glGenTextures(); //bind the new texture openGlState.glBindTexture(GL_TEXTURE_2D, texturePointer); //how are we gonna wrap the texture?? this.setWrap(openGlState, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); this.setWrap(openGlState, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); //set the border color to black this.setBorderColor(openGlState, new float[]{ 0.0f, 0.0f, 0.0f, 1.0f }); //set magnification and minification operation sampling strategies this.setMinFilter(openGlState, GL_LINEAR); this.setMagFilter(openGlState, GL_LINEAR); LoggerInterface.loggerRenderer.DEBUG("Create texture data buffers"); //load the image here ByteBuffer data; width = 1; height = 1; try { BufferedImage image_data = ImageIO.read(FileUtils.getAssetFile(path)); // //transparency check if ( image_data.getType() == BufferedImage.TYPE_3BYTE_BGR || image_data.getType() == BufferedImage.TYPE_INT_RGB ){ hasTransparency = false; } else if( image_data.getType() == BufferedImage.TYPE_4BYTE_ABGR || image_data.getType() == BufferedImage.TYPE_INT_ARGB ){ hasTransparency = true; } // //create buffer width = image_data.getWidth(); height = image_data.getHeight(); if(hasTransparency){ data = BufferUtils.createByteBuffer(width * height * 4); } else { data = BufferUtils.createByteBuffer(width * height * 3); } // //error check if(data == null){ throw new IllegalStateException("Failed to allocate buffer for texture"); } // //buffer data for(int y = height - 1; y > -1; y--){ for(int x = 0; x < width; x++){ Color temp = new Color(image_data.getRGB(x, y), hasTransparency); if(data.position() + 3 > data.limit() + 1){ throw new IllegalStateException("Hit buffer limit!"); } data.put((byte)temp.getRed()); data.put((byte)temp.getGreen()); data.put((byte)temp.getBlue()); if(hasTransparency){ if(data.position() + 1 > data.limit() + 1){ throw new IllegalStateException("Hit buffer limit!"); } data.put((byte)temp.getAlpha()); } } } } catch (IOException ex) { ex.printStackTrace(); hasTransparency = false; data = BufferUtils.createByteBuffer(3); data.put((byte)0); data.put((byte)0); data.put((byte)0); } LoggerInterface.loggerRenderer.DEBUG("Flip buffer"); data.flip(); //call if width != height so opengl figures out how to unpack it properly if(width != height){ glPixelStorei(GL_UNPACK_ALIGNMENT, 1); } LoggerInterface.loggerRenderer.DEBUG("Upload texture buffer"); //buffer the texture information if(hasTransparency){ this.pixelFormat = GL_RGBA; this.datatype = GL_UNSIGNED_BYTE; this.glTexImage2D(openGlState, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); } else { this.pixelFormat = GL_RGB; this.datatype = GL_UNSIGNED_BYTE; this.glTexImage2D(openGlState, width, height, GL_RGB, GL_UNSIGNED_BYTE, data); } glGenerateMipmap(GL_TEXTURE_2D); //OPTIONAL free the original image data now that it's on the gpu // System.gc(); //check build status String errorMessage = RenderingEngine.getErrorInEnglish(Globals.renderingEngine.getError()); if(errorMessage != null){ LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Texture Constructor[from bufferedimage]: " + errorMessage)); } } } /** * Generates a texture based on a buffer (for use passing data to gpu) * @param buffer The buffer of data * @param width the 'width' of the 'texture' * @param height the 'height' of the 'texture' */ public Texture(OpenGLState openGlState, ByteBuffer buffer, int width, int height){ if(!Globals.HEADLESS){ //generate the texture object on gpu this.texturePointer = glGenTextures(); //bind the new texture openGlState.glBindTexture(GL_TEXTURE_2D, texturePointer); //how are we gonna wrap the texture?? this.setWrap(openGlState, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); this.setWrap(openGlState, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); //disable mipmap this.setMinFilter(openGlState, GL_LINEAR); //call if width != height so opengl figures out how to unpack it properly glPixelStorei(GL_UNPACK_ALIGNMENT, 4); //GL_RED = 32bit r value //buffer the texture information this.pixelFormat = GL_RED; this.datatype = GL_FLOAT; GL40.glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, width, height, 0, GL_RED, GL_FLOAT, buffer); //check build status String errorMessage = RenderingEngine.getErrorInEnglish(Globals.renderingEngine.getError()); if(errorMessage != null){ LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Texture Constructor[from bytebuffer]: " + errorMessage)); } } } /** * Binds the texture to unit 0 * @param openGLState The opengl state */ public void bind(OpenGLState openGLState){ if(texturePointer == -1){ LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Tring to bind a texture object that has not been initialized yet")); } if(texturePointer == 0){ LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Trying to bind texture object that has texturepointer of 0")); } // openGLState.glActiveTexture(GL_TEXTURE0); // openGLState.glBindTexture(GL_TEXTURE_2D, texturePointer); openGLState.glBindTextureUnit(GL_TEXTURE0,this.texturePointer,GL_TEXTURE_2D); } /** * Binds the texture * @param openGLState The opengl state * @param attrib_val The texture unit number */ public void bind(OpenGLState openGLState, int attrib_val){ if(texturePointer == -1){ LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Tring to bind a texture object that has not been initialized yet")); } if(texturePointer == 0){ LoggerInterface.loggerRenderer.ERROR(new IllegalStateException("Trying to bind texture object that has texturepointer of 0")); } openGLState.glBindTextureUnit(GL_TEXTURE0 + attrib_val,this.texturePointer,GL_TEXTURE_2D); Globals.renderingEngine.checkError(); // openGLState.glActiveTexture(GL_TEXTURE0 + attrib_val); // openGLState.glBindTexture(GL_TEXTURE_2D, texturePointer); } /** * Checks if the texture has transparency or not * @return true if transparent, false otherwise */ public boolean isTransparent(){ return hasTransparency; } /** * Gets the path of the texture * @return The path */ public String getPath(){ return path; } /** * Gets the pointer of the texture * @return The pointer */ public int getTexturePointer(){ return texturePointer; } /** * Sets the wrap strategy of the texture * @param wrapDir The direction to wrap * @param wrapType The type of wrapping to perform */ public void setWrap(OpenGLState openGlState, int wrapDir, int wrapType){ //TODO: store wrap type for the direction in this object openGlState.glBindTexture(GL_TEXTURE_2D,texturePointer); glTexParameteri(GL_TEXTURE_2D, wrapDir, wrapType); } /** * Sets the border color * @param borderColor The color (must be 4 floats) */ public void setBorderColor(OpenGLState openGlState, float borderColor[]){ this.borderColor = borderColor; openGlState.glBindTexture(GL_TEXTURE_2D,texturePointer); glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); } /** * Sets the min filter * @param minFilter The min filter */ public void setMinFilter(OpenGLState openGlState, int minFilter){ this.minFilter = minFilter; openGlState.glBindTexture(GL_TEXTURE_2D,texturePointer); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); } /** * Sets the max filter * @param maxFilter The max filter */ public void setMagFilter(OpenGLState openGlState, int maxFilter){ this.maxFilter = maxFilter; openGlState.glBindTexture(GL_TEXTURE_2D,texturePointer); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, maxFilter); } /** * Specifies a 2d image * @param width The width of the image * @param height The height of the image * @param format The format of the pixels (ie GL_RGB, GL_RGBA, etc) * @param datatype The data type of a single component of a pixel (ie GL_BYTE, GL_UNSIGNED_INT, etc) */ public void glTexImage2D(OpenGLState openGLState, int width, int height, int format, int datatype){ //store provided values this.width = width; this.height = height; this.pixelFormat = format; this.datatype = datatype; //static values going into call int level = 0; int border = 0; //this must be 0 according to docs openGLState.glBindTexture(GL_TEXTURE_2D,texturePointer); GL40.glTexImage2D(GL_TEXTURE_2D, level, format, width, height, border, format, datatype, MemoryUtil.NULL); } /** * Specifies a 2d image * @param width The width of the image * @param height The height of the image * @param format The format of the pixels (ie GL_RGB, GL_RGBA, etc) * @param datatype The data type of a single component of a pixel (ie GL_BYTE, GL_UNSIGNED_INT, etc) * @param data The data to populate the image with */ public void glTexImage2D(OpenGLState openGLState, int width, int height, int format, int datatype, ByteBuffer data){ //store provided values this.width = width; this.height = height; this.pixelFormat = format; this.datatype = datatype; //static values going into call int level = 0; int border = 0; //this must be 0 according to docs openGLState.glBindTexture(GL_TEXTURE_2D,texturePointer); GL40.glTexImage2D(GL_TEXTURE_2D, level, format, width, height, border, format, datatype, data); } /** * Gets the width of the texture * @return The width */ public int getWidth(){ if(pixelFormat == -1){ throw new IllegalStateException( "The width of the texture you are trying to query from has not been set yet." + " The texture was likely constructed by passing the opengl texture pointer into the texture object." ); } return width; } /** * Gets the height of the texture * @return The height */ public int getHeight(){ if(pixelFormat == -1){ throw new IllegalStateException( "The height of the texture you are trying to query from has not been set yet." + " The texture was likely constructed by passing the opengl texture pointer into the texture object." ); } return height; } /** * Gets the format of the pixels * @return The format of the pixels (ie GL_RGBA, GL_RGB, etc) */ public int getFormat(){ if(pixelFormat == -1){ throw new IllegalStateException( "The pixel format of the texture you are trying to query from has not been set yet." + " The texture was likely constructed by passing the opengl texture pointer into the texture object." ); } return pixelFormat; } /** * Gets the datatype of the pixels * @return The datatype (IE GL_FLOAT, GL_BYTE, etc) */ public int getDataType(){ if(datatype == -1){ throw new IllegalStateException( "The datatype of the texture you are trying to query from has not been set yet." + " The texture was likely constructed by passing the opengl texture pointer into the texture object." ); } return datatype; } /** * Gets for errors with the texture * @param state The opengl state */ public void checkStatus(OpenGLState state){ this.bind(state); int errorCode = Globals.renderingEngine.getError(); if(errorCode != GL40.GL_NO_ERROR){ switch(errorCode){ case GL40.GL_INVALID_VALUE: { if(this.width < 0){ LoggerInterface.loggerRenderer.ERROR("Texture has width less than 0", new IllegalStateException("Texture has width less than 0")); } if(this.width > state.getMAX_TEXTURE_WIDTH()){ LoggerInterface.loggerRenderer.ERROR("Texture is greater width than environment allows", new IllegalStateException("Texture is greater width than environment allows")); } } break; } } } @Override public String toString(){ String rVal = "" + "Texture[" + "path=\"" + path + "\", " + "texturePointer=\"" + texturePointer + "\", " + "width=\"" + width + "\", " + "height=\"" + height + "\", " + "]" ; return rVal; } }