Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Improving performance in my map drawing

This thread is locked; no one can reply to it. rss feed Print
Improving performance in my map drawing
Elverion
Member #6,239
September 2005
avatar

Let me begin by explaining how my map drawing currently works. First, there is a tile_layer BITMAP that is slightly larger than the 'screen'. Currently, there is 64 pixels bounding space on all sides. Each frame, tile_layer is blitted onto the frame_buffer, followed by any other sprites that need blitting. Make sense so far? Good, cuz this is where it gets complicated.

Whenever the screen view goes beyond what is contained within the tile_layer, it is repositioned and redrawn. This means the tile_layer is only redrawn ever few frame while the screen is moving, at not at all while it's not moving (I thought this was a good idea, at least). Here is the function that handles that (most functions are self-explanatory; if you don't understand what something does, just ask.):

1int CEngine::redraw_tiles_if_needed()
2{ // just like the function's name says, it
3 // positions and redraws the tile_layer if needed
4 bool update = false;
5
6 if ( view_xoffset < tile_xoffset ) {
7 tile_layer_snap_left();
8 update = true; }
9 else if ( (view_xoffset + fb_width) > (tile_xoffset + tile_layer_width - tile_size) ) {
10 tile_layer_snap_right();
11 update = true; }
12
13 if ( view_yoffset < tile_yoffset ) {
14 tile_layer_snap_top();
15 update = true; }
16 else if ( (view_yoffset + fb_height) > (tile_yoffset + tile_layer_height - tile_size) ) {
17 tile_layer_snap_bottom();
18 update = true; }
19
20
21 if (update)
22 { // redraw the tile_layer
23 tile_layer_redraw();
24 }
25
26 return 1;
27}

And the function call to tile_layer_redraw() is as follows:

1void CEngine::tile_layer_redraw()
2{ // this function will redraw the "tile_layer"
3 int begin_x, end_x;
4 int begin_y, end_y;
5
6 begin_x = tile_xoffset / tile_size;
7 end_x = begin_x + (tile_layer_width / tile_size);
8
9 begin_y = tile_yoffset / tile_size;
10 end_y = begin_y + (tile_layer_height / tile_size);
11
12 // make SURE that the map section we're trying to update
13 // isnt outside the map's boundaries.
14
15 if (begin_x < 0)
16 begin_x = 0;
17 if (end_x > map_max_width)
18 end_x = map_max_width;
19
20 if (begin_y < 0)
21 begin_y = 0;
22 if (end_y > map_max_height)
23 end_y = map_max_height;
24
25 for (int b = begin_y; b < end_y; b++)
26 {
27 for (int a = begin_x; a < end_x; a++)
28 {
29 int index = b * map_max_width + a;
30 add_tile( MAP[index].graphic, a*tile_size - tile_xoffset, b*tile_size - tile_yoffset);
31 }
32 }
33}

**NOTE: MAP is an array of tile objects.

and add_tile(...) is just a shortcut for blitting to the correct surface. The idea was to make it shorter, simpler, and less error prone.

// draws a tile to tile_layer, at the given coordinates.
void CEngine::add_tile(BITMAP *tile, int x, int y)
{
  blit(tile, tile_layer, 0, 0, x, y, tile_size, tile_size);
}

Now, for the problem...It's still slow. It's much, much faster than redrawing all of the tiles each frame, sure, but if the screen is scrolling till it reaches the edge of the tile_layer (time to redraw), you can notice the shift...it's rather jerky looking, and overall I think it uses too much CPU (but this is kind of hard to check). Does anyone have any suggestions on how to improve this?

I would also appreciate if anyone who tests this would tell me how well it ran for them, and on what OS. Since I've changed to page-flipping, someone reported to me saying it no longer runs on Linux (it segfaults).

*source is included if your either interested in "borring" my work (which is fine by me, but I dont know why you would want to), or for reference. Sorry about it being so sloppy, but the project is only for testing purposes. I will rewrite it once I figure out how to get the thing running as fast as I possibly can.

--
SolarStrike Software - MicroMacro home - Automation software.

OICW
Member #4,069
November 2003
avatar

Ok, basicaly 30 fps is not good (Compaq Amrmada e500 - 500 MHz proc, 128 MB RAM, 8MB gfx, Win 2k). You're right that when it's scrolling it looks jerky and fps drops to 26. So first advice is - blit everything onto buffer and then onto screen - avoid 2 large bitmap blitting it's too slow. The rest looks quite good, but I'm not sure if I fully understand your redraw_tiles_if_needed_function() - this is probably the second thing which slowers it.

