Add SpriteBatch

This commit is contained in:
Miron Alexandru 2022-07-12 21:45:54 +03:00 committed by Chris Thrasher
parent 5bd3722598
commit e09cbbfc90
No known key found for this signature in database
GPG Key ID: 56FB686C9DFC8E2C
24 changed files with 1136 additions and 3 deletions

View File

@ -26,6 +26,7 @@ if(SFML_BUILD_GRAPHICS)
add_subdirectory(shader)
add_subdirectory(island)
add_subdirectory(vulkan)
add_subdirectory(sprite_batch)
endif()
if(SFML_OS_WINDOWS)

View File

@ -0,0 +1,435 @@
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#include <array>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <map>
#include <numeric>
#include <random>
#include <vector>
#ifdef SFML_SYSTEM_IOS
#include <SFML/Main.hpp>
#endif
std::filesystem::path resourcesDir()
{
#ifdef SFML_SYSTEM_IOS
return "";
#else
return "resources";
#endif
}
enum class BatchDrawables
{
Sprite,
Text,
Shape,
ShapeNoTexture
};
std::string getRandomString(std::mt19937& mt)
{
static const std::array<std::string, 7> texts =
{"",
"Hello World!",
"London is the capital \nof Great Britain.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ut augue in dui \n"
"posuere sodales nec a orci. Phasellus sit amet tellus lobortis, scelerisque \n"
"dui dictum, posuere risus. Nam pellentesque gravida elit, id convallis tortor \n"
"aliquet sed. Duis vitae cursus risus, fermentum condimentum ex. Proin mattis \n"
"orci a malesuada commodo. Nunc egestas erat non volutpat elementum. Vivamus \n"
"pulvinar tortor eleifend libero convallis, at volutpat turpis lobortis. \n",
"'Tis but a flesh wound.",
"SFML Test Demo Thingy Stuff (Epic)",
"?!?!?!111(one)"};
return texts[std::uniform_int_distribution<std::size_t>(0, texts.size() - 1)(mt)];
}
std::string getDisplayString(sf::SpriteBatch::BatchMode batchMode)
{
static const std::map<sf::SpriteBatch::BatchMode, std::string> modeDisplay =
{{sf::SpriteBatch::BatchMode::Deferred, "Deferred"},
{sf::SpriteBatch::BatchMode::TextureSort, "TextureSort"},
{sf::SpriteBatch::BatchMode::DepthSort, "DepthSort"}};
if (modeDisplay.find(batchMode) != modeDisplay.end())
return modeDisplay.at(batchMode);
return "Unknown";
}
std::string getDisplayString(BatchDrawables drawableMode)
{
static const std::map<BatchDrawables, std::string> modeDisplay =
{{BatchDrawables::Sprite, "Sprite"},
{BatchDrawables::Text, "Text"},
{BatchDrawables::Shape, "Shape"},
{BatchDrawables::ShapeNoTexture, "Shape (No Texture)"}};
if (modeDisplay.find(drawableMode) != modeDisplay.end())
return modeDisplay.at(drawableMode);
return "Unknown";
}
////////////////////////////////////////////////////////////
/// Entry point of application
///
/// \return Application exit code
///
////////////////////////////////////////////////////////////
int main()
{
std::random_device rd;
std::mt19937 mt(rd());
// Define some constants used within the demo
const int windowWidth = 1600;
const int windowHeight = 900;
const int sameTextureChance = 85;
const std::size_t drawableCountChange = 100;
const std::size_t maxTimesSaved = 60;
const std::size_t drawablesPerLayer = 250;
// Create the window of the application
sf::VideoMode videMode({windowWidth, windowHeight}, 32);
sf::RenderWindow window(videMode, "SFML SpriteBatch", sf::Style::Titlebar | sf::Style::Close);
window.setVerticalSyncEnabled(true);
// Load texture assets
std::vector<sf::Texture> textures;
sf::Texture tempTexture;
int currentTex = 0;
while (tempTexture.loadFromFile(resourcesDir() / ("Tex" + std::to_string(currentTex) + ".png")))
{
textures.push_back(std::move(tempTexture));
tempTexture = sf::Texture();
++currentTex;
}
// Load font for debug text
sf::Font font;
if (!font.loadFromFile(resourcesDir() / "tuffy.ttf"))
return EXIT_FAILURE;
// Different types of drawables
// For simplicity, we keep N objects of each type
std::vector<sf::Sprite> sprites;
std::vector<sf::CircleShape> shapes;
std::vector<sf::CircleShape> shapesNoTexture;
std::vector<sf::Text> texts;
// Text used for debug info
sf::Text debugInfoText("", font);
debugInfoText.setOutlineColor(sf::Color::Blue);
debugInfoText.setOutlineThickness(4);
debugInfoText.setFillColor(sf::Color::White);
// Define some state used in the demo
std::size_t drawableCount = 100;
sf::SpriteBatch batch;
sf::SpriteBatch::BatchMode batchMode = sf::SpriteBatch::BatchMode::Deferred;
bool batchEnabled = false;
bool useStaticBatch = false;
std::vector<sf::Time> timeHistory;
sf::Time timeReference;
std::size_t framesTillRefUpdate = 0;
sf::Clock clock;
BatchDrawables benchmarkedDrawable = BatchDrawables::Sprite;
while (window.isOpen())
{
// Handle events
for (sf::Event event; window.pollEvent(event);)
{
// Window closed or escape key pressed: exit
if (event.type == sf::Event::Closed ||
(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape))
{
window.close();
break;
}
// W key pressed: increase drawable count
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Up)
{
drawableCount += drawableCountChange;
}
// S key pressed: decrease drawable count
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Down)
{
drawableCount -= std::min(drawableCount, drawableCountChange);
}
// R key pressed: cycle through the drawables to render
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::D)
{
switch (benchmarkedDrawable)
{
case BatchDrawables::Sprite:
benchmarkedDrawable = BatchDrawables::Text;
break;
case BatchDrawables::Text:
benchmarkedDrawable = BatchDrawables::Shape;
break;
case BatchDrawables::Shape:
benchmarkedDrawable = BatchDrawables::ShapeNoTexture;
break;
case BatchDrawables::ShapeNoTexture:
benchmarkedDrawable = BatchDrawables::Sprite;
break;
}
}
// F or G key pressed: cycle through batch modes
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::F)
{
switch (batchMode)
{
case sf::SpriteBatch::BatchMode::Deferred:
batchMode = sf::SpriteBatch::BatchMode::TextureSort;
break;
case sf::SpriteBatch::BatchMode::TextureSort:
batchMode = sf::SpriteBatch::BatchMode::DepthSort;
break;
case sf::SpriteBatch::BatchMode::DepthSort:
batchMode = sf::SpriteBatch::BatchMode::Deferred;
break;
}
}
// G key pressed: toggle batching
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::G)
{
batchEnabled = !batchEnabled;
}
// H key pressed: toggle static batch
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::H)
{
useStaticBatch = !useStaticBatch;
}
// Window size changed, adjust view appropriately
if (event.type == sf::Event::Resized)
{
sf::View view;
view.setSize({windowWidth, windowHeight});
view.setCenter({windowWidth / 2.f, windowHeight / 2.f});
window.setView(view);
}
}
// Clear the window
window.clear(sf::Color(50, 50, 50));
// Delete excess drawables
while (drawableCount < sprites.size())
{
sprites.pop_back();
texts.pop_back();
shapes.pop_back();
shapesNoTexture.pop_back();
}
// Add new drawables
while (drawableCount > sprites.size())
{
float x = std::uniform_real_distribution<float>(0, windowWidth)(mt);
float y = std::uniform_real_distribution<float>(0, windowHeight)(mt);
float degrees = std::uniform_real_distribution<float>(0, 360)(mt);
const sf::Texture* texture = &textures[std::uniform_int_distribution<std::size_t>(0, textures.size() - 1)(mt)];
// X% chance to use the previous texture instead
// The ordered batcher performs badly when textures change extremely often.
if (!sprites.empty() && std::uniform_int_distribution<int>(0, 99)(mt) < sameTextureChance)
texture = (sprites.end() - 1)->getTexture();
auto& sprite = sprites.emplace_back();
sprite.setPosition(sf::Vector2f(x, y));
sprite.setRotation(sf::degrees(degrees));
sprite.setTexture(*texture);
auto& shape = shapes.emplace_back(60.0f, 30);
shape.setPosition(sf::Vector2f(x, y));
shape.setRotation(sf::degrees(degrees));
shape.setTexture(texture);
shape.setOutlineColor(sf::Color::Blue);
shape.setOutlineThickness(2);
auto& shapeNoTexture = shapesNoTexture.emplace_back(60.0f, 30);
shapeNoTexture.setPosition(sf::Vector2f(x, y));
shapeNoTexture.setRotation(sf::degrees(degrees));
shapeNoTexture.setOutlineColor(sf::Color::Blue);
shapeNoTexture.setOutlineThickness(2);
auto& text = texts.emplace_back(getRandomString(mt), font, 60);
text.setPosition(sf::Vector2f(x, y));
text.setRotation(sf::degrees(degrees));
text.setFillColor(sf::Color::Black);
text.setOutlineColor(sf::Color::White);
text.setOutlineThickness(8);
text.setStyle(sf::Text::Bold);
}
// Update drawables
for (std::size_t i = 0; i < sprites.size(); i++)
{
const sf::Angle amount = sf::degrees(2);
sprites[i].setRotation(sprites[i].getRotation() + amount);
texts[i].setRotation(texts[i].getRotation() + amount / 8);
shapes[i].setRotation(shapes[i].getRotation() + amount);
shapesNoTexture[i].setRotation(shapesNoTexture[i].getRotation() + amount);
}
// Batch drawables
clock.restart();
float depth = 1.f;
std::size_t count = 0;
if (batchEnabled && !useStaticBatch)
{
batch.clear();
batch.setBatchMode(batchMode);
for (std::size_t i = 0; i < sprites.size(); i++)
{
const sf::Batchable* batchable = nullptr;
switch (benchmarkedDrawable)
{
case BatchDrawables::Sprite:
batchable = &sprites[i];
break;
case BatchDrawables::Shape:
batchable = &shapes[i];
break;
case BatchDrawables::ShapeNoTexture:
batchable = &shapesNoTexture[i];
break;
case BatchDrawables::Text:
batchable = &texts[i];
break;
}
batch.batch(*batchable, depth);
count++;
// Move to another layer for DepthSort case
// In a
if (count > drawablesPerLayer)
{
count -= drawablesPerLayer;
depth -= 0.01f;
}
}
}
if (!batchEnabled)
{
for (std::size_t i = 0; i < sprites.size(); i++)
{
const sf::Drawable* drawable = nullptr;
switch (benchmarkedDrawable)
{
case BatchDrawables::Sprite:
drawable = &sprites[i];
break;
case BatchDrawables::Shape:
drawable = &shapes[i];
break;
case BatchDrawables::ShapeNoTexture:
drawable = &shapesNoTexture[i];
break;
case BatchDrawables::Text:
drawable = &texts[i];
break;
}
window.draw(*drawable);
}
}
if (batchEnabled)
batch.draw(window, sf::RenderStates(sf::BlendAlpha, sf::Transform::Identity, nullptr, nullptr));
sf::Time time = clock.restart();
timeHistory.push_back(time);
if (timeHistory.size() > maxTimesSaved)
timeHistory.erase(timeHistory.begin(), timeHistory.end() - static_cast<int>(maxTimesSaved));
const sf::Time average = std::accumulate(timeHistory.begin(), timeHistory.end(), sf::Time::Zero) /
static_cast<float>(timeHistory.size());
if (framesTillRefUpdate <= 0)
{
framesTillRefUpdate = 18;
timeReference = time;
}
framesTillRefUpdate--;
// clang-format off
debugInfoText.setPosition(sf::Vector2f(0, 0));
debugInfoText.setString(
"Batching enabled:\n"
"Current drawable:\n"
"Time:\n"
"Time (average):\n"
"Drawables:\n"
"Batch mode:\n");
window.draw(debugInfoText);
debugInfoText.setPosition(sf::Vector2f(250, 0));
debugInfoText.setString(
std::string(batchEnabled ? "true" : "false") + "\n" +
getDisplayString(benchmarkedDrawable) + "\n" +
std::to_string(timeReference.asSeconds() * 1000.0f) + "\n" +
std::to_string(average.asSeconds() * 1000.0f) + "\n" +
std::to_string(drawableCount) + "\n" +
getDisplayString(batchMode) + "\n" +
(useStaticBatch ? "Static batch is active" : ""));
// clang-format on
window.draw(debugInfoText);
// Display things on screen
window.display();
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,22 @@
# all source files
set(SRC Batching.cpp)
if (SFML_OS_IOS)
set(RESOURCES
${CMAKE_CURRENT_SOURCE_DIR}/resources/Tex0.png
${CMAKE_CURRENT_SOURCE_DIR}/resources/Tex1.png
${CMAKE_CURRENT_SOURCE_DIR}/resources/Tex2.png
${CMAKE_CURRENT_SOURCE_DIR}/resources/Tex3.png
${CMAKE_CURRENT_SOURCE_DIR}/resources/Tex4.png
${CMAKE_CURRENT_SOURCE_DIR}/resources/Tex5.png
${CMAKE_CURRENT_SOURCE_DIR}/resources/Tex6.png
${CMAKE_CURRENT_SOURCE_DIR}/resources/Tex7.png
${CMAKE_CURRENT_SOURCE_DIR}/resources/Tex8.png)
set_source_files_properties(${RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
endif()
# define the sprite batch demo target
sfml_add_example(sprite_batch GUI_APP
SOURCES ${SRC}
BUNDLE_RESOURCES ${RESOURCES}
DEPENDS SFML::Graphics
RESOURCES_DIR resources)

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

View File

@ -46,6 +46,7 @@
#include <SFML/Graphics/Shader.hpp>
#include <SFML/Graphics/Shape.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/SpriteBatch.hpp>
#include <SFML/Graphics/Text.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Transform.hpp>

View File

@ -0,0 +1,77 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2022 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
#pragma once
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Graphics/Export.hpp>
namespace sf
{
class SpriteBatch;
////////////////////////////////////////////////////////////
/// \brief Abstract base class for objects that can be batched
/// to a sf::SpriteBatch
///
////////////////////////////////////////////////////////////
class SFML_GRAPHICS_API Batchable
{
public:
////////////////////////////////////////////////////////////
/// \brief Virtual destructor
///
////////////////////////////////////////////////////////////
virtual ~Batchable() = default;
////////////////////////////////////////////////////////////
/// \brief Batch the object using the given SpriteBatch
///
/// This is a pure virtual function that has to be implemented
/// by the derived class to define how the drawable should be
/// batched.
///
/// \param spriteBatch The SpriteBatch to use
/// \param depth The depth at which to render the object
///
////////////////////////////////////////////////////////////
virtual void batch(SpriteBatch& spriteBatch, float depth = 0.f) const = 0;
};
} // namespace sf
////////////////////////////////////////////////////////////
/// \class sf::Batchable
/// \ingroup graphics
///
/// sf::Batchable is an interface for drawables that can be
/// batched using an sf::SpriteBatch. To implement the interface,
/// you need to define a batch method that makes use of
/// sf::SpriteBatch::batch(), and supplies it the vertices, texture,
/// transform and other data for drawing.
///
////////////////////////////////////////////////////////////

View File

@ -29,6 +29,7 @@
////////////////////////////////////////////////////////////
#include <SFML/Graphics/Export.hpp>
#include <SFML/Graphics/Batchable.hpp>
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/Transformable.hpp>
#include <SFML/Graphics/VertexArray.hpp>
@ -43,7 +44,7 @@ class Texture;
/// \brief Base class for textured shapes with outline
///
////////////////////////////////////////////////////////////
class SFML_GRAPHICS_API Shape : public Drawable, public Transformable
class SFML_GRAPHICS_API Shape : public Drawable, public Transformable, public Batchable
{
public:
////////////////////////////////////////////////////////////
@ -248,6 +249,15 @@ public:
////////////////////////////////////////////////////////////
FloatRect getGlobalBounds() const;
////////////////////////////////////////////////////////////
/// \brief Batch the Shape using the given SpriteBatch
///
/// \param spriteBatch The SpriteBatch to use
/// \param depth The depth at which to render the shape
///
////////////////////////////////////////////////////////////
void batch(SpriteBatch& spriteBatch, float depth = 0.f) const override;
protected:
////////////////////////////////////////////////////////////
/// \brief Default constructor

View File

@ -29,6 +29,7 @@
////////////////////////////////////////////////////////////
#include <SFML/Graphics/Export.hpp>
#include <SFML/Graphics/Batchable.hpp>
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/Transformable.hpp>
@ -44,7 +45,7 @@ class Texture;
/// own transformations, color, etc.
///
////////////////////////////////////////////////////////////
class SFML_GRAPHICS_API Sprite : public Drawable, public Transformable
class SFML_GRAPHICS_API Sprite : public Drawable, public Transformable, public Batchable
{
public:
////////////////////////////////////////////////////////////
@ -206,6 +207,15 @@ public:
////////////////////////////////////////////////////////////
FloatRect getGlobalBounds() const;
////////////////////////////////////////////////////////////
/// \brief Batch the Sprite using the given SpriteBatch
///
/// \param spriteBatch The SpriteBatch to use
/// \param depth The depth at which to render the sprite
///
////////////////////////////////////////////////////////////
void batch(SpriteBatch& spriteBatch, float depth = 0.f) const override;
private:
////////////////////////////////////////////////////////////
/// \brief Draw the sprite to a render target

View File

@ -0,0 +1,256 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2022 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
#pragma once
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Graphics/Export.hpp>
#include <SFML/Graphics/Batchable.hpp>
#include <SFML/Graphics/RenderStates.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/Text.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Vertex.hpp>
#include <vector>
namespace sf
{
class RenderTarget;
////////////////////////////////////////////////////////////
/// \brief Batching utility for drawables, meant for increasing
/// rendering performance
///
////////////////////////////////////////////////////////////
class SFML_GRAPHICS_API SpriteBatch : public Drawable
{
public:
enum class BatchMode
{
////////////////////////////////////////////////////////////
/// \brief Creates a batch for each contiguous groups of drawables
/// that use the same texture.
///
////////////////////////////////////////////////////////////
Deferred,
////////////////////////////////////////////////////////////
/// \brief Organizes objects into a single layer, then reorders
/// them by texture before drawing.
///
////////////////////////////////////////////////////////////
TextureSort,
////////////////////////////////////////////////////////////
/// \brief Organizes objects into multiple layers. Groups objects
/// into layers based on depth, then reorders objects in
/// each layer by texture before drawing the layers from
/// furthest to closest.
///
////////////////////////////////////////////////////////////
DepthSort,
};
////////////////////////////////////////////////////////////
/// \brief Sets the batch mode to use
///
/// \param batchMode The new batch mode to use
///
////////////////////////////////////////////////////////////
void setBatchMode(BatchMode batchMode);
////////////////////////////////////////////////////////////
/// \brief Batches the given drawable
///
/// \param drawable The drawable to be batched
/// \param depth The depth at which the drawable will be
/// rendererd. This value is used only when
/// BatchMode::DepthSort mode is used
///
////////////////////////////////////////////////////////////
void batch(const Batchable& drawable, float depth = 0.f);
////////////////////////////////////////////////////////////
/// \brief Batches an array of vertices
///
/// \param vertices The array of vertices to be batched
/// \param count How many vertices to batch
/// \param type The primitive formed by the vertices.
/// Only triangle primitives are supported
/// \param texture The texture to use for rendering
/// \param transform The transform to apply on the vertices
/// \param depth The depth at which the drawable will be
/// rendererd. This value is used only when
/// BatchMode::DepthSort mode is used.
///
////////////////////////////////////////////////////////////
void batch(const Vertex* vertices,
std::size_t count,
PrimitiveType type,
const Texture* texture,
const Transform& transform = Transform::Identity,
float depth = 0.f);
////////////////////////////////////////////////////////////
/// \brief Renders the batch to the render target
///
/// \param target The RenderTarget to draw to
/// \param states The RenderStates to use. The texture is ignored
///
////////////////////////////////////////////////////////////
void draw(RenderTarget& target, const RenderStates& states) const override;
////////////////////////////////////////////////////////////
/// \brief Clears the batch, removing all drawables that were
/// added
///
////////////////////////////////////////////////////////////
void clear();
private:
////////////////////////////////////////////////////////////
/// \brief Pushes a triangle into internal storage
///
/// \param a First vertex
/// \param b Second vertex
/// \param c Third vertex
/// \param transform Transform to apply before inserting
/// \param texture Texture to apply to triangle
/// \param depth The depth to use for depth sorting
///
////////////////////////////////////////////////////////////
void pushTriangle(const Vertex& a,
const Vertex& b,
const Vertex& c,
const Transform& transform,
const Texture* texture,
float depth);
////////////////////////////////////////////////////////////
/// \brief Updates the batch, preparing its internal state for
/// efficient usage
///
////////////////////////////////////////////////////////////
void updateBatch() const;
////////////////////////////////////////////////////////////
/// \brief Holds information about a batched triangle
///
////////////////////////////////////////////////////////////
struct TriangleInfo
{
const Texture* texture{}; //!< The triangle's texture
float depth{}; //!< The depth of the triangle
TriangleInfo() = default;
TriangleInfo(const Texture* theTexture, float theDepth) : texture(theTexture), depth(theDepth)
{
}
};
////////////////////////////////////////////////////////////
/// \brief Holds information for rendering a batch
///
////////////////////////////////////////////////////////////
struct BatchInfo
{
const Texture* texture{}; //!< The texture used to render the batch
std::size_t vertexCount{}; //!< The number of contiguous vertices to render
BatchInfo() = default;
BatchInfo(const Texture* theTexture, std::size_t theVertexCount) :
texture(theTexture),
vertexCount(theVertexCount)
{
}
};
////////////////////////////////////////////////////////////
// Member data
////////////////////////////////////////////////////////////
// Batched Triangles
std::vector<TriangleInfo> m_triangles; //!< Info about batched triangles
std::vector<Vertex> m_unsortedVertices; //!< Vertices currently batched
mutable std::vector<BatchInfo> m_batches; //!< Prepared batch information, ready for rendering
mutable std::vector<Vertex> m_vertices; //!< Prepared vertices, ready for rendering
// Batch Settings
BatchMode m_batchMode{BatchMode::Deferred}; //!< The current batch strategy
mutable bool m_updateRequired{true}; //!< If true, batch must be sorted before rendering
};
} // namespace sf
////////////////////////////////////////////////////////////
/// \class sf::SpriteBatch
/// \ingroup graphics
///
/// sf::SpriteBatch is a batching utility for drawables. It
/// increases rendering performance by reordering and combining
/// drawables, such that they are rendered using fewer draw calls
/// and state switches.
///
/// sf::SpriteBatch supports multiple batching strategies. Each
/// comes with its benefits and drawbacks, so the optimal choice
/// depends on your specific needs.
///
/// \c sf::BatchMode::Deferred is best used when rendering contiguous
/// groups ofdrawables that use the same texture. This mode creates
/// batches for each such group, improving performance when many
/// drawables with few vertices are drawn (such as hundreds to
/// thousands of sf::Sprite drawables).
/// The render order of drawables is the same as the order they
/// were batched in. This is similar how rendering works with
/// sf::RenderTarget.
///
/// \c sf::BatchMode::TextureSort is best used when drawing objects
/// that are positioned on a "layer", and do not overlap each other.
/// This mode assumes that the draw order is not important within
/// a layer, and sorts all objects by texture before batching them.
/// This provides great performance improvements, especially when
/// rendering objects using different textures (since it will reorder
/// them back in contiguous groups).
/// Care should be taken when when drawing overlapping objects, since
/// it is not guaranteed that they will be ordered correctly.
///
/// \c sf::BatchMode::DepthSort is best used when drawing objects
/// that are positioned on multiple "layers" based on depth, and objects
/// within a layer do not overlap each other.
/// Within each layer, objects are sorted by texture before drawing,
/// similar to \c TextureSort. However, objects in a layer with a
/// higher "depth" value will be drawn behind objects with a lower
/// "depth" value.
/// You can also use this mode to render objects in an order that is
/// different from the order they were batched in, since objects
/// will be reordered by depth before rendering.
///
/// An example of how to use sf::SpriteBatch can be found in the
/// examples/sprite_batch project.
///
////////////////////////////////////////////////////////////

View File

@ -29,6 +29,7 @@
////////////////////////////////////////////////////////////
#include <SFML/Graphics/Export.hpp>
#include <SFML/Graphics/Batchable.hpp>
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/Transformable.hpp>
@ -47,7 +48,7 @@ class Font;
/// \brief Graphical text that can be drawn to a render target
///
////////////////////////////////////////////////////////////
class SFML_GRAPHICS_API Text : public Drawable, public Transformable
class SFML_GRAPHICS_API Text : public Drawable, public Transformable, public Batchable
{
public:
////////////////////////////////////////////////////////////
@ -417,6 +418,15 @@ public:
////////////////////////////////////////////////////////////
FloatRect getGlobalBounds() const;
////////////////////////////////////////////////////////////
/// \brief Batch the Text using the given SpriteBatch
///
/// \param spriteBatch The SpriteBatch to use
/// \param depth The depth at which to render the text
///
////////////////////////////////////////////////////////////
void batch(SpriteBatch& spriteBatch, float depth = 0.f) const override;
private:
////////////////////////////////////////////////////////////
/// \brief Draw the text to a render target

View File

@ -4,6 +4,7 @@ set(SRCROOT ${PROJECT_SOURCE_DIR}/src/SFML/Graphics)
# all source files
set(SRC
${INCROOT}/Batchable.hpp
${SRCROOT}/BlendMode.cpp
${INCROOT}/BlendMode.hpp
${INCROOT}/Color.hpp
@ -36,6 +37,8 @@ set(SRC
${INCROOT}/RenderWindow.hpp
${SRCROOT}/Shader.cpp
${INCROOT}/Shader.hpp
${SRCROOT}/SpriteBatch.cpp
${INCROOT}/SpriteBatch.hpp
${SRCROOT}/Texture.cpp
${INCROOT}/Texture.hpp
${SRCROOT}/TextureSaver.cpp

View File

@ -27,6 +27,7 @@
////////////////////////////////////////////////////////////
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/Shape.hpp>
#include <SFML/Graphics/SpriteBatch.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <cmath>
@ -148,6 +149,28 @@ FloatRect Shape::getGlobalBounds() const
}
////////////////////////////////////////////////////////////
void Shape::batch(SpriteBatch& spriteBatch, float depth) const
{
// Draw the shape itself
{
const std::size_t shapeCount = m_vertices.getVertexCount();
const Vertex* shapePtr = shapeCount > 0 ? &m_vertices[0] : nullptr;
spriteBatch.batch(shapePtr, shapeCount, m_vertices.getPrimitiveType(), m_texture, getTransform(), depth);
}
// Draw outline
if (m_outlineThickness != 0)
{
const std::size_t outlineCount = m_outlineVertices.getVertexCount();
const Vertex* outlinePtr = outlineCount > 0 ? &m_outlineVertices[0] : nullptr;
spriteBatch.batch(outlinePtr, outlineCount, m_outlineVertices.getPrimitiveType(), nullptr, getTransform(), depth);
}
}
////////////////////////////////////////////////////////////
Shape::Shape() = default;

View File

@ -27,6 +27,7 @@
////////////////////////////////////////////////////////////
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/SpriteBatch.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <cstdlib>
@ -130,6 +131,14 @@ FloatRect Sprite::getGlobalBounds() const
}
////////////////////////////////////////////////////////////
void Sprite::batch(SpriteBatch& spriteBatch, float depth) const
{
if (m_texture)
spriteBatch.batch(m_vertices, 4, PrimitiveType::TriangleStrip, m_texture, getTransform(), depth);
}
////////////////////////////////////////////////////////////
void Sprite::draw(RenderTarget& target, const RenderStates& states) const
{

View File

@ -0,0 +1,248 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2022 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/RenderStates.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/SpriteBatch.hpp>
#include <SFML/System/Err.hpp>
#include <algorithm>
#include <cassert>
#include <numeric>
namespace sf
{
////////////////////////////////////////////////////////////
void SpriteBatch::setBatchMode(BatchMode batchMode)
{
m_batchMode = batchMode;
m_updateRequired = true;
}
////////////////////////////////////////////////////////////
void SpriteBatch::batch(const Batchable& drawable, float depth)
{
drawable.batch(*this, depth);
}
////////////////////////////////////////////////////////////
void SpriteBatch::batch(const Vertex* vertices,
std::size_t count,
PrimitiveType type,
const Texture* texture,
const Transform& transform,
float depth)
{
const bool unsupportedType = type != PrimitiveType::TriangleFan && type != PrimitiveType::TriangleStrip &&
type != PrimitiveType::Triangles;
if (unsupportedType)
{
err() << "SpriteBatch supports only triangle-based primitive types.";
return;
}
// Anything to batch?
if (count == 0)
return;
// Split into triangles
switch (type)
{
case PrimitiveType::TriangleStrip:
for (std::size_t i = 2; i < count; i++)
pushTriangle(vertices[i - 2], vertices[i - 1], vertices[i], transform, texture, depth);
break;
case PrimitiveType::TriangleFan:
for (std::size_t i = 2; i < count; i++)
pushTriangle(vertices[0], vertices[i - 1], vertices[i], transform, texture, depth);
break;
case PrimitiveType::Triangles:
for (std::size_t i = 2; i < count; i += 3)
pushTriangle(vertices[i - 2], vertices[i - 1], vertices[i], transform, texture, depth);
break;
default:
err() << "A non-triangle primitive type was encountered when decomposing into triangles." << std::endl;
assert(false);
break;
}
}
////////////////////////////////////////////////////////////
void SpriteBatch::draw(RenderTarget& target, const RenderStates& states) const
{
if (m_updateRequired)
{
updateBatch();
m_updateRequired = false;
}
RenderStates statesCopy = states;
std::size_t startTriangle = 0;
for (const auto& batch : m_batches)
{
statesCopy.texture = batch.texture;
target.draw(&m_vertices[startTriangle], batch.vertexCount, PrimitiveType::Triangles, statesCopy);
startTriangle += batch.vertexCount;
}
}
////////////////////////////////////////////////////////////
void SpriteBatch::pushTriangle(const Vertex& a,
const Vertex& b,
const Vertex& c,
const Transform& transform,
const Texture* texture,
float depth)
{
m_triangles.emplace_back(texture, depth);
m_unsortedVertices.emplace_back(transform * a.position, a.color, a.texCoords);
m_unsortedVertices.emplace_back(transform * b.position, b.color, b.texCoords);
m_unsortedVertices.emplace_back(transform * c.position, c.color, c.texCoords);
m_updateRequired = true;
}
////////////////////////////////////////////////////////////
void SpriteBatch::updateBatch() const
{
if (m_batchMode == BatchMode::Deferred)
{
// Batch based on texture change
std::size_t startIndex = 0;
std::size_t nextIndex = 0;
const Texture* lastTexture = nullptr;
while (nextIndex < m_triangles.size())
{
const Texture* nextTexture = m_triangles[nextIndex].texture;
if (nextTexture != lastTexture)
{
m_batches.emplace_back(lastTexture, (nextIndex - startIndex) * 3);
lastTexture = nextTexture;
startIndex = nextIndex;
}
nextIndex++;
}
// Deal with leftovers
if (startIndex != m_triangles.size())
m_batches.emplace_back(lastTexture, (m_triangles.size() - startIndex) * 3);
m_vertices = m_unsortedVertices;
}
else if (m_batchMode == BatchMode::TextureSort || m_batchMode == BatchMode::DepthSort)
{
std::vector<std::size_t> indices(m_triangles.size());
std::iota(indices.begin(), indices.end(), 0);
if (m_batchMode == BatchMode::TextureSort)
{
const auto comp = [this](const std::size_t a, const std::size_t b)
{ return m_triangles[a].texture < m_triangles[b].texture; };
std::stable_sort(indices.begin(), indices.end(), comp);
}
else
{
const auto comp = [this](const std::size_t a, const std::size_t b)
{
if (m_triangles[a].depth != m_triangles[b].depth)
return m_triangles[a].depth > m_triangles[b].depth;
else
return m_triangles[a].texture < m_triangles[b].texture;
};
std::stable_sort(indices.begin(), indices.end(), comp);
}
// Create the array of sorted vertices
// We need them sorted so that we feed chunks to RenderTarget
m_vertices.resize(m_unsortedVertices.size());
for (std::size_t i = 0; i < indices.size(); i++)
{
const std::size_t newPos = i * 3;
const std::size_t oldPos = indices[i] * 3;
m_vertices[newPos] = m_unsortedVertices[oldPos];
m_vertices[newPos + 1] = m_unsortedVertices[oldPos + 1];
m_vertices[newPos + 2] = m_unsortedVertices[oldPos + 2];
}
// Generate batches
std::size_t startIndex = 0;
std::size_t nextIndex = 0;
const Texture* lastTexture = nullptr;
while (nextIndex < m_triangles.size())
{
const Texture* nextTexture = m_triangles[indices[nextIndex]].texture;
if (nextTexture != lastTexture)
{
m_batches.emplace_back(lastTexture, (nextIndex - startIndex) * 3);
lastTexture = nextTexture;
startIndex = nextIndex;
}
nextIndex++;
}
// Deal with leftovers
if (startIndex != m_triangles.size())
m_batches.emplace_back(m_triangles[indices[startIndex]].texture, (m_triangles.size() - startIndex) * 3);
}
}
////////////////////////////////////////////////////////////
void SpriteBatch::clear()
{
m_unsortedVertices.clear();
m_triangles.clear();
m_vertices.clear();
m_batches.clear();
m_updateRequired = false;
}
} // namespace sf

View File

@ -27,6 +27,7 @@
////////////////////////////////////////////////////////////
#include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/SpriteBatch.hpp>
#include <SFML/Graphics/Text.hpp>
#include <SFML/Graphics/Texture.hpp>
@ -369,6 +370,33 @@ FloatRect Text::getGlobalBounds() const
}
////////////////////////////////////////////////////////////
void Text::batch(SpriteBatch& spriteBatch, float depth) const
{
if (m_font)
{
ensureGeometryUpdate();
const Texture* texture = &m_font->getTexture(m_characterSize);
// Draw outline
if (m_outlineThickness != 0)
{
const std::size_t outlineCount = m_outlineVertices.getVertexCount();
const Vertex* outlinePtr = outlineCount > 0 ? &m_outlineVertices[0] : nullptr;
spriteBatch.batch(outlinePtr, outlineCount, m_outlineVertices.getPrimitiveType(), texture, getTransform(), depth);
}
// Draw the text itself
{
const std::size_t textCount = m_vertices.getVertexCount();
const Vertex* textPtr = textCount > 0 ? &m_vertices[0] : nullptr;
spriteBatch.batch(textPtr, textCount, m_vertices.getPrimitiveType(), texture, getTransform(), depth);
}
}
}
////////////////////////////////////////////////////////////
void Text::draw(RenderTarget& target, const RenderStates& states) const
{