Hello,
I’m creating some non-power-of-two textures procedurally, and then creating the texture using the following call:
gl.glTexImage2D(GL.GL_TEXTURE_2D,
0,
GL.GL_RGB,
w,
h,
0,
GL.GL_RGB,
GL.GL_UNSIGNED_BYTE,
bb);
Where ‘bb’ is the ByteBuffer holding the data.
I was expecting that I should simply lay the data out in the byte buffer as bytes as follows: R,G,B,R,G,B,… but what I’m finding is that I’m having to ensure that the data for each row starts on a four byte boundary, meaning that if w is not a multiple of 4 (as it can be, since I’m using NPOT textures), the texture appears sheared when rendered.
Is this what I should expect ? I’m running on an nVidia Quadro FX 350M, and I can’t tell if this is a driver bug or not.
The following program illustrates the shearing:
package rob.jogltest;
import java.nio.ByteBuffer;
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import javax.swing.JFrame;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
public class JoglTest extends GLJPanel implements GLEventListener {
private int[] texture1;
private float size = 30.0f;
private float alpha = 1.0f;
private static final int SIZE = 255;
public JoglTest() {
super(new GLCapabilities());
addGLEventListener(this);
}
public void init(GLAutoDrawable glad) {
GL gl = glad.getGL();
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
if (!gl.isExtensionAvailable("GL_ARB_texture_non_power_of_two")) {
System.out.println("Need NPOT Textures!");
System.exit(1);
}
}
public void displayChanged(GLAutoDrawable glad, boolean modeChanged, boolean deviceChanged) {
}
public void display(GLAutoDrawable glad) {
GL gl = glad.getGL();
GLU glu = new GLU();
int clearBits = 0;
if (true) {
clearBits |= GL.GL_DEPTH_BUFFER_BIT;
}
if (clearBits != 0) {
gl.glClear(clearBits);
}
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glLoadIdentity();
render(gl, glu);
gl.glFlush();
}
public void reshape(GLAutoDrawable glad, int i, int x, int width, int height) {
GL gl = glad.getGL();
GLU glu = new GLU();
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glLoadIdentity();
if (true) {
double aspectRatio = (double) width / (double) height;
glu.gluPerspective(45.0, aspectRatio, 1.0, 400.0);
} else {
gl.glOrtho(0.0, width, height, 0.0, -100.0, 100.0);
}
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glLoadIdentity();
}
private void renderFace(GL gl, int[] t, float w, float h) {
int imgw = SIZE;
int imgh = SIZE;
if (imgw > imgh) {
h *= ((float) imgh) / imgw;
} else {
w *= ((float) imgw) / imgh;
}
float w2 = w / 2f;
float h2 = h / 2f;
//t.enable();
//t.bind();
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glBindTexture(GL.GL_TEXTURE_2D, texture1[0]);
gl.glColor4f(alpha, alpha, alpha, alpha);
gl.glBegin(GL.GL_QUADS);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(-w2, h2, 0f);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(w2, h2, 0f);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(w2, -h2, 0f);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(-w2, -h2, 0f);
gl.glEnd();
//t.disable();
}
public void initTexture1(GL gl) {
BufferedImage bi = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_RGB);
Graphics g = bi.getGraphics();
g.setColor(Color.RED);
g.fillRect(0, 0, SIZE, SIZE);
g.setColor(Color.BLUE);
g.fillRect(5, 5, SIZE - 10, SIZE - 10);
g.setColor(Color.WHITE);
g.setFont(new Font(Font.MONOSPACED, Font.BOLD, 30));
g.drawString("Hello", 50, 50);
texture1 = new int[1];
gl.glGenTextures(1, texture1, 0);
gl.glBindTexture(GL.GL_TEXTURE_2D, texture1[0]);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
int w = bi.getWidth();
int h = bi.getHeight();
ByteBuffer bb = convertToByteBuffer(bi);
bb.rewind();
gl.glTexImage2D(GL.GL_TEXTURE_2D,
0, // Mipmap level
//GL.GL_COMPRESSED_RGB, // internal format
GL.GL_RGB, // internal format
w,
h,
0, // border
GL.GL_RGB, // supplied format
GL.GL_UNSIGNED_BYTE,
bb);
}
public void render(GL gl, GLU glu) {
if (texture1 == null) {
initTexture1(gl);
}
if (alpha == 0f) {
return;
}
glu.gluLookAt(0, 10, 90, 0, 0, 0, 0, 1, 0);
float size2 = size * 0.75f;
if (alpha <= 1.0f) {
// enable blending, using the SrcOver rule
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
}
// use the GL_MODULATE texture function to effectively multiply
// each pixel in the texture by the current alpha value (this controls
// the opacity of the cube)
gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);
// front
gl.glPushMatrix();
gl.glTranslatef(0f, 0f, size2);
renderFace(gl, texture1, size, size);
gl.glPopMatrix();
if (alpha <= 1.0f) {
gl.glDisable(GL.GL_BLEND);
}
}
private ByteBuffer convertToByteBuffer(BufferedImage image) {
ByteBuffer ret = null;
int[] data = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
int h = image.getHeight();
int w = image.getWidth();
int www = roundUpToMultipleOfFour(w);
ret = ByteBuffer.allocateDirect(h * www * 3);
System.out.println("data size is " + data.length);
System.out.println("bi width is " + w);
System.out.println("bi height is " + h);
int tindex = 0;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
//tindex = 3 * (y * w + x);
int sindex = y * w + x;
int val = data[sindex];
int blue = val & 0xff;
int green = (val >> 8) & 0xff;
int red = (val >> 16) & 0xff;
ret.put(tindex++, (byte) red);
ret.put(tindex++, (byte) green);
ret.put(tindex++, (byte) blue);
}
// UNCOMMENT TO FIX THE SHEARING
// while ((tindex % 4) != 0) {
// tindex++;
// }
}
return ret;
}
private int roundUpToMultipleOfFour(int num) {
if ((num % 4) != 0) {
num = num + 4 - (num % 4);
}
return num;
}
public static void main(String[] args) {
JFrame frame = new JFrame("JOGL Texture Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JoglTest());
frame.pack();
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}