PDA

View Full Version : Impossibly slow frame rate drawing OBJ Model



JCDClark
04-06-2012, 07:43 PM
Firstly hello everyone, first post here :). I've been teaching myself OpenGL recently, mainly just experimenting with random techniques such as lighting, texturing as well as learning some of the 3D mathematics. Any problems I've come across I've fixed myself through perseverance or the all-mighty Google. This has me a bit stumped however.

The most recent thing I've tried is loading in and displaying models in the Wavefront OBJ format so I can start playing around with some more interesting objects. The model loads in fine yet the drawing of the model is criminally slow. I get 1 frame roughly every 8.5 seconds. The model I'm using is pretty large (2154 Vertices, 4204 Faces) but I'd guess it shouldn't be anywhere near that slow :p

Since the code to load the model seems to work nice and quickly, I'll just provide the drawing code:



void drawModel(){

vector<wavefront::WavefrontOBJFace> modelFaces = model.getModelFaces();

glEnable(GL_LIGHTING);

glBegin(GL_TRIANGLES);

//For every face in the model.
for(int i = 0; i < model.getNumberOfFaces(); i++){

float begin = glutGet(GLUT_ELAPSED_TIME);

/*
* Find the indices of the model vertices for the three
* vertices that make up the face.
*/
vector<int> vertexIndices = modelFaces.at(i).getVertexIndices();

/*
* Get the WavefrontOBJVertex object corresponding to
* each vertex in the face.
*/
wavefront::WavefrontOBJVertex vertex1 = model.getModelVertices().at(vertexIndices.at(0));
wavefront::WavefrontOBJVertex vertex2 = model.getModelVertices().at(vertexIndices.at(1));
wavefront::WavefrontOBJVertex vertex3 = model.getModelVertices().at(vertexIndices.at(2));

CVector3 coords1 = *vertex1.getCoords();
CVector3 coords2 = *vertex2.getCoords();
CVector3 coords3 = *vertex3.getCoords();

CVector3 normal1 = *vertex1.getNormal();
CVector3 normal2 = *vertex2.getNormal();
CVector3 normal3 = *vertex3.getNormal();

glNormal3f(normal1.x, normal1.y, normal1.z);
glVertex3f(coords1.x, coords1.y, coords1.z);
glNormal3f(normal2.x, normal2.y, normal2.z);
glVertex3f(coords2.x, coords2.y, coords2.z);
glNormal3f(normal3.x, normal3.y, normal3.z);
glVertex3f(coords3.x, coords3.y, coords3.z);

float end = glutGet(GLUT_ELAPSED_TIME);

std::cout << "ELAPSED TIME FOR 1 FACE: " << (end - begin) / 1000.0 << "\n";

}

glEnd();

glDisable(GL_LIGHTING);

}


I've done a bit of scouting around and a lot of people suggest using VBOs in situations like these however I assume as far as models go, this is reasonably small and I have used another loader written by someone else that uses a similar method for storing the data and also draws the model using immediate drawing mode. The model displays perfectly fine - no slowdown at all (that loader even incorporates materials and textures - mine is only vertices and normals!). Because of this, I'm thinking that the problem lies in my drawing method but I can't for the life of me think why.

I'm building using MinGW on Windows 7, coded in C++ using Eclipse. I've also tried building using Visual Studio 2010 Express and that was even slower.

Any help on this issue will be greatly appreciated, thanks.

V-man
04-07-2012, 05:10 AM
Sounds like it is running in software mode.
Query OpenGL with glGetString
http://www.opengl.org/wiki/GlGetString

BionicBytes
04-07-2012, 06:21 AM
What hardware do you have? What open gl version is reported?
Additionally you will need to move to vertex arrays and buffer objects as the models get larger and the scene more complex.

JCDClark
04-07-2012, 07:06 AM
Sounds like it is running in software mode.
Query OpenGL with glGetString
http://www.opengl.org/wiki/GlGetString


Hi, thanks for the reply.

Using glGetString with the following values gives me the following results:

Renderer: GeForce GT 330M/PCI/SSE2
Version: 3.3.0
Vendor: NVIDIA Corporation

I'm guessing this means it's not operating in software mode. Perhaps my graphics hardware is to blame?

JCDClark
04-07-2012, 07:15 AM
What hardware do you have? What open gl version is reported?
Additionally you will need to move to vertex arrays and buffer objects as the models get larger and the scene more complex.

Hi, thanks for the reply.

I'm using Windows 7 Home Premium 64 bit, the OpenGL version is as I posted above, 3.3.0.

CPU: Intel core i5 M480 @2.7Ghz
RAM: 6GB
GPU: NVIDIA Geforce GT 330M 3732MB driver version: 8.17.12.5926

Yeah from reading up on VBOs I know that's definitely the way to go but I want to play around in the sand pit a bit longer before I get into that ;).

Dark Photon
04-07-2012, 07:34 AM
Looks like you've got a lot of overhead in that loop you may not realize, like allocating and copying coords and vectors into temporaries and deleting them, besides using immediate mode. Suggest you comment out the GL calls that loop and optimize it. Read up on using references.

Dan Bartlett
04-07-2012, 07:43 AM
You're drawing your model in about the slowest way possible - as BionicBytes said, you should be using vertex arrays + buffer objects instead of using immediate mode rendering.

If you were to do this, your code would look more like:



void initModel()
{
// Init model - by loading vertex + index data into buffer objects
// if the model doesn't allowing getting raw pointer to vertex + index data,
// you might need to use a for loop to retrieve all the required data,
// but this would only be done once on initialization, rather than every draw call
glGenBuffers(1, &amp;model_vertices);
glBindBuffer(GL_ARRAY_BUFFER, model_vertices);
glBufferData(GL_ARRAY_BUFFER, model.getNumberOfVertices(), model.getModelVertices().rawPointerToData(), GL_STATIC_DRAW);

glGenBuffers(1, &amp;model_indices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model_indices);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, model.getNumberOfFaces() * 3, model.getModelIndices.rawPointerToData(), GL_STATIC_DRAW);
}

// draw the model
void drawModel()
{
glEnable(GL_LIGHTING);
glBindBuffer(GL_ARRAY_BUFFER, model_vertices);
glVertexPointer(3, GL_FLOAT, vertex_size, BUFFER_OFFSET(0));
glNormalPointer(GL_FLOAT, vertex_size, BUFFER_OFFSET(12));

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model_vertices);
glDrawElements(GL_TRIANGLES, model.getNumberOfFaces() * 3, GL_UNSIGNED_INT{or _SHORT, _BYTE), BUFFER_OFFSET(0));
glDisable(GL_LIGHTING);
}

JCDClark
04-07-2012, 09:21 AM
Looks like you've got a lot of overhead in that loop you may not realize, like allocating and copying coords and vectors into temporaries and deleting them, besides using immediate mode. Suggest you comment out the GL calls that loop and optimize it. Read up on using references.


Yes I thought this may have been a problem as well so I did try removing a lot of the unnecessary allocation and copying - accessing the data within the draw call directly. This didn't have an affect however, it was still taking longer than 8 seconds to draw one frame. I also tried limiting the number of faces to 100 yet the frame rate was still unusable. This leads me to believe it has something to do with my implementation here or somewhere else.

As I said before, I can use the binary of another OBJ loader and it loads this model in perfectly with very similar code (using immediate mode, also including textures).


You're drawing your model in about the slowest way possible - as BionicBytes said, you should be using vertex arrays + buffer objects instead of using immediate mode rendering.

If you were to do this, your code would look more like:

Firstly thanks for the code. I will eventually look into using VBOs as I know immediate mode is slow and even deprecated but for now I just want to focus on more of the basics. My issue is that assuming my implementation is OK, it surely shouldn't be this slow. As I said, even if I limit the loop to drawing 100 faces along with optimizations (not shown), I can still barely move the camera. This combined with the fact that the binary of the other similar loader loads the same model in just fine surely means my implementation is poor yet I can't see where!

