Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: How to reduce draw calls? (and some questions)  (Read 11345 times)

0 Members and 3 Guests are viewing this topic.

AFS

  • Full Member
  • ***
  • Posts: 115
    • View Profile
How to reduce draw calls? (and some questions)
« on: July 01, 2013, 08:24:41 am »
Hello everyone :D

I'm making a game, and I'm having performance issues, sadly. At first I thought it was because of some code of mine (like collision detection) but after spending some hours I noticed that it's really because of the amount of sprites.

I have all my sprites on a linked list, and draw them one by one, thinking that this is the only way of drawing a lot of stuff. The code is simply like this:


std::list <sf::Sprite> :: iterator i;

for (i = mySprites.begin(); i != mySprites.end(); i++)
      myWindow.draw(*mySprite);

 

So, I get an awful performance hit while drawing like 30 or so sprites, which is a pretty low amount for my needs. The sprites use one of 8 or so textures, which have a size of around 400x400 pixels each.

However, I noticed that I can draw a lot of sprites without the performance hit (like 200 or so) if all the sprites have the same texture. The performance gets worse the bigger the amount of different textures used by the sprites, even if I draw the exact same amount of them. Why is that?

I kind of imagine the reason, but hopefully someone could give me an explanation :)

Anyway, while searching for a couple of hours, I read that performance may improve if I reduce the number of draw calls, and to do so I need to use "batching" (a new concept to me) and/or use sf::VectorArray (I don't know if using the vector array is actually part of batching or just a different process).

So, why batching/using a vector array is better than drawing each sprite one by one? And finally, could somebody please show me a small example of code with a vector array in action?

Sorry for the long post and for the dumb questions. I searched a lot but I didn't really understood what I found, it's like chinese to me  :P

Thanks in advance.
« Last Edit: July 01, 2013, 08:29:58 am by AFS »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: How to reduce draw calls? (and some questions)
« Reply #1 on: July 01, 2013, 09:10:44 am »
Quote
However, I noticed that I can draw a lot of sprites without the performance hit (like 200 or so) if all the sprites have the same texture. The performance gets worse the bigger the amount of different textures used by the sprites, even if I draw the exact same amount of them. Why is that?
A texture switch costs a lot for the graphics card.

Quote
sf::VectorArray
Quote
vector array
Vertex array.

Quote
So, why batching/using a vector vertex array is better than drawing each sprite one by one?
Because what costs the most is a call to the draw function. Less calls = better performances.

Quote
And finally, could somebody please show me a small example of code with a vector vertex array in action?
There is a tutorial, have you read it first? I don't think you did, because you would not say "vector array" otherwise ;)
But in your case it may not help. It's very strange that you get so bad performances with only 30 sprites. What's your OS and graphics card? Are your graphics drivers up-to-date? Could you show a complete and minimal example that reproduces the problem? (i.e. a small program that loads 8 400x400 textures and draws 30 sprites using them)
Laurent Gomila - SFML developer

AFS

  • Full Member
  • ***
  • Posts: 115
    • View Profile
Re: How to reduce draw calls? (and some questions)
« Reply #2 on: July 01, 2013, 06:09:08 pm »
Thanks for the reply.

Yes, I meant vertexArray  :-*. Sorry, it was like 4 AM. I have used vertex arrays but just to draw simple lines (using sf::LinesStrip).

And yes, I haven't read the tutorial, I didn't know there was one about it. Now I found it; how silly of me, sorry about that.

Thanks for the explanation; switching textures really slows things down. Unfortunely I can't minimize texture switching because the sprites need to be drawn in a particular order.

My PC? An HP laptop using Windows 7 64 bits, 3 GB Ram, a 2 GHz Intel Core 2 Duo CPU, and a lame integrated Intel graphics card ("Mobile Intel 4 Series ECF") that uses 1GB of memory and can handle textures up to 2K x 2K and all drivers up to date. While the graphics card is pretty bad, I don't think its that bad to struggle with 30 sprites, isn't it?

One thing I didn't mention (as I thought it was irrelevant) is that all my 30 or so sprites are not stored in a single list. I use 4 or 5 lists, each one having the sprites from a different layer (background, foreground, etc) and each layer has their own camera (so I can move them at different speeds to create parallax scrolling), although I don't think the cameras nor the amount of lists are the problem, but I could be wrong.

The textures are not necessarily 400x400; some are slighty different, but that's more or less the average.

Finally, if I change the resolution (from 1280x800 to 800x600) but keeping the camera size the same so I can see the same on screen, I get better performance. So yeah, maybe it's my PC or I'm using sprites that are way too big, I don't know.

I can't provide you with code right now, but I load the textures and set up my sprites and cameras outside of the main loop, and inside the loop I just iterate through each "layer" by setting up the respective camera to my window first, and then drawing one by one the sprites of that layer. I don't think I'm doing anything redundant, to be honest.

Thanks for your time ;)
« Last Edit: July 01, 2013, 06:21:21 pm by AFS »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: How to reduce draw calls? (and some questions)
« Reply #3 on: July 01, 2013, 07:14:00 pm »
Quote
While the graphics card is pretty bad, I don't think its that bad to struggle with 30 sprites, isn't it?
Yep. Even a NES can display 30 sprites at a decent framerate :P

