Search code examples
c++openglgraphicsopengl-3

OpenGL 3.3: GL_INVALID_OPERATION when calling glBindBuffer


Alright, so I am having a pretty tenacious issue setting up my OpenGL code. I'm trying to refactor my graphics code into a renderer object, but I can't seem to get to the bottom of a tricky GL_INVALID_OPERATION error (code 1282).

I start by creating a mesh object that initializes a loosely defined collection of OpenGL objects, and manages their lifespan in an attempt at RAII style:

struct OpenGLMesh
{
   OpenGLMesh(OpenGLRenderer& renderer, 
              int shader_index, 
              const char* fpath);

   ~OpenGLMesh();

   GLuint vbo_;
   GLuint ebo_;
   GLuint texture_;
   std::vector<float> vertices_;
   std::vector<unsigned int> indices_;
   GLuint shader_id_;
   GLuint mvp_id_;
};

OpenGLMesh::OpenGLMesh(OpenGLRenderer& renderer, int shader_index, const char* fpath)
{
   glGenBuffers(1, &vbo_);
   glGenBuffers(1, &ebo_);
   glGenTextures(1, &texture_);
   renderer.loadTexture(*this, fpath);

   const std::vector<GLuint>& shaders = renderer.getShaders();
   shader_id_ = shaders.at(shader_index);
   mvp_id_ = glGetUniformLocation(shader_id_, "MVP");
}

OpenGLMesh::~OpenGLMesh()
{
   glDeleteBuffers(1, &vbo_);
   glDeleteBuffers(1, &ebo_);
   glDeleteTextures(1, &texture_);
}

At the same time I have a renderer object that owns the majority of the initialization and rendering functions. For example, the loadTexture function in the above constructor is part of the my OpenGLRenderer class:

OpenGLRenderer::OpenGLRenderer()
{
   glGenVertexArrays(1, &vao_); // allocate + assign a VAO to our handle
   shaders_.push_back(loadShaders("shaders/texture.vert", "shaders/texture.frag"));
}

OpenGLRenderer::~OpenGLRenderer()
{
   std::vector<GLuint>::iterator it;
   for (it = shaders_.begin(); it != shaders_.end(); ++it)
   {
      glDeleteProgram(*it);
   }
   glDeleteVertexArrays(1, &vao_);
}

My first concern is that compartmentalization of these function calls may have somehow invalidated some part of my OpenGL setup calls. However, the error doesn't make an appearance until I attempt to bind my mesh's VBO.

Below is the code from a stripped down test module I built to debug this issue:

// create the renderer object
OpenGLRenderer renderer;

// create and store a mesh object
std::vector<OpenGLMesh> meshes;
meshes.push_back(OpenGLMesh(renderer, 0, "./assets/dune_glitch.png"));

// SDL Event handling loop

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glBindVertexArray(vao_);
glBindBuffer(GL_ARRAY_BUFFER, mesh.vbo_);
printOpenGLError(); // prints out error code 1282

I've verified that it is definitely this line that breaks every time, although it doesn't seem to send a kill signal until the next iteration of the loop.

I haven't been able to find any insight on this problem - seems like glBindBuffer doesn't normally generate this kind of error. I've also made sure that the mesh.vbo_ ID still points to the same location.

For some reason, my application's stack trace doesn't play well with GDB, so I haven't been able to look at the trace as much as I would normally want to. Any advice would be a help, from debugging tips to possible sources of failure - thanks in advance!

(This is my first real post, let me know if I messed anything up!)


Solution

  • It the constructor of the calss OpenGLMesh the object buffers are generated (glGenBuffers). In the destructor OpenGLMesh::~OpenGLMesh the object buffers are destroyed (glDeleteBuffers).

    In the following line:

    meshes.push_back(OpenGLMesh(renderer, 0, "./assets/dune_glitch.png"));
    

    you push_back the OpenGLMesh to a std::vector. This means that a temporary OpenGLMesh object is generated, an the object bffers are generated in its constructor. At this point all the data are valid and the object buffers are generated (GPU). When calling std::vector::push_back, then a new OpenGLMesh object is is generated, in the std::vector. The object is constructed by the default copy constructor and gets a copy of all the members of the first OpenGLMesh object. Immediately after this is done, the temporary OpenGLMesh object gets destroyed and the object buffers get deleted (glDeleteBuffers) by the destructor OpenGLMesh::~OpenGLMesh of the temporary object. At this point all the data are gone.

    See std::vector::push_back. Put a breakpoint in the destrutor OpenGLMesh::~OpenGLMesh, then you can simply track the expiration.

    You should make the class not copyable and not copy constructable, but specify a move constructor and move operator.

    class OpenGLMesh 
    {
        OpenGLMesh(const OpenGLMesh &) = delete;
        OpenGLMesh & operator = (const OpenGLMesh &) = delete;
    
        OpenGLMesh( OpenGLMesh && );
        OpenGLMesh & operator = ( OpenGLMesh && );
    
        ....
    };
    

    You can quickly fix this behavior for debug reasons, by replacing

    meshes.push_back(OpenGLMesh(renderer, 0, "./assets/dune_glitch.png"));
    

    by

    meshes.emplace_back(renderer, 0, "./assets/dune_glitch.png");
    

    (see std::vector::emplace_back)


    For the implementation of a move constructor and move operator see: