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.
Are you sure you are actually reducing the number of indirect memory accesses by doing this?
With your system you are essentially going to do this when issuing the final SFML draw call:
Batch -> std::vector -> sf::Texture -> OpenGL Texture ID
By simply storing pointers to the textures just like how sf::Sprite already does it, it would look like this:
Batch -> sf::Texture -> OpenGL Texture ID
Sure, a std::vector lookup is cheap, but it still costs something, and if it can be left out, I don't see why it shouldn't.
Also, if you consider a "typical" scenario where the user queues multiple sprites to your batcher, in any thought out entity system, the user will often already specify the sprites almost in the right order for drawing. Like you said, since draw order matters when drawing sf::Sprites yourself, there will be nothing to divide into layers/buckets. I really think just specifying a numerical value as a layer identifier and using a well suited (performs well for lists which are already almost sorted) sorting algorithm on the final queue would still be more efficient than how it is currently implemented.
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.
Draw calls are only 1 side of the story. It is what many laypeople/gamers/etc. think is the main bottleneck of graphics APIs/GPUs because of certain misinformation (*cough* excuses *cough*) that game developers happen to come up with to explain why their software performs so poorly. If you want some good information about how to cut down on the OpenGL overhead and redundant state changes I recommend watching
. It is aimed primarily at OpenGL developers, but I think the parts that might interest you start from around 31:55. In order to write a good batcher, you mustn't only think about reducing draw calls or saving a few CPU memory accesses here and there, you need to look at the whole picture (and OpenGL is a really big part of that picture). I estimate that a well implemented batcher can make at least an order of magnitude difference, especially when you throw some initially really poorly optimized drawing implementations at it.
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.cpp
I've already looked at the code, quite hard to read if you are not used to it.
When 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.
You are basically making the user pre-sort the sprites in the right order already by giving them multiple buckets. The same could be done by just using multiple batchers (1 per layer) and drawing them in the right order when you are done constructing the queues. This kind of wastes potential optimization possibilities between layers.
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.
As stated above, the same could be done by saving a pointer to the sf::Texture along with each sprite in the queue instead.
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.
This is definitely not realistic.
What SFML users perform when ordering their sprites themselves is called the
painter's algorithm, you draw back to front. Since SFML doesn't support depth, this is the only option they have. It is a mistake to assume that providing a batcher allows them to all of a sudden forget about ordering all together. They will always want to order sprites, even within the same layer.
You might not know this, but the very fact that SFML doesn't support depth can have a significant impact on
raw GPU (not driver) performance.
Overdraw is the phenomenon that any experienced graphics programmer will always try to hunt down and exterminate. This means that ironically, drawing front to back actually yields higher performance if you have depth enabled, especially in scenes where you have many
non-transparent entities overlapping each other. If you have transparent entities, you are better off sticking to back to front draw order unless you are very very experienced and know how to do it front to back as well.
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.
You should really just combine this into a single queue that is sorted before drawing like I described above. Introducing too many separate concepts that are only there to solve specific edge cases will clutter up what could be a simple and intuitive API.
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...
I hope it has become obvious from what I just said that having at least a basic understanding of OpenGL is essential in order to target the
real bottlenecks.
It isn't that hard if you are willing to commit a bit of time to it. Some people might not agree with me, but I think that the modern API is easier to learn
and completely understand than the legacy API. There are way less functions and states to know about, and if you start out with familiarizing yourself with the pipeline and the concepts surrounding it, you will quickly realize that it isn't as complicated as some might think at first glance.
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.
This only works out in very very very optimal scenarios. In reality you would probably still have a few thousand draw calls for those 10000 sprites since there will often be incompatible state changes that will break the batches. This could be solved by re-ordering the sprites in order to minimize the state changes, but like I said above, this might not be what the user wants/expects. You will have to perform a lot of behind-the-scenes "magic" to reduce state changes and still produce the same final image as if (yes... just like as-if in C++
) the sprites were individually drawn using the standard method. I don't know what you still have planned, but I don't really see any of this "magic" yet.