Quote
although I don't think the cameras nor the amount of lists are the problem, but I could be wrong
That would be suprising, indeed.

Don't hesitate to ask more questions, or to come back if you suceed to isolate the bug to a smaller code.
Laurent Gomila - SFML developer

AFS

  • Full Member
  • ***
  • Posts: 115
    • View Profile
Re: How to reduce draw calls? (and some questions)
« Reply #4 on: July 01, 2013, 09:32:00 pm »
I'll look into it and post any useful information I can find.

I just tested the game on two different computers; one a little older than my laptop that had an Intel integrated graphics card just like mine, but slighty worse; I got the exact same results (around 30 FPS) by drawing 30 or 40 sprites using different textures (with the smallest one being around 200x200 and the biggest one being around 1500x600). Then tested it in a desktop with a very good GPU (an ATI HD 57xx) and I got like 1000 FPS, although this is not surprising.

Maybe Intel GPUs don't like OpenGL :P

But anyway, I'll post any important info I find. Thanks for your time and patience ;)
« Last Edit: July 02, 2013, 01:28:06 am by AFS »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: How to reduce draw calls? (and some questions)
« Reply #5 on: July 01, 2013, 09:48:42 pm »
Do you use setFramerateLimit or setVerticalSyncEnabled?
Laurent Gomila - SFML developer

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: How to reduce draw calls? (and some questions)
« Reply #6 on: July 01, 2013, 09:51:49 pm »
You can also try to analyze the runtime behavior with a profiler. Like this, you will know more exactly what is slow.

But I also suggest you create a trivial example of a list with sprites and some texture switches, which are slow at you.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

AFS

  • Full Member
  • ***
  • Posts: 115
    • View Profile
Re: How to reduce draw calls? (and some questions)
« Reply #7 on: July 01, 2013, 10:14:11 pm »
Do you use setFramerateLimit or setVerticalSyncEnabled?

I tested with and without setVerticalSyncEnabled but on the laptops with Intel I got 30 FPS regardless. On the desktop I got 60 FPS with Vsync and 1000 without it, which is to be expected.

By the way, I finished reading the VectorVertexArray tutorial (which I should have done BEFORE making this topic) and now I understand why it won't work in this case. Still, it's a interesting thing to keep in mind, so thanks :P

You can also try to analyze the runtime behavior with a profiler. Like this, you will know more exactly what is slow.

But I also suggest you create a trivial example of a list with sprites and some texture switches, which are slow at you.

I'm afraid I have no idea about using profilers. I will use them as a last option in case everything else fails, thanks for the suggestion.

I'll follow your second suggestion now: I'll create a small program from scratch focusing exclusively on drawing. I'll use several lists, cameras and the same textures to reproduce the problem, and I'll upload it so you guys can check it out if you want ;)

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: How to reduce draw calls? (and some questions)
« Reply #8 on: July 01, 2013, 10:23:49 pm »
Quote
I'll use several lists, cameras
You can start with that, but after testing, if you realize that they don't change anything to the problem, you can remove them to make the code as minimal as possible ;)
Laurent Gomila - SFML developer

AFS

  • Full Member
  • ***
  • Posts: 115
    • View Profile
Re: How to reduce draw calls? (and some questions)
« Reply #9 on: July 01, 2013, 10:58:26 pm »
You can start with that, but after testing, if you realize that they don't change anything to the problem, you can remove them to make the code as minimal as possible ;)

I'll keep that in mind ;)

AFS

  • Full Member
  • ***
  • Posts: 115
    • View Profile
Re: How to reduce draw calls? (and some questions)
« Reply #10 on: July 02, 2013, 01:26:01 am »
So, I made a little code to just draw 30 sprites on screen at random, just using one sprite list and one camera. Here's the download link (it contains the textures I used, which are varied in size, and the code, of course).

