title: Instancing millions of objects in OpenGL: improving frames-per-second
body: <p>My ultimate goal is to render 1 million spheres of different sizes and colors at 60 fps.  I want to be able to move the camera around the screen as well.  </p>
  
  <p>I have modified the code on <a href="http://learnopengl.com/#!Advanced-OpenGL/Instancing" rel="nofollow">this page of the tutorial I am studying</a> to try to instance many spheres.  However, I find that at as little as 64 spheres my fps falls below 60, and at 900 spheres my fps is a measly 4.  My understanding of instancing is naive, but I believe that I should be getting more frames-per-second than this.  60 fps should be attainable with only 64 spheres.  I believe that I am, in some way, causing the CPU and GPU to communicate more often than they should have to.  So my question is: How do I instance so many objects (ideally millions) without causing the fps to fall low (ideally 60 fps)?</p>
  
  <p>I am calculating fps by calculating <code>(10 / time_elapsed)</code> every 10 frames, where <code>time_elapsed</code> is the time that has elapsed since the last fps call.  I am printing this out using <code>printf</code> on line 118 of my code.   </p>
  
  <p>I have been learning OpenGL through <a href="http://www.learnopengl.com/" rel="nofollow">this tutorial</a> and so I use <a href="http://glew.sourceforge.net/" rel="nofollow">32-bit GLEW</a> and <a href="http://www.glfw.org/" rel="nofollow">32-bit GLFW</a> in Visual Studio 2013.  I have 8 GB of RAM on a 64-bit operating system (Windows 7) with a <a href="http://ark.intel.com/products/85213/Intel-Core-i5-5300U-Processor-3M-Cache-up-to-2_90-GHz" rel="nofollow">2.30 GHz CPU</a>.</p>
  
  <p>I have tried coding my own example based on the tutorial above.  Source code:</p>
  
  <p>(set line #2 to be the number of spheres to be instanced. Make sure line#2 has a whole-number square root.  Set line 4 to be the detail of the sphere, the lowest it can go is 0.  Higher number = more detailed.)</p>
  
  <pre><code>// Make sure NUM_INS is a square number
  #define NUM_INS 1
  
  // Detail up to 4 is probably good enough
  #define SPHERE_DETAIL 4
  
  #include &lt;vector&gt;
  
  // GLEW
  #define GLEW_STATIC
  #include &lt;GL/glew.h&gt;
  
  // GLFW
  #include &lt;GLFW/glfw3.h&gt;
  
  // GL includes
  #include "Shader.h"
  
  // GLM Mathemtics
  #include &lt;glm/glm.hpp&gt;
  #include &lt;glm/gtc/matrix_transform.hpp&gt;
  #include &lt;glm/gtc/type_ptr.hpp&gt;
  
  // Properties
  GLuint screenWidth = 800, screenHeight = 600;
  
  // Function prototypes
  void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
  std::vector&lt;GLfloat&gt; create_sphere(int recursion);
  
  // The MAIN function, from here we start our application and run the Game loop
  int main()
  {
    // Init GLFW
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
  
    GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed
    glfwMakeContextCurrent(window);
  
    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);
  
    // Initialize GLEW to setup the OpenGL Function pointers
    glewExperimental = GL_TRUE;
    glewInit();
  
    // Define the viewport dimensions
    glViewport(0, 0, screenWidth, screenHeight);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Comment to remove wireframe mode
  
    // Setup OpenGL options
    glEnable(GL_DEPTH_TEST);
  
    // Setup and compile our shader(s)
    Shader shader("core.vs", "core.frag");
  
    // Generate a list of 100 quad locations/translation-vectors
    std::vector&lt;glm::vec2&gt; translations(NUM_INS);
    //glm::vec2 translations[NUM_INS];
    int index = 0;
    GLfloat offset = 1.0f / (float)sqrt(NUM_INS);
    for (GLint y = -(float)sqrt(NUM_INS); y &lt; (float)sqrt(NUM_INS); y += 2)
    {
      for (GLint x = -(float)sqrt(NUM_INS); x &lt; (float)sqrt(NUM_INS); x += 2)
      {
        glm::vec2 translation;
        translation.x = (GLfloat)x / (float)sqrt(NUM_INS) + offset;
        translation.y = (GLfloat)y / (float)sqrt(NUM_INS) + offset;
        translations[index++] = translation;
      }
    }
  
    // Store instance data in an array buffer
    GLuint instanceVBO;
    glGenBuffers(1, &amp;instanceVBO);
    glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * NUM_INS, &amp;translations[0], GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
  
    // create 12 vertices of a icosahedron
    std::vector&lt;GLfloat&gt; vv = create_sphere(SPHERE_DETAIL);
  
    GLuint quadVAO, quadVBO;
    glGenVertexArrays(1, &amp;quadVAO);
    glGenBuffers(1, &amp;quadVBO);
    glBindVertexArray(quadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
    glBufferData(GL_ARRAY_BUFFER, vv.size() * sizeof(GLfloat), &amp;vv[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
    // Also set instance data
    glEnableVertexAttribArray(2);
    glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glVertexAttribDivisor(2, 1); // Tell OpenGL this is an instanced vertex attribute.
    glBindVertexArray(0);
  
    // For printing frames-per-second
    float counter = 0;
    double get_time = 0;
    double new_time;
  
    // Game loop
    while (!glfwWindowShouldClose(window))
    {
      // Print fps by printing (number_of_frames / time_elapsed)
      counter += 1;
      if (counter &gt; 10) {
        counter -= 10;
        new_time = glfwGetTime();
        printf("fps: %.2f ", (10/(new_time - get_time)));
        get_time = new_time;
      }
  
      // Check and call events
      glfwPollEvents();
  
      // Clear buffers
      //glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
      glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
      // Draw 100 instanced quads
      shader.Use();
  
      glm::mat4 model;
      model = glm::rotate(model, 0.0f, glm::vec3(1.0f, 0.0f, 0.0f));
      // Camera/View transformation
      glm::mat4 view;
      GLfloat radius = 10.0f;
      GLfloat camX = sin(glfwGetTime()) * radius;
      GLfloat camZ = cos(glfwGetTime()) * radius;
      view = glm::lookAt(glm::vec3(camX, 0.0f, camZ), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
      // Projection 
      glm::mat4 projection;
      projection = glm::perspective(45.0f, (GLfloat)screenWidth / (GLfloat)screenHeight, 0.1f, 100.0f);
      // Get the uniform locations
      GLint modelLoc = glGetUniformLocation(shader.Program, "model");
      GLint viewLoc = glGetUniformLocation(shader.Program, "view");
      GLint projLoc = glGetUniformLocation(shader.Program, "projection");
      // Pass the matrices to the shader
      glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
      glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
      glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
  
      glBindVertexArray(quadVAO);
      glDrawArraysInstanced(GL_TRIANGLES, 0, vv.size() / 3, NUM_INS); // 100 triangles of 6 vertices each
      glBindVertexArray(0);
  
      // Swap the buffers
      glfwSwapBuffers(window);
    }
  
    glfwTerminate();
    return 0;
  }
  
  // Is called whenever a key is pressed/released via GLFW
  void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
  {
    if (key == GLFW_KEY_ESCAPE &amp;&amp; action == GLFW_PRESS)
      glfwSetWindowShouldClose(window, GL_TRUE);
  }
  
  std::vector&lt;GLfloat&gt; add_color(std::vector&lt;GLfloat&gt; sphere) {
    // Add color
    std::vector&lt;GLfloat&gt; colored_sphere;
    for (GLint i = 0; i &lt; sphere.size(); i+=9) {
      colored_sphere.push_back(sphere[i]);
      colored_sphere.push_back(sphere[i+1]);
      colored_sphere.push_back(sphere[i+2]);
      colored_sphere.push_back(0.0f);
      colored_sphere.push_back(0.0f);
      colored_sphere.push_back(0.0f);
      colored_sphere.push_back(sphere[i+3]);
      colored_sphere.push_back(sphere[i+4]);
      colored_sphere.push_back(sphere[i+5]);
      colored_sphere.push_back(0.0f);
      colored_sphere.push_back(0.0f);
      colored_sphere.push_back(0.0f);
      colored_sphere.push_back(sphere[i+6]);
      colored_sphere.push_back(sphere[i+7]);
      colored_sphere.push_back(sphere[i+8]);
      colored_sphere.push_back(0.0f);
      colored_sphere.push_back(0.0f);
      colored_sphere.push_back(0.0f);
    }
  
    return colored_sphere;
  }
  
  std::vector&lt;GLfloat&gt; tesselate(std::vector&lt;GLfloat&gt; shape, int recursion) {
  
    if (recursion &gt; 0) {
      std::vector&lt;GLfloat&gt; new_sphere = {};
      for (GLint i = 0; i &lt; shape.size(); i += 9) {
  
        // 1.902113 approximately
        GLfloat radius = sqrt(1.0f + pow((1.0f + sqrt(5.0f)) / 2.0f, 2));
  
        // Every 9 points is a triangle.  Take 1 triangle and turn it into 4 triangles.  
        GLfloat p_one[] = {shape[i], shape[i + 1], shape[i + 2]};
        GLfloat p_two[] = {shape[i + 3], shape[i + 4], shape[i + 5]};
        GLfloat p_thr[] = {shape[i + 6], shape[i + 7], shape[i + 8]};
        GLfloat p_one_two[] = { (p_one[0] + p_two[0]) / 2.0f, (p_one[1] + p_two[1]) / 2.0f, (p_one[2] + p_two[2]) / 2.0f };
        GLfloat p_one_thr[] = { (p_one[0] + p_thr[0]) / 2.0f, (p_one[1] + p_thr[1]) / 2.0f, (p_one[2] + p_thr[2]) / 2.0f };
        GLfloat p_two_thr[] = { (p_two[0] + p_thr[0]) / 2.0f, (p_two[1] + p_thr[1]) / 2.0f, (p_two[2] + p_thr[2]) / 2.0f };
  
        GLfloat r_one_two = sqrt((p_one_two[0]*p_one_two[0]) + (p_one_two[1]*p_one_two[1]) + (p_one_two[2]*p_one_two[2]));
        GLfloat r_one_thr = sqrt((p_one_thr[0]*p_one_thr[0]) + (p_one_thr[1]*p_one_thr[1]) + (p_one_thr[2]*p_one_thr[2]));
        GLfloat r_two_thr = sqrt((p_two_thr[0]*p_two_thr[0]) + (p_two_thr[1]*p_two_thr[1]) + (p_two_thr[2]*p_two_thr[2]));
  
        GLfloat t_one_two[] = { radius * p_one_two[0] / r_one_two, radius * p_one_two[1] / r_one_two, radius * p_one_two[2] / r_one_two };
        GLfloat t_one_thr[] = { radius * p_one_thr[0] / r_one_thr, radius * p_one_thr[1] / r_one_thr, radius * p_one_thr[2] / r_one_thr };
        GLfloat t_two_thr[] = { radius * p_two_thr[0] / r_two_thr, radius * p_two_thr[1] / r_two_thr, radius * p_two_thr[2] / r_two_thr };
  
        // Triangle 1:
          new_sphere.push_back(p_one[0]);
        new_sphere.push_back(p_one[1]);
        new_sphere.push_back(p_one[2]);
  
        new_sphere.push_back(t_one_two[0]);
        new_sphere.push_back(t_one_two[1]);
        new_sphere.push_back(t_one_two[2]);
  
        new_sphere.push_back(t_one_thr[0]);
        new_sphere.push_back(t_one_thr[1]);
        new_sphere.push_back(t_one_thr[2]);
  
        // Triangle 2:
          new_sphere.push_back(p_two[0]);
        new_sphere.push_back(p_two[1]);
        new_sphere.push_back(p_two[2]);
  
        new_sphere.push_back(t_one_two[0]);
        new_sphere.push_back(t_one_two[1]);
        new_sphere.push_back(t_one_two[2]);
  
        new_sphere.push_back(t_two_thr[0]);
        new_sphere.push_back(t_two_thr[1]);
        new_sphere.push_back(t_two_thr[2]);
  
        // Triangle 3: 
          new_sphere.push_back(p_thr[0]);
        new_sphere.push_back(p_thr[1]);
        new_sphere.push_back(p_thr[2]);
  
        new_sphere.push_back(t_one_thr[0]);
        new_sphere.push_back(t_one_thr[1]);
        new_sphere.push_back(t_one_thr[2]);
  
        new_sphere.push_back(t_two_thr[0]);
        new_sphere.push_back(t_two_thr[1]);
        new_sphere.push_back(t_two_thr[2]);
  
        // Center Triangle:
  
          new_sphere.push_back(t_one_two[0]);
        new_sphere.push_back(t_one_two[1]);
        new_sphere.push_back(t_one_two[2]);
  
        new_sphere.push_back(t_one_thr[0]);
        new_sphere.push_back(t_one_thr[1]);
        new_sphere.push_back(t_one_thr[2]);
  
        new_sphere.push_back(t_two_thr[0]);
        new_sphere.push_back(t_two_thr[1]);
        new_sphere.push_back(t_two_thr[2]);
      }
      return tesselate(new_sphere, recursion - 1);
    }
  
    printf("number of vertices to be rendered: %d || ", shape.size());
    return shape;
  }
  
  std::vector&lt;GLfloat&gt; create_sphere(int recursion) {
  
    // Define the starting icosahedron
    GLfloat t_ = (1.0f + sqrt(5.0f)) / 2.0f;
  
    std::vector&lt;GLfloat&gt; icosahedron = {
      -1.0f,  t_,  0.0f, -t_,  0.0f,  1.0f, 0.0f,  1.0f,  t_,
      -1.0f,  t_,  0.0f, 0.0f,  1.0f,  t_, 1.0f,  t_,  0.0f,
      -1.0f,  t_,  0.0f, 1.0f,  t_,  0.0f, 0.0f,  1.0f, -t_,
      -1.0f,  t_,  0.0f, 0.0f,  1.0f, -t_, -t_,  0.0f, -1.0f,
      -1.0f,  t_,  0.0f, -t_,  0.0f, -1.0f, -t_,  0.0f,  1.0f,
  
      1.0f,  t_,  0.0f, 0.0f,  1.0f,  t_,  t_,  0.0f,  1.0f, 
      0.0f,  1.0f,  t_, -t_,  0.0f,  1.0f,  0.0f, -1.0f,  t_, 
      -t_,  0.0f,  1.0f, -t_,  0.0f, -1.0f, -1.0f, -t_,  0.0f, 
      -t_,  0.0f, -1.0f,  0.0f,  1.0f, -t_,  0.0f, -1.0f, -t_, 
      0.0f,  1.0f, -t_,  1.0f,  t_,  0.0f,  t_,  0.0f, -1.0f, 
  
      1.0f, -t_,  0.0f, t_,  0.0f,  1.0f, 0.0f, -1.0f,  t_,
      1.0f, -t_,  0.0f, 0.0f, -1.0f,  t_,-1.0f, -t_,  0.0f,
      1.0f, -t_,  0.0f,-1.0f, -t_,  0.0f, 0.0f, -1.0f, -t_,
      1.0f, -t_,  0.0f, 0.0f, -1.0f, -t_, t_,  0.0f, -1.0f,
      1.0f, -t_,  0.0f, t_,  0.0f, -1.0f, t_,  0.0f,  1.0f,
  
      0.0f, -1.0f,  t_, t_,  0.0f,  1.0f, 0.0f,  1.0f,  t_,
      -1.0f, -t_,  0.0f, 0.0f, -1.0f,  t_,-t_,  0.0f,  1.0f,
      0.0f, -1.0f, -t_,-1.0f, -t_,  0.0f,-t_,  0.0f, -1.0f,
      t_,  0.0f, -1.0f, 0.0f, -1.0f, -t_, 0.0f,  1.0f, -t_,
      t_,  0.0f,  1.0f, t_,  0.0f, -1.0f, 1.0f,  t_,  0.0f,
    };
  
    // Tesselate the icososphere the number of times recursion
    std::vector&lt;GLfloat&gt; colorless_sphere = tesselate(icosahedron, recursion);
  
    // Add color and return
    return add_color(colorless_sphere);
  }
  </code></pre>
  
  <p>Vertex Shader: (named core.vs) </p>
  
  <pre><code>#version 330 core
  layout (location = 0) in vec3 position;
  layout (location = 1) in vec3 color;
  layout (location = 2) in vec2 offset;
  
  out vec3 fColor;
  
  uniform mat4 model;
  uniform mat4 view;
  uniform mat4 projection;
  
  void main()
  {
    gl_Position = projection * view * model * vec4(position.x + offset.x, position.y + offset.y, position.z, 1.0f);
    fColor = color;
  } 
  </code></pre>
  
  <p>Fragment Shader: (named core.frag)</p>
  
  <pre><code>#version 330 core
  in vec3 fColor;
  out vec4 color;
  
  void main()
  {
    color = vec4(fColor, 1.0f);
  }
  </code></pre>
  
  <p>Shader class: (named Shader.h)</p>
  
  <pre><code>#ifndef SHADER_H
  #define SHADER_H
  
  #include &lt;string&gt;
  #include &lt;fstream&gt;
  #include &lt;sstream&gt;
  #include &lt;iostream&gt;
  
  #include &lt;GL/glew.h&gt;
  
  class Shader
  {
    public:
    GLuint Program;
    // Constructor generates the shader on the fly
    Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
    {
      // 1. Retrieve the vertex/fragment source code from filePath
      std::string vertexCode;
      std::string fragmentCode;
      std::ifstream vShaderFile;
      std::ifstream fShaderFile;
      // ensures ifstream objects can throw exceptions:
        vShaderFile.exceptions(std::ifstream::badbit);
      fShaderFile.exceptions(std::ifstream::badbit);
      try
      {
        // Open files
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        std::stringstream vShaderStream, fShaderStream;
        // Read file's buffer contents into streams
        vShaderStream &lt;&lt; vShaderFile.rdbuf();
        fShaderStream &lt;&lt; fShaderFile.rdbuf();
        // close file handlers
        vShaderFile.close();
        fShaderFile.close();
        // Convert stream into string
        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
      }
        catch (std::ifstream::failure e)
        {
        std::cout &lt;&lt; "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" &lt;&lt; std::endl;
        }
        const GLchar* vShaderCode = vertexCode.c_str();
        const GLchar * fShaderCode = fragmentCode.c_str();
        // 2. Compile shaders
        GLuint vertex, fragment;
        GLint success;
        GLchar infoLog[512];
        // Vertex Shader
        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &amp;vShaderCode, NULL);
        glCompileShader(vertex);
        // Print compile errors if any
        glGetShaderiv(vertex, GL_COMPILE_STATUS, &amp;success);
        if (!success)
        {
        glGetShaderInfoLog(vertex, 512, NULL, infoLog);
        std::cout &lt;&lt; "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" &lt;&lt; infoLog &lt;&lt; std::endl;
        }
        // Fragment Shader
        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &amp;fShaderCode, NULL);
        glCompileShader(fragment);
        // Print compile errors if any
        glGetShaderiv(fragment, GL_COMPILE_STATUS, &amp;success);
        if (!success)
        {
        glGetShaderInfoLog(fragment, 512, NULL, infoLog);
        std::cout &lt;&lt; "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" &lt;&lt; infoLog &lt;&lt; std::endl;
        }
        // Shader Program
        this-&gt;Program = glCreateProgram();
        glAttachShader(this-&gt;Program, vertex);
        glAttachShader(this-&gt;Program, fragment);
        glLinkProgram(this-&gt;Program);
        // Print linking errors if any
        glGetProgramiv(this-&gt;Program, GL_LINK_STATUS, &amp;success);
        if (!success)
        {
        glGetProgramInfoLog(this-&gt;Program, 512, NULL, infoLog);
        std::cout &lt;&lt; "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" &lt;&lt; infoLog &lt;&lt; std::endl;
        }
        // Delete the shaders as they're linked into our program now and no longer necessery
        glDeleteShader(vertex);
        glDeleteShader(fragment);
  
    }
      // Uses the current shader
      void Use()
      {
        glUseProgram(this-&gt;Program);
      }
  };
  
  #endif
  </code></pre>