PDA

View Full Version : OpenGL VAO VBO EBO explained



gremlin
10-16-2016, 12:10 PM
Hello.
I'm new to opengl, and graphics programming too.
I have an idea (and some tris drawned at practice) on how opengl drawing stuff. The following questions have arisen in the course of training.

Suppose my final scene should contain dozens of dynamic animated sprites and static backgrounds. What is in this case the sequence of creation VertexArrays and filling it?
Lets assume i have Sprite class that basically draws simple textured quad, that can be animated and can translate on the scene. And in my scene i have N of them.

1) Should i have only one VAO (VBO?) with 4 vectors in it (trow away colors and textCoords for a moment), and draw that array (with each Sprite transformations) N times, where N is amount of Sprites in the scene?

2) What if i have many different types of Sprites, that can be not only quads?

Thanks for advice!
PS Sory for bad english!

Dark Photon
10-16-2016, 01:59 PM
There's what's possible, and then what subset of that is fast. Are you asking about what permutations are likely to perform better than others?

It sounds like before you consider either, you need to learn what the relevant object types are. Recommended reading:

* Vertex Buffer Objects (VBOs) (https://www.opengl.org/wiki/Vertex_Specification#Vertex_Buffer_Object)
* Vertex Array Objects (VAOs) (https://www.opengl.org/wiki/Vertex_Specification#Vertex_Array_Object)

VBOs are buffers that contain vertex attribute and index data (the latter is sometimes called element data). VAOs are nothing more than wrappers (think structs) of draw call setup parameters -- e.g. pointers to VBOs and such. VAOs can speed things up when you need to render the same draw calls with the same parameters (aka batches) multiple times, because they save you from needing to re-specify everything again on the 2nd and subsequent renders.

There are lots of ways you can map your problem to this model. For best performance, generally you want to minimize the number of times you make the driver bind a new buffer object, and you want to use VAOs to "cache" draw call settings where you may be rendering the same batches multiple times. Both of these apply "unless" you're using NVidia's bindless buffer extensions, and then neither matters -- you'll likely get better performance from just using bindless.

Also for best performance, you generally want to minimize the number of state change groups and batches (draw calls) you're sending down the pipe each frame.

Hopefully that gives you a start on how to map your problem to OpenGL with good performance. As you get into this, please do follow-up here if you have more detailed questions. And feel free to post a short GLUT test program illustrating your question(s). That often helps spark folks to try it and chime in with useful feedback.

gremlin
10-17-2016, 07:59 AM
Alright! I got a little information about the VBO and i wanna know do I understand it correctly:

Imagine my program is 2d scene with only "sprite" like drawables.
I create VBO and fill it with 4 points(vertex) FOR EACH individual "sprite" instance on the scene and render it with glDrawArray?
What should i do with VBO if one of sprites no longer exists on the scene? Refill it again?

mhagain
10-17-2016, 08:31 AM
What should i do with VBO if one of sprites no longer exists on the scene? Refill it again?

The simplest way is to just not draw the sprite. You don't need to touch the VBO for this.

For example, assume that you have 5 sprites in your VBO, numbered 1, 2, 3, 4, 5. Draw them like so:
glDrawArrays (GL_QUADS, 0, 4);
glDrawArrays (GL_QUADS, 4, 4);
glDrawArrays (GL_QUADS, 8, 4);
glDrawArrays (GL_QUADS, 12, 4);
glDrawArrays (GL_QUADS, 16, 4);Now assume that you no longer wish to draw sprite number 3. Don't touch the VBO, just change the code to:
glDrawArrays (GL_QUADS, 0, 4);
glDrawArrays (GL_QUADS, 4, 4);
glDrawArrays (GL_QUADS, 12, 4);
glDrawArrays (GL_QUADS, 16, 4);

gremlin
10-17-2016, 08:51 AM
Thanks for advice!

What if i have not 5 but 500 sprites?
glBufferData with DYNAMIC_DRAW flag is for these type of buffering?
Assuming most of sprites are translated and rotated and scaled each frame.

GClements
10-17-2016, 09:45 AM
What if i have not 5 but 500 sprites?
glBufferData with DYNAMIC_DRAW flag is for these type of buffering?
Assuming most of sprites are translated and rotated and scaled each frame.
If you're going to be changing most of the vertex data every frame, you may as well just replace the entire buffer with glBufferData() each time.

That has an advantage over using glBufferSubData() or glMapBuffer() in that it doesn't need to wait for the GPU to finish reading the data; it can detach any existing memory block from the buffer immediately then free it once the GPU has finished reading from it.

gremlin
10-17-2016, 11:47 AM
Great! It has become much clearer.

Now I would like to clarify the simple way to achieve individual sprites drawning, with acceptable perfomance.



This is my tryout for simple triangle. It is any corect?



C#, i think it does really matter, otherwise - please say

namespace Test
{
public struct Vertex
{
public Vector2 pos;
public Color4 col;
public Vector2 tex;

public Vertex(Vector2 pos, Color4 col, Vector2 tex)
{
this.pos = pos;
this.col = col;
this.tex = tex;
}
}

class VboRenderer
{
private int VboId;
private int EboId;

private Vertex[] Vertices;
private ushort[] Indices;

public VboRenderer()
{
InitializeVBO();
}

private void InitializeVBO()
{
//Initialize vertex data and index data
//Simple triangle in ccw winding
Vertices = new Vertex[3]
{
new Vertex(new Vector2(0, 0), Color4.White, new Vector2(0, 0)),
new Vertex(new Vector2(100, 0), Color4.White, new Vector2(1, 0)),
new Vertex(new Vector2(0, 100), Color4.White, new Vector2(0, 1))
};

//Index data for what order to draw vertices
Indices = new ushort[3]
{
0, 1, 2
};

//Generate a vbo buffer
GL.GenBuffers(1, out VboId);
GL.BindBuffer(BufferTarget.ArrayBuffer, VboId);
GL.BufferData(BufferTarget.ArrayBuffer,
(IntPtr)(Vertices.Length * BlittableValueType.StrideOf(new Vertex())),
Vertices, BufferUsageHint.StaticDraw);
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

//Generate a ebo buffer
GL.GenBuffers(1, out EboId);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, EboId);
GL.BufferData(BufferTarget.ElementArrayBuffer,
(IntPtr)(Indices.Length * sizeof(ushort)),
Indices, BufferUsageHint.StaticDraw);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
}

public void RenderVBO()
{
GL.PushMatrix();
GL.EnableClientState(ArrayCap.VertexArray);
GL.EnableClientState(ArrayCap.ColorArray);
GL.EnableClientState(ArrayCap.TextureCoordArray);

GL.BindBuffer(BufferTarget.ArrayBuffer, VboId);

// here is the where i stuck
GL.VertexPointer( 2 , VertexPointerType.Float, BlittableValueType.StrideOf( ? ), ? ); // ??? Vertex.pos
GL.VertexPointer( ? , VertexPointerType. ? , BlittableValueType.StrideOf( ? ), ? ); // ??? Vertex.col
GL.VertexPointer( 2 , VertexPointerType.Float, BlittableValueType.StrideOf( ? ), ? ); // ??? Vertex.tex


GL.BindBuffer(BufferTarget.ElementArrayBuffer, EboId);
GL.DrawElements(PrimitiveType.Triangles, Indices.Length, DrawElementsType.UnsignedShort, 0);

GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);

GL.DisableClientState(ArrayCap.VertexArray);
GL.DisableClientState(ArrayCap.ColorArray);
GL.DisableClientState(ArrayCap.TextureCoordArray);
GL.PopMatrix();
}
}
}