http://www.mediafire.com/?t746lkri9d654ba

Here's the code (I'm deleting some stuff like camera movement, showing FPS on screen and pressing Esc to close the window, as they are unrelated to the problem).

#include <iostream>
#include <list>
#include <sstream>

#include <SFML/Graphics.hpp>

int main() {

    // Window.
    sf::RenderWindow window(sf::VideoMode(1280, 800), "Test", sf::Style::Fullscreen);

    // Camera.
    sf::Vector2f cameraSize(window.getSize().x, window.getSize().y);
    sf::Vector2f cameraCenter(cameraSize.x/2, cameraSize.y/2);
    sf::View camera(cameraCenter, cameraSize);

    // Loading textures.
    int amountTextures = 11;
    sf::Texture textures[11];
    std::string names[11];

    ...

    for (int i = 0; i < amountTextures; i++)
        textures[i].loadFromFile(names[i]);

    // Creating sprites.
    std::list <sf::Sprite> sprites;
    for (int i = 0; i < 30; i++)            // 30 sprites.
        sprites.push_back( sf::Sprite() );

    std::list <sf::Sprite> :: iterator it;   // Sprite properties.
    srand(time(0));
    for (it = sprites.begin(); it != sprites.end(); it++) {

        // Texture at random.
        int random = rand() % amountTextures;                            
        it->setTexture( textures[random] );

        // Using center of sprite as origin.
        it->setOrigin( it->getGlobalBounds().width/2, it->getGlobalBounds().height/2 );

        // Position at random (inside the camera).
        int randX = rand() % (int)cameraSize.x;
        int randY = rand() % (int)cameraSize.y;
        it->setPosition( randX, randY );
    }

    /// Game loop.
    sf::Event event;
    while (window.isOpen() ) {

        ...

        // Drawing.
        window.clear(sf::Color::Cyan);
        window.setView(camera);

        for (it = sprites.begin(); it != sprites.end(); it++)
            window.draw(*it);

        // Display.
        window.display();
    }

    return 0;

}
 

So, the result? Around 45 FPS average after drawing 30 sprites. I managed to get 100+ FPS if all the sprites use small textures, and below 30 if they use big ones.

With that said, I guess the solution is either:
1.- Using a small resolution but keeping the camera at its original size. With this I can get over 60, but it looks blurry. Not that bad if I use "setSmooth()" on all the textures, but still...
2.- Using smaller textures, which is kind of a bummer.

Still, I thought that even a very bad GPU could handle this amount of sprites easily, regardless of size  ???

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: How to reduce draw calls? (and some questions)
« Reply #11 on: July 02, 2013, 08:01:32 am »
Quote
...
It is very important to see the full code.
Laurent Gomila - SFML developer

mateandmetal

  • Full Member
  • ***
  • Posts: 171
  • The bird is the word
    • View Profile
    • my blog
Re: How to reduce draw calls? (and some questions)
« Reply #12 on: July 02, 2013, 11:54:19 am »
Have you tryed using std::array as Sprite container?

#include <array>
...
std::array <sf::Sprite, 30> spriteContainer;
 

Also try to reduce the number of textures to the minimum (Use texture atlas)
- Mate (beverage) addict
- Heavy metal addict _lml
- SFML 2 addict
- My first (and free) game: BichingISH!

AFS

  • Full Member
  • ***
  • Posts: 115
    • View Profile
Re: How to reduce draw calls? (and some questions)
« Reply #13 on: July 02, 2013, 06:40:28 pm »
It is very important to see the full code.

I deleted irrelevant stuff to keep the code short ;)

The first thing I deleted was just initializing the names of the textures before loading. The names are hard coded.

The second thing I deleted was inside the game loop, it was just irrelevant code like camera movement, pressing Esc to exit and updating the string of an sf::text to show FPS on screen and then drawing it. Without this code the results were exactly the same (using Fraps).

I guess the problem is my computer drawing big sprites, that's all. To solve it I'll just offer the option of using smaller textures with scaled sprites if the player has bad performance, or simply offer to lower the resolution.

Have you tryed using std::array as Sprite container?

Also try to reduce the number of textures to the minimum (Use texture atlas)

I need to use linked lists, as I load/delete sprites while I move the camera ;)

I'll use an sprite sheet when I get the final textures, but for now, as I'm constantly modifying textures, I'll keep them separated. Besides, that only will work on smaller textures, as the big ones are already close to the limit of 2K.

Thanks everyone for your time ;)
« Last Edit: July 02, 2013, 06:47:57 pm by AFS »