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 :)
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"));