Search code examples
c++openglwavefront

Drawing VBOs in OpenGL not working


I'm writing a simple class that parses an OBJ and renders the obj after that. I've already written a method for drawing vertex arrays from RAM and I wanted to optimize it a bit (I read that using VBOs gets you x4 times the framerate!).

Sadly I'm failing spectacularly, I've followed a bunch of tutorials to no avail. Here's the parser and the calls to store the vertices and indices buffers on GPU, I've througly debugged the parser itself and it's ok. Both vertexBuffer and vertexIndices are loaded up fine (I've printed them and manually checked them against the obj file).

Mesh::Mesh(string path) {
float* normals;
float* faces;
float* textures;
string fullpath = path + ".obj";
this->mats = loadMtl(path); //Loads a Material list, not used now drawing white
string line;
ifstream objFile(fullpath.c_str());
if (objFile.is_open()) {
    objFile.seekg(0, ios::end);
    long fileSize = long(objFile.tellg());
    objFile.seekg(0, ios::beg);

    // Init buffers and indices
    unsigned int inserted = 0;

    string currentMtl = "";

    float* vertexBuffer = new float[fileSize];
    float* textureBuffer = new float[fileSize];
    float* normalBuffer = new float[fileSize];

    unsigned int* vertexIndices = new unsigned int[fileSize];
    unsigned int* normalIndices = new unsigned int[fileSize];
    unsigned int* textureIndices = new unsigned int[fileSize];

    int connectedVert = 0;
    int textureCoords = 0;
    int normalsCount = 0;

    int vertexIndex = 0;

    string prefix = "usemtl";

    while (!objFile.eof()) {
        getline(objFile, line);
        if (line.c_str()[0] == 'v' && line.c_str()[1] == ' ') {
            line[0] = ' ';
            sscanf_s(line.c_str(), "%f %f %f ",
                &vertexBuffer[connectedVert],
                &vertexBuffer[connectedVert + 1],
                &vertexBuffer[connectedVert + 2]);
            connectedVert += 3;
        }
        else if (line.c_str()[0] == 'v' && line.c_str()[1] == 't') {
            line[0] = ' ';
            line[1] = ' ';
            sscanf_s(line.c_str(), "%f %f ",
                &textureBuffer[textureCoords],
                &textureBuffer[textureCoords + 1]);
            textureCoords += 2;
        }
        else if (line.c_str()[0] == 'v' && line.c_str()[1] == 'n') {
            line[0] = ' ';
            line[1] = ' ';
            sscanf_s(line.c_str(), "%f %f %f ",
                &normalBuffer[normalsCount],
                &normalBuffer[normalsCount + 1],
                &normalBuffer[normalsCount + 2]);
            normalsCount += 3;
        }
        else if (line.c_str()[0] == 'f') {
            line[0] = ' ';

            if (textureCoords > 0) {
                sscanf_s(line.c_str(), "%i/%i/%i %i/%i/%i %i/%i/%i",
                    &vertexIndices[vertexIndex], &textureIndices[vertexIndex], &normalIndices[vertexIndex],
                    &vertexIndices[vertexIndex + 1], &textureIndices[vertexIndex +1], &normalIndices[vertexIndex +1],
                    &vertexIndices[vertexIndex + 2], &textureIndices[vertexIndex +2], &normalIndices[vertexIndex +2]);
            }
            else {
                sscanf_s(line.c_str(), "%i//%i %i//%i %i//%i",
                    &vertexIndices[vertexIndex], &normalIndices[vertexIndex],
                    &vertexIndices[vertexIndex+1], &normalIndices[vertexIndex +1],
                    &vertexIndices[vertexIndex+2], &normalIndices[vertexIndex +2]);
            }

            for (int j = 0; j < 3; j++) {
                vertexIndices[vertexIndex+j] -= 1;
                normalIndices[vertexIndex +j] -= 1;
                textureIndices[vertexIndex +j] -= 1;
            }
            vertexIndex += 3;
        }
        else if (!strncmp(line.c_str(), prefix.c_str(), strlen(prefix.c_str()))) {
            inserted++;
            // No more vertices, normals or texture coords to load, gen arrays on GPU
            if (currentMtl == "") {
                //Copy vertices to GPU
                glGenBuffers(1, &this->verticesID);
                glBindBuffer(GL_ARRAY_BUFFER, this->verticesID);
                glBufferData(GL_ARRAY_BUFFER, sizeof(float) * connectedVert, vertexBuffer, GL_STATIC_DRAW);
                /*
                //Won't be bothered with normals or textures right now...
                //Copy normals to GPU
                glGenBuffers(1, &this->normalsID);
                glBindBuffer(GL_ARRAY_BUFFER, this->normalsID);
                glBufferData(GL_ARRAY_BUFFER, sizeof(float) * normalsCount, &normalBuffer[0], GL_STATIC_DRAW);

                //Copy UV to GPU
                if (textureCoords > 0) {
                    glGenBuffers(1, &this->textID);
                    glBindBuffer(GL_ARRAY_BUFFER, this->textID);
                    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * textureCoords, &textureBuffer[0], GL_STATIC_DRAW);
                }*/
            }
            else {
                // Filled up an index array, copy it.
                this->indices.push_back(0);
                this->faces.push_back(vertexIndex);
                glGenBuffers(1, &this->indices[this->indices.size() - 1]);
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->indices[this->indices.size() - 1]);
                glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * vertexIndex, vertexIndices, GL_STATIC_DRAW);
            }
            currentMtl = line.substr(7, line.length());
            vertexIndex = 0;
        }
    }
    // Copy the last indices buffer.
    if (currentMtl != "" && indices.size() < inserted) {
        this->indices.push_back(0);
        this->faces.push_back(vertexIndex);
        glGenBuffers(1, &this->indices[this->indices.size() - 1]);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->indices[this->indices.size() - 1]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * vertexIndex, vertexIndices, GL_STATIC_DRAW);
    }

    objFile.close();
    delete[] vertexBuffer;
    delete[] textureBuffer;
    delete[] normalBuffer;

    delete[] vertexIndices;
    delete[] normalIndices;
    delete[] textureIndices;
}
else {
    throw runtime_error("Unable to load obj file: " + path + ".obj");
}
}

