Possible bug wt GLSL access of GL_TEXTURE_2D on Geforce 8

I believe I can demonstrate a bug with the GeForce 8800 GTX/PCI/SSE2 renderer, version 2.1.2 (ForceWare 169.25, on Vista). I wish very much to be wrong, since then it will be within my control to fix . Please take class below, and let me know of results of trying to reproduce on same or different hardware.

First, like the JOGL Texture class, I wish to dynamically use OpenGL 2.0 features when detected. For textures, that means using GL_TEXTURE_2D textures, instead of GL_TEXTURE_RECTANGLE_ARB.

I am using GLSL, so doing this also requires that the source for the shader be generated on the fly. This is not an obstacle, since I am already doing this to do things like specify sizes of arrays.

My self contained JOGL class, Texture2DProblem, takes a boolean argument isNPO2, which sets 3 members that control which type of texture to use. A PBuffer is created, but a framebuffer is used to all rendering / retrieving results from. This is what the program does in init():

  • Creates a fragment shader, which returns it’s X gl_TexCoord & the value from a texture read using it’s coordinate.
  • Builds a texture, containing the offset from the beginning in each of it’s 4 elements.
  • Associates that texture with the sampler in the shader
  • Creates a frame buffer with single attachment, mapped to above texture

The display() method, renders a 1 row quad, and displays a row of X coord & texture value.

When using GL_TEXTURE_RECTANGLE_ARB everything works as expected. The GL_TEXTURE_2D version gets the X coord right, the texture lookup returns the first texel, or the last texel. Is there a way this could not be a bug?

FYI, this class is meant to be as straight forward as possible. Not included but also checked was:

  • Using older ARG version of shaders produces the same results.
  • Using a PBuffer by itself also produces the same results.

This is my output::
`results of GL_TEXTURE_RECTANGLE_ARB:

uniform sampler2DRect texNm;
void main() {
vec4 tCoords = floor(gl_TexCoord[0]);
vec4 texture = texture2DRect(texNm, vec2(tCoords) );
texture.x = tCoords.x;
gl_FragColor = texture;
}
Frag X coord : 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0
Texture Value: 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0

results of GL_TEXTURE_2D:

uniform sampler2D texNm;
void main() {
vec4 tCoords = floor(gl_TexCoord[0]);
vec4 texture = texture2D(texNm, vec2(tCoords) );
texture.x = tCoords.x;
gl_FragColor = texture;
}
Frag X coord : 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0
Texture Value: 0.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0`

Class:

import java.nio.*;
import javax.media.opengl.*;
import javax.media.opengl.glu.GLU; 
import com.sun.opengl.util.BufferUtil;

public class Texture2DProblem implements GLEventListener{
    public static void main(String[] argv) throws Exception{
        System.out.println("\nresults of GL_TEXTURE_RECTANGLE_ARB:  ");
        System.out.println("============================");
        Texture2DProblem card = new Texture2DProblem(false);        
        card.pBuffer.display();
        card.pBuffer.destroy();
        
        System.out.println("\nresults of GL_TEXTURE_2D:  ");
        System.out.println("============================");
        card = new Texture2DProblem(true);        
        card.pBuffer.display();
        card.pBuffer.destroy();
    }
    final int       textureTarget;
    final String    texUniform;
    final String    texFunc;
    final GLPbuffer pBuffer;

    static final int WDITH = 10; // changing this, changes the amount of screw-up
    static final int HEIGHT = 1;
    
    static final int TEX_UNIT_NO = 0; // texture units sequential, so offset
    static final int FBO_ATTACHMENT = GL.GL_COLOR_ATTACHMENT0_EXT;
    
