title: OpenGL basics: calling glDrawElements once per object
body: <p>continuing on from my explorations of the basics of OpenGL (see <a href="https://stackoverflow.com/questions/4618892/basic-opengl-vertex-buffers-and-pyglet">this question</a>), I'm trying to figure out the basic principles of drawing a scene with OpenGL.</p>
  
  <p>I am trying to render a simple cube repeated n times in every direction.</p>
  
  <p>My method appears to yield terrible performance : 1000 cubes brings performance below 50fps (on a QuadroFX 1800, roughly a GeForce 9600GT).</p>
  
  <p>My method for drawing these cubes is as follows:</p>
  
  <p>done once:</p>
  
  <ul>
  <li>set up a vertex buffer and array buffer containing my cube vertices in model space</li>
  <li>set up an array buffer indexing the cube for drawing as 12 triangles</li>
  </ul>
  
  <p>done for each frame:</p>
  
  <ul>
  <li>update uniform values used by the vertex shader to move all cubes at once</li>
  </ul>
  
  <p>done for each cube, for each frame:</p>
  
  <ul>
  <li>update uniform values used by the vertex shader to move each cube to its position</li>
  <li>call glDrawElements to draw the positioned cube</li>
  </ul>
  
  <p>Is this a sane method ? If not, how does one go about something like this ? I'm guessing I need to minimize calls to glUniform, glDrawElements, or both, but I'm not sure how to do that.</p>
  
  <hr>
  
  <p>Full code for my little test : (depends on <a href="http://codeflow.org/entries/2009/jul/31/gletools-advanced-pyglet-utilities/" rel="nofollow noreferrer">gletools</a> and pyglet)</p>
  
  <p>I'm aware that my init code (at least) is really ugly; I'm concerned with the rendering code for each frame right now, I'll move to something a little less insane for the creation of the vertex buffers and such later on.</p>
  
  <pre><code>import pyglet
  from pyglet.gl import *
  from pyglet.window import key
  from numpy import deg2rad, tan
  from gletools import ShaderProgram, FragmentShader, VertexShader, GeometryShader
  
  vertexData = [-0.5, -0.5, -0.5, 1.0,
                -0.5, 0.5, -0.5, 1.0,
                0.5, -0.5, -0.5, 1.0,
                0.5, 0.5, -0.5, 1.0,
                -0.5, -0.5, 0.5, 1.0,
                -0.5, 0.5, 0.5, 1.0,
                0.5, -0.5, 0.5, 1.0,
                0.5, 0.5, 0.5, 1.0]
  
  elementArray = [2, 1, 0, 1, 2, 3,## back face
                  4, 7, 6, 4, 5, 7,## front face
                  1, 3, 5, 3, 7, 5,## top face
                  2, 0, 4, 2, 4, 6,## bottom face
                  1, 5, 4, 0, 1, 4,## left face
                  6, 7, 3, 6, 3, 2]## right face
  
  def toGLArray(input):
      return (GLfloat*len(input))(*input)
  
  def toGLushortArray(input):
      return (GLushort*len(input))(*input)
  
  def initPerspectiveMatrix(aspectRatio = 1.0, fov = 45):
      frustumScale = 1.0 / tan(deg2rad(fov) / 2.0)
      fzNear = 0.5
      fzFar = 300.0
      perspectiveMatrix = [frustumScale*aspectRatio, 0.0         , 0.0                            , 0.0 ,
                           0.0                     , frustumScale, 0.0                            , 0.0 ,
                           0.0                     , 0.0         , (fzFar+fzNear)/(fzNear-fzFar)  , -1.0,
                           0.0                     , 0.0         , (2*fzFar*fzNear)/(fzNear-fzFar), 0.0 ]
      return perspectiveMatrix
  
  class ModelObject(object):
      vbo = GLuint()
      vao = GLuint()
      eao = GLuint()
      initDone = False
  
      verticesPool = []
      indexPool = []
  
      def __init__(self, vertices, indexing):
          super(ModelObject, self).__init__()
          if not ModelObject.initDone:
              glGenVertexArrays(1, ModelObject.vao)
              glGenBuffers(1, ModelObject.vbo)
              glGenBuffers(1, ModelObject.eao)
              glBindVertexArray(ModelObject.vao)
              initDone = True
          self.numIndices = len(indexing)
          self.offsetIntoVerticesPool = len(ModelObject.verticesPool)
          ModelObject.verticesPool.extend(vertices)
          self.offsetIntoElementArray = len(ModelObject.indexPool)
          ModelObject.indexPool.extend(indexing)
  
          glBindBuffer(GL_ARRAY_BUFFER, ModelObject.vbo)
          glEnableVertexAttribArray(0) #position
          glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0)
          glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ModelObject.eao)
          glBufferData(GL_ARRAY_BUFFER, len(ModelObject.verticesPool)*4, toGLArray(ModelObject.verticesPool), GL_STREAM_DRAW)
          glBufferData(GL_ELEMENT_ARRAY_BUFFER, len(ModelObject.indexPool)*2, toGLushortArray(ModelObject.indexPool), GL_STREAM_DRAW)
  
      def draw(self):
          glDrawElements(GL_TRIANGLES, self.numIndices, GL_UNSIGNED_SHORT, self.offsetIntoElementArray)
  
  
  class PositionedObject(object):
      def __init__(self, mesh, pos, objOffsetUf):
          super(PositionedObject, self).__init__()
          self.mesh = mesh
          self.pos = pos
          self.objOffsetUf = objOffsetUf
  
      def draw(self):
          glUniform3f(self.objOffsetUf, self.pos[0], self.pos[1], self.pos[2])
          self.mesh.draw()
  
  
  
  w = 800
  h = 600
  AR = float(h)/float(w)
  window = pyglet.window.Window(width=w, height=h, vsync=False)
  window.set_exclusive_mouse(True)
  pyglet.clock.set_fps_limit(None)
  
  ## input
  forward = [False]
  left = [False]
  back = [False]
  right = [False]
  up = [False]
  down = [False]
  inputs = {key.Z: forward, key.Q: left, key.S: back, key.D: right,
            key.UP: forward, key.LEFT: left, key.DOWN: back, key.RIGHT: right,
            key.PAGEUP: up, key.PAGEDOWN: down}
  
  ## camera
  camX = 0.0
  camY = 0.0
  camZ = -1.0
  
  def simulate(delta):
      global camZ, camX, camY
      scale = 10.0
      move = scale*delta
      if forward[0]:
          camZ += move
      if back[0]:
          camZ += -move
      if left[0]:
          camX += move
      if right[0]:
          camX += -move
      if up[0]:
          camY += move
      if down[0]:
          camY += -move
  pyglet.clock.schedule(simulate)
  
  @window.event
  def on_key_press(symbol, modifiers):
      global forward, back, left, right, up, down
      if symbol in inputs.keys():
          inputs[symbol][0] = True
  
  @window.event
  def on_key_release(symbol, modifiers):
      global forward, back, left, right, up, down
      if symbol in inputs.keys():
          inputs[symbol][0] = False
  
  
  ## uniforms for shaders
  camOffsetUf = GLuint()
  objOffsetUf = GLuint()
  perspectiveMatrixUf = GLuint()
  camRotationUf = GLuint()
  
  program = ShaderProgram(
      VertexShader('''
      #version 330
      layout(location = 0) in vec4 objCoord;
      uniform vec3 objOffset;
      uniform vec3 cameraOffset;
      uniform mat4 perspMx;
      void main()
      {
          mat4 translateCamera = mat4(1.0f, 0.0f, 0.0f, 0.0f,
                                      0.0f, 1.0f, 0.0f, 0.0f,
                                      0.0f, 0.0f, 1.0f, 0.0f,
                                      cameraOffset.x, cameraOffset.y, cameraOffset.z, 1.0f);
          mat4 translateObject = mat4(1.0f, 0.0f, 0.0f, 0.0f,
                                      0.0f, 1.0f, 0.0f, 0.0f,
                                      0.0f, 0.0f, 1.0f, 0.0f,
                                      objOffset.x, objOffset.y, objOffset.z, 1.0f);
  
          vec4 modelCoord = objCoord;
          vec4 positionedModel = translateObject*modelCoord;
          vec4 cameraPos = translateCamera*positionedModel;
  
          gl_Position = perspMx * cameraPos;
      }'''),
      FragmentShader('''
      #version 330
      out vec4 outputColor;
      const vec4 fillColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
  
      void main()
      {
          outputColor = fillColor;
      }''')
  )
  
  
  shapes = []
  def init():
      global camOffsetUf, objOffsetUf
      with program:
          camOffsetUf = glGetUniformLocation(program.id, "cameraOffset")
          objOffsetUf = glGetUniformLocation(program.id, "objOffset")
          perspectiveMatrixUf = glGetUniformLocation(program.id, "perspMx")
          glUniformMatrix4fv(perspectiveMatrixUf, 1, GL_FALSE, toGLArray(initPerspectiveMatrix(AR)))
  
          obj = ModelObject(vertexData, elementArray)
          nb = 20
          for i in range(nb):
              for j in range(nb):
                  for k in range(nb):
                      shapes.append(PositionedObject(obj, (float(i*2), float(j*2), float(k*2)), objOffsetUf))
  
          glEnable(GL_CULL_FACE)
          glCullFace(GL_BACK)
          glFrontFace(GL_CW)
          glEnable(GL_DEPTH_TEST)
          glDepthMask(GL_TRUE)
          glDepthFunc(GL_LEQUAL)
          glDepthRange(0.0, 1.0)
          glClearDepth(1.0)
  
  def update(dt):
      print pyglet.clock.get_fps()
  pyglet.clock.schedule_interval(update, 1.0)
  
  @window.event
  def on_draw():
      with program:
          pyglet.clock.tick()
          glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
          glUniform3f(camOffsetUf, camX, camY, camZ)
  
          for shape in shapes:
              shape.draw()
  
  
  init()
  pyglet.app.run()
  </code></pre>