How many VAOs and VBOs
Asked Answered
G

2

7

As far as I understand it: A VAO represents a certain state. If I bind a VAO, add some VBOs and element buffers for indicies and stuff I can save a certain state of the object(s) I want to draw and activate and draw them easily later down the road when I want to render stuff. Right?

So a VBO holds the actual data, while a VAO is just a "wrapper-object" that holds pointers to all the buffers I defined for it?

Changing the VAOs is costly (as is changing the VBOs?!). Currently I load in Meshes and combine them to models. Each Model uses it's own VAO and has a VBO (with the vertices) and an Element Buffer with their indices.

Now as I understand this, this is bullshit, as each object in my world (that has a model) uses it's own VAO. Not a problem for the ~30 objects in my world, but I want to do this right and in the future there could be hundreds or thousands of objects and then the performance would tremendously drop.

So, many objects are "the same" in terms of their models. I mean if, for example, you have a certain Tree-type in the world you probably use the same model multiple times, just in different locations.

Now how would I go about this? Should I manually keep track of my VAOs like: (pseudo-code following!)

treesVAOId = 1;
rabbitsVAOId = 2;

and then if I load a model just check if the ID was already bound (how?) and add another set of VBOs there (or even add to the right VBO? if so, how?)

I'm thinking about a big game here. Let's assume there are thousands of characters in the game. Sure not all of these are rendered at the same time, but I can't create a VAO and VBO for each of them, can I?

I feel like there is a greater part missing... like instancing and using the (more or less) same data again for different purposes efficiently.

That's the one step I'm missing, how to really optimize the use of VAOs and VBOs in the real world.

