1
votes

I have a simple OpenGL application where I'm drawing coastline data. The data is a long list of polygons (about 41000) where each polygon is a list of (x,y) points. The polygons are all different lengths (since some coastlines are longer than others). Now, using a display list this is trivial to draw - this is Python code but I'm sure you get the gist:

self.coastlines_dl = GL.glGenLists(1)
GL.glNewList(self.coastlines_dl, GL.GL_COMPILE)
for poly in coastline_polys:
    GL.glBegin(GL.GL_LINE_STRIP)
        for point in poly:
            GL.glVertex3f(point[0], point[1], 0.0)
    GL.glEnd()
GL.glEndList()

and then during the render loop:

GL.glCallList(self.coastlines_dl)

This performs very well.

My question is: how do I do the equivalent using VBOs? My initial approach, since I've never used VBOs before and just wanted to get it working, was to create a VBO for each polygon, and then draw each VBO in the render loop, something like:

for v in self.vbos:
    v.bind()
    GL.glEnableClientState(GL.GL_VERTEX_ARRAY)
    GL.glVertexPointerf(v)
    GL.glDrawArrays(GL.GL_LINE_STRIP, 0, v.data.shape[0])
    GL.glDisableClientState(GL.GL_VERTEX_ARRAY)
    v.unbind()

This works, in that it draws the data, but the framerate is appalling, which is not surprising since I'm binding and drawing 41000+ times per frame.

So can I put all my polys into one big VBO and just do one bind and one glDrawArrays? What I don't understand is how glDrawArrays would know how many points are in each polygon, since it's different for every one?

Apologies if this is very trivial, but I keep reading that anything I can do with display lists can be done with VBOs, but I can't find an example that shows what I need to do.


Edit 1 - In Light of Slow VBO Performance, Here's My Platform details

Windows 7 64 bit
i5-2430M
GPU is just onboard Intel HD Graphics 3000
4GB RAM

Application is Python OpenGL. Python version is 2.7.9 64 bit, with PyOpenGL 3.1.0 amd64, and using a QT QGLWidget via PySide 1.2.2 amd64.

Here's a dump from gDEBugger:

General
-------
OpenGL Version           3.1
Shading Language Version 1.40  - Intel Build 8.15.10.2345
Is Backward-Compatible Yes
Is Forward-Compatible No
Is Debug Context Yes
Renderer Vendor Intel
Renderer Name Intel(R) HD Graphics Family
Renderer Version 3.1.0 - Build 8.15.10.2345
Renderer Type Installable client
Marked for Deletion      No
OpenGL Sharing Contexts  None

Pixel Format Information
------------------------
Pixel Format ID          9
Hardware Acceleration    Full
Double-Buffered          Yes
Stereographic            No
Native Rendering         No

Channel                  Bits
Red                      8
Green                    8
Blue                     8
Alpha                    8
Depth                    24
Stencil                  8
Accumulation             64
4
What is your target? State of the art graphics chips? Or anything made in past 5+ years? As stated in another answer there are better features in newer gl versions but you will be limited to newer nvidia and amd cards - djgandy
More like anything made in the past 5+ years I think. Like version 3.1 onwards? - lemondifficult

4 Answers

3
votes

You should probably not be making a VBO for each polygon. That will be terribly slow and is not what the driver will be doing behind the scenes in your first implementation. You should have a single VBO (perhaps a few chunked VBOs when you are optimising) with all the vertex data you need. You then index into the VBO to draw each primitive. So your glDrawArrays will start using the second parameter 'first'.

Also there is no need to enable and disable ClientState each time in the loop, doing so may be causing the driver to perform unnecessary validation

Please excuse my lack of python-gl knowledge but it would pseudo look something like this.

v.bind()
GL.glEnableClientState(GL.GL_VERTEX_ARRAY)
GL.glVertexPointerf(v)
for v in self.vbos:
    GL.glDrawArrays(GL.GL_LINE_STRIP, vbo_offset_goes_here, v.data.shape[0])
GL.glDisableClientState(GL.GL_VERTEX_ARRAY)
v.unbind()

This is still going to be suboptimal too because it will require a lot of drawcalls, and drawcalls can be fairly costly. But it should be a simple incremental improvement from your example. After that you can try glMultiDrawArrays which should enable you to remove the loop entirely since you only have a single primitive type.

2
votes

glDrawArrays takes an offset and a number-of-vertices arguments. Do you see where this is going? And then there's glMultiDrawArrays to batch a large number of drawing batches.