[My website][CppReference][Pixelate][Allegators worldwide][Who's online]
"Final Fantasy XIV, I feel that anything I could say will be repeating myself, so I'm just gonna express my feelings with a strangled noise from the back of my throat. Graaarghhhh..." - Yahtzee
"Uhm... this is a.cc. Did you honestly think this thread WOULDN'T be derailed and ruined?" - BAF
"You can discuss it, you can dislike it, you can disagree with it, but that's all what you can do with it"

Elverion
Member #6,239
September 2005
avatar

Yes, it would seem logical to NOT draw bitmap A to bitmap B, then bitmap B to screen, but theres a reason for it. Based on experimentation, it's much faster to draw one large bitmap than many small ones. What I'm saying is, the reason for the tile_layer is to avoid having to redraw each tile each frame. This method is very slow.

I tried running this code:

1 while( !quit )
2 {
3 if ( key[KEY_ESC] )
4 quit = true;
5
6 for (int y = 0; y < WINDOW_HEIGHT; y++)
7 {
8 for (int x = 0; x < WINDOW_WIDTH; x++)
9 {
10 blit(grass, frame_buffer, 0, 0, x * 32, y * 32, 32, 32);
11 }
12 }
13
14 fps++;
15 blit(frame_buffer, screen, 0, 0, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
16 }

Crude, but an effective test. It ran at about half the FPS of the tile_layer method.

As for my function to redraw the tile_layer...well, it is difficult to explain, but in short the first part (the ifs and if-elses) just repositions the tile_layer. Both the "screen" and tile_layer are based off world coordinates (their x and y positions from 0, 0: The top left of the map). No need to worry about that part, as it works right. The highlight was that it only actually will call the redraw_tile_layer() if the screen goes out of bounds of the tile_layer.

--
SolarStrike Software - MicroMacro home - Automation software.

HoHo
Member #4,534
April 2004
avatar

It didn't compile for me* but probably this is a problem with my setup.
*)g++ *.cpp `allegro-config --libs`
One way to get about 2-3x faster* would be to use RLE sprites instead of regular BITMAP's for the tiles.

*) of cource depending on used graphics. The more blank area you have the faster it will be.

__________
In theory, there is no difference between theory and practice. But, in practice, there is - Jan L.A. van de Snepscheut
MMORPG's...Many Men Online Role Playing Girls - Radagar
"Is Java REALLY slower? Does STL really bloat your exes? Find out with your friendly host, HoHo, and his benchmarking machine!" - Jakub Wasilewski

Thomas Harte
Member #33
April 2000
avatar

Quote:

it would seem logical to NOT draw bitmap A to bitmap B, then bitmap B to screen, but theres a reason for it. Based on experimentation, it's much faster to draw one large bitmap than many small ones.

Could that perhaps be because you are failing to acquire/release your video memory bitmaps?

Elverion
Member #6,239
September 2005
avatar

HoHo, that was good advice, but theres a problem. I modified the code I used for testing earlier to get something like this:

1 frame_buffer = create_bitmap(WINDOW_WIDTH, WINDOW_HEIGHT);
2 grass = load_bitmap("grass.bmp", NULL);
3 rle_grass = get_rle_sprite(grass);
4
5 while( !quit )
6 {
7 if ( key[KEY_ESC] )
8 quit = true;
9
10 for (int y = 0; y < WINDOW_HEIGHT; y++)
11 {
12 for (int x = 0; x < WINDOW_WIDTH; x++)
13 {
14 draw_rle_sprite(frame_buffer, rle_grass, x * 32, y * 32);
15 //blit(grass, frame_buffer, 0, 0, x * 32, y * 32, 32, 32);
16 }
17 }
18
19 fps++;
20 blit(frame_buffer, screen, 0, 0, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
21 }

If I comment out the blitting of tiles to the screen (use draw_rle_sprite), I get 25 FPS. Using regular blit, I get 38. Of course, the drawing does go beyond the screen, which might have something to do with it. If I just have it draw enough tiles to fill the screen, they both run at the maximum speed of ~60 FPS. Either way, should I be concerned that there may be a special case when BITMAP * would be faster?

Thomas: I'm not entirely sure what you mean. I beleive I'm aquiring/releasing the video bitmaps correctly, but also I just changed the project to use page-flipping. Before then, it still had the same problem so I'm almost sure it has nothing to do with that. Could you re-explain it?

--
SolarStrike Software - MicroMacro home - Automation software.

Thomas Harte
Member #33
April 2000
avatar

Quote:

I'm not entirely sure what you mean. I beleive I'm aquiring/releasing the video bitmaps correctly, but also I just changed the project to use page-flipping. Before then, it still had the same problem so I'm almost sure it has nothing to do with that. Could you re-explain it?

Modern operating systems like to run all the various programs in separate memory spaces so that any crashes don't bring everything down. However, video memory is a global resource. The act of acquiring a video memory bitmap gives your process access to the global resource. This is probably exclusive access, so you should release your bitmap so that other processes can make use of the video memory. In windows you should release because you won't get any user input while the front buffer is acquired.

Acquiring and releasing is a slow operation. If you attempt to draw to a bitmap that requires acquiring/releasing and you have not done so then Allegro must.

Hence, if you had a loop like the one you just posted but frame_buffer was in video memory (e.g. if it was "screen") then every time you call draw_rle_sprite Allegro has to acquire and then release the bitmap. If you add "acquire_bitmap(bmpname)" before your loop and "release_bitmap(bmpname)" afterwards then Allegro does not need to acquire within draw_rle_sprite because the bitmap is already acquired.

If you aren't modifying your bitmaps much except by blitting from other bitmaps, have you looked into just using video bitmaps for everything? On 99.9% of Windows machines that would give a gigantic speed boost by taking work off the CPU. Just make sure you include a fallback for OSs where Allegro doesn't use the hardware.

Elverion
Member #6,239
September 2005
avatar

Ooh, I understand now. I didn't even know about the acquire_bitmap/release_bitmap functions (or, at least I didn't understand them), but now I see how to correctly use them to make it faster to blit many tiles onto the screen. Thanks.

Which OSs do not support video bitmaps? I plan to make my games portable mainly to Windows (obviously), and Unix/Linux.

--
SolarStrike Software - MicroMacro home - Automation software.

Thomas Harte
Member #33
April 2000
avatar

Quote:

Which OSs do not support video bitmaps?

Certainly the Mac OS X port of Allegro doesn't. The Linux port may not depending what video system it is relying on. Similarly the Windows port may not if the graphics card doesn't. That's a rare thing nowadays but if you want to write rock solid code you should allow for the possibility.

Go to: