Search code examples
c++performancesfml

SFML slow when drawing more than 500 shapes


I am new to SFML and I would like to implement a fluid boids simulation. However, I realized that when more than 500 shapes are drawn at the same time, the fps drop quickly.

How can I improve the code below to make it much faster to run?

#include <SFML/Graphics.hpp>
#include <vector>
#include <iostream>
sf::ConvexShape newShape() {
    sf::ConvexShape shape(3);
    shape.setPoint(0, sf::Vector2f(0, 0));
    shape.setPoint(1, sf::Vector2f(-7, 20));
    shape.setPoint(2, sf::Vector2f(7, 20));
    shape.setOrigin(0, 10);
    shape.setFillColor(sf::Color(49, 102, 156, 150));
    shape.setOutlineColor(sf::Color(125, 164, 202, 150));
    shape.setOutlineThickness(1);
    shape.setPosition(rand() % 800, rand() % 600);
    shape.setRotation(rand() % 360);
    return shape;
}

int main() { 
    sf::Clock dtClock, fpsTimer;
    sf::RenderWindow window(sf::VideoMode(800, 600), "Too Slow");
    std::vector<sf::ConvexShape> shapes;
    for (int i = 0; i < 1000; i++) shapes.push_back(newShape());
    while (window.isOpen()) {
        window.clear(sf::Color(50, 50, 50));
        for (auto &shape : shapes) { shape.rotate(0.5); window.draw(shape); }
        window.display();
        float dt = dtClock.restart().asSeconds();
        if (fpsTimer.getElapsedTime().asSeconds() > 1) {
            fpsTimer.restart();
            std::cout << ((1.0 / dt > 60) ? 60 : (1.0 / dt)) << std::endl;
        }
    }
}

I have the following performance:

Shapes   FPS
10       60
100      60
500      60
600      60
700      55
800      50
900      45
1000     21

My goal is to have about 5k boids on the screen.

EDIT

I am building the project on Windows 11 under WSL2 with vGPU enabled. When testing natively on Windows 11 with Visual Studio I get much better performance (I can run 5k boids at 60 FPS)


Solution

  • The problems are a lot of draw calls. That is slow part of this program. In order to fix this, we can put all triangles into single vertex array and call draw upon only that array. That way we will speed up program. Problem with it is that you must implement your own rotate method. I implemented the method below and edited so the function returns triangles in single vertex array.

    #include <SFML/Graphics.hpp>
    #include <iostream>
    //This function returns vertex array by given number of triangles
    sf::VertexArray newShape(int numberOfTriangles) {
        sf::VertexArray shape(sf::Triangles);
        //we are going trough every point in each triangle
        for (int i=0;i<3*numberOfTriangles;i++){
            //creating points of triangles as vertexes 1, 2 and 3
            sf::Vertex v1(sf::Vector2f(rand() % 800, rand() % 600));
            sf::Vertex v2(sf::Vector2f(v1.position.x - 7, v1.position.y - 20));
            sf::Vertex v3(sf::Vector2f(v1.position.x + 7, v1.position.y - 20));
            //setting color
            v1.color = v2.color = v3.color = sf::Color(49, 102, 156, 150);
            //rotating for random angle
            sf::Transform transform;
            transform.rotate(rand()%90, (v2.position.x + v3.position.x) / 2,v1.position.y - 10);
            v1.position = transform.transformPoint(v1.position);
            v2.position = transform.transformPoint(v2.position);
            v3.position = transform.transformPoint(v3.position);
            //appending them into vertex array
            shape.append(v1);
            shape.append(v2);
            shape.append(v3);
    
        }
        return shape;   
    }
    //rotate function finds the middle of 3 vertexes and rotates them
    void rotate(sf::VertexArray& array, double angle){
        for (int i=0;i<array.getVertexCount();i+=3){
            sf::Transform transform;
            transform.rotate(angle, (array[i+1].position.x + array[i+2].position.x) / 2, (array[i].position.y + array[i+1].position.y)/2);
            array[i].position = transform.transformPoint(array[i].position);
            array[i+1].position = transform.transformPoint(array[i+1].position);
            array[i+2].position = transform.transformPoint(array[i+2].position);
        }
    
    }
    int main() { 
        sf::Clock dtClock, fpsTimer;
        sf::RenderWindow window(sf::VideoMode(800, 600), "Too Slow");
        //creating new array with 30000 triangles
        sf::VertexArray shapes = newShape(30000);
        window.setFramerateLimit(60);
        while (window.isOpen()) {
            //event to close window on close button
            sf::Event event;
            while (window.pollEvent(event))
            {
                if (event.type == sf::Event::Closed)
                    window.close();
            }
    
            window.clear(sf::Color(50, 50, 50));
            //no need for for now, as you can rotate them all in function and draw them together
            rotate(shapes,5); 
            window.draw(shapes); 
            window.display();
            float dt = dtClock.restart().asSeconds();
            if (fpsTimer.getElapsedTime().asSeconds() > 1) {
                fpsTimer.restart();
                std::cout << ((1.0 / dt > 60) ? 60 : (1.0 / dt)) << std::endl;
            }
        }
    }
    

    The bottleneck now is not drawing but rotating, i tested this code with 100000 triangles and im getting around 45 fps. With 1000000 i am getting bad framerate because of rotation.