Dan Bartlett
04-07-2012, 01:06 PM
It shouldn't be taking 8.5 seconds to render 1 frame, you could try with basic values plugged into your glNormal/glVertex calls + commenting out lines till you find what is causing the slowdown (or use a profiler if available). In 8.5 seconds using recent hardware you could possibly render billions of triangles, not just 4204 triangles, even if it falls back to software for some reason it should be much faster than this, so the problem is likely in your code.

maybe your calls such as:


wavefront::WavefrontOBJVertex vertex1 = model.getModelVertices().at(vertexIndices.at(0));

are more expensive than you think. There seem to be a lot of function calls to get the data for 1 triangle, and we've got no idea how expensive the calls are.

Trying to measure the time it takes 1 triangle to draw is usually pretty pointless as the time is usually less than the resolution of the timer. And printing out the results of the time measured for each triangle will most likely take longer than drawing the triangle too.

JCDClark
04-07-2012, 11:09 PM
It shouldn't be taking 8.5 seconds to render 1 frame, you could try with basic values plugged into your glNormal/glVertex calls + commenting out lines till you find what is causing the slowdown (or use a profiler if available). In 8.5 seconds using recent hardware you could possibly render billions of triangles, not just 4204 triangles, even if it falls back to software for some reason it should be much faster than this, so the problem is likely in your code.

maybe your calls such as:


wavefront::WavefrontOBJVertex vertex1 = model.getModelVertices().at(vertexIndices.at(0));

are more expensive than you think. There seem to be a lot of function calls to get the data for 1 triangle, and we've got no idea how expensive the calls are.

Trying to measure the time it takes 1 triangle to draw is usually pretty pointless as the time is usually less than the resolution of the timer. And printing out the results of the time measured for each triangle will most likely take longer than drawing the triangle too.

OK problem solved! :D

Since yourself and others suggested I optimise the drawing code and I myself thought the problem was somewhere in there, I thought I'd give it another go and think a bit more deeply.

I changed the method to this:



void drawModel(){

vector<wavefront::WavefrontOBJFace> modelFaces = model.getModelFaces();
vector<wavefront::WavefrontOBJVertex> modelVertices = model.getModelVertices();
int numFaces = model.getNumberOfFaces();

glEnable(GL_LIGHTING);

glBegin(GL_TRIANGLES);

//For every face in the model.
for(int i = 0; i < numFaces; i++){

vector<int> vertexIndices = modelFaces.at(i).getVertexIndices();

glNormal3f(modelVertices[vertexIndices[0]].getNormal()->x,
modelVertices[vertexIndices[0]].getNormal()->y,
modelVertices[vertexIndices[0]].getNormal()->z);
glVertex3f(modelVertices[vertexIndices[0]].getCoords()->x,
modelVertices[vertexIndices[0]].getCoords()->y,
modelVertices[vertexIndices[0]].getCoords()->z);

glNormal3f(modelVertices[vertexIndices[1]].getNormal()->x,
modelVertices[vertexIndices[1]].getNormal()->y,
modelVertices[vertexIndices[1]].getNormal()->z);
glVertex3f(modelVertices[vertexIndices[1]].getCoords()->x,
modelVertices[vertexIndices[1]].getCoords()->y,
modelVertices[vertexIndices[1]].getCoords()->z);

glNormal3f(modelVertices[vertexIndices[2]].getNormal()->x,
modelVertices[vertexIndices[2]].getNormal()->y,
modelVertices[vertexIndices[2]].getNormal()->z);
glVertex3f(modelVertices[vertexIndices[2]].getCoords()->x,
modelVertices[vertexIndices[2]].getCoords()->y,
modelVertices[vertexIndices[2]].getCoords()->z);

}

glEnd();

glDisable(GL_LIGHTING);

}


It now runs perfectly well, even with four of the models being drawn. What a huge difference. I guess this highlights my inexperience and naivety with C++ and OpenGL but I'm learning a lot through exercises like this :).

I see that there are perhaps further optimisations to be made but I think this will do for now.

Thanks for your help guys.