Finally, here's the draw call. I'm guessing the problem is here but I can't quite put my finger on the issue. I've used glIntercept to see if there's something messed up but I got no errors there.

void Mesh::draw() {
    //Binding Vertex array
    glBindBuffer(GL_ARRAY_BUFFER, this->verticesID);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, (void*)0);
    for (unsigned int i = 0; i < this->indices.size(); i++) {
        glColor3f(1.0f, 1.0f, 1.0f); // Draw it in plain ol' white
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->indices[i]);
        glDrawElements(GL_TRIANGLES,this->faces[i], GL_UNSIGNED_INT,(void*)0);
    }
    glDisableClientState(GL_VERTEX_ARRAY);
}

In case it's needed, here's the Mesh class declaration:

class Mesh {
    public:
        GLuint verticesID, textID, normalsID;
        vector<GLuint> indices;
        vector<GLuint> faces;
        vector<Material> mats;
    public:
       Mesh(string path);
       void draw();
        ~Mesh();
};

Setting up OpenGL major version as 2, and minor as 1. As I'm mentioned before, I think OpenGL is set up correctly because I'm able to draw triangles in both direct mode and using vertex pointers from RAM. Any help welcomed :)


Solution

  • It the constructor of the calss Mesh the file is read and the object buffers are generated (glGenBuffers). In the destructor Mesh::~Mesh the object buffers are destroyed (glDeleteBuffers).

    But you push_back the Mesh to a std::vector. This means that a temporary Mesh object is generated, which reads the file and generate the object buffer 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 Mesh objectis 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 Mesh object. Immediately after this is done, the temporary Mesh object gets destroyed and the object buffers get deleted (glDeleteBuffers) by the destructor Mesh::~Mesh of the temporary object. At this point all the data are gone.

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

    You should not do the generation and the deletion of GPU objects in the constructor and destructor of a class except you make the class not copyable and not copy constructable:

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

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

    test.meshes.push_back(Mesh("tri"));
    

    by

    test.meshes.emplace_back("tri");
    

    (see std::vector::emplace_back)


    But at the end you should do something like:

    vector<std::unique_ptr<Mesh>> meshes;
    
    meshes.emplace_back(new Mesh("tri"));