2

As usual when I try to get my fingers wet with modern OpenGL, using one of the clever demos I can find on some blogs, something goes wrong.

Expected behavior: Draw a triangle and the colors should be interpolated between the 3 vertices.

Found behavior: Triangle is red. And it does not matter which colors I write into the color array.

Code first (sorry 160 lines - modern OpenGL is spammy...).

open System
open System.Drawing
open System.Collections.Generic

open OpenTK
open OpenTK.Graphics
open OpenTK.Graphics.OpenGL
open OpenTK.Input

module Shaders =
    let vertexShader = 
        """#version 330

in vec3 vPosition;
in  vec3 vColor;
out vec4 color;
uniform mat4 modelview;

void
main()
{
    gl_Position = modelview * vec4(vPosition, 1.0);

    color = vec4( vColor, 1.0);
}
"""
    let fragmentShader = 
        """#version 330

in vec4 color;
out vec4 outputColor;

void
main()
{
  outputColor = color;
}
"""
    let initShaders () : int =
        let makeShader shaderType src =
            let sh = GL.CreateShader(shaderType)
            GL.ShaderSource(sh,src)
            GL.CompileShader(sh)
            sh
        let pgmId = GL.CreateProgram()
        let vsh = makeShader ShaderType.VertexShader vertexShader
        let fsh = makeShader ShaderType.FragmentShader fragmentShader
        GL.AttachShader(pgmId,vsh)
        GL.AttachShader(pgmId,fsh)
        GL.LinkProgram(pgmId)
        pgmId

let failMinusOne = function
    | -1 -> failwith "Something is -1 which should not be -1!"
    | x -> x

type Game(width,height) =
    inherit GameWindow(width, height, GraphicsMode.Default, "F# OpenTK Sample")
    do base.VSync <- VSyncMode.On

    let mutable shaderProgramId = -1
    let mutable attribute_vcol = -1
    let mutable attribute_vpos = -1
    let mutable uniform_mview = -1

    let mutable vbo_col = 0
    let mutable vbo_pos = 0

    let vertex_data = 
        [|
            Vector3(-0.8f, -0.8f, 0.f)
            Vector3( 0.8f, -0.8f, 0.f)
            Vector3( 0.f,  0.8f, 0.f)
        |]

    let col_data =
        [|
            Vector3( 1.f, 1.f, 1.f)
            Vector3( 0.f, 0.f, 1.f)
            Vector3( 0.f, 1.f, 0.f)
        |]

    let mview_data = [| Matrix4.Identity |]

    /// <summary>Load resources here.</summary>
    /// <param name="e">Not used.</param>
    override o.OnLoad e =
        base.OnLoad(e)
        o.Title <- "Hello OpenTK!"
        shaderProgramId <- Shaders.initShaders ()

        GL.ClearColor(Color.CornflowerBlue)
        GL.Enable(EnableCap.DepthTest)

        attribute_vpos <- GL.GetAttribLocation(shaderProgramId, "vPosition") |> failMinusOne 
        attribute_vcol <- GL.GetAttribLocation(shaderProgramId, "vColor") |> failMinusOne
        uniform_mview <- GL.GetUniformLocation(shaderProgramId, "modelview") |> failMinusOne

        vbo_col <- GL.GenBuffer()
        vbo_pos <- GL.GenBuffer()

    /// <summary>
    /// Called when your window is resized. Set your viewport here. It is also
    /// a good place to set up your projection matrix (which probably changes
    /// along when the aspect ratio of your window).
    /// </summary>
    /// <param name="e">Not used.</param>
    override o.OnResize e =
        base.OnResize e
        GL.Viewport(base.ClientRectangle.X, base.ClientRectangle.Y, base.ClientRectangle.Width, base.ClientRectangle.Height)
        let projection = Matrix4.CreatePerspectiveFieldOfView(float32 (Math.PI / 4.), float32 base.Width / float32 base.Height, 1.f, 64.f)
        GL.MatrixMode(MatrixMode.Projection)
        GL.LoadMatrix(ref projection)


    /// <summary>
    /// Called when it is time to setup the next frame. Add you game logic here.
    /// </summary>
    /// <param name="e">Contains timing information for framerate independent logic.</param>
    override o.OnUpdateFrame e =
        base.OnUpdateFrame e
        if base.Keyboard.[Key.Escape] then base.Close()
        else
            GL.BindBuffer(BufferTarget.ArrayBuffer,vbo_col)
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer,Array.length col_data * Vector3.SizeInBytes,col_data,BufferUsageHint.StaticDraw)
            GL.VertexAttribPointer(attribute_vcol, 3, VertexAttribPointerType.Float, false, 0, 0)

            GL.BindBuffer(BufferTarget.ArrayBuffer,vbo_pos)
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer,Array.length vertex_data * Vector3.SizeInBytes,vertex_data,BufferUsageHint.StaticDraw )
            GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0)

            GL.UniformMatrix4(uniform_mview,false,ref mview_data.[0])
            GL.BindBuffer(BufferTarget.ArrayBuffer,0)

            GL.UseProgram(shaderProgramId)

    /// <summary>
    /// Called when it is time to render the next frame. Add your rendering code here.
    /// </summary>
    /// <param name="e">Contains timing information.</param>
    override o.OnRenderFrame(e) =
        base.OnRenderFrame e
        GL.Clear(ClearBufferMask.ColorBufferBit ||| ClearBufferMask.DepthBufferBit);
        GL.EnableVertexArrayAttrib(attribute_vpos,0)
        GL.EnableVertexArrayAttrib(attribute_vcol,0)

        GL.DrawArrays(PrimitiveType.Triangles,0,Array.length vertex_data)

        GL.DisableVertexArrayAttrib(attribute_vpos,0)
        GL.DisableVertexArrayAttrib(attribute_vcol,0)

        GL.Flush()
        base.SwapBuffers()

