So, yesterday I thought to myself, ooh, having a sprite batch of sorts in SFML would be very nice, so I started on one, and in my ignorance failed to check the wiki/forum for results. Which... resulted in this:
https://github.com/dabbertorres/SwiftSpriteBatch
For those who don't know what a SpriteBatch is or does, it is essentially a container of vertices that all use one texture. So, rather than having 1 draw call per sf::Sprite, it allows you to have only 1 draw call for (almost) any number of vertices, or swift::Sprites, which turns out to be much faster.
It works very much like the sprite batch on the wiki (https://github.com/SFML/SFML/wiki/Source%3A-High-Performance-Sprite-Container), but rather than working based off indices, it uses classes.
It consists of 2 classes, a SpriteBatch class, and a Sprite Class.
Here's the example from the Github page:
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <iostream>
#include "SpriteBatch.hpp"
#include "Sprite.hpp"
int main(int argc, char **argv)
{
sf::RenderWindow window;
window.create({800, 600, 32}, "SFML");
sf::Texture texture;
if(!texture.loadFromFile("ftlGrizzly.png"))
{
std::cerr << "error loading texture\n";
}
swift::SpriteBatch batch(texture, 2);
swift::Sprite spriteOne(batch);
spriteOne.setOrigin({spriteOne.getLocalBounds().width / 2, spriteOne.getLocalBounds().height / 2});
spriteOne.setScale({2, 2});
spriteOne.setPosition({400, 400});
spriteOne.scale({0.5, 0.5});
swift::Sprite spriteTwo(batch);
spriteTwo.setPosition({400, 200});
while(window.isOpen())
{
sf::Event event;
while(window.pollEvent(event))
{
switch(event.type)
{
case sf::Event::Closed:
window.close();
break;
default:
break;
}
}
window.clear();
window.draw(batch);
window.display();
}
return 0;
}
The constructor of the SpriteBatch takes a sf::Texture, and the number of Sprites you would like to have. I went with locking the size because Sprites work with pointers, and if the memory needs to be relocated due to resizing, that invalidates pointers, which causes a whole bunch of issues.
There are 2 constructors for Sprite, both take the batch you want to add the Sprite to, and 1 takes a sf::IntRect to set as the texture rectangle.
Upon destruction of a Sprite, the vertices the Sprite used are kept, but all with position (0, 0) and color (0, 0, 0, 0). This effectively makes it non-visible, without invalidating pointers.
I tried to keep similar (if not the exact same) behavior as an sf::Sprite in regards to functions to call on swift::Sprite. In my testing, it seems to act the same, so here's to hoping it is truly that way. Hehe.
I'd love any feedback anyone has and am happy to answer any questions anyone may have!
EDIT: Should add that it uses C++11.
I think you might have a problem with invalid pointers in certain situations.
You use:
vertices = batch.addSprite();
where vertices ist a vector<sf::Vertex *>.
But if your Vertex-vector in SpriteBatch gets reallocated because it grows to big, your Pointers in the Sprite wont be valid anymore.
You use:
vertices = batch.addSprite();
where vertices ist a vector<sf::Vertex *>.
But if your Vertex-vector in SpriteBatch gets reallocated because it grows to big, your Pointers in the Sprite wont be valid anymore.
I haven't taken a closer look at the code, but this statement itself is not true.
Yes a vector can reallocate its elements, but that is only an issue, if you hold a pointer to an element of the vector, because the address this pointer is pointing towards doesn't hold the element anymore since it got relocated.
But what we have here is a vector of pointers, thus as long as the object, "referenced" by the pointer, stays alive, the pointer in the vector will be valid. If the vector reallocates its elements, the "content" of the elements won't change, thus the pointer will still point to the same address after reallocation.
I hope that made sense. :D
I think you missunderstood me.
SpriteBatch has a std::vector<sf::Vertex>
Sprite has Pointers to the vertices in SpriteBatch with std::vector<sf::Vertex *>.
So if SpriteBatch should reallocate its vertices, the Pointer in Sprite won't be valid anymore.
Please correct me if I got it wrong.
But I didn't see, that he won't add elements to his SpriteBatch-Vector if a certain size is reached (in which case he will just return an nullptr). So the Pointers in the return will stay valid.
std::array<sf::Vertex*, 4> SpriteBatch::addSprite()
{
if((spriteNum + 1) * 4 <= vertices.size())
{
unsigned int s = spriteNum * 4;
spriteNum++;
return {&vertices[s], &vertices[s + 1], &vertices[s + 2], &vertices[s + 3]};
}
else
return {nullptr};
}