PDA

View Full Version : One buffer per object



Wasabi
10-02-2011, 11:00 AM
I've been working on a program for a while now but I've been away from it for a long while now. The program reads a file for different objects' coordinates and shows them on the screen. The objects must be dealt with individually, so they are best placed in different buffers. However, obviously they must be dealt with equally by the shaders. So I did the following:


void MyNGLWidget::OpenFile()
{
//...
Environment.GenAttribArrays(NumFaults);
//...
}
void NOpenGLEnvironment::GenAttribArrays(int NumFaults)
{
//Each object will have its own Color, Geometry and Index buffers.
VertexColorAttrib = new unsigned int[NumFaults];
VertexGeometryAttrib = new unsigned int[NumFaults];
VIO = new unsigned int[NumFaults];
}
// The program then goes on to generate the buffers
void MyNGLWidget::GenBuffers()
{
cout << "NGLWidget::GenBuffers" << endl;
const NFault* faults = Faults.GetFaults();

for(int i=0;i<Faults.GetNumFaults();i++)
{
glGenBuffers(1, &amp;Environment.VertexGeometryAttrib[i]);
glGenBuffers(1, &amp;Environment.VertexColorAttrib[i]);
glGenBuffers(1, &amp;Environment.VIO[i]);
}

UpdateFaultGeometry();
UpdateFaultColors();

for(int i=0;i<Faults.GetNumFaults();i++) //send index list of each fault to GPU
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,Environment.V IO[i]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int)*faults[i].GetNumIndexes(), faults[i].GetIndexes(),GL_STATIC_DRAW);
}
}
void MyNGLWidget::UpdateFaultGeometry()
{
const NFault* faults = Faults.GetFaults();

for(int i=0;i<Faults.GetNumFaults();i++)
{
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexGeometryAttrib[i]);
glBufferData(GL_ARRAY_BUFFER, faults[i].GetNumVertexes()*sizeof(NVertexGeometry), faults[i].GetGeometry(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0); //VERTEX COORDINATES
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexGeometry), BUFFER_OFFSET(0));
glEnableVertexAttribArray(1); //NORMALS
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexGeometry), BUFFER_OFFSET(12));
}
}
void MyNGLWidget::UpdateFaultColors()
{
const NFault* faults = Faults.GetFaults();

for(int i=0;i<Faults.GetNumFaults();i++)
{
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexColorAttrib[i]);
glBufferData(GL_ARRAY_BUFFER, faults[i].GetNumVertexes()*sizeof(NVertexColors), faults[i].GetColors(), GL_STATIC_DRAW);
glEnableVertexAttribArray(2); //VERTEX COLORS
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexColors), BUFFER_OFFSET(0));
glEnableVertexAttribArray(3); //MESH COLORS
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexColors), BUFFER_OFFSET(12));
}
}

If I run this with a file containing data for only one object, it runs perfectly. However, if I try it with with a file with two objects, I get what is clearly a "garbage" result.

I'd understood that I could create multiple buffers and use glEnableVertexAttribArray() to "unite" their attributes, but I'm clearly misunderstanding or doing it wrong. Any help or hints?

Dan Bartlett
10-02-2011, 12:10 PM
The information given with glVertexAttribPointer (http://www.opengl.org/sdk/docs/man/xhtml/glVertexAttribPointer.xml) and whether an attribute is enabled/disabled with glEnableVertexAttribArray (http://www.opengl.org/sdk/docs/man/xhtml/glEnableVertexAttribArray.xml) is only used when you issue a draw call, they basically say: "the next time I issue a draw call, retrieve information for attribute N in this way". If you're calling glVertexAttribPointer/glEnableVertexAttrib etc. in a loop for the same attribute, without issuing a draw call, then all the iterations except for the last one are ignored.
You either shouldn't have these calls at setup time, and instead issue them at draw time, or vertex array objects (VAOs) could be used to store this vertex array state, and switch between VAOs at draw time.

Also, instead of generating the buffer objects one by one:

for(int i=0;i<Faults.GetNumFaults();i++)
{
glGenBuffers(1, &amp;Environment.VertexGeometryAttrib[i]);
glGenBuffers(1, &amp;Environment.VertexColorAttrib[i]);
glGenBuffers(1, &amp;Environment.VIO[i]);
}you could just use:

int n = Faults.GetNumFaults();
glGenBuffers(n, &amp;Environment.VertexGeometryAttrib[0]);
glGenBuffers(n, &amp;Environment.VertexColorAttrib[0]);
glGenBuffers(n, &amp;Environment.VIO[0]);

And variable naming wise, they are also called vertices/indices rather than vertexes/indexes.

Wasabi
10-02-2011, 01:12 PM
Are you saying I have to do:

foreach(object)
{
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexGeometryAttrib[i]);
glBufferData(GL_ARRAY_BUFFER, faults[i].GetNumVertexes()*sizeof(NVertexGeometry), faults[i].GetGeometry(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0); //VERTEX COORDINATES
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexGeometry), BUFFER_OFFSET(0));
//and all the other attributes...
glDrawElements(GL_TRIANGLES, faults[i].GetNumIndexes(), GL_UNSIGNED_INT, BUFFER_OFFSET(0));
}
But wont that mean I'll be re-copying the glBufferData() at every frame?

