PDA

View Full Version : Drawing Large Amounts of Information stored in VBO's ...



tmason
05-03-2014, 01:31 AM
Hello,

I have fairly large C++ objects which load mesh data into memory and then draws based on an OnDisplay callback.

The problem is that the refresh rate is really slow which I suspect is because my code is poorly written.

Anyway; here is what my class looks like (function prototypes shown to give you an idea of how my class is set up).

What I want to know is if it is possible to just call the "glDrawElements" function somehow on what is in memory if most of my VBOs haven't changed and skip my Begin and end draw functions as shown below.


Mostly I will just have the camera moving through the scene.

I set these functions up based on tutorials and documentation so I know they work; I just want to speed up the drawing, especially when the meshes I am loading in are 100MB + in size.

Thank you so much for your time and any assistance you can provide.



class MyMeshData
{
public:
MyMeshData();
~MyMeshData();

// Save up data into GPU buffers.
bool Initialize(const MeshDataFromFileClass * StaticMeshData);

// Update vertex positions for deformed meshes.
void UpdateVertexPosition(const MeshDataFromFileClass * StaticMeshData, const MyVector4Class * pVertices) const;

// Bind buffers, set vertex arrays, turn on lighting and texture.

void BeginDraw(ShadingMode pShadingMode) const;

// Draw all the faces with specific material with given shading mode.

void Draw(int pMaterialIndex, ShadingMode pShadingMode) const;

// Unbind buffers, reset vertex arrays, turn off lighting and texture.
void EndDraw() const;

// Get the count of material groups
int GetSubMeshCount() const { return mSubMeshes.GetCount(); }

private:
enum
{
VERTEX_VBO,
NORMAL_VBO,
UV_VBO,
INDEX_VBO,
VBO_COUNT,
};

// For every material, record the offsets in every VBO and triangle counts
struct SubMesh
{
SubMesh() : IndexOffset(0), TriangleCount(0) {}

int IndexOffset;
int TriangleCount;
};

GLuint mVBONames[VBO_COUNT];

MyMeshArray<SubMesh*> mSubMeshes;
bool mHasNormal;
bool mHasUV;
bool mAllByControlPoint; // Save data in VBO by control point or by polygon vertex.
};


And here is my Initialize Function:




bool Initialize(const MeshDataFromFileClass * StaticMeshData) {
[...]
/*
Earlier code that retrieves data from file removed.

Only the point where the data is transferred to the GPU is shown.
*/

// Create VBOs
glGenBuffers(VBO_COUNT, mVBONames);

// Save vertex attributes into GPU
glBindBuffer(GL_ARRAY_BUFFER, mVBONames[VERTEX_VBO]);
glBufferData(GL_ARRAY_BUFFER, lPolygonVertexCount * VERTEX_STRIDE * sizeof(float), lVertices, GL_STATIC_DRAW);
delete [] lVertices;

if (mHasNormal)
{
glBindBuffer(GL_ARRAY_BUFFER, mVBONames[NORMAL_VBO]);
glBufferData(GL_ARRAY_BUFFER, lPolygonVertexCount * NORMAL_STRIDE * sizeof(float), lNormals, GL_STATIC_DRAW);
delete [] lNormals;
}

if (mHasUV)
{
glBindBuffer(GL_ARRAY_BUFFER, mVBONames[UV_VBO]);
glBufferData(GL_ARRAY_BUFFER, lPolygonVertexCount * UV_STRIDE * sizeof(float), lUVs, GL_STATIC_DRAW);
delete [] lUVs;
}

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBONames[INDEX_VBO]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, lPolygonCount * TRIANGLE_VERTEX_COUNT * sizeof(unsigned int), lIndices, GL_STATIC_DRAW);

delete [] lIndices;
}



Here is my BeginDraw Function:



void MyMeshData::BeginDraw(ShadingMode pShadingMode) const
{

glBindBuffer(GL_ARRAY_BUFFER, mVBONames[VERTEX_VBO]);
/*
glVertexPointer(VERTEX_STRIDE, GL_FLOAT, 0, 0);
glEnableClientState(GL_VERTEX_ARRAY);
*/
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, VERTEX_STRIDE, GL_FLOAT, GL_FALSE, 0, 0);



// Set normal array.
if (mHasNormal && pShadingMode == SHADING_MODE_SHADED)
{

glBindBuffer(GL_ARRAY_BUFFER, mVBONames[NORMAL_VBO]);
glNormalPointer(GL_FLOAT, 0, 0);
glEnableClientState(GL_NORMAL_ARRAY);

}

// Set UV array.
if (mHasUV && pShadingMode == SHADING_MODE_SHADED)
{
glBindBuffer(GL_ARRAY_BUFFER, mVBONames[UV_VBO]);
glTexCoordPointer(UV_STRIDE, GL_FLOAT, 0, 0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}


glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBONames[INDEX_VBO]);

if (pShadingMode != SHADING_MODE_SHADED)
{
glColor4fv(DEFAULT_WIREFRAME_COLOR);
}
}


My Draw function ...