So here's what you do: Put all your coastline data into one large VBO. For each polygon / line loop determine the offset and the number of vertices and append them to arrays. Finally you bind the VBO and call glMultiDrawArrays passing the offset/vertexcount arrays as parameters and the number of elements in the arrays passed (which will be your 41000 figure).

2
votes

You haven't exolicitely stated which OpenGL version you are targeting, so I'm going to suggest more modern alternatives to the answers already posted so far:

  1. Using the primitive restart feature: You well need indexed rendering for that, and simply can define a special index which will end the current line strip and start a new one. Using this technique, you can draw the whole data with a single draw call. However, you will need an index buffer which you did not need before. And it is very unlikely that you will be able to share vertcies in your scenario, so you won't gain much by using it. Primtive Restart is in GL since version 3.1, so it is reasonable to use it nowadays.

  2. As an extension to datenwolf's answer: use GL_ARB_multi_draw_indirect (in OpenGL since 4.3). The difference to glMultiDrawArrays is that also the array of draw parameters is kept in a buffer object, so that the draw call does not depend on client memory at all any more, which is more efficient. Since this feature is only available on quite modern GPUs, you probably want to still use glMultiDrawArrays and optionally provide a code path for the indirect variant if it is available.

0
votes

OK, once I knew to search for glMultiDrawArrays, based on the suggestions here, I found this:

Python glMultiDrawArrays / VBO example

which is more or less doing what I want.

Here's what I'm doing now: firstly, setting up the VBO:

# In this example 'self.polys' is a list of polygons, where each polygon is a
# list of tuples, so something like:
# [[(30.0, 30.0, 0.0), (30.0, -30.0, 0.0), (0.0, -30.0, 0.0),
#   (0.0, 30.0, 0.0), (30.0, 30.0, 0.0)],
#  [(-30.0, 20.0, 0.0), (-25.0, 0.0, 0.0), (-35.0, 0.0, 0.0),
#   (-30.0, 20.0, 0.0)],
#  [(-40.0, -40.0, 0.0), (-30.0, -40.0, 0.0), (-30.0, -50.0, 0.0),
#   (-40.0, -40.0, 0.0)]]

# self.counts holds an integer for each polygon, giving its length, so
# [5, 4, 4] to continue our example
self.counts = [len(poly) for poly in self.polys]

# Creates an array of cumulative values of each element in self.counts,
# so array([5, 9, 13]) to continue our example:
tmp_first = numpy.array(self.counts).cumsum()
# Turn this into a list of the indexes of the start of each poly. The first
# one starts at zero, obviously, and in our example the second one starts
# at 5, and the second one at 9, so in our example self.first here ends up
# as [0, 5, 9]:
self.first = list(numpy.hstack((numpy.array([0]), tmp_first[:-1])))

# Now 'flatten' our polygon list of lists into a single list of points, so
# that to continue our example we end up with something like:
# [(30.0, 30.0, 0.0), (30.0, -30.0, 0.0), (0.0, -30.0, 0.0),
#  (0.0, 30.0, 0.0), (30.0, 30.0, 0.0), (-30.0, 20.0, 0.0),
#  (-25.0, 0.0, 0.0), (-35.0, 0.0, 0.0), (-30.0, 20.0, 0.0),
#  (-40.0, -40.0, 0.0), (-30.0, -40.0, 0.0), (-30.0, -50.0, 0.0),
#  (-40.0, -40.0, 0.0)]
polys_unwrapped = [item for sublist in polys for item in sublist]
# Convert this flattened list into a numpy array, and use to create our VBO:
self.single_vbo = vbo.VBO(numpy.array(polys_unwrapped, dtype=numpy.float32),
                          usage='GL_STATIC_DRAW')

And then in the render loop I do this:

self.single_vbo.bind()
GL.glEnableClientState(GL.GL_VERTEX_ARRAY)
GL.glVertexPointerf(self.single_vbo)
GL.glMultiDrawArrays(GL.GL_LINE_STRIP, self.first, self.counts,
                     len(self.counts))
GL.glDisableClientState(GL.GL_VERTEX_ARRAY)
self.single_vbo.unbind()

And that's it. It works, but the framerate is much worse than the Display List example. For reference I'm getting about 4.4FPS doing it this way, compared to about 48FPS using the Display List - obviously this is very hardware dependent, but the relative difference is huge (this is just using onboard Intel HD Graphics 3000).