Unexpected Behavior for Uniform Variable in Shader

Hi Folks,

I am working on a simple shader and am encountering some unexpected behavior that I though one of you might be able to help me with. I have reduced the problem down to the simplest example I can think of.

My fragment shader simply runs a for loop over a specified uniform int (3 in this case) and sets the color for that index to 1.0. This should result in a white fragment. Instead, I get a yellow fragment, as if the passed value were 2 (red + green). I can hard code the value to 3 in the shader and everything works as expected.

As near as I can figure, my problem is one of these:

  1. Uniforms have trouble in for loops.
  2. I am not passing things right (even though I can read the 3 back).
  3. Something is wrong with how I am setting up the uniform variable.
  4. My GL implementation has a bug.

Please have a look if you don’t mind and let me know if you see anything wrong. At a minimum please run the program and post whether you get a white or a yellow rectangle (or something else–that would be bad.).

I tried to self-contain everything. The shader setup code is all in the setupShaderProgram function. The variable in question is “foo”.

Thanks for any help you can provide.

-Mark

import java.awt.BorderLayout;

import javax.media.opengl.*;
import javax.swing.JFrame;

import com.sun.opengl.util.Animator;

public class UniformTest extends JFrame implements GLEventListener
{
    private static final long serialVersionUID = -3280170375188457053L;
    private final GLCanvas canvas = new GLCanvas();
    
    public UniformTest()
    {
        super("UniformTest App");
        
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        final Animator animator = new Animator(canvas);
        
        canvas.addGLEventListener(this);
        this.add(canvas, BorderLayout.CENTER);
        setSize(800, 600);
        
        animator.start();
        
        setVisible(true);
    }

    /* (non-Javadoc)
     * @see javax.media.opengl.GLEventListener#display(javax.media.opengl.GLAutoDrawable)
     */
    public void display(GLAutoDrawable drawable)
    {
        GL gl = drawable.getGL();
        
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
        
        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT | GL.GL_STENCIL_BUFFER_BIT);
        
        //Just draw a square
        gl.glBegin(GL.GL_QUADS);
        gl.glVertex3f(-0.5f, -0.5f, 0.0f);
        gl.glVertex3f( 0.5f, -0.5f, 0.0f);
        gl.glVertex3f( 0.5f,  0.5f, 0.0f);
        gl.glVertex3f(-0.5f,  0.5f, 0.0f);
        gl.glEnd();
    }

    public void init(GLAutoDrawable drawable)
    {
        GL gl = drawable.getGL();

        gl.setSwapInterval(1);

        setupShaderProgram(gl);
    }
    
    private static void setupShaderProgram(GL gl)
    {
        //Just use the fixed function pipeline
        String vs = "void main() { gl_Position = ftransform(); }";
        int shaderIndex = gl.glCreateShader(GL.GL_VERTEX_SHADER);
        gl.glShaderSource(shaderIndex, 1, new String[] { vs }, new int[] { vs.length() }, 0);
        gl.glCompileShader(shaderIndex);
        
        //This is the code block in question.  If you replace "foo" with the
        //number 3 rather than using the uniform variable, you get the expected
        //behavior - A white fragment.  For some reason I get a yellow one
        //(red + green).
        String fs = 
            "uniform int foo;" +
            "void main()" +
            "{" +
            "    int bar = 3;" +
            "    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);" +
            "    for(int i = 0; i < foo; i++)" +
            "    {" +
            "        gl_FragColor[i] = 1.0;" +
            "    }" +
            "}";
        
        int fragmentIndex = gl.glCreateShader(GL.GL_FRAGMENT_SHADER);
        gl.glShaderSource(fragmentIndex, 1, new String[] { fs }, new int[] { fs.length() }, 0);
        gl.glCompileShader(fragmentIndex);
        
        int programIndex = gl.glCreateProgram();
        gl.glAttachShader(programIndex, shaderIndex);
        gl.glAttachShader(programIndex, fragmentIndex);
        gl.glLinkProgram(programIndex);
        gl.glUseProgram(programIndex);
        gl.glValidateProgram(programIndex);
        
        //This should give a white fragment color using the above shader.
        gl.glUniform1i(gl.glGetUniformLocation(programIndex, "foo"), 3);
        
        //Just for grins, make sure that foo is 3
        int[] foo = new int[1];
        gl.glGetUniformiv(programIndex, gl.glGetUniformLocation(programIndex, "foo"), foo, 0);
        System.out.println("foo is returned as " + foo[0]);
    }
    
    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height)
    {
        GL gl = drawable.getGL();  
        
        //Set the projection
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glOrtho(-1.0f, 1.0f, -1.0, 1.0, -1.0, 1.0);
        
        //Go back to MV mode
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }
    
    /**
     * @param args
     */
    public static void main(String[] args)
    {
        Runnable runnable = new Runnable()
        {
            public void run()
            {
                new UniformTest();
            }
        };
        
        new Thread(runnable, "UniformTest").start();
    }
    
    //Unused
    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { }
}

Well, I found another machine to run the sample program on. On that one the result was a whte fragment when using the uniform in the loop (even when reducing the value of foo to 0 or 1). In my further research online the best I can tell is that using uniform variables in for loops is not recommended. I am curious if this is the case with the new NVidia cards (the “Cuda” ones). Both machines I ran the test on are fairly new OpenGL 2.0 cards with all of the desired extensions (The original is a 17" MacBook Pro and the other is some kind of Dell Workstation. I’d have to do a little looking for the exact hardware configuration, but the card should be quite good.).

Anyways, I am still interested in any of your results, comments, or knowledge on this matter.

Works as it should on my computer (linux, ati x1950 pro).
With foo = 0 the result is black, foo = 1 red, foo = 3 white.
Replacing foo with its value in the for condition gives the same results.

Doesn’t gl_FragColor[i] = 1.0; equate to:


gl_FragColor[i] = vec4(1.0, 1.0, 1.0, 1.0);

Im surprised it works at all…

DP


glFragColor[0] == glFragColor.r
glFragColor[1] == glFragColor.g
glFragColor[2] == glFragColor.b
glFragColor[3] == glFragColor.a

The only problem could be that you can set the glFragColor only once on some hardware?

So you might want to replace your code with:


vec4 temp = vec4(0.0, 0.0, 0.0, 1.0);
for(int i=0; i<foo; i++)
   temp[i] = 1.0;
glFragColor = temp;

and see how that works out.

For multiple render targets, you use gl_FragData instead of gl_FragColor

DP

:-\

He clearly doesn’t want multiple render-targets.

But well, it’s a nice tip, if he’d ever go that way :wink:

surely gl_FragColor is an array of vec4 not an array of float and thus looping through it and setting [0] would mean setting the vec4 at index 0?

So if he was to do gl_FragColor[0] = … and gl_FragColor[1] = …, that means he wants MRT

But maybe i need to re-read the specs :slight_smile:

No, I don’t want multiple rendering targets, but thanks for the tip. The point of the code is to debug/understand the use of a uniform variable in a for loop. The test fragment program is a simple example in which the loop turns on each color component of r, g, and b.

Also, unless I am way off, gl_FragColor[i] = 1.0 only sets the index at i to 1.0 and is not the same as gl_FragColor = 1.0 which sets all 4 components to 1.0.