void MyMeshData::Draw(int pMaterialIndex, ShadingMode pShadingMode) const
{
// Where to start.
GLsizei lOffset = mSubMeshes[pMaterialIndex]->IndexOffset * sizeof(unsigned int);
if ( pShadingMode == SHADING_MODE_SHADED)
{
const GLsizei lElementCount = mSubMeshes[pMaterialIndex]->TriangleCount * 3;
glDrawElements(GL_TRIANGLES, lElementCount, GL_UNSIGNED_INT, reinterpret_cast<const GLvoid *>(lOffset));
}
else
{
for (int lIndex = 0; lIndex < mSubMeshes[pMaterialIndex]->TriangleCount; ++lIndex)
{
glDrawElements(GL_LINE_LOOP, TRIANGLE_VERTEX_COUNT, GL_UNSIGNED_INT, reinterpret_cast<const GLvoid *>(lOffset));
lOffset += sizeof(unsigned int) * TRIANGLE_VERTEX_COUNT;
}
}
}


My EndDraw Function ...



void VBOMesh::EndDraw() const
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}

Brokenmind
05-03-2014, 03:30 AM
The solution is: instancing. Assuming your triangle count is very large, there's no wonder your drawing routine is slow.

This might help your understand:


void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei primcount);
{
if (mode, count, or type is invalid )
generate appropriate error
else {
for (int i = 0; i < primcount ; i++) {
instanceID = i;
glDrawElements(mode, count, type, indices);
}
instanceID = 0;
}
}
source (http://www.opengl.org/sdk/docs/man3/xhtml/glDrawElementsInstanced.xml)

What this function does is basically what you do in your third code section, with the difference that you have [TriangleCount] draw calls (which are fairly slow), the glDrawElementsInstanced function has only one. So you might want to translate your function appropriately.

Dan Bartlett
05-03-2014, 04:28 AM
If your meshes are 100 MB+ the overhead of setting up will be relatively small compared to the draw time. Your wire-frame mode will be very slow as you're making a draw call per triangle, to do it with a single draw call you could try using:

else
{
//might also want to disable lighting etc.
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
const GLsizei lElementCount = mSubMeshes[pMaterialIndex]->TriangleCount * 3;
glDrawElements(GL_TRIANGLES, lElementCount, GL_UNSIGNED_INT, reinterpret_cast<const GLvoid *>(lOffset));
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

To make your shaded code path any faster you will probably need to simplify the shaders / reduce the data size, but profile first to make sure you aren't executing code that you shouldn't be.

tmason
05-03-2014, 03:28 PM
The solution is: instancing. Assuming your triangle count is very large, there's no wonder your drawing routine is slow.

This might help your understand:


void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei primcount);
{
if (mode, count, or type is invalid )
generate appropriate error
else {
for (int i = 0; i < primcount ; i++) {
instanceID = i;
glDrawElements(mode, count, type, indices);
}
instanceID = 0;
}
}
source (http://www.opengl.org/sdk/docs/man3/xhtml/glDrawElementsInstanced.xml)

What this function does is basically what you do in your third code section, with the difference that you have [TriangleCount] draw calls (which are fairly slow), the glDrawElementsInstanced function has only one. So you might want to translate your function appropriately.

Thank you. This does work a little better.

I am wondering if somehow I can take this further.

Looking at my code, let's say there are 600 instances of my MyMeshData class, each with their own set of meshes, etc.

Now, in my scene, 590 of these meshes do not change whatsoever (they are walls, stationary objects, etc.)

So now, is there a way to group all of the 590 instances of my MyMeshData's OpenGL buffers IDs into one call to OpenGL and ask OpenGL to draw all of the associated Buffered Data at once?

I am a beginner at this, so I hope my explanation makes sense.

I'll use some pseudo-code below to explain what I am hoping to do in code:



/*

Loop through all of my code, gathering all of the instances of MyMeshData that didn't change.

*/

Array InstancesThatDidNotChange;

while (i=0; i < MyMeshDataCount; i++) {
if MyMeshData[i].SomethingChange = false{
InstancesThatDidNotChange.Add(MyMeshData.Associate dBufferID);
}
}

/*

Somewhere not that far later in code

*/

while (InstancesThatDidNotChange Loop) {
Fancy GL Function Here (Draw All of the Associated Buffered IDs that Did Not Change);
}


I hope I am explaining this right.

Please ask any questions if I am not clear.

tmason
05-03-2014, 03:32 PM
If your meshes are 100 MB+ the overhead of setting up will be relatively small compared to the draw time. Your wire-frame mode will be very slow as you're making a draw call per triangle, to do it with a single draw call you could try using:

else
{
//might also want to disable lighting etc.
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
const GLsizei lElementCount = mSubMeshes[pMaterialIndex]->TriangleCount * 3;
glDrawElements(GL_TRIANGLES, lElementCount, GL_UNSIGNED_INT, reinterpret_cast<const GLvoid *>(lOffset));
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

To make your shaded code path any faster you will probably need to simplify the shaders / reduce the data size, but profile first to make sure you aren't executing code that you shouldn't be.

Thank you, I have been using gDEBugger for optimization but that is super old (2010).

What is the hot new OpenGL debugger/profiler out now?