    public Texture2DProblem(boolean isNPO2){
        if (isNPO2) {
            textureTarget = GL.GL_TEXTURE_2D;
            texUniform = " sampler2D ";
            texFunc = "texture2D";
        } else {
            textureTarget = GL.GL_TEXTURE_RECTANGLE_ARB;
            texUniform = " sampler2DRect ";
            texFunc = "texture2DRect";
        }
        
        GLCapabilities caps = new GLCapabilities();
        caps.setDoubleBuffered(false);

        // using fbo for all rendering, so get minimum sized pbuffer
        pBuffer = GLDrawableFactory.getFactory().createGLPbuffer(caps, null, 1, 1, null);         
        // not thread safe to let 'this' escape in constructor, do not do in production
        pBuffer.addGLEventListener(this);
    }
    public void init(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();
//        gl = new TraceGL(gl, System.err);
        
        int shaderId = createFragmentShader(gl,
                "uniform " + texUniform + " texNm;" +
                "\nvoid main() {" +
                "\n    vec4 tCoords = floor(gl_TexCoord[0]);" +
                "\n    vec4 texture = " + texFunc + "(texNm, vec2(tCoords) );" +
                "\n    texture.x = tCoords.x;" +
                "\n    gl_FragColor = texture;"+
                "\n}",
                true);
        
        int prgmId = createAttachLinkUseProgram(gl, new int[]{shaderId});
        
        // load identity texture, and associate with sampler & bind to texture unit
        int texId = loadTexture(gl, get4ElementIdentityBuffer(WDITH, HEIGHT) );   
        associateTexture(gl, prgmId, TEX_UNIT_NO, "texNm", texId);
        
        // create and assign FBO to above texture,  can get away with only 1 fbo,
        // since only reading the same texel as writing
      	int[] fb = new int[1];
        gl.glGenFramebuffersEXT(1, fb, 0);
 	gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, fb[0]);
    	gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT, FBO_ATTACHMENT, textureTarget, texId, 0);
    }
    public void display(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();
//        gl = new TraceGL(gl, System.err);
        
        renderQuad(gl, FBO_ATTACHMENT, WDITH, HEIGHT);
        
        // copy Attachment to a float buffer
        FloatBuffer buf = BufferUtil.newFloatBuffer(4 * WDITH * HEIGHT);        
        gl.glReadBuffer (FBO_ATTACHMENT);
        gl.glReadPixels(0, 0, WDITH, HEIGHT, GL.GL_RGBA, GL.GL_FLOAT, buf);
        
        // read thru float buffer and display results
        String coord = "Frag X coord :  ";
        String value = "Texture Value:  ";
        float[] pix = new float[4];
        for (int w = 0; w < WDITH; w++) {
            buf.get(pix);
            coord += ((w> 0) ? ", " : "") + pix[0];
            value += ((w> 0) ? ", " : "") + pix[1];
        }
        System.out.println(coord);
        System.out.println(value);
    }
    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {}
    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {} 

    int createFragmentShader(GL gl, String source, boolean display){ 
        int shaderID = gl.glCreateShader(GL.GL_FRAGMENT_SHADER); 

        String[] srcArray = source.split("\n");
        int count = srcArray.length;

        int[] lengths = new int[count];
        for (int i = 0; i < count; i++) {
            if (display) System.out.println(srcArray[i]);
            lengths[i] = srcArray[i].length();
        }
        gl.glShaderSource(shaderID, count, srcArray, lengths, 0);
        gl.glCompileShader(shaderID);

        return shaderID;
    }
    int createAttachLinkUseProgram(GL gl, int[] shaderIDs){
        // create the program
        int prgmID = gl.glCreateProgram();
        
        // attach each of the shaders
        for(int i = 0; i < shaderIDs.length; i++){
             gl.glAttachShader(prgmID, shaderIDs[i] );
        }
        
        //link & use the program
        gl.glLinkProgram(prgmID);
        gl.glUseProgram(prgmID); 
        
        return prgmID;
   }
 
    int loadTexture(GL gl, FloatBuffer buf){        
        int[] texID = new int[1];
        // create a new texture name
        gl.glGenTextures(1, texID, 0);

        // bind the texture name to a texture target
        gl.glBindTexture(textureTarget, texID[0]);
        gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
        gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
        gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP);
        gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP);

        gl.glTexImage2D(textureTarget, 0, GL.GL_RGBA32F_ARB, WDITH, HEIGHT, 0, GL.GL_RGBA, GL.GL_FLOAT, buf);
        gl.glEnable(textureTarget);

        return texID[0];
        }
     void associateTexture(GL gl, int prgmID, int unitNo, String samplerNm, int textureID) {        
        int sampler = gl.glGetUniformLocation(prgmID, samplerNm);
        gl.glUniform1i(sampler, unitNo);

        gl.glActiveTexture(GL.GL_TEXTURE0 + unitNo);
        gl.glBindTexture(textureTarget, textureID );
    }
     FloatBuffer get4ElementIdentityBuffer(int width, int height) {
        int nTexels = width * height;
        int tVals = 4 * nTexels;
        FloatBuffer buffer = BufferUtil.newFloatBuffer(tVals);

        for (int i = 0; i < nTexels; i++)
            buffer.put(new float[] {i, i, i, i} );

        buffer.rewind();
        return buffer;
    }
     void renderQuad(GL gl, int buffer, int width, int height){
        gl.glViewport(0, 0, width, height); 
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        (new GLU()).gluOrtho2D(0, width, 0, height);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
         
        // assign buffer to render
        gl.glDrawBuffer(buffer);
        
        // ensure Texture unit 0 has the coordinates, explicitly
        gl.glBegin(GL.GL_QUADS);
        gl.glTexCoord2i(0    , 0     ); gl.glVertex2i(0    , 0     ); 
        gl.glTexCoord2i(width, 0     ); gl.glVertex2i(width, 0     ); 
        gl.glTexCoord2i(width, height); gl.glVertex2i(width, height); 
        gl.glTexCoord2i(0    , height); gl.glVertex2i(0    , height); 
        gl.glEnd();
    }
}

