I don't really understand what the purpose of those texture and layer handles is.
According to the comments "Textures must be bound in advance for maximum performance. (No associative lookups!)". Why is this necessary? You end up passing the texture reference to .draw() as a sf::RenderState anyway, so you might as well let the user simply construct the texture themselves and just pass it on to SFML when the time comes. This also doesn't require any associative lookups.
Also, why do we need handles for layers? All layers are meant to do is specify an order in which the buckets of draw calls are actually dispatched. Simply storing a numerical value would suffice if you ask me.
The design of this sprite batch allows it to be quickly integrated in projects using `sf::Sprite` without any batching.
Whenever you bind a texture to the batch manager, you get an unique integer (starting from 0) identifying that texture.
Whenever you create a layer in the batch manager, the layer automatically creates contiguous data structures (`std::vector` instances, for now) for every bound texture.
When you want to draw a Batch::Sprite on a specific layer, having the sprite know the ID of the texture it has and the ID of the layer it needs to be drawn onto results in some contiguous memory direct access lookups and 4 `sf::Vertex` emplacements.
The user can simply store a Batch::Sprite instance in its game entity class (or replace `sf::Sprite` instances), and can fire-and-forget multiple Batch::Sprite draw calls - the batch manager, thanks to the unique IDs of textures and layers, will deal with minimizing draw calls.
The code for the sprite batch is here (still very primitive), but I hope it clarifies the idea:
https://github.com/SuperV1234/Experiments/blob/master/Random/batching.cppWhen you call `Batch::Sprite::draw()` you're just asking the manager to "enqueue" the sprite in the right layer, in the right vertices container for its texture.
As stated above, I don't think they are even necessary. Just let the user use their own sf::Textures as usual and specify numerical values instead of layer handles.
The user manages the lifetime of their own `sf::Textures`. Binding them to the Batch::Manager is pure convenience - having an handle object that refers to that texture that can be used in Batch::Sprites allows the user to not having to specify the wanted texture during the draw call.
I'm trying to make the system as easy as possible to substitute to existing `sf::Sprite`-based code.
The idea behind layers is that the user does not care about the drawing order of sprites in the same layer - but that may not be realistic.
One thing I'm considering to add, is another layer type, where instead of having separate buffers for every texture, there is an unique buffer where vertices are sorted inside the same buffer by an user-specified Z-order.
If the user requires more fine-tuning with it's Z-order, that type of layer can be used, but it will definitely result in more draw calls.
Better batching.
In terms of OpenGL draw calls, yes, they do get reduced, but only in optimal scenarios. Since you don't re-order the sprites in order to minimize state changes, a user specifying their sprites in a really disadvantageous way will not benefit at all from your batching. In fact, it will even add additional overhead in that case.
Where your batcher does save time is within the sf::RenderTarget methods. When batching does work, less time is spent in there and potentially on the GPU since you pre-transform vertices (this leads to more optimistic paths being taken when the GPU realizes it doesn't have to do anything). However, in exchange for reducing the time spent in those locations, we need to consider the extra time that will be spent in your batcher. From your example, it seems like it will scale linearly with the number of sprites that you actually intend to draw with it since you seem to have to reconstruct the draw queue again every frame. I just don't see the advantage your class is supposed to provide over "manual batching" via sf::VertexArray.
Have you run any performance tests using your batcher? Where does it save time? On the GPU, in the driver or in the application?
The current state of the batcher is a start, but there is still much more to do in order to be useful in real world scenarios if you ask me. When writing my own batchers (similar to how I designed the SFGUI renderers) I like to measure the amount of draw calls that are actually issued to OpenGL in total every frame. I estimate that for a typical 2D SFML application that doesn't make too much use of shaders, it can easily be dropped below 10 in total per frame. This of course requires more advanced techniques such as texture atlasing, but that is what a batcher is there for...
Having only minimally used OpenGL without SFML, I do not really have a lot of experience/knowledge on the subject. Maybe I'm approaching this in the wrong way...
You're correct when you say that "it will scale linearly with the number of sprites". I am re-creating the draw queue every frame.
But I was under the impression that calling `sf::Sprite::draw()` does actually execute an OpenGL draw call.
Drawing 10000 `sf::Sprite` instances with the same texture would result in 10000 OpenGL draw calls.
Drawing 10000 `Batch::Sprite` instances with the same texture would result in a single OpenGL draw call.
The advantage of my system, over `sf::VertexArray`, is pure convenience - binding textures and layers to the manager, and having `Batch::Sprite` instances store their texture ID and layer ID, allows the user to "think in terms of `sf::Sprite`" and still get some performance benefits from automatic batching.