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

Author Topic: Storing sprites in a container for better performance  (Read 3900 times)

0 Members and 1 Guest are viewing this topic.

Jimmyee

  • Newbie
  • *
  • Posts: 21
    • View Profile
Storing sprites in a container for better performance
« on: June 06, 2017, 01:52:34 pm »
Will I have better performance if I store created sprites in a container instead of creating them everytime while rendering graphics?

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10815
    • View Profile
    • development blog
    • Email
Re: Storing sprites in a container for better performance
« Reply #1 on: June 06, 2017, 01:56:04 pm »
Depends on the amount of sprites, but generally creating sprite objects is quite cheap and the bigger issues are usually the draw calls.

Pick whatever solution fits your needs and if you run into a bottleneck you can always refactor the code.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Jimmyee

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: Storing sprites in a container for better performance
« Reply #2 on: June 06, 2017, 02:04:07 pm »
That's the direction I'm going to take. I won't probably use many sprites so I don't have to store them somewhere. Thanks for quick and good reply  :)

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re: Storing sprites in a container for better performance
« Reply #3 on: June 09, 2017, 01:48:10 am »
You'll probably get better performance by using VertexArrays and drawing in batches (as eXpl0it3r said, draw calls are the problem usually). Don't prematurely optimize, though. Unless you're drawing thousands of sprites, you'll probably be okay.
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

DarkRoku12

  • Full Member
  • ***
  • Posts: 203
  • Lua coder.
    • View Profile
    • Email
Re: Storing sprites in a container for better performance
« Reply #4 on: June 09, 2017, 07:49:57 am »
if I store created sprites in a container instead of creating

From the point of view of the compileer engine engineering and analying a real 2D game (Discarding the GPU)

On a real game you'll:
- Drawing a bunch of sprites
- Apply effects and transformations to them
- Have some kind of data-persistence

So at the end you'll end up using a container!!!

Ok, how about create new:

spriteVector[ index ] = sf::Sprite() ;
spriteVector[ index ].setPosition( x , y ) ;
spriteVector[ index ].setColor( color ) ;
 

or reuse:

spriteVector[ index ].setPosition( x , y ) ;
spriteVector[ index ].setColor( color ) ;
 

You may think is cheaper to reuse, but is not:

When you reuse, you need to reset states you're not using => You end up with more memory access, so x86_64 and ARM addressing modes to write on memory you must at least use a intermediate register, popullating all the registers with the mess of reseting the state of an sprite can force the compiler to use the stack memory, stacks slots often remains in cache, in theory is faster than random heap access, but still much slower than registers read/write.

Thats is without counting you're making it harder to debug.

Better solution:

When you'll be updating the sprite's properties linearly you can schedule them to take the best of your processor like:

sprites[ A ].setPosition( x , y ) ;
sprites[ B ].setScale( sx , sy ) ;
sprites[ C ].setRotation( rot ) ;
 

you can make a vector of RenderStates and draw them all together:


/*
  Where:
  1 -> sprites is an std::vector< sf::Sprite >
  2 -> renderStates is an  std::vector< sf::RenderStates >
  3 -> sprite.size() == renderStates.size() is true.
  4 -> sprites[ 3 ] use the render states stored in renderStates[ 3 ]  ;

  Hint: if the #3 is false you can use std::min() as the size count in the loop, and after the loop you put
  "compensation code" if necessary.

*/

for( int i = 0 ; i < sprites.size() ; ++i )
{
    window.draw( sprites[i] , renderStates[i] ) ;
}

 

Because internally you'll end up calling:

// taken from: RenderTarget.cpp
void RenderTarget::draw(const Vertex* vertices, std::size_t vertexCount,PrimitiveType type, const RenderStates& states);
 

You'll end up using likely all the data from both entities so the processor cache and pipeline is widely used, thus executing it faster.


« Last Edit: June 09, 2017, 08:33:03 pm by DarkRoku »
I would like a spanish/latin community...
Problems building for Android? Look here

Jimmyee

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: Storing sprites in a container for better performance
« Reply #5 on: July 05, 2017, 03:41:11 pm »
Thanks for answers guys, I appreciate it.
My project indeed is intented to be a real game, actualy it's Endless Online clone (that doesn't sound serious and even shouldn't sound like that). I have just started with map rendering and didn't really get any good performance (27% CPU usage while drawing a map of 6 layers). I didn't implement any of suggested methods yet and now probably I will. Here's my current code, feel free to comment it - would be great to get some hints on that. Anyway, I've been working on such rendering like 2 years ago and the best performance was while I used vertex arrays. By the way, the map is isometric.

