Search code examples
c#openglopentk

glDrawElements doesn't draw the second time it's called, using the same vertex array object and the same shader program


I'm very new to opengl and I'm getting this really unexpected behavior.

I'm implementing a basic scene graph and trying to add the ability to reuse shaders and mesh data.

I have a GeometryData object which contains the references to the vertex array object, vertex buffer, and index buffer. It has a render method which draws the model using glDrawElements.

The GeometryData has a reference to a Shader object, which has a reference to the program shader handle.

The problem is that if I render consecutive objects that have the same shader instance, only the first one is rendered, and the others are not for some reason, with no errors. If I alternate shader assignments between consecutive object draws, all of them are drawn normally.

The Render method of my GeometryData is as follows:

    public override void Render()
    {
        GL.BindVertexArray(vertexArray);

        if (UseShader != null)
            UseShader.Use();

        var err = GL.GetError();

        GL.DrawElements(PrimitiveType.Triangles, numIndices, DrawElementsType.UnsignedInt, IntPtr.Zero);

        err = GL.GetError();
        GL.BindVertexArray(0);
    }

The UseShader.Use() method is this

    public void Use()
    {
        int current = GL.GetInteger(GetPName.CurrentProgram);

        if (current != ProgramHandle) 
            GL.UseProgram(ProgramHandle);

        // Use a uniform block for the project and modelview matrices
        var index = GL.GetUniformBlockIndex(ProgramHandle, "Matrices");
        GL.UniformBlockBinding(ProgramHandle, index, World.BINDING_POINT);

        // assume interleaved vertex data
        GL.VertexAttribPointer(positionLoc, 3, VertexAttribPointerType.Float, false, 2 * Vector3.SizeInBytes, Vector3.SizeInBytes);
        GL.EnableVertexAttribArray(positionLoc);

        GL.VertexAttribPointer(colorLoc, 3, VertexAttribPointerType.Float, false, 2 * Vector3.SizeInBytes, 0);
        GL.EnableVertexAttribArray(colorLoc);
    }

Here are some screenshots. The boxes are created using this loop:

 for (int i = -3; i <= 3; i+=3)
 {
     Transform child = new Transform();
     child.Translation = new Vector3(i, 0, 0);

     // this causes the second red box to not render
     Geometry geom = new Geometry(i > 0 ? coloredShader : redShader, box); 


     // this causes all boxes to render normally
     // Geometry geom = new Geometry(i == 0 ? coloredShader : redShader, box);

     child.addChild(geom);

     root.addChild(child);
 }

Picture with alternating shaders (red, colored, red) Picture with alternating shaders (red, colored, red)

Picture with two consecutive objects using the same shader (red, red, colored). The second red object is not drawn. The only change I made to the scene is shader assignments.

Picture with two consecutive object using the same shader (red, red, colored)

Update

After further inspection and debugging, it seems the source of the problem is the uniform block. I'm using a uniform block to send the projection and modelview matrices to my shaders. I suspect that the second call to glBufferSubData to update the modelview matrix is not updating the uniform, or that the shader is not reading that value from the uniform without switching to another shader first.

I tried taking the modelview matrix out of the uniform block and sending it to each shader separately. That made all objects render correctly, but I still would like to use a uniform block for that to avoid redundant uploads.


Solution

  • Solved! This was really silly. I needed to call glFlush() after updating the uniform block value using glBufferSubData.

        public static void setModelView(ref Matrix4 modelView)
        {
            GL.BindBuffer(BufferTarget.UniformBuffer, uniformBuffer);
            GL.BufferSubData(BufferTarget.UniformBuffer, (IntPtr)(16 * sizeof(float)), (IntPtr)(16 * sizeof(float)), ref modelView);
            GL.Flush(); // I just added this line
            GL.BindBuffer(BufferTarget.UniformBuffer, 0);
        }