Or can I do:

//Set up the buffers...
foreach(object)
{
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexGeometryAttrib[i]);
glBufferData(GL_ARRAY_BUFFER, faults[i].GetNumVertexes()*sizeof(NVertexGeometry), faults[i].GetGeometry(), GL_STATIC_DRAW);
}
//and then when I want to draw...
foreach(object)
{
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexGeometryAttrib[i]);
glEnableVertexAttribArray(0); //VERTEX COORDINATES
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexGeometry), BUFFER_OFFSET(0));
//and all the other attributes...
glDrawElements(GL_TRIANGLES, faults[i].GetNumIndexes(), GL_UNSIGNED_INT, BUFFER_OFFSET(0));
}

But its good to know about that shorter way of generating the buffers. I wasn't sure about the meaning of the "count" argument and while I knew it defined how many buffers were created, I didn't know if it created one for each element ([i]) or "n" buffers in that same element (whatever that would mean), so I chose to do it safely.

And regarding the names, vertexes and indexes are also permissible plural forms (though indices and indexes do have slightly different meanings and indices would be more appropriate in this case).

Dan Bartlett
10-04-2011, 06:44 AM
The second approach. However, if you could store multiple objects in the same buffer object you could reduce the number of buffer switches / vertex pointer commands per draw call though, perhaps being able to issue multiple draw calls only by changing the count + indices offset argument to glDrawElements.

Also, if you were using vertex array objects, you could set up the vertex array state ahead of time, like this:



//Set up the buffers...
foreach(object)
{
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexGeometryAttrib[i]);
glBufferData(GL_ARRAY_BUFFER, faults[i].GetNumVertexes()*sizeof(NVertexGeometry), faults[i].GetGeometry(), GL_STATIC_DRAW);
}
glGenVertexArrays(n, &amp;Environment.VAO[0]);
foreach(object)
{
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexGeometryAttrib[i]);
glBindVertexArray(Environment.VAO[i]);
glEnableVertexAttribArray(0); //VERTEX COORDINATES
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexGeometry), BUFFER_OFFSET(0));
//and all the other attributes...
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Environment.VIO[i]);
}
//and then when I want to draw...
foreach(object)
{
glBindVertexArray(Environment.VAO[i]);
glDrawElements(GL_TRIANGLES, faults[i].GetNumIndexes(), GL_UNSIGNED_INT, BUFFER_OFFSET(0));
}

It's a shame glDrawElementsBaseVertex + family (GL_ARB_draw_elements_base_vertex (http://www.opengl.org/registry/specs/ARB/draw_elements_base_vertex.txt)) weren't available in earlier versions of OpenGL (than 3.2), because before them when inserting multiple objects to be drawn into a buffer object, you either have to adjust the indices of objects you put into it if you only want to use a single batch of glVertexAttribPointer calls per buffer, or if you don't want to adjust indices, you need to call glVertexAttribPointer etc. before drawing each object to set to the starting location of each object.

Wasabi
10-04-2011, 10:33 AM
Thanks for the reply.

I'm trying to implement this now, but the reason I can't put all the objects in one buffer is because I might alter one object but not the others and as far as I know, there's no glAppendToBuffer() or glEditBufferSegment(), so I'd have to put all the data in a single buffer, which is somewhat impractical. There are no such commands, right? Because I agree that would be best.

How do people usually do this, anyways? Because the only way I can think off the top of my head to have multiple independent objects in one buffer without such commands would be (and this seems insanely impractical) to have a class containing the static arrays of data and then a child class (whose instances share the same arrays of parent data) used as the Object class with references to different parts of the parent array.

I am probably not realizing something obvious.

Dan Bartlett
10-04-2011, 12:45 PM
If the number of vertices of the objects isn't going to change much (or is constant), you can modify part of a buffer object with glBufferSubData (OpenGL 1.5+) or glMapBufferRange (OpenGL 3.0+ or extension GL_ARB_map_buffer_range). If the number of vertices grows a lot, then it might be easiest to store it in a separate buffer object.

To fill the buffer with data you could either:
a) Provide each object with a pointer + get each object to fill in their data. The pointer you provide it could be from glMapBuffer/glMapBufferRange, or from a pointer to a buffer in client memory.
b) Ask the object to provide a pointer to data, but this would involve an extra copy.
c) Allow both of these approaches, but prefer the first method where objects support it.

Here's a few examples where you provide the object with a pointer that it should fill:

1) You could map an entire buffer with glMapBuffer + call FillData(...) for every single object, they don't need to modify the data at all:


Buffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
Object1.FillData(&amp;Buffer[FindOffset(Object1)]);
Object2.FillData(&amp;Buffer[FindOffset(Object2)]);
glUnmapBuffer();


2) You could map part of a buffer with glMapBufferRange + call FillData(...) for 1 particular object:


Buffer = glMapBufferRange(GL_ARRAY_BUFFER, FindOffset(Object1), FindLength(Object1), GL_MAP_WRIE_BIT);
Object1.FillData(&amp;Buffer[FindOffset(Object1)]);
glUnmapBuffer();


3) You could even provide a pointer to a buffer under your control, rather than OpenGL's, but this requires an extra copy.


float data[1024*1024];
Buffer = data;
Object1.FillData(&amp;Buffer[FindOffset(Object1)]);
Object2.FillData(&amp;Buffer[FindOffset(Object2)]);
glBufferData(GL_ARRAY_BUFFER, BufferSize, &amp;Buffer[0], GL_STATIC_DRAW);


If you decide to instead ask the object for a pointer containing the data required (+optionally the size+offset location of data), then you could have something like this:


uint offset, size;
void* ptr;
if Object1.HasDataReady()
{
ptr = Object1.MapData();
// glBufferSubData won't return until data copied entirely
glBufferSubData(GL_ARRAY_BUFFER, FindOffset(Object1), FindLength(Object1), ptr);
Object1.UnmapData();
}

Wasabi
10-16-2011, 11:41 AM
Thanks for those other methods, but after some time away from the code, I've implemented the multiple-VAO method (easier to do with what I already had).

I'm finally able to get two different objects drawn (a big step forward!), but the second one remains a garbage result. The first one is drawn perfectly, but the second one is not. However, I have the console print out the bounding box of each object when it's created and the values for both objects are reasonable. This tells me that the problem is not with how my program interprets the data for the second fault but how my program is asking this data to be drawn the second time around. Here's what seems to be the relevant code:


// Generating the OpenGL buffers...
void MyNGLWidget::GenBuffers()
{
const NFault* faults = Faults.GetFaults();
int n = Faults.GetNumFaults();

glGenBuffers(n, Environment.VertexGeometryAttrib);
glGenBuffers(n, Environment.VertexColorAttrib);
glGenBuffers(n, Environment.VIO);
glGenVertexArrays(n, Environment.VAO);

UpdateFaultGeometry();
UpdateFaultColors();

for(int i=0;i<Faults.GetNumFaults();i++)
{
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexGeometryAttrib[i]);
glBindVertexArray(Environment.VAO[i]);
glEnableVertexAttribArray(0); //VERTEX COORDINATES
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexGeometry), BUFFER_OFFSET(0));
glEnableVertexAttribArray(1); //NORMALS
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexGeometry), BUFFER_OFFSET(12));

glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexColorAttrib[i]);
glEnableVertexAttribArray(2); //VERTEX COLORS
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexColors), BUFFER_OFFSET(0));
glEnableVertexAttribArray(3); //MESH COLORS
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(NVertexColors), BUFFER_OFFSET(12));
}

for(int i=0;i<Faults.GetNumFaults();i++)
{
glBindVertexArray(Environment.VAO[i]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,Environment.V IO[i]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int)*faults[i].GetNumIndexes(), faults[i].GetIndexes(),GL_STATIC_DRAW);
}
}
void MyNGLWidget::UpdateFaultGeometry()
{
const NFault* faults = Faults.GetFaults();

for(int i=0;i<Faults.GetNumFaults();i++)
{
glBindVertexArray(Environment.VAO[i]);
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexGeometryAttrib[i]);
glBufferData(GL_ARRAY_BUFFER, faults[i].GetNumVertexes()*sizeof(NVertexGeometry), faults[i].GetGeometry(), GL_STATIC_DRAW);
}
}
void MyNGLWidget::UpdateFaultColors()
{
const NFault* faults = Faults.GetFaults();

for(int i=0;i<Faults.GetNumFaults();i++)
{
glBindVertexArray(Environment.VAO[i]);
glBindBuffer(GL_ARRAY_BUFFER, Environment.VertexColorAttrib[i]);
glBufferData(GL_ARRAY_BUFFER, faults[i].GetNumVertexes()*sizeof(NVertexColors), faults[i].GetColors(), GL_STATIC_DRAW);
}
}
// Drawing the objects...
void MyNGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0,0,0,0);

const NFault* faults = Faults.GetFaults();
if(Initialized)
{
for(int i=0;i<Faults.GetNumFaults();i++)
{
glBindVertexArray(Environment.VAO[i]);
glDrawElements(GL_TRIANGLES, faults[i].GetNumIndexes(), GL_UNSIGNED_INT, BUFFER_OFFSET(0));
}
}
}

What seems odd is that all this is done through a series of for-loops, so if it worked for the first object, why not the second, given that the vertex data doesn't seem to be flawed?

And thanks for the help thus far.

Wasabi
10-18-2011, 07:49 AM
Sorry for the double-post, but just to update and say I figured it out. I was still thinking the wrong way (before using VAO's) and thought all the object data had to be "together". Therefore, when creating each object's VIO, I was adding an _accumVertexes term to the index-values so that no object had the same indexes.

But then I suddenly realized that, by using VAO's, I was now separating the different objects entirely and each of them would thus have to have indexes starting at zero. So I commented out one line of code and now everything is working like a charm.

Many thanks to everyone who posted here helping me out.