PDA

View Full Version : glDrawElements and multidimensional array



miroslav_karpis
02-18-2010, 02:50 PM
Hi,
please can you help me with this? I need to draw a grid (VBO) with 64x64. I defined the vertexes but when I want to use them via ┤indices┤. The problem is following.
When I create a defined multidimensional array for example:
GLushort idxBlock_1[63][127];, then load it via for loop the glDrawElements does not display data properly. When I check them, the data are the same - between the arrays.

When I then define the indices array as empty, for example:
GLushort idxBlock_1[][127]; and the define the the data manually (without for loop) I get the data displayed correctly.

The only difference is the comma (,) between the arrays. Example:
GLushort indices[][2] = {{0, 1}, {2, 3}};

The point is, that I would like to use only one glDrawElements call (because of the performance).

Thank you in advance.

Dark Photon
02-18-2010, 03:55 PM
Example:
GLushort indices[][2] = {{0, 1}, {2, 3}};
The point is, that I would like to use only one glDrawElements call (because of the performance).
glDrawElements takes a flat list of indices. There are no "2D indices". Similarly, vertex arrays take a flat list of vertex attributes. There are no 2D attributes.

What you need to do is take your mesh, and assign a "single" index to each vertex.

For instance, for good vertex cache coherence with a regular list, just grab a subgrid of N*8 or N*16 verts, and walk them along the the short (8 or 16) dimension as primary, then the long dimension as secondary (e.g. L->R, T->B if you pick your 8 or 16 in width). As you touch a vertex, assign it an vertex index, and serialize its vertex data into flat vertex attribute arrays (or a single interleave array). Repeat for other subgrids in the mesh until you're done. Though you can alternative B->T with T->B for each for maybe a tiny bit better vertex cache usage.

Then after you've assigned all your mesh verts an index, go back and traverse the verts in the same order, streaming out vertex indices into your flat index array.

With this you can render the entire mesh with one glDrawElements call.

miroslav_karpis
02-18-2010, 11:11 PM
What you need to do is take your mesh, and assign a "single" index to each vertex.

For instance, for good vertex cache coherence with a regular list, just grab a subgrid of N*8 or N*16 verts, and walk them along the the short (8 or 16) dimension as primary, then the long dimension as secondary (e.g. L->R, T->B if you pick your 8 or 16 in width). As you touch a vertex, assign it an vertex index, and serialize its vertex data into flat vertex attribute arrays (or a single interleave array). Repeat for other subgrids in the mesh until you're done. Though you can alternative B->T with T->B for each for maybe a tiny bit better vertex cache usage.

Then after you've assigned all your mesh verts an index, go back and traverse the verts in the same order, streaming out vertex indices into your flat index array.

With this you can render the entire mesh with one glDrawElements call.

Thank you for the tip - but unfortunately am not able to get the idea. Does this ┤method┤ have a name/can I find some examples of it? I didn┤t mention that but I need to draw it as TRIANGLE_STRIP.

Dark Photon
02-19-2010, 02:56 PM
Thank you for the tip - but unfortunately am not able to get the idea. Does this ┤method┤ have a name/can I find some examples of it? I didn┤t mention that but I need to draw it as TRIANGLE_STRIP.
I doubt it. It's pretty simple. And yes, this is completely consistent with using TRIANGLE_STRIPs for drawing.

It's not complicated. For starters just think about walking all the way across the top row of triangles from left-to-right. Repeat for all subsequent rows, top-to-bottom. Number your vertices in the order that you touch them "first" when traversing the mesh, and dump new vertex indices into the index list in the appropriate order to get them to strip correctly. You're done!

All I was trying to say with splitting the mesh up into 8-vert-wide or 16-vert-wide chunks is that you can get better vertex cache coherence with that if you're vertex transform bound. But don't worry about that yet -- just get it working.

miroslav_karpis
02-19-2010, 04:52 PM
It's not complicated. For starters just think about walking all the way across the top row of triangles from left-to-right. Repeat for all subsequent rows, top-to-bottom. Number your vertices in the order that you touch them "first" when traversing the mesh, and dump new vertex indices into the index list in the appropriate order to get them to strip correctly. You're done!


hmm..still don┤t have the full picture. So for example if we take following vertices (2x2 grid) - (I have organized in TRIANGLE_STRIP order, left-right, top-bottom as you mentioned):

GLfloat block[][3]={
{0.0, 1.0, -1.0}, {0.0, 0.0, -1.0}, {1.0, 1.0, -1.0},// row 0
{1.0, 0.0, -1.0}, {2.0, 1.0, -1.0}, {2.0, 0.0, -1.0},// row 0
{0.0, 0.0, -1.0}, {0.0, -1.0, -1.0}, {1.0, 0.0, -1.0},// row 1
{1.0, -1.0, -1.0}, {2.0, 0.0, -1.0}, {2.0, -1.0, -1.0},// row 1
};

Now I should create indices array, but here I┤m lost because of the TRIANGLE_STRIP. I can not figure out how to create indices only in one index array.

Dark Photon
02-19-2010, 05:56 PM
hmm..still don┤t have the full picture. So for example if we take following vertices (2x2 grid) - (I have organized in TRIANGLE_STRIP order, left-right, top-bottom as you mentioned):

GLfloat block[][3]={
{0.0, 1.0, -1.0}, {0.0, 0.0, -1.0}, {1.0, 1.0, -1.0},// row 0
{1.0, 0.0, -1.0}, {2.0, 1.0, -1.0}, {2.0, 0.0, -1.0},// row 0
{0.0, 0.0, -1.0}, {0.0, -1.0, -1.0}, {1.0, 0.0, -1.0},// row 1
{1.0, -1.0, -1.0}, {2.0, 0.0, -1.0}, {2.0, -1.0, -1.0},// row 1
};

Now I should create indices array, but here I┤m lost because of the TRIANGLE_STRIP. I can not figure out how to create indices only in one index array.


Simple. Your vertex indices are:


0,1,2,3,4,5,6,7,8,9,10,11

right? So just rip those into an index list in the order that you need for the primitive you're drawing.

HOWEVER, you can improve this. Notice that you're specifying vertex positions for the y=0 vertices "twice". This is wasteful, causes the GPU needless work, and will cause your rendering to be slower if you're vertex bound. So... what to do?

Get rid of the duplicates! This is what I was telling you about assigning a vertex index the "first" time you hit it. Pictorially, this is really easy to see:



0 2 4

1 3 5

6 7 8


Then you just rip the positions for each one of the indices into the vertex arrays in index order! Referring to the above cheesy grid diagram :


GLfloat block[][3]={
{0.0, 1.0, -1.0}, // 0
{0.0, 0.0, -1.0}, // 1
{1.0, 1.0, -1.0}, // 2
{1.0, 0.0, -1.0}, // 3
{2.0, 1.0, -1.0}, // 4
{2.0, 0.0, -1.0}, // 5
{0.0, -1.0, -1.0}, // 6
{1.0, -1.0, -1.0}, // 7
{2.0, -1.0, -1.0}, // 8
};

Notice that we only put "9" vertex positions into the positions list (i.e. the position vertex attribute array), where as you put "12" -- because 3 of yours were duplicate. Here we didn't duplicate, and thus the GPU can save some work by "re-using" the old computations it made for a vertex the first time it hit it when it hits it again (if you keep your grid stride down to 8-16 or so, so that that vertex is still in the vertex cache).

So with this new list, what does this index list look like?

If you were rendering with GL_TRIANGLES, you'd have:


GLushort indices[] = { 0,1,2, 2,1,3, 2,3,4, ... }

But if you are (as you said) rendering with GL_TRIANGLE_STRIP, then you have:


GLushort indices1[] = { 0,1,2,3,4,5 }
GLushort indices2[] = { 1,6,3,7,5,8 }


That's one possibility. These can be rendered with two glDrawElements calls (or a glMultiDrawElements call, which is the same thing), both with the same vertex positions list.

Also, you can of course combine these two strips into one long strip by using a trick such as degenerate triangles or primitive restart.

Also note that this ordering isn't the absolute optimal for rendering a regular grid but it'll get you going so you can lock in the concept of glDrawElements and rendering with vertex indices.

For more tips on the optimal triangle/vertex order, see:

* Optimal Grid Rendering (Ignacio Casta˝o) (http://castano.ludicon.com/blog/2009/02/02/optimal-grid-rendering/)

miroslav_karpis
02-20-2010, 03:27 AM
Also, you can of course combine these two strips into one long strip by using a trick such as degenerate triangles or primitive restart.


Millions of thanks for your reply! - I┤m working on a terrain rendering (VBO) so I will go for the performance (just one drawElements call). Based on what I read I think that primitive restart might be the most fast and less performance consuming. But unfortunately I┤m figuring out that it is GL_PRIMITIVE_RESTART is not supported on my pc (macBook), so would have to go for the degenerate triangles.

One more question about the ┤degenerate triangles┤- I want to manipulate z-coordinate of the grid in a vertex shader. Will I not face problems because of duplicity vertices (in some places)?

Dark Photon
02-22-2010, 06:48 PM
Millions of thanks for your reply!
No problem!


...GL_PRIMITIVE_RESTART is not supported on my pc (macBook), so would have to go for the degenerate triangles.

One more question about the ┤degenerate triangles┤- I want to manipulate z-coordinate of the grid in a vertex shader. Will I not face problems because of duplicity vertices (in some places)?
Shouldn't, no. Degenerate triangles result in triangles with zero area, which the card detects and throws out. Besides, it detects that the verts are duplicates because you used the same index for two verts, not because you used the same vertex shader inputs or got the same outputs.

However, I've seen vendor docs that say avoid degenerate triangles. Dunno the driver-guts reason why. They work. But there you have it.

However, in practice using indexed TRIANGLES is the same perf as using TRIANGLE_STRIPs, assuming the same vertex and triangle order and triangles per batch. The "only" difference is the extra index list bandwidth, which is in practice never the bottleneck (never heard of it being for anyone else and it's never been for me).

So you can avoid degenerates merely by using TRIANGLEs instead of TRIANGLE_STRIPs. For instance:

TRIANGLE STRIP indices: 0,1,2,3,4,5,...
TRIANGLEs indices: 0,1,2,2,1,3,2,3,4,4,3,5,...

dCelsius
03-02-2010, 11:24 AM
Hey! I'm having a problem with glDrawElements too! I'm studying using the OpenGL redbook.

This is what my code is showing:
http://dl.dropbox.com/u/42786/OrderIssue.png

Is was suppose to show a spinning cube, but the faces should be right and they just appear weird. Any one has any idea what's going on?

I thought the problem was with the order of the indices, but I tried a lot of combinations and it didn't worked. =/

My code:



static GLfloat spin = 0.0;

static GLfloat box1[][6] =
{// X Y Z R G B
{1.0, 1.0, 10.0, 1.0, 0.0, 0.0} , // 0 -- RED
{10.0, 1.0, 10.0, 0.0, 1.0, 0.0}, // 1 -- GREEN
{10.0, 10.0, 10.0, 0.0, 0.0, 1.0 }, // 2 -- BLUE
{1.0, 10.0, 10.0, 1.0, 1.0, 1.0 }, // 3 -- WHITE
{1.0, 1.0, 1.0, 0.5, 0.5, 0.5}, // 4 -- GRAY
{10.0, 1.0, 1.0, 1.0, 1.0, 0.0 }, // 5 -- YELLOW
{10.0, 10.0, 1.0, 1.0, 0.0, 1.0}, // 6 -- MAGENT
{1.0, 10.0, 1.0, 0.0, 1.0, 1.0 } // 7 -- CYAN
};

GLubyte indices[][4] = {
{ 0, 3, 2, 1 },
{ 4, 7, 2, 5 },
{ 0, 3, 7, 4 },
{ 5, 6, 2, 1 },
{ 7, 3, 2, 6 },
{ 0, 4, 5, 1 } };

void init(void) {
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);

GLuint bufPointer[2];

glGenBuffers(2, bufPointer);

glBindBuffer(GL_ARRAY_BUFFER, bufPointer[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(box1), box1, GL_STATIC_DRAW);

glColorPointer(3, GL_FLOAT, 6*sizeof(GLfloat), BUFFER_OFFSET(3*sizeof(GLfloat)));
glVertexPointer(3, GL_FLOAT, 6*sizeof(GLfloat), BUFFER_OFFSET(0));

glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufPointer[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

}

void display(void) {
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glPushMatrix();
glRotated(spin, 1, 1, 1);
glScaled(40, 40, 40);

glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));

glPopMatrix();
glutSwapBuffers();
}

void process(void){
spin += 0.05;
if (spin > 360.0) spin = spin - 360.0;
glutPostRedisplay();
}

void reshape(int w, int h){
glViewport(0, 0, (GLdouble) w, (GLdouble) h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-w, w, -h, h, -1000.0, 1000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

int main(int argc, char** argv){
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA );
glutInitWindowSize(250, 250);
glutInitWindowPosition(100, 100);
glutCreateWindow("OpenGL Test");

init();

glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutIdleFunc(process);

glutMainLoop();
return 0;
}



Thanks!

Stanley L
03-02-2010, 01:38 PM
@dCelcius, your application is missing a depth buffer.

In your main...


glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);


In your init...


glEnable(GL_DEPTH_TEST);


In your display...


glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

dCelsius
03-03-2010, 05:35 AM
Thank you very much Stanley L!

Now it works!