It isn’t obvious what the problem is from your description. Regardless, I don’t think there’s any bug in JOGL here. I suspect it’s either a bug in your code / shader or in the OpenGL driver. JOGL does no interposition or anything else with any of these calls.

I’ve only a quick moment to look through your code, so I may be mistaken but I believe the issue you’re seeing is in the texture coordinates. Texture rectangles use coordinates between 0 and the resolution in that dimension and don’t support mirroring, wrapping, repeating. Otherwise texture coordinates need to be between 0 and 1, and values beyond that range are clamped, wrapped, repeated, or mirrored depending on the state set by glTexParameteri().

So you might want to double check that the coordinates are correct for the appropriate mode.

Actually yes, that looks to be exactly the problem. Frag X coord in the non-rectangle case should actually be 0.0, 0.1, 0.2, 0.3, … 1.0

[quote]This is my output::
results of GL_TEXTURE_RECTANGLE_ARB:

uniform sampler2DRect texNm;
void main() {
vec4 tCoords = floor(gl_TexCoord[0]);
vec4 texture = texture2DRect(texNm, vec2(tCoords) );
texture.x = tCoords.x;
gl_FragColor = texture;
}
Frag X coord : 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0
Texture Value: 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0

results of GL_TEXTURE_2D:

uniform sampler2D texNm;
void main() {
vec4 tCoords = floor(gl_TexCoord[0]);
vec4 texture = texture2D(texNm, vec2(tCoords) );
texture.x = tCoords.x;
gl_FragColor = texture;
}
Frag X coord : 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0
Texture Value: 0.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0
[/quote]

Chris,

Thanks! I guess, I created unnecessary problems in trying to do some future proofing of my app. I had not found any info which contrasts all of the various texture targets, in enough detail, in a way I understood, for me to spot this. GL_TEXTURE_RECTANGLE_ARB is not even mentioned in Redbook…

I need exact texels from my texture lookups, so I’ll just use GL_TEXTURE_RECTANGLE_ARB.