Best practices to render multiple 2D Sprite with VBO

I searched through the net and forum and I still can’t get it.
So, I was going to create my own 2D game based on OpenTK (OpenGL binding for C#, I tried ask there and i got no answer…)

My game is heavily depend on performance, it’s a musical game, the music will stay synchronize even the fps is stutter, however it require good and stable fps to make it playable, to accomplish this, I decide to create simple 2D Engine with VBO implementation.

However, it’s seems I doing it wrong, everything just works but it doesn’t so fast, the game consist with a lot of moving objects with some backgrounds (fyi, the game resolution is only 800x600), no 3D, just 2D objects.

I’m trying to handle 2D Rendering with VBO and avoid Shader for some reasons…
And here the classes that i created:

  1. VertexBufferObject, it has functions to create the buffer handle, uploading vertices and render them.
  2. Sprite, each instance of it has VertexBufferObject and Texture, the texcoord, position and transform of sprite may changed often during gameplay.
  3. Texture, it can be Texture Map/Atlas (Texture that contains lot of objects / animation of specific object) or Single Texture Object.

And here my questions:

  1. What is best practice to initialize the VBO? Currently in my engine, when an instance of VBO is created, it create buffer handle, uploading empty data with amount of size of current Vertices, and upload the vertices data before it render (via glBufferSubData). if the vertices size is changed, it will upload whole vertices.

  2. Is it good to have single instance VBO per sprite instance? or should i create single VBO instance with bunch of vertices to handle multiple sprites and update (update = modify the vertices when required and upload it to memory) the vertices before render each instance of sprite? or create VBO per unique texture?

  3. When / Should I destroy and recreate VBO instance in certain situations? e.g: switching between scenes that need to load and drop some game contents.

  4. What common errors when implementing VBO implementation? (especially 2D case)

  5. Finally, can someone provide step by step the best way to implement this?

Sorry for my bad English
and thanks in advance!

No. It’s the opposite of good.

The biggest factor regarding performance is the number of glDraw* calls. You should aim to be able to draw the entire scene in as few distinct draw calls as possible. For a 2D sprite game, I’d expect that no more than half a dozen calls should be necessary. Each draw call should draw all sprites of a particular type.

If you are changing uniform variables for each sprite, those uniforms need to be vertex attributes instead. If that makes the attribute data too larger, consider using instancing or a geometry shader to reduce it. Don’t use more precision than necessary (e.g. don’t use 32-bit floats when 8-bit or 16-bit normalised values would suffice).

Similarly, your texture data should be split across as few textures as possible. Use maximum-size textures, use array textures, bind multiple textures. For a 2D sprite game, you can probably fit all of your sprites into a single array texture, which means that you don’t need to break up draw calls to switch textures.

If some attributes (e.g. position) change every frame while others (e.g. texture coordinates) don’t, put those attributes in different VBOs or in different regions of a VBO (i.e. don’t interleave them), so that you can replace only the data which has changed.

Thanks, so I need to reduce draw calls.
Anyway, as I said before, I’m intent to not use Shader…

for bind multiple textures, can you provide link where i can learn this?

Without shaders, binding multiple textures isn’t much use here. But you can still use an array texture without shaders; the third component of the texture coordinates selects the array layer.

Actually, I still don’t get the idea…
So, I only need one instance VBO for all sprites right?

basically, each sprites has different texture (some may share same texture), mean different sizes and also vertices.
so is that mean I need to upload the vertices to the buffer each time I render each instance of sprite?
in this case, draw calls will called equal to number of sprite in the game

can you provide me / give me the external resource links the example of proper implementation (and this “array texture”)?

Yes.

Try to pack all of the texture data into a single array texture (or a small number of array textures) so that you don’t need to break up draw calls in order to switch textures.

Upload the vertices for all of the sprites at once, then draw all of the sprites with a single call.

[QUOTE=SirusDoma;1280245]
can you provide me / give me the external resource links the example of proper implementation (and this “array texture”)?[/QUOTE]
A 2D array texture is created using glTexImage3D() with a type of GL_TEXTURE_2D_ARRAY. It differs from a 3D texture in that mipmaps and filtering apply to each 2D slice separately, and the third texture coordinate is rounded to an integer and used as the layer number, rather than being a proportion of the depth in the range 0…1.

Thanks for the explanation!
I cannot test or run any codes for a while, so I’m still figuring out whether i understand it correctly.

I might can upload the vertices at once, but it’s seems i’m still doubt about drawing them at once.
If I try to understand correctly: it’s seems this only work for sprites that uses same texture (or using texture array I expect), so i only need to bind the texture once.

My vertex struct has 3 attributes and my vbo has 3 ArrayBuffer to handles those vertex attributes: _positionId, _colorId and _texCoordId

These handles generated when the corresponding data is being uploaded, here one of my codes, this codes used to upload the TexCoords (sorry for using C# code, but I think you will still understand the code):


                if (_texCoordId != 0) // check if the buffer is already exist
                    GL.DeleteBuffers(1, ref _texCoordId); // delete buffer if it's exist
                
                Vector2[] verticesTexCoords = new Vector2[Vertices.GetVertexCount()]; // Vector2 is a Vector struct with 2 Components (X and Y)
                for (int i = 0; i < Vertices.GetVertexCount(); i++)
                    verticesTexCoords[i] = Vertices[i].TexCoord; // fetch the TexCoords inside the Vertices

                GL.GenBuffers(1, out _texCoordId); // generate the buffer
                GL.BindBuffer(BufferTarget.ArrayBuffer, _texCoordId); // bind it

                // and upload it
                GL.BufferData(BufferTarget.ArrayBuffer, 
                                   (IntPtr)(Vertices.GetVertexCount() * 2 * sizeof(float)), 
                                   verticesTexCoords, 
                                   BufferUsageHint.StaticDraw);

I use similar code to upload Position and Color of the Vertices
and I use following codes to draw it


//position array buffer
GL.BindBuffer(BufferTarget.ArrayBuffer, _positionId);
GL.VertexPointer(2, VertexPointerType.Float, Vector2.SizeInBytes, IntPtr.Zero);
 
//color array buffer
GL.BindBuffer(BufferTarget.ArrayBuffer, _colorId);
GL.ColorPointer(4, ColorPointerType.UnsignedByte, 4, IntPtr.Zero);
 
//texCoord array buffer
GL.BindBuffer(BufferTarget.ArrayBuffer, _texCoordId);
GL.TexCoordPointer(2, TexCoordPointerType.Float, Vector2.SizeInBytes, IntPtr.Zero);

// Draw it
GL.DrawArrays(PrimitiveType.Quads, 0, Vertices.GetVertexCount());

Vector2.SizeInBytes will return 8, because the data type is float (4 bytes), and vector2 has 2 components (X and Y), 4x2=8
4 for Color Pointer, because ARGB with single byte for each.

I’m still doubt and here the questions left behind:

  1. Is the Position, Color and TexCoords Pointer will gonna work to draw multiple sprites? since I need to make a single draw call, I expect I only need to set those pointers once before drawing (CMIIW) (I’m not quite sure the offset is gonna correct if I got 2 quads or more there…)
  2. is it good to have separate handles for each attribute? or should i make it one?
  3. Am I doing it right?

About texture array, i’m gonna find more info on google, thanks anyway!

Using floats may be excessive for vertex positions and is definitely excessive for texture coordinates. The former may be able to use 16-bit normalised values, the latter can definitely use 16-bit normalised values and may even be able to use 8-bit normalised values.

If you change the vertex positions every frame, but change the texture coordinates or colours less often, then using separate VBOs makes it easier to avoid updating the data which hasn’t changed. Using glBufferData() to replace the buffer’s storage entirely avoids the need to wait until the buffer isn’t being used by the hardware. But if you’re going to be changing all of the attributes of all of the vertices each frame, then you may as well just use a single VBO with interleaved attributes.

It would be ideal if the data for the different attributes was stored in separate arrays, rather than using a structure. That way, you could just upload the entire array directly rather than having to make a de-interleaved copy of the data.

Depending upon the amount of texture data involved, array textures may not even be necessary. OpenGL 3 and later requires support for at least 1024x1024 textures, and you’re unlikely to find a modern desktop OpenGL implementation which doesn’t support much larger textures (16384x16384 is quite common). If you can pack all of the texture data for a particular “layer” of sprites into that, then you don’t need to use array textures (which essentially just let you pack many textures into one).

It’s not strictly necessary to be able to bind all of the texture data at once. Using a dozen draw calls for a scene (e.g. one call for each distinct category of sprite, changing the “sprite sheet” in between) isn’t a problem. The problem is with using a draw call for each sprite, or for a small number of sprites (e.g. for groups of consecutive sprites which coincidentally happen to use the same texture data, rather than organising rendering so that similar sprites are rendered as a batch).

Thank you for detailed explanations!
You make lot things clear now, but just to be safe, I want to clarify few things:

Oh yea, I will change this, thanks for the suggestion!

Just want to clarify; So, it safe to upload some attributes / whole vertices data of multiple sprites on each frame?
Let’s say I have 20+ sprites with position / texcoords that always change so often (probably each frame), is it ok to upload those attributes to the buffer each frame? or is there better way? (e.g: use matrix stack functions, like glTranslate to modify the position or glColor to modify the color)

EDIT: Also, I found this Vertex Specification Best Practices - OpenGL Wiki since position and texcoord is the probably that often changes, I guess i have no choice but to keep all of them separately

I will change this as well, thanks for the suggestion!

I just noticed that i might use 20 - 50 textures images for a single scene (or even 50+ (but it safe to assume that it will be less than 100) at certain worse cases, since the game is not yet completed).

However, i don’t want to pack the images into single texture file, because I want the game elements can be customized easily by modifying the texture (image file) directly, I only use spritesheet / texture atlas for special element in games and the object that has animations / different states (e.g: button, that has 3 states, pressed, normal and hovered, those 3 states are packed into single texture)

It’s seems i cant use single (or few) draw call here when i have multiple textures, because i need switch (bind) them before apply the vertices.
So, should I use array texture? (anyway I can’t find the example without messing with shader)

[QUOTE=GClements;1280250]
It’s not strictly necessary to be able to bind all of the texture data at once. Using a dozen draw calls for a scene (e.g. one call for each distinct category of sprite, changing the “sprite sheet” in between) isn’t a problem. The problem is with using a draw call for each sprite, or for a small number of sprites (e.g. for groups of consecutive sprites which coincidentally happen to use the same texture data, rather than organising rendering so that similar sprites are rendered as a batch).[/QUOTE]

I got the point here, thanks for the explanation!

You have to remember that it does not matter how you upload your data, it is still the same amount of data. For example, if you use immediate mode to draw your sprites, you are still uploading all the positions and texcoords every frame. So whether you use immediate mode or VBO’s to upload all the data every frame does not make a huge difference. The benefit of using VBO’s is that you can upload the data in bulk instead of piece meal. This can safe a big number of gl calls.

[QUOTE=SirusDoma;1280269]I just noticed that i might use 20 - 50 textures images for a single scene (or even 50+ (but it safe to assume that it will be less than 100) at certain worse cases, since the game is not yet completed).

However, i don’t want to pack the images into single texture file, because I want the game elements can be customized easily by modifying the texture (image file) directly, I only use spritesheet / texture atlas for special element in games and the object that has animations / different states (e.g: button, that has 3 states, pressed, normal and hovered, those 3 states are packed into single texture)

It’s seems i cant use single (or few) draw call here when i have multiple textures, because i need switch (bind) them before apply the vertices.
So, should I use array texture? (anyway I can’t find the example without messing with shader)[/QUOTE]
There are algorithms that allow you to pack all the images into a single texture on the fly. You still store them as separate images but you construct one big image at the start of your application.

Using matrix functions means using a separate draw call for each sprite, which is much worse than simply uploading updated data each frame.

If the amount of data is such that memory bandwidth becomes and issue (for 20 sprites, it isn’t; probably not even for 20,000 sprites), then you should consider using a vertex shader and instancing to “compress” the data.

If you’ll be updating most of the data every frame, you may as well interleave the attributes so that you can just copy your entire array as a single operation (that assumes that the client-side data is actually in a suitable format and you can just get a pointer to it). If e.g. position and texture coordinates change every frame, you can interleave those in one VBO but put colour in a second VBO.

There’s no particular need to have a 1-to-1 correspondence between files and textures. You can pack images into textures upon loading. Although, this will be easier if the dimensions of each image are known in advance; optimally packing an arbitrary set of rectangles inside fixed-size rectangles is a hard problem. It’s somewhat easier if the images are constrained to power-of-two sizes.

Well, you sort of have it backwards. If you want good performance, you have to design the rendering process around what’s convenient for the hardware. Breaking up draw calls hurts performance, therefore the texture data should be packed into a few large textures, therefore you can’t just use whatever texture sizes you feel like and expect the engine to deal with it.

On the other hand, if the number of sprites is actually in the dozens (rather than the thousands), “best practices” don’t really matter. You can do pretty much whatever you want and it will still only use 1% of the power of a modern GPU.

[QUOTE=Cornix;1280272]You have to remember that it does not matter how you upload your data, it is still the same amount of data. For example, if you use immediate mode to draw your sprites, you are still uploading all the positions and texcoords every frame. So whether you use immediate mode or VBO’s to upload all the data every frame does not make a huge difference. The benefit of using VBO’s is that you can upload the data in bulk instead of piece meal. This can safe a big number of gl calls.
[/QUOTE]

It make sense of everything now! thanks for detailed info!

I see, so make 2 vbo will ideal here, I will consider this depend on my actual need for the game
Thanks for the suggestion!

That is good idea! it might took a lil bit memory to store the images, but it seems good for the performance

Thanks for everything!
I got no more questions haha

[QUOTE=GClements;1280276]Using matrix functions means using a separate draw call for each sprite, which is much worse than simply uploading updated data each frame.

If the amount of data is such that memory bandwidth becomes and issue (for 20 sprites, it isn’t; probably not even for 20,000 sprites), then you should consider using a vertex shader and instancing to “compress” the data.
[/QUOTE]

ah you right! using matrix stack functions will break up into separate draw calls, that make sense
Thanks for the info!

I got the idea here, I will consider this too!
Thanks for the suggestion!

I see, since I don’t use thousands of textures, I think it will be fine to leave the textures separately
But it also good idea to pack small pieces textures into one (especially at “same layer category”)

Thanks for the information!
Your answers is really helpful!