NPOT Textures and glTexImage2D

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

If I uncomment the lines with //UNCOMMENT TO FIX THE SHEARING, I get what I expect as in this image:

A simple idea:

Make an image in photoshop or something, then using the built in texture classes in com.sun.opengl.util.texture, use TextureIO.newTextureData(File file, boolean mipmap, String fileSuffix) to load the texture. Then, look at the data that got loaded into the TextureData’s buffer and see how it’s arrangement compares to yours. Good luck!

You need to use glPixelStorei function (see OpenGL manual for more details).

The exact one to use is glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1) which means that dimensions have to be multiples of 1. For some reason, by default the alignment is 4, which is why you were experiencing the original problem (and with power-of-two textures is generally unnoticeable).

All,

Thank you for your replies - fantastic - that is indeed the fix.

I can’t imagine I would ever have found this on my own, so your replies are greatly appreciated.

Rob