void Map::Draw()
{
    S &s = S::GetInstance();

    int width = s.emf->width;
    int height = s.emf->height;

    int gfx_id[5] = { 3, 4, 5, 6, 6 };
    int g_xoff[5] = { 0, 0, 0, 0, 0 };
    int g_yoff[5] = { 0, 0, 0, 0, 0 };

    int min_x = s.character.x - 26 >= 0? s.character.x - 26 : 0;
    int min_y = s.character.y - 26 >= 0? s.character.y - 26 : 0;
    int max_x = s.character.x + 26 < width? s.character.x + 26 : width;
    int max_y = s.character.y + 26 < height? s.character.y + 26 : height;

    int cursor_closest_dist = 1000;
    int cursor_closest_dist_x = 0;
    int cursor_closest_dist_y = 0;
    int cursor_closest_dist_draw_x = 0;
    int cursor_closest_dist_draw_y = 0;
    shared_ptr<sf::Texture> tex = s.gfx_loader.LoadTexture(gfx_id[0], s.emf->fill_tile);
    sf::Sprite spr_fill(*tex);
    for(int y = min_y; y < max_y; ++y)
    {
        for(int x = min_x; x < max_x; ++x)
        {
            int graphic_id = s.emf->fill_tile;
            if(graphic_id != 0)
            {
                int player_x = s.character.x * 64 - s.character.x * 32 - s.character.y * 32;
                int player_y = s.character.y * 32 + s.character.x * 16 - s.character.y * 16;
                int screen_x = 640 / 2 - 32 - player_x;
                int screen_y = 480 / 2 - 480 / 4 + 16 - player_y;

                int xoff = x * 64;
                int yoff = y * 32;

                xoff -= x * 32;
                yoff += x * 16;
                xoff -= y * 32;
                yoff -= y * 16;

                //shared_ptr<sf::Texture> tex = s.gfx_loader.LoadTexture(gfx_id[0], graphic_id);
                //sf::Sprite spr(*tex);
                spr_fill.setPosition(screen_x + xoff + g_xoff[0], screen_y + yoff + g_yoff[0]);
                s.window.draw(spr_fill);

                if(x == s.character.x && y == s.character.y)
                {
                    tex = s.gfx_loader.LoadTexture(3, 0);
                    //spr.setTexture(*tex);
                    //s.window.draw(spr);
                }

                sf::Vector2i pos = sf::Mouse::getPosition(s.window);
                int distance = path_length(screen_x + xoff + 32, screen_y + yoff + 16, pos.x, pos.y);
                if(distance < cursor_closest_dist)
                {
                    cursor_closest_dist = distance;
                    cursor_closest_dist_x = x;
                    cursor_closest_dist_y = y;
                    cursor_closest_dist_draw_x = screen_x + xoff;
                    cursor_closest_dist_draw_y = screen_y + yoff;
                }
            }
        }
    }

    if(cursor_closest_dist_x != 0)
    {
        shared_ptr<sf::Texture> tex = s.gfx_loader.LoadTexture(2, 24);
        sf::Sprite spr(*tex, sf::IntRect(0, 0, 64, 32));
        spr.setPosition(cursor_closest_dist_draw_x, cursor_closest_dist_draw_y);
        s.window.draw(spr);
    }

    for(int y = min_y; y < max_y; ++y)
    {
        for(int x = min_x; x < max_x; ++x)
        {
            for(int l = 0; l < 5; ++l)
            {
                int graphic_id = s.emf->GetGraphicID(l, x, y);
                if(graphic_id != 0)
                {
                    int player_x = s.character.x * 64 - s.character.x * 32 - s.character.y * 32;
                    int player_y = s.character.y * 32 + s.character.x * 16 - s.character.y * 16;
                    int screen_x = 640 / 2 - 32 - player_x;
                    int screen_y = 480 / 2 - 480 / 4 + 16 - player_y;

                    int xoff = x * 64;
                    int yoff = y * 32;

                    xoff -= x * 32;
                    yoff += x * 16;
                    xoff -= y * 32;
                    yoff -= y * 16;

                    shared_ptr<sf::Texture> tex = s.gfx_loader.LoadTexture(gfx_id[l], graphic_id);

                    if(l == 1) // objects
                    {
                        g_xoff[l] = 32 - tex->getSize().x / 2;
                        g_yoff[l] = 0 - (tex->getSize().y - 32);
                    }
                    else if(l == 3) // wall down
                    {
                        g_xoff[l] = -16 + tex->getSize().x / 2;
                        g_yoff[l] = -tex->getSize().y + 32;
                    }
                    else if(l == 4) // wall right
                    {
                        g_xoff[l] = 16 + tex->getSize().x / 2;
                        g_yoff[l] = -tex->getSize().y + 32;
                    }

                    sf::Sprite spr(*tex);
                    spr.setPosition(screen_x + xoff + g_xoff[l], screen_y + yoff + g_yoff[l]);
                    s.window.draw(spr);

                    if(l == 0 && x == s.character.x && y == s.character.y)
                    {
                        tex = s.gfx_loader.LoadTexture(3, 0);
                        spr.setTexture(*tex);
                        spr.setPosition(screen_x + xoff, screen_y + yoff);
                        s.window.draw(spr);
                    }
                    if(l == 0 && cursor_closest_dist_x != 0 && x == cursor_closest_dist_x && y == cursor_closest_dist_y)
                    {
                        tex = s.gfx_loader.LoadTexture(2, 24);
                        spr = sf::Sprite(*tex, sf::IntRect(0, 0, 64, 32));
                        spr.setPosition(cursor_closest_dist_draw_x, cursor_closest_dist_draw_y);
                        s.window.draw(spr);
                    }
                }
            }
        }
    }
}
 
« Last Edit: July 05, 2017, 03:48:35 pm by Jimmyee »