SFML community forums
Help => Graphics => Topic started by: mongrol on January 22, 2010, 10:56:51 am
-
Hi folks,
My game was mainly written on OSX with XCode, I've now moved to linux on a netbook for on the go hacking and find performance is just dreadful. My game is turn based strategy, with ascii based with sf::string tiles. I generate a cache of tiles of ascii chars (32-127) and keep them in a std::map. Here's my cache code.
//create lookup table of tiles
//first make a char array of our tile GfxId's (ascii table 32-127)
std::string gfxStr;
for (int i=32;i<128;i++){
char chr=i;
gfxStr.append(1,i);
}
//std::cout << "gfxStr = " << gfxStr << std::endl;
//then iterate it creating SFML drawables
for (int i=0; i < gfxStr.size();i++){
const char gfxChr=gfxStr[i];
std::string str(1,gfxChr);
sf::String tile;
tile.SetText(str);
tile.SetFont(m_displayFont);
tile.SetSize(TILEHEIGHT-TEXTPADDING);
tile.SetStyle(sf::String::Regular);
//add the drawable to the tile cache
tileLookupMap[gfxChr]=tile;
}
My screen is draw by sending an array of ScreenTile's objects, a member of which is a char depicting the tile to be shown. The screen is 80x26 tiles and every one must be drawn every frame. However, I only call a redraw on events or when animating a single projectile. When animating it calls the redraw (which draws every tile) as fast as possible until the animation is finished. Currently my single projectile tile crawls over the screen at about 1fps. Not good.
Here's the draw code. My latest effort looks up the tile object from the container map and modifies it's position and attribs (bold etc) before drawing it.
void GfxEngine::render(std::vector<std::vector<ScreenTile> > screenArray, gameState_t gameState)
{
m_mainWindow.Clear(sf::Color(0, 0, 0));
//iterate and draw map
for (int n=0; n < SCREENTILEHEIGHT; n++){
for (int m=0; m < SCREENTILEWIDTH; m++){
sf::String *tile=&tileLookupMap[screenArray[n][m].gfxId];
tile->SetColor(colorLookup[screenArray[n][m].color]);
tile->SetPosition(m*TILEWIDTH,n*TILEHEIGHT);
//set attributes
if (screenArray[n][m].attribBold) tile->SetStyle(sf::String::Bold);
if (screenArray[n][m].attribReverse){
//if reverse we draw a coloured square underneath the tile then the ascii on top.
tile->SetColor(colorLookup[0]);
sf::Image background(TILEWIDTH, TILEHEIGHT, colorLookup[screenArray[n][m].color]);
sf::Sprite sprite(background);
sprite.SetPosition(m*TILEWIDTH,n*TILEHEIGHT);
m_mainWindow.Draw(sprite);
}
m_mainWindow.Draw(tileLookupMap[screenArray[n][m].gfxId]);
}
}
I've posted gprof scores at the link below that shows large chunks of time stuck in the render routine and sf::string. Top also shows my program using 60% CPU and Xorg the remaining 40% while animating. Other OpenGL apps appear to work fine and use little CPU. Is my approach sane and should it actually work? The netbook is a N280 1.66Mhz (about the speed of a 900Mhz P3) with a GMA950 chipset (945G). Any help is greatly appreciated.
http://code.bulix.org/1ntsw4-73971
-
This kind of code should be much faster in SFML 2. If you want you can already try it from the SVN repository.
You should also cache every single tile of your screen, so that your rendering loop is reduced to calling Draw on each tile (changing a drawable attributes all the time has a small cost).
If it's still too slow, you can try using sprites (in this case you'll have to generate the glyphs yourself with an image editor), they may be faster than 1-character texts.
In SFML 2 it may even be possible to use a sf::Font and sf::Sprite together; that should be a better strategy than using sf::Text in your case.
But try SFML 2 first ;)
EDIT: oh my god, I didn't see that you were creating a sf::Image on the fly everytime that you want to draw a colored square. this is insane. Rather use a sf::Shape, and better: create it once and store it!
-
Thanks for the fast reply Laurent. Is SFML v2.0 usable on OSX? I though the Mac port was now unsupported. Regarding the sf::Image there would only ever be one of those at any one time. It's basically used to show a "reverse" character when a tile is targetting or selected. When it's the AI's turn (which I use for benchmarking as it's trigger happy) it's never used.
I'll look into expanding my cache. This does mean creating 96 tiles, 16 colours each with both bold and reverse. 96x16x2 = 3072 tiles.
-
Is SFML v2.0 usable on OSX? I though the Mac port was now unsupported
Ah, you're right. I forgot that you were using it, sorry.
Regarding the sf::Image there would only ever be one of those at any one time. It's basically used to show a "reverse" character when a tile is targetting or selected. When it's the AI's turn (which I use for benchmarking as it's trigger happy) it's never used.
Ok, that sounds better than what it looks like in your code ;)
But even if it's almost never used, it is still a good practice to handle it properly.
I'll look into expanding my cache. This does mean creating 96 tiles, 16 colours each with both bold and reverse. 96x16x2 = 3072 tiles.
Well, I didn't mean to precompute every possible combination that you will have to draw. I rather say that you should have one sf::Sprite instance for every tile drawn (so that what you see is what you have in memory). You don't have to store 16 times a sprite, you can still change its color during runtime. But don't do every time you draw it, do it when the color actually changes.
-
And... you should pass your vectors by const reference rather than by value. Each time you call render(), you do an entire copy of your tile array. This can improve the performances a lot, too.
void GfxEngine::render(const std::vector<std::vector<ScreenTile> >& screenArray, gameState_t gameState)
-
Good tips. After having a good look at my pipeline it appears that my n00b programmer days are showing through. This was all written a year ago when I first started C++ and I'm copying arrays of data all over the place before it even gets to the renderer. I've already got a 50% speedup by changing value passes to reference and there lot's of room for improvement to go. Thanks.
-
Just a quickie. Is it possible for sf::string objects to have a solid background color? That way I can do away with sf::Images altogether and save myself a chunk of drawing.
-
No. But like I said you don't need a sf::Image + sf::Sprite, just use a sf::Shape (create it with the sf::Shape::Rectangle function).
-
Ok thanks. As an option I've got the idea now of creating a single background image of my terrain tiles (sf::string) and drawing that then blitting my creatures and objects over the top. I would then recreate this background image when terrain changes or a creature moves (FOV update). What's the best way to compile a single sf::image (or sf::sprite) from multiple sf::strings?
I suppose in this instance I might be better off rendering my own tiles in a graphics package, arrange them in memory and creating an sf::image from that chunk of memory.
-
In SFML 1.x you can't, but in SFML 2 there's a RenderImage class (which is basically a RenderWindow that draws to a sf::Image rather than a window).
-
Ok. So my plan is to draw some ASCII images and use them instead of sf::string. If drawing 1560 (60x26) sprites every frame can't be done quickly enough I plan to "compose" a single image from the 1560 tiles when required. Due to FOV this will be when the player moves a tile (think of XCom) so I'll be rendering 1560 sprites AND doing a Copy of those sprite's sf::Image's into a bigger single sf::Image.
Is that feasible? There's some big warnings about copying images on the wiki.
-
You should first test drawing all your sprites, it may be fast enough so that you don't have to find a complicated solution ;)
-
I've now converted over to drawing each tile as a sprite and nothing else. I'm using a single sprite image that's preloaded and cached. My current loop looks like this;
void GfxEngine::createMap(std::vector<std::vector<Terrain> >& mapArray)
{
for (int n=0; n < MAPHEIGHT; n++){
for (int m=0; m < MAPWIDTH; m++){
tileSprite.SetPosition(m*TILEWIDTH,n*TILEHEIGHT);
tileSprite.SetColor(colorLookup[mapArray[n][m].screenTile.color]);
m_mainWindow.Draw(tileSprite);
}
}
}
This draws 1560 sprites (or changes the position and colour that many times) and I'm getting about 1-2fps. Due to they nature of the map and my FOV code even if I optimise it further and draw only changes, that's still going to be over 50% of the tiles, and near the end of a level where every tile has been uncovered it could approach 100% of the tiles being drawn. I've also narrowed it down to SFML rendering by not drawing the map and leaving all other game code turned on. It then flies along. Turn on the map and boom, slowdown.
I find it hard to believe that SFML rendering is this slow. Other OpenGL and SDL games (Crawl, Naev, glblur) run at full speed. Am I doing something that unusual?
-
Is createMap only being called once or is it called every frame?
-
It's called every frame. As explained earlier due to FOV its not worthing caching a giant single map image as the amount of changes will be large needing a new image needing copied.
-
You still didn't understand my solution ;)
I suggested that you precompute and store one sf::Sprite instance per tile, not a single instance for all of them.
Then, instead of storing the color in colorLookup[mapArray[n][m].screenTile.color], you can directly change the sprite's color when it changes.
-
Ah I have it. Then when the tile changes, I simply change the sprite's image pointer.
-
Yeah, same for any of the sprite's attribute (color, position, ...). So that in your drawing function you just have to... draw ;)
If it's still too slow we can talk about other improvements.
-
Righty. I now have a nested vector of sprites created once. THen every frame I iterate and draw them. GetFrameTime() shows an average of 0.35 per frame so around 2-3fps.
void GfxEngine::createMap(std::vector<std::vector<Terrain> >& mapArray)
{
// this is called once
mapSprites.resize(MAPHEIGHT);
for (int i=0; i<MAPHEIGHT; i++){
mapSprites[i].resize(MAPWIDTH);
};
for (int n=0; n < MAPHEIGHT; n++){
for (int m=0; m < MAPWIDTH; m++){
sf::Sprite tileSprite;
tileSprite.SetImage(tileImage);
tileSprite.SetPosition(m*TILEWIDTH,n*TILEHEIGHT);
tileSprite.SetColor(colorLookup[mapArray[n][m].screenTile.color]);
mapSprites[n][m]=tileSprite;
}
}
}
void GfxEngine::renderMap()
{
// this is called every frame
for (int n=0; n < MAPHEIGHT; n++){
for (int m=0; m < MAPWIDTH; m++){
m_mainWindow.Draw(mapSprites[n][m]);
}
}
}
-
How many FPS do you get if you don't call the renderMap() function (just to know how much we can optimize)?
I currently see two solutions for optimizing further:
- if not all the sprites are visible on screen, you can try to avoid drawing those that are hidden
- try SFML 2
-
If I turn off renderMap then the only thing that is drawing is the overlay. This is drawing 3 players (squad based game) which are drawn using last weeks sf::String method. It now shows 0.04 with GetFrameTime so over 20fps. I'll change this routine to sprites as well.
SFML2 isn't an option until a Mac version shows up. I'll implement delta's on the map, get rid of any legacy sf::String stuff, further optimise my own bit of the pipe and it'll probably be acceptable. My main aim at this point is to have the netbook good enough to develop on, rather than actually have users play the game on it.
It would be interesting to see how see how it runs on Windows7 which this machine dual boots with. I'd not be surprised to find some Intel/XOrg driver issues here as well.
Thanks very much for your patience and help Laurent.