So in RenderVBO method i need to call GL.VertexPointer to define vertices layout and i think i need to use "offset"parameter ?

How do i do this kind of defining the data with custom Vertex type, contains Vector2 and Color4 ... ?
How do i find correct stride value fo Vertex ?

mhagain
10-17-2016, 01:48 PM
You wouldn't put a single triangle into a VBO; that's hugely inefficient as you would need to change buffer objects every time you draw a triangle. Instead you put many triangles into a single buffer object, then use the first and count parameters of glDrawArrays to specify the range of the VBO to draw.

The stride is the size of your vertex struct; in C or C++ you would use sizeof, I don't know the C# equivalent.

gremlin
10-17-2016, 03:36 PM
You wouldn't put a single triangle into a VBO;

Exactly. I apologize that I forgot to say about it.
I understand that in order to maximize performance, I need to push away repetitive binding calls, and keep sprites data in a little amount of individual buffers.



So..
To keep things simple, i need to make some kind of Batcher-like(?) class, that will be manage those types of buffers, and have some methods to load and handle textures and draw final sprites geometry, so i can draw it with single call: Batcher.DrawSprite(texture, x, y, angle, scale, color);

Is that a good way for a simple 2d scene?

john_connor
10-17-2016, 04:51 PM
I understand that in order to maximize performance, I need to push away repetitive binding calls, and keep sprites data in a little amount of individual buffers.

i recently answered a that in another topic
https://www.opengl.org/discussion_boards/showthread.php/198926-Weird-glVertexAttribPointer-bug?p=1284180#post1284180

more detailed information can be found here:
https://www.opengl.org/wiki/Vertex_Specification_Best_Practices

in short:
use 1 vertex array for 1 vertex layout
use 1 buffer for all vertex attributes, or 1 for each attribute
if you need a buffer for dynamic data (e.g. a dynamically changing model), use 1 buffer for all the dynamic mesh data
consider double buffering + transform feedback if you have dynamically changing mesh data



To keep things simple, i need to make some kind of Batcher-like(?) class, that will be manage those types of buffers, and have some methods to load and handle textures and draw final sprites geometry, so i can draw it with single call: Batcher.DrawSprite(texture, x, y, angle, scale, color);

Is that a good way for a simple 2d scene?


for a simple 2D renderer (with static meshes), you first create the mesh data and put everything into 1 buffer

struct DrawCall {
unsigned int Primitive, VertexOffset, VertexCount;
} triangle, quad, star, youknowwhat, ...;

std::vector<Vertex> vertices;
// create triangle vertices and set the drawcall data:
triangle.primitive = GL_TRIANGLES;
triangle.VertexOffset = vertices.size();
vertices.pushback(Vertex(0, 0));
vertices.pushback(Vertex(1, 0));
vertices.pushback(Vertex(0, 1));
triangle.VertexCount = vertices.size() - triangle.VertexOffset;
// do the same for quad / star / youknowwhat...
// when done, put all the data into 1 big buffer


if you want to draw everything, call
glUseProgram(myShader);
glBindVertexArray(myVAO);

for (..alltriangles..)
// set uniform data here
glDrawArrays(triangle.primitive, triangle.VertexOffset, triangle.VertexCount);


ask yourself the question:
-- why should a triangle have its own buffer instead of sharing it with the quad ?
-- has the triangle and the quad the same vertex attributes ? if yes, sharing the same vertex array / program / shaders is a good idea