TheCherno / Sparky

Cross-Platform High Performance 2D/3D game engine for people like me who like to write code.
Apache License 2.0
1.1k stars 222 forks source link

BatchRenderer2D Optimization #35

Open ed4053 opened 9 years ago

ed4053 commented 9 years ago

As we saw in one of your livestream, when you render your sprites with a max texture per drawcall of 2 you had an insane 93 drawcalls while you only had a total of 8 different textures. The reason is that your sprites are not sorted. So this advice will work both with static and dynamic buffer renderers, batched or unbatched. The idea is this:

When you submit a renderable via void submit(const Renderable2D* renderable), you don't do anything except populate a vector with the info of that submit (meaning you save a pointer to the renderable and a copy of the transformation matrix).

Then when you flush, that is where you do all the work. But before you do, the first thing is you need to sort the vector. Here you just need to sort by the texture id. that way, whatever order you submit your sprites in, if you have a max texture of 2 and 8 textures, you will have 4 draw calls max.

This becomes even more important when you can have different shaders per renderable. you then sort first by shader, second by material then by mesh.

Then you need to track the state of the opengl pipeline and you only call glUseProgram when you change shader, then for each shader, you only set the material uniforms when you change material etc. All that is quite simple to implement and is necessary to drastically reduce the number of drawcalls.

Oh and in general you shouldn't unbind things unless you absolutely know it is necessary. This is to avoid things like: glBind (something) glBind(0) --> unnecessary api call glBind(somethingElse)

Maxchii commented 9 years ago

What if you want a specific draw order in your layer? Say, Object A and B are both on the same layer, but you want B drawn over A whilst B having a lower texture ID?

ed4053 commented 9 years ago

Don't forget that this is a 3D engine so even if you use it to render 2D sprites their positions are still represented by 3D vectors. Easy to then use the Z coord to deal with this.

As a rule, the rendering of the scene should always and only be dictated by the data, not the order you submit things in.

Maxchii commented 9 years ago

Could you provide a example? Where and what vector do I need to sort. (i'm just being curious as i don't know how long the series are being halted for)

MatthewDLudwig commented 9 years ago

Ossadtchii - To my understanding this is not just a simple sorting of vector that ed is suggesting. It's also a wonderful idea and I will personally be implementing into my own engine (thank you and I can't believe I didn't think of it, but I guess that is why I went looking here for suggestions). In order to do it your self, you will need to change the submit method to store some key information about the renderable being submitted (I would use a struct that stored the shader id, the material id / name (if you are using them), and the texture id) alongside a pointer to the renderable and I would store that in the struct as well. Now when it comes to the flush method I would do as suggested and go down and sort the vector storing all of these structs first by shader id, then material id, then texture id. At the end you then just render the renderables in the new order obtained by this newly sorted vector. But I don't believe it is as simple as needing to sort a vector as the system just isn't built for it. The only way you could make such a simple change is in the Simple2DRenderer where we are still storing everything in a bulk manner. The batch renderer just ruins everything by rendering to the buffer on submission (yet again, not saying this is bad but it doesn't help in this scenario). With this suggested method you would just render them all to the buffer after sorting so you still get the benefit of the batching but also the benefits of the sorting.

Breakdown of efficiency improvements (+) and places where efficiency is hurt (-): +submit now is doing much less (unless creating the struct and storing it in the vector is more costly than writing to the vector). -flush is doing a lot more than just the draw call to render the buffer. -sorting the vector will take some time. -we are now doing all of the batching that was done each submission all in the flush method. +draw calls (the things that take up the most time) have been drastically reduced and in my opinion should far outweigh the small inefficiencies stated above as they are all things that if they were to bring your engine to it's knees show a larger underlying problem.

I hope I was able to help.

eatplayhate commented 8 years ago

Because 2D rendering doesn't define a canonical order (and, typically, requires fairly complex, order dependent alpha compositing), BatchRenderer provides a strict ordering guarantee in the form of submission order. The system is designed to perform well, even given that the input data isn't going to be optimal.