[<EntryPoint>]
let main argv = 
    use game = new Game(800,600)
    do game.Run(30.,30.)
    0 // return an integer exit code

After a few hours trying to find what is going wrong I ran out of ideas. Adding more triangles also seems to flop. But then, the fact that the triangle shows as the vertices suggest, makes me think, the download of the data to the gpu is okay-ish.

But for you guys who do that day in day out, it will probably be easy to spot where things go wrong.

BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • 1
    This works for me, on NVidia desktop, after changing code syntax to current OTK version. This could be the typical, hellishly buggy OpenGL support. Check on which GPUs, drivers, and OTK versions this happens. Be prepared that modern OpenGL code can display five different results on five "typical" computers. Especially older AMD and Intel drivers can so buggy that calling modern OpenGL "supported" is borderline fraudulent. What GPU, driver version, and OpenTK version are you using? – Vandroiy Jan 25 '17 at 11:18
  • 1
    My OpenTK is the current nuget package I got only yesterday (version 2.0.0). My NVIDIA GPU is a bit older (GTX 650). 3D apps usually run with the usual shaky reliability of contemporary 3D driver buggyness and instabilities. I have no idea what OTK is (I referenced OpenTK.dll and nothing else). Driver version: 21.21.13.7290 (think it comes with windows 10 updates) – BitTickler Jan 25 '17 at 11:32
  • 1
    Oh! My bad, apparently the 2.0 upgrade removed a deprecated warning, so I wrongly guessed you were on an older version. But I was. The error seems to be with the vertex array activation index; give me a moment to answer. – Vandroiy Jan 25 '17 at 11:48
  • I spent quite a while trying to find documentation of that second parameter (which appears to be new). I toyed with it to no effect. – BitTickler Jan 25 '17 at 11:55

1 Answers1

4

You are using an OpenGL 4.5 function, EnableVertexArrayAttrib, to enable your vertex attrib array on vertex array object (VAO) with ID 0. This is peculiar, since your shaders are versioned as 330, which is much, much older, but more importantly invalid, since you aren't using any VAO.

You can enable/disable the vertex attrib arrays the classical way, like this:

GL.EnableVertexAttribArray(attribute_vpos)
GL.EnableVertexAttribArray(attribute_vcol)

GL.DisableVertexAttribArray(attribute_vpos)
GL.DisableVertexAttribArray(attribute_vcol)

This results in the properly colored triangle on my NVidia GTX 760 desktop, because it acts on the currently active vertex array information.

I would advise to have a second look at the way you are using the state machine. Enabling vertex arrays and defining their structure via VertexAttribPointer is program-specific and belongs together. Usually, you'd use a VAO to group this information and unbind it after drawing finishes. If the vertex attrib arrays are supposed to be global state, there's no point in disabling them, and this fact should be well-documented. As is, the code is in danger of developing spurious interactions between seemingly unrelated functions, because it's sharing complex state of the OpenGL state machine.

A possible approach would be:

  • Setup

    • Create shaders, keep shader program handle and attrib locations

    • Create buffers, initialize data, then save their handles and leave them unbound

    • Create vertex array structure as VAO, then save its handle and leave it unbound

      • For each vertex array, enable and specify (e.g. VertexAttribPointer)
  • Drawing

    • Bind VAO
    • Bind changing/additional buffers (if necessary)
    • Bind program
    • Draw call
    • Unbind everything again (to fail fast if something wrongly relies on OpenGL state)

Approaches like this group the interaction with the state machine in a more structured way, and reduce reliance of your program on OpenGL state machine values remaining unchanged.

There's no need for the various mutable values and their initialization to -1. They can be bound in order, directly assigning the correct handle (assuming the OpenGL context is already present; see comments.)

Vandroiy
  • 6,163
  • 1
  • 19
  • 28
  • Yes that was the problem. Thanks a lot! Now I see what I expected to see! – BitTickler Jan 25 '17 at 12:03
  • As for the the straight let bindings... I was not sure if I could do that, given that maybe before OnLoad() is called, opengl might not even be initialized. Also, at that point the shader program is not created yet. – BitTickler Jan 25 '17 at 12:48
  • 1
    @BitTickler I *think* the context should be there; it should be created with the GameWindow and if I remember construction order correctly, this should work. Though I don't use inheritance, but instead use GameWindow events to interact with it, which is nicer for encapsulation. Sadly, the OpenTK documentation is in a rather chaotic state. The shader program can be created first, if you need to read indices into it. If I change the definition to `let shaderProgramId = Shaders.initShaders ()` in your code, it works fine. – Vandroiy Jan 25 '17 at 13:12