avic's picture

Simple VBO Troubles

Hi,
I'm new to opengl and opentk and I'm trying to write a program which takes in data from a device that does a scan of a sample and dynamically renders the data the device collects as a 3d image as the data is collected. To do this I figured I need to use vertex buffer objects so I'm trying to write a little test program hacked together from all the tutorials I can find online. I'm writing in vb .net.

So the test program draws a pyramid on the screen and using the method of rotation from the opentk tutorial (rotate on application.idle) it's supposed to rotate the pyramid. I had this working all right when I used just vertex arrays. But when I switched over to using a vertex buffer object, the rotation speed slowed down drastically, to the point where you can barely see the pyramid moving. Does anyone know what I've done wrong?

Here is how I define my pyramid (it's in form load):

 
vertexArray = New Double() {0, 1, 0, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1}

Note: I haven't done anything with color at all...there's just one vbo and that's for the vertices...so it's just a white pyramid.

Here is my render function:

Private Sub GlControl1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles GlControl1.Paint
        If Not loaded Then
            Return
        End If
 
 
        GL.Clear(ClearBufferMask.ColorBufferBit Or ClearBufferMask.DepthBufferBit)
 
        GL.MatrixMode(MatrixMode.Modelview)
        GL.LoadIdentity()
        GL.Translate(x, 0, -0.5) 
        GL.Rotate(rotation, 1, 1, 0)
        GL.GenBuffers(1, buffer)
        GL.BindBuffer(BufferTarget.ArrayBuffer, buffer(0))
        GL.BufferData(BufferTarget.ArrayBuffer, vertexArray.Length * 8, vertexArray, BufferUsageHint.StreamDraw)  'size of double = 8
        GL.VertexPointer(3, VertexPointerType.Double, 0, IntPtr.Zero)
        GL.EnableClientState(EnableCap.VertexArray)
        GL.DrawArrays(BeginMode.TriangleFan, 0, 6)
        GL.DisableClientState(EnableCap.VertexArray)
        GL.BindBuffer(BufferTarget.ArrayBuffer, 0)
        GlControl1.SwapBuffers()
    End Sub

This is the rotate event handler. It's pretty much copy pasted from the opentk tutorial:

Private Sub rotate()
        sw.Stop()
        Dim milliseconds = sw.Elapsed.Milliseconds
        sw.Reset()
        sw.Start()
        Dim deltaRotation = milliseconds / 20.0
        rotation += deltaRotation
        GlControl1.Invalidate()
    End Sub

And I also have the translate keypress event handler from the tutorial:

Private Sub GlControl1_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles GlControl1.KeyDown
        If e.KeyCode = Keys.Space Then
            x += 0.01
            GlControl1.Invalidate()
        End If
    End Sub

Thanks for your help!


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
kvark's picture

You seem like creating the buffer and filling it with data each frame. This causes huge slowdown. And you don't need to enable/disable of VertexArray anymore. Just move the init functionality (GenBuffers & BufferData) out of the paint event handler.

Inertia's picture

What kvark said. Additionaly, if you want to repeatedly update an existing VBO at runtime, either use:

  1. GL.BufferSubData(), this is rather efficient because you may only update relevant portions instead of the whole buffer.
  2. Call GL.BufferData() twice, the first call with null or IntPtr.Zero as parameters (tells the driver to invalidate the buffer once it's not used for drawing anymore) and a second call to send the new data (driver will allocate the new storage regardless whether the old data is still in use).
avic's picture

Thank you very much!

I moved as much of the init code out of the paint subroutine but this didn't seem to help at all. The paint subroutine now only contains these lines:

Private Sub GlControl1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles GlControl1.Paint
        If Not loaded Then
            Return
        End If
 
 
        GL.Clear(ClearBufferMask.ColorBufferBit Or ClearBufferMask.DepthBufferBit)
 
        GL.MatrixMode(MatrixMode.Modelview)
        GL.LoadIdentity()
        GL.Translate(x, 0, -0.5)
        GL.Rotate(rotation, 1, 1, 0)
        GL.BindBuffer(BufferTarget.ArrayBuffer, buffer(0))
        GL.DrawArrays(BeginMode.TriangleFan, 0, 6)
        GL.BindBuffer(BufferTarget.ArrayBuffer, 0)
        GlControl1.SwapBuffers()
    End Sub

And these lines are called once at initialization:

        GL.GenBuffers(1, buffer)
        GL.BindBuffer(BufferTarget.ArrayBuffer, buffer(0))
        GL.BufferData(BufferTarget.ArrayBuffer, vertexArray.Length * 8, vertexArray, BufferUsageHint.StreamDraw)
        GL.VertexPointer(3, VertexPointerType.Double, 0, IntPtr.Zero)
        GL.EnableClientState(EnableCap.VertexArray)

I got the same results as before though. I put in a timer control that would update the rotation value and invalidate the glcontrol just to see if that would work and it actually did. Also, it worked equally well regardless of whether the unnecessary initialization code was in the paint subroutine or not and that seemed weird to me. What am I missing? When you use vertex buffers, does something change with when the application deems itself to be idle? Did I not move the right code out of the paint routine? At this point I'm just curious.

Also thanks a lot for the information about updating a VBO. That will be very helpful for when I'm writing my real program.

the Fiddler's picture

This line of code:

GL.Rotate(rotation, 1, 1, 0)

is framerate dependent. In other words, the more times you call Paint(), the faster your pyramid will rotate.

To maintain a stable rotation speed, multiply rotation with the elapsed time between two consecutive Paint events. For best results, calculate the elapsed time using a System.Diagnostics.Stopwatch and make sure to reset the Stopwatch every frame:

Dim watch As New System.Diganostics.Stopwatch()
 
Private Sub GlControl1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles GlControl1.Paint
        If Not loaded Then
            Return
        End If
 
        double elapsed = watch.Elapsed.TotalSeconds
        watch.Reset()
        watch.Start()
        ...
        ' 'rotation' should define the rotational speed measured in degrees / sec
        ' if you wish to maintain a constant rotational speed, make sure 'rotation' does not change!
        GL.Rotate(rotation * elapsed, 1, 1, 0)
        ...
        GlControl1.SwapBuffers()
    End Sub
avic's picture

That didn't seem to fix the problem either. Isn't that kind of what I was doing anyway earlier in the Application.Idle event handler? (The rotate() sub from my first post...I guess I forgot to say that sw was my stopwatch.) Like I said, that was pretty much lifted from the openTK introduction tutorial.

Basically my question comes down to this: Am I using the vertex buffer object correctly in my current code? The program worked just fine when I used immediate mode and nothing bad happened when I switched over to using vertex arrays. The fact that the animation speed slowed to a halt when I switched over to VBOs (which as I understand are the preferred method) tells me that I'm doing something wrong. Am I? Or is this some weirdness with the application.idle event? So to reiterate, is this the correct way to use a vertex buffer object? Or am I not including something that I should be? Thanks again for all your help.

the Fiddler's picture

As long as you update the position according to the frame time, it should work.

What *is* your frametime? Could it be that you are getting so high framerates (low frametimes) that you are hitting into a precision limit? I encountered that on Linux with Ati GL3 drivers. Try adding a call to System.Threading.Thread.CurrentThread.Sleep(1) into your paint event and see if that helps.

avic's picture

Whoa. The Thread.Sleep worked like magic. What happened there? Why did I need to put in a sleep when using a VBO and not when I was using vertex arrays or immediate mode? Was it because using a vbo was so fast that the application idle times became too small for the application to handle? If that's the case then all I can say is, sweeeeeeeeeeeet.

the Fiddler's picture

It's one of two things:

  • either the framerate became so high that precision suffered
  • or rotation speed increased along with the framerate, to the point where its apparent speed slowed down (the same effect happens in real life: watch a wheel gain in speed. Every little while, it will appear to slow down, stop and start rotating in the opposite direction).

Best solution: enable vsync for fluid-smooth animation (GraphicsContext.VSync = true).

avic's picture

Cool. Solved. I enabled vsync for the glcontrol rather than with GraphicsContext. Is there a difference?

the Fiddler's picture

No, GLControl.VSync is there for convenience (it calls GraphicsContext.VSync internally).