Grallatorial answered 8/4, 2015 at 13:1 Comment(2)
sounds like you want ARB_vertex_attrib_bindingBareheaded
@ratchetfreak as I must support OpenGL 3.3, nope. :(Grallatorial
B
10

What you described here is called Resource manager or at least a part of resource manager. Describing resources in an external file is a good practice, so you need a resource file where all your meshes are described in some way (consider using XML or JSON).

Class hierarchy

Here is a possible approach to class hierarchy:

Each VAO represents a mesh, defining it's vertex coordinates, texture coordinates, normals, vertex colors and so on. I think there is no reason to use same VBO in several VAOs until you have a very special case of visualization. So lets assume you use each set of data only once, i.e. classes which use VAOs should not know anything about underlying VBOs and it's not necessary to write a class wrapper for VBO.

A set of meshes (maybe containing only one mesh) represents a model. Minimal model class should include handle(s) to VAO(s) and geometry transformations information (rotate, translate, whatever you want). Why not strictly one mesh per model? Sometimes you may need to apply one transformation to a group of meshes, which of them in it's turn has it's own model-local transformation. For example such composition may be used for a kind of skeletal animation or just for rendering a character with arbitrary weapon taken from library of possible weapons. Moreover, you may group such models together getting more complex models with the same interface, so you will get a resemblance of scene graph. Anyway it's a good idea to use composite pattern for model class.

Scene should include collections of models, light sources, force fields and so on.

Resource manager

But where from will the scene (or similar game object) get it's models? Resource manager should answer this question. Make each model defined by some kind of unique identifier. In the simplest case a path in real or virtual filesystem may be considered as identifier, but it's not very flexible. To my mind, it's better to define all meshes in your resource file using expressive human-readable names and bind each name to set of data (all types of coords, colors, etc) and attributes. All your code shouldn't use models directly, but it should use handles given to you by resource manager. Obviously resource manager must keep state during program execution and between calls from different places. It's intended to track which meshes are already stored in memory and keeps VAO identifiers of all stored meshes. Consider using singleton pattern for the resource manager.

Example:

ModelHandle footman = resMan->getModel("footman.model");
//.....
footman->setLocation(x,y,z);
footman->draw();

Here the call to getModel("footman.model") begins construction of the model causing calls like

MeshHandle resMan->getMesh("footman1.mesh");

to get all meshes. And getMesh does the job caused all this explanation. It checks if such mesh was loaded before and if yes, it just returns handle to VAO containing this mesh. Otherwise it creates new VAO object, loads requested data into it and returns handle to newly created object. All subsequent requests of this object will not cause new VAO allocation.

Surely, described scene graph organization is just a rough approximation to what it should look like. For example, it does not make distinction between model and abstract scene graph node, but developing and fine-tuning such hierarchy for your engine is up to you.

Final interface to resource manager class is another topic to discuss and design. Some questions and ideas:

  • Will you use singleton or you decide to use global variable for some reason?
  • If you decide to use singleton, maybe you wish to have some other private non-singleton resource managers for some limited set of resources? Then consider designing singleton as a wrapping template class, to make such code possible:
    ResourceHandle h1 = Singleton<ResourceMan>::instance->getResource("foo");
    ResourceMan myPrivateManager;
    ResourceHandle h2 = myPrivateManager.getResource("bar");
  • Will you manage all types of resources with one comprehensive manager or use special manager class for each resource types? Second approach is better. Developing idea of second approach, charge your compiler with writing the code for you! Use template resource manager class with small subset of specialized method. Just specialize one resource creation method per resource type and get all other resource management code untouched!
  • Think over resource lifetime. When a particualr VAO should be destroyed? Consider implementing reference counter and\or borrowed reference.
  • Caching? Delete data from host memory immediately after loading it to device (video card) or keep it for some time? For how long?
  • What about streaming? It shouldn't be resource manager's domain, but streaming support can affect it.
  • glIsVertexArray function and it's analogs may be useful.

Sorting

VAOs are not the only resource you need to change while rendering a scene. You also need to change textures, shaders and even framebuffers The common way to reduce number of state changes is to sort displayable objects by some property.

For example, it's very likely that you will render a given mesh with only one shader. That's why first you may sort all meshes by shader, so you minimize number of shader changes.Then for each shader (i.e. in the list of meshes for the given shader) you may sort meshes by VAO to reduce number of VAO changes to the feasible minimum. Sort by texture? It depends on your application if you need to sort object by texture and where to do that.

Conclusion

To sum up, if you are writing a game engine, you will need a resource manager anyway. If you write a quick-and-dirty partial solution for VAOs, then you will face exactly the same questions and problems handling textures, additional framebuffers and many other objects, so it's better to implement a good resource manager once.

Useful articles to start with:

http://www.gamedev.net/page/resources/_/technical/game-programming/a-resource-manager-for-game-assets-r3807

http://www.gamedev.net/page/resources/_/technical/game-programming/a-simple-fast-resource-manager-using-c-and-stl-r2503

Very useful book:

http://www.gameenginebook.com/

Basseterre answered 8/4, 2015 at 17:52 Comment(3)
Thank you. Well, actually your answer is a bit off-topic, because I'm still not sure how many VAOs and VBOs are okay to have. If every model has its own VAO I am were I am right now. (I do have a Model and a Mesh class already. It's just that each Entity in my project has one model, hence it's own VAO) The problem I'm facing is that I want my game to perform well with hundreds of objects, not just dozens and I'm wondering if this approach isn't too resource-intense as switching VAOs and VBOs is rather expensive.Grallatorial
@Teolha I think it's impossible to give an exact number VAOs that will perform well because it depends on many things: chip model, OS and driver, host machine type. The only thing you can do is reduce number of state changes and draw calls. I added a chapter about sorting, but also may need frustum culling, occlusion queries or some type of space partitioning. CPU-performed culling techniques may (but not necessary) increase performance because they may exclude some types of meshes from processing and consequently reduce not only number of state changes, but also amount of vertex shader work.Basseterre
Of course you're right and I didn't assume that there is a global answer to "how many vaos/vbos are okay" but rather on to how to structure one's app as to reuse vaos/vbos for the same models in an efficient way while also being able to set them on different positions and manipulate just one model instead of all. Stuff like that. If I hit a Tree-Model I want this and only this tree-model to respond to this, not every single one, although they are referencing the same vao/vbo.Grallatorial
D
2

Changing VAOs is not that expensive. The exact numbers are obviously highly hardware and platform dependent. But just to give you a rough idea, I measured numbers for VAO switches/second in the low millions on laptops a few years back. Let's hypothetically say that your machine could switch VAOs 6 million times/second. If you want to reach 60 fps at that rate, you could switch VAOs 100,000 times per frame.

Now, of course you don't want to use all your CPU time for switching VAOs. Your app will have plenty of other state to change, you have to handle your own app logic, and ideally you don't want to keep your overall CPU load as low as possible. So I wouldn't want to get anywhere close to the number above. Still, switching VAOs 1000 times per frame should not be a problem on a reasonably high performance computer/device.

It's really comparable to other state changes you typically make between draw calls. You always want to minimize them (as well as the number of draw calls themselves). But as far as state changes go, binding a different VAO is typically a relatively cheap one.

If you have objects that share the same geometry, like the trees in your example, you certainly shouldn't have multiple copies of the same data. That's just common sense, and does not even have much to do with graphics. Wasting memory is of course undesirable. And even if you're not in a desperate memory crunch, having multiple copies of the same data can still hurt performance because it will likely reduce your cache hit rates.

How to design your game to make this work best is somewhat of a broad question for this format. Well, and I never wrote a serious game, so I'm not ideally qualified to give you advice anyway. My first instinct would be to have a group of classes that define distinct shapes. For example, you could have a TreeShape that owns the geometry (VAO and VBO) for a tree. Same thing for each kind of character. Then, if your scene contains a bunch of trees, you have Tree class that describes specific trees, where the instances might just contain information about the position/size of the tree, but they all share a reference to the same TreeShape. So all trees share the same VAO/VBO by using the same TreeShape, and each specific Tree only contains the information that is actually different for each tree instance.

Dineric answered 9/4, 2015 at 2:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.