Hm okay, so it should be the same in JOGL as in C if I understand you correctly… But then is my implementation of images problematic ? How could an image be stored like this in OpenGL, so that they can be created and used “on the fly” and not be statically loaded ? I mean I just don’t know how I would implement it if not by a texture, I’ve already tried with a BufferedImage but due to the conversions from Java to OpenGL objects this is very slow and it uses about the same amount of textures anyways…
And I’ve also been stuck on this issue for about 2 months now, tried about 3 different image architectures, this was the first relatively fast one, and that worked perfectly, this is the only issue left. :’(
Just in case this could help find if I did something wrong.
My current image implementation
This is SImage.java
package lib;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.File;
import java.io.IOException;
import java.nio.IntBuffer;
import javax.imageio.ImageIO;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
public class SImage {
SApplet context;
private boolean initialized;
private int texture;
public int pixels[];
public int width;
public int height;
protected SImage(SApplet context, int width, int height, IntBuffer pixelRaster) {
this.initialized = false;
this.width = width;
this.height = height;
pixels = new int[width * height];
this.context = context;
init(pixelRaster);
}
protected SImage(SApplet context, int width, int height) {
this.initialized = false;
this.width = width;
this.height = height;
pixels = new int[width * height];
this.context = context;
init();
}
protected SImage(SApplet context) {
this.initialized = false;
width = 0;
height = 0;
this.context = context;
}
protected SImage(SApplet context, int texture) {
this.initialized = false;
width = 0;
height = 0;
this.context = context;
init();
loadTexture(texture);
}
@Override
public void finalize() {
try {super.finalize();}
catch (Throwable e) {e.printStackTrace();}
System.out.println("DELETING TEXTURE ID : ["+texture+"]");
IntBuffer b = IntBuffer.allocate(1);
b.put(this.texture);
this.context.gl.glDeleteTextures(1, b);
}
protected void loadTexture(int tex) {
if(!this.initialized) init();
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, tex);
IntBuffer buff = IntBuffer.allocate(1);
this.context.gl.glGetTexLevelParameteriv(GL.GL_TEXTURE_2D, 0, GL2.GL_TEXTURE_WIDTH, buff);
width = buff.get(0);
buff.clear();
this.context.gl.glGetTexLevelParameteriv(GL.GL_TEXTURE_2D, 0, GL2.GL_TEXTURE_HEIGHT, buff);
height = buff.get(0);
IntBuffer pixelRaster = IntBuffer.allocate(width * height);
this.context.gl.glGetTexImage(GL.GL_TEXTURE_2D, 0, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE, pixelRaster);
pixels = pixelRaster.array();
updatePixels();
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
}
protected void init(IntBuffer pixelRaster) {
IntBuffer tex = IntBuffer.allocate(1);
this.context.gl.glGenTextures(1, tex);
texture = tex.get(0);
System.out.println("USING TEXTURE ID (init wt buff) : ["+texture+"]");
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, texture);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_BASE_LEVEL, 0);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAX_LEVEL, 0);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP);
this.context.gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL2.GL_RGBA8, width, height, 0, GL2.GL_BGRA, GL2.GL_UNSIGNED_BYTE, pixelRaster);
this.initialized = true;
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
}
protected void init() {
IntBuffer dat = IntBuffer.allocate(width * height);
IntBuffer tex = IntBuffer.allocate(1);
this.context.gl.glGenTextures(1, tex);
texture = tex.get(0);
System.out.println("USING TEXTURE ID (init no buff) : ["+texture+"]");
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, texture);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_BASE_LEVEL, 0);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAX_LEVEL, 0);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP);
this.context.gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL2.GL_RGBA8, width, height, 0, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE, dat);
this.initialized = true;
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
}
protected int getTexture() {
if(!this.initialized) init();
return this.texture;
}
public void resize(int width, int height) {
if(!this.initialized) init();
this.width = Math.max(1, width);
this.height = Math.max(1, height);
pixels = new int[this.width * this.height];
IntBuffer b = IntBuffer.allocate(1);
b.put(texture);
this.context.gl.glDeleteTextures(0, b);
init();
}
public void loadPixels() {
if(!this.initialized) init();
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, texture);
IntBuffer pixelRaster = IntBuffer.allocate(width * height);
this.context.gl.glGetTexImage(GL.GL_TEXTURE_2D, 0, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE, pixelRaster);
pixels = pixelRaster.array();
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
}
public void updatePixels() {
if(!this.initialized) init();
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, texture);
this.context.gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA8, width, height, 0, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE, IntBuffer.wrap(pixels));
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
}
public void save(String filename) {
if(!this.initialized) init();
loadPixels();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
final int[] a = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
System.arraycopy(pixels, 0, a, 0, pixels.length);
File file = new File(filename);
String[] sections = filename.split("\\.");
BufferedImage img;
if(sections[sections.length - 1].equals("jpg") ||
sections[sections.length - 1].equals("jpeg") ||
sections[sections.length - 1].equals("JPG") ||
sections[sections.length - 1].equals("JPEG")) {
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
img.createGraphics().drawImage(image, 0, 0, null);
}
else if(sections[sections.length - 1].equals("bmp") ||
sections[sections.length - 1].equals("BMP")){
img = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
img.createGraphics().drawImage(image, 0, 0, null);
}
else if(sections[sections.length - 1].equals("wbmp") ||
sections[sections.length - 1].equals("WBMP")){
img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
img.createGraphics().drawImage(image, 0, 0, null);
}
else {
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
img.createGraphics().drawImage(image, 0, 0, null);
}
img.createGraphics().finalize();
try {
ImageIO.write(img, sections[sections.length - 1], file);
} catch (IOException e) {
e.printStackTrace();
}
}
public SImage copy() {
if(!this.initialized) init();
return new SImage(this.context, texture);
}
private float getXTex(SImage img, float x) {
return (x+0.5f) / (float) img.width;
}
private float getYTex(SImage img, float y) {
return (y + 0.5f) / (float) img.height;
}
private float getXScreen(float x) {
return 2f * (x+0.5f) / (float) context.width - 1f;
}
private float getYScreen(float y) {
return 2f * (y + 0.5f) / (float) context.height - 1f;
}
public void copy(float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh) {
if(!this.initialized) init();
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, texture);
IntBuffer pixelRaster = IntBuffer.allocate(width * height);
this.context.gl.glGetTexImage(GL.GL_TEXTURE_2D, 0, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE, pixelRaster);
IntBuffer texBuffer = IntBuffer.allocate(1);
this.context.gl.glGenTextures(1, texBuffer);
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, texBuffer.get(0));
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_BASE_LEVEL, 0);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAX_LEVEL, 0);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP);
this.context.gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP);
this.context.gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL2.GL_RGBA8, width, height, 0, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE, pixelRaster);
//context.getTextureRenderer() RETURNS THE ID OF A STATIC FBO, GENERATED ON init() THAT SHOULD BE USED FOR IMAGE MANIPULATION
this.context.gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, context.getTextureRenderer());
this.context.gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, texture, 0);
this.context.gl.glDrawBuffer(GL2.GL_COLOR_ATTACHMENT0);
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, texBuffer.get(0));
float sx0 = getXTex(this, sx);
float sy0 = getYTex(this, sy);
float sx1 = getXTex(this, sx + sw);
float sy1 = getYTex(this, sy + sh);
float dx0 = getXScreen(dx);
float dy0 = getYScreen(dy);
float dx1 = getXScreen(dx + dw);
float dy1 = getYScreen(dy + dh);
this.context.gl.glColor4f(1f, 1f, 1f, 1f);
this.context.gl.glBegin(GL2.GL_QUADS);
this.context.gl.glTexCoord2f(sx0, sy0);
this.context.gl.glVertex2f(dx0, dy0);
this.context.gl.glTexCoord2f(sx0, sy1);
this.context.gl.glVertex2f(dx0, dy1);
this.context.gl.glTexCoord2f(sx1, sy1);
this.context.gl.glVertex2f(dx1, dy1);
this.context.gl.glTexCoord2f(sx1, sy0);
this.context.gl.glVertex2f(dx1, dy0);
this.context.gl.glEnd();
this.context.gl.glFlush();
this.context.gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, 0, 0);
this.context.gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
this.context.gl.glDeleteTextures(1, texBuffer);
this.context.gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
this.context.gl.glDrawBuffer(GL.GL_FRONT_AND_BACK);
}
public void copy(SImage src, float sx, float sy, float sw, float sh, float dx, float dy, float dw, float dh) {
if(!this.initialized) init();
if(src.getTexture() == texture) {
copy(sx, sy, sw, sh, dx, dy, dw, dh);
}
else {
//context.getTextureRenderer() RETURNS THE ID OF A STATIC FBO, GENERATED ON init() THAT SHOULD BE USED FOR IMAGE MANIPULATION
this.context.gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, context.getTextureRenderer());
this.context.gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, texture, 0);
this.context.gl.glDrawBuffer(GL2.GL_COLOR_ATTACHMENT0);
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, src.getTexture());
float sx0 = getXTex(src, sx);
float sy0 = getYTex(src, sy);
float sx1 = getXTex(src, sx + sw);
float sy1 = getYTex(src, sy + sh);
float dx0 = getXScreen(dx);
float dy0 = getYScreen(dy);
float dx1 = getXScreen(dx + dw);
float dy1 = getYScreen(dy + dh);
this.context.gl.glColor4f(1f, 1f, 1f, 1f);
this.context.gl.glBegin(GL2.GL_QUADS);
this.context.gl.glTexCoord2f(sx0, sy0);
this.context.gl.glVertex2f(dx0, dy0);
this.context.gl.glTexCoord2f(sx0, sy1);
this.context.gl.glVertex2f(dx0, dy1);
this.context.gl.glTexCoord2f(sx1, sy1);
this.context.gl.glVertex2f(dx1, dy1);
this.context.gl.glTexCoord2f(sx1, sy0);
this.context.gl.glVertex2f(dx1, dy0);
this.context.gl.glEnd();
this.context.gl.glFlush();
this.context.gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
this.context.gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL2.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, 0, 0);
this.context.gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
this.context.gl.glDrawBuffer(GL.GL_FRONT_AND_BACK);
}
}
public SImage get() {
return this;
}
public int get(int x, int y) {
if(x >= 0 && x < width && y >= 0 && y < height) {
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, texture);
IntBuffer pixelRaster = IntBuffer.allocate(width * height);
this.context.gl.glGetTexImage(GL.GL_TEXTURE_2D, 0, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE, pixelRaster);
this.context.gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
return pixelRaster.get(x + y * width);
}
else return SGraphics.color(0, 0, 0);
}
public SImage get(int x, int y, int w, int h) {
IntBuffer initialRaster = IntBuffer.allocate(w * h);
SImage copy = new SImage(this.context, w, h, initialRaster);
float actual_x0 = (float) Math.max(x, 0);
float actual_y0 = (float) Math.max(y, 0);
float actual_x1 = (float) Math.min(x + w, width);
float actual_y1 = (float) Math.min(y + h, height);
float actual_w = actual_x1 - actual_x0;
float actual_h = actual_y1 - actual_y0;
copy.copy(this, actual_x0, actual_y0, actual_w, actual_h, actual_x0 - x, actual_y0 - y, actual_w, actual_h);
return copy;
}
public void set(int x, int y, int c) {
int pixel[] = {c};
this.context.gl.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, x, y, 1, 1, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, IntBuffer.wrap(pixel));
}
public void set(int x, int y, SImage img) {
copy(img, 0, 0, img.width, img.height, x, y, img.width, img.height);
}
protected BufferedImage toImage() {
loadPixels();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
final int[] a = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
System.arraycopy(pixels, 0, a, 0, pixels.length);
return image;
}
}
This is the code that is run at each image (so around 60 times a second)
@Override
public void draw() {
background(255);
SImage im = createImage(1280, 720);
stroke(0);
strokeWeight(2f);
noFill(255, 0, 0);
rect(mouseX, mouseY, 20, 20);
}
And this is the createImage() method
public SImage createImage(int width, int height) {
return new SImage(this, width, height);
}