Renderer/src/main/java/electrosphere/renderer/texture/Texture.java
austin 831df3d6af
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
add buffer limit checks to texture construction
2024-08-28 22:57:08 -04:00

524 lines
20 KiB
Java

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