Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » al_draw_bitmap faster on backbuffer than on another bitmap?

Credits go to Edgar Reynaldo for helping out!
This thread is locked; no one can reply to it. rss feed Print
al_draw_bitmap faster on backbuffer than on another bitmap?
Dash125
Member #13,764
November 2011

First of all, hello everyone.
I have recently began building a simple tile engine with learning purposes, and have implemented a simple layered-tile-map drawing function (Code below), which had a rather large flaw: It built the map on each frame instead of drawing the tile layers on a persisting Bitmap which could then be used for updates.

When I tried to implement this (I.E. Using a bitmap to store the generated map image and then draw this on the backbuffer on all successive updates), I noticed things worked great as long as I stuck with really small maps, but when I tried larger maps (50x50) everything went to hell. The initial building of the map into the buffer bitmap took around 4 minutes, whereas drawing it on each frame into the backbuffer as done previously took a few seconds (Which was the reason why I tried to improve its performance by only drawing it once).

Is it possible that drawing on an ALLEGRO_BITMAP is a LOT slower than drawing on the backbuffer?
If so, any suggestions on how I can improve my drawing function?

#SelectExpand
1void CTileMap::paint(ALLEGRO_DISPLAY *display) 2{ 3 if (!m_bmpMapBuffer) { 4 TileCoord tc; 5 TileGrid *tgTiles = NULL; 6 CLayer *lyrCurrentLayer = NULL; 7 CTileset *tlsCurrentTileset = NULL; 8 CTilesetImg *tliPalette = NULL; 9 ALLEGRO_BITMAP *bmpMesh = NULL; 10 int nTileIndex; 11 // int nTranspColor; 12 13 m_bmpMapBuffer = al_create_bitmap(m_nWidth * m_nTileWidth, m_nHeight * m_nTileHeight); 14 //al_set_target_bitmap(al_get_backbuffer(display)); 15 al_set_target_bitmap(m_bmpMapBuffer); 16 17 // Iterate through the layers from bottom to top painting each one 18 for (LayerVector::iterator itLayer = m_vLayers.begin(); itLayer != m_vLayers.end(); ++itLayer) { 19 lyrCurrentLayer = (*itLayer); 20 21 tgTiles = lyrCurrentLayer->getTileGrid(); 22 23 // Iterate through the rows of the current layer 24 for (int x = 0; x < lyrCurrentLayer->getWidth(); x++) 25 { 26 tc.x = x; 27 28 // Iterate through the columns of the current row of the layer 29 for (int y = 0; y < lyrCurrentLayer->getHeight(); y++) 30 { 31 tc.y = y; 32 33 // If the coordinate has no Tile element, skip to the next 34 if (tgTiles->find(tc) == tgTiles->end()) { 35 continue; 36 } 37 38 nTileIndex = tgTiles->at(tc)->getTileIndex(); 39 40 // Iterate backwards through the available tilesets for the map in order to find the one corresponding to the current GId 41 for (TilesetVector::reverse_iterator itTileset = m_vTilesets.rbegin(); itTileset != m_vTilesets.rend(); ++itTileset) 42 { 43 tlsCurrentTileset = (*itTileset); 44 45 // If the GId is within the current tileset, draw it 46 if (nTileIndex >= tlsCurrentTileset->getFirstGId()) { 47 tliPalette = tlsCurrentTileset->getTilePalette(); 48 49 bmpMesh = tliPalette->getImage(); 50 51 52 al_draw_bitmap_region(bmpMesh, 53 tlsCurrentTileset->getTileX(nTileIndex), 54 tlsCurrentTileset->getTileY(nTileIndex), 55 tlsCurrentTileset->getTileW(), 56 tlsCurrentTileset->getTileH(), 57 (m_nTileWidth * x), (m_nTileHeight * y), 0); 58 59 // End the loop in order to avoid the tile from being overwritten by an empty image 60 break; 61 } 62 } 63 } 64 } 65 } 66 } 67 al_set_target_bitmap(al_get_backbuffer(display)); 68 al_draw_bitmap(m_bmpMapBuffer, 0, 0, 0); 69}

Edgar Reynaldo
Member #8,592
May 2007
avatar

Dash125 said:

When I tried to implement this (I.E. Using a bitmap to store the generated map image and then draw this on the backbuffer on all successive updates), I noticed things worked great as long as I stuck with really small maps, but when I tried larger maps (50x50) everything went to hell. The initial building of the map into the buffer bitmap took around 4 minutes, whereas drawing it on each frame into the backbuffer as done previously took a few seconds (Which was the reason why I tried to improve its performance by only drawing it once).

If your buffer bitmap was 50x50 tiles, then it could easily have exceeded the size limit of video bitmaps on your hardware and become a memory bitmap. If it was, and you then tried to draw your video bitmap tiles onto a memory bitmap it would have been horrendously slow due to reading from video memory and writing back to regular memory.

I think your best bet is to a single tile sheet in video memory and create your tiles as sub bitmaps of this sheet. Then when you draw your tile map use al_hold_bitmap_drawing before and after you draw all of your sub bitmap tiles.

The main problem is probably due to memory bitmaps being in use somewhere.

Edit

After looking at your code, you are creating a bitmap (likely a memory bitmap) on every drawing frame and then drawing that to your backbuffer. You don't want to create and redraw the background buffer on every frame. That defeats the purpose of pre drawing it. You're better off drawing directly to the backbuffer every frame, using sub bitmaps instead of drawing regions, and drawing all the sub bitmaps that share a common parent at the same time if you can.

Dash125
Member #13,764
November 2011

After looking at your code, you are creating a bitmap (likely a memory bitmap) on every drawing frame and then drawing that to your backbuffer. You don't want to create and redraw the background buffer on every frame. That defeats the purpose of pre drawing it. You're better off drawing directly to the backbuffer every frame, using sub bitmaps instead of drawing regions, and drawing all the sub bitmaps that share a common parent at the same time if you can.

Actually, I'm only drawing the memory bitmap only once: m_bmpMapBuffer is a class member, and is initialized as NULL when the object is created, and will be created only once (Hence the null check at the beginning of the function), and will remain alive as long as the object lives.

Edgar Reynaldo said:

If your buffer bitmap was 50x50 tiles, then it could easily have exceeded the size limit of video bitmaps on your hardware and become a memory bitmap. If it was, and you then tried to draw your video bitmap tiles onto a memory bitmap it would have been horrendously slow due to reading from video memory and writing back to regular memory.

I think your best bet is to a single tile sheet in video memory and create your tiles as sub bitmaps of this sheet. Then when you draw your tile map use al_hold_bitmap_drawing before and after you draw all of your sub bitmap tiles.

The main problem is probably due to memory bitmaps being in use somewhere.

A few questions on this, since I'm quite a newbie on both Allegro and game development from scratch:
- How can I can tell if a bitmap I declared lives in video memory or regular memory? (I thought that was automatically handled by Allegro).
- The buffer bitmap being created on my method is 800x800px (16x16 px per tile), but according to al_get_display_option(display, ALLEGRO_MAX_BITMAP_SIZE), the maximum size for bitmaps is 4096. How is this possible?

Just in case, I will briefly describe the map circuit, since maybe my error is deeper than just the drawing code :)

- The map is parsed and all pertinent objects are generated and initialized.
- The tile sheet is loaded as an ALLEGRO_BITMAP.
- When the paint method is called, I check if the buffer bitmap has been created.
- If the buffer bitmap has not been created, I get the tile bitmap from the tilesheet and draw it onto the buffer bitmap (previously it was done on the backbuffer directly) for each tile of each layer.
- Once I'm sure the buffer bitmap exists, I draw it onto the backbuffer.

(I know you can deduce most of this from my code, but I guess it can't harm to add all the information I have)

edit: I just added the al_hold_bitmap_drawing to the method, and after some reading, set all bitmaps to be video bitmaps by default, and found no changes whatsoever

Edgar Reynaldo
Member #8,592
May 2007
avatar

Major Edit

Dash125 said:

Actually, I'm only drawing the memory bitmap only once: m_bmpMapBuffer is a class member, and is initialized as NULL when the object is created, and will be created only once (Hence the null check at the beginning of the function), and will remain alive as long as the object lives.

I totally missed the if (!m_bmpMapBuffer) { line. :-[:-[:-[

Well, it doesn't look like you are buffering properly at all. Your paint method does this :

#SelectExpand
1paint() { 2 create_buffer() 3 set_target(buffer) 4 for each layer 5 for each column x 6 for each row y 7 if (Tiles->find(Tile(x,y))) {goto next y} 8 reverse for each tileset tls 9 if (tls.contains(tileindex)) { 10 draw_bitmap_region(tileset->get_palette()->get_image(),....); 11 break for each tileset 12 } 13 end for 14 end for 15 end for 16 end for 17 set_target(allegro_backbuffer) 18 draw(buffer) 19}

If you are calling that every time you need to draw your tilemap, then your buffer is completely wasted and unnecessary and you should have drawn to allegro's backbuffer instead. Your paint method also leaks your buffer bitmap every time you call it.

Quote:

- How can I can tell if a bitmap I declared lives in video memory or regular memory? (I thought that was automatically handled by Allegro).

Use al_get_bitmap_flags to check what properties the bitmap has. Use al_set_new_bitmap_flags to tell allegro what kind of bitmap to create when you create or clone a new bitmap. I don't know what the default kind of bitmap is anymore, but I thought it would be a 'fail if unsuccessful video bitmap'.

Quote:

- The buffer bitmap being created on my method is 800x800px (16x16 px per tile), but according to al_get_display_option(display, ALLEGRO_MAX_BITMAP_SIZE), the maximum size for bitmaps is 4096. How is this possible?

As long as al_get_new_bitmap_flags() & ALLEGRO_VIDEO_BITMAP is true, it should be making a video bitmap for you, especially if it is only 800x800.

Quote:

- When the paint method is called, I check if the buffer bitmap has been created.
- If the buffer bitmap has not been created, I get the tile bitmap from the tilesheet and draw it onto the buffer bitmap (previously it was done on the backbuffer directly) for each tile of each layer.
- Once I'm sure the buffer bitmap exists, I draw it onto the backbuffer.

That's not what the code you showed me does, unless you really only call paint once, and you have some other method that draws the buffer to the allegro back buffer. (In which case, why have paint draw onto the backbuffer, and not just call <code>paint();displaybuffer();</code>).

Ideally, I would do something like this (Edit - he already is) :

#SelectExpand
1class TileMap { 2public : 3 void Display() { 4 if (!buffer_drawn) {DrawBuffer();} 5 al_set_target_bitmap(al_get_backbuffer(display)); 6 al_draw_bitmap(buffer , -camerax , -cameray , 0); 7 } 8 void DrawBuffer() { 9 if (!buffer) { 10 buffer = MakeBuffer(); 11 } 12 // draw tile map onto buffer 13 buffer_drawn = true; 14 } 15};

I am still not sure what you did to make it take 4 minutes, or even several seconds to draw unless memory bitmaps were involved somehow.

Edit

Quote:

edit: I just added the al_hold_bitmap_drawing to the method, and after some reading, set all bitmaps to be video bitmaps by default, and found no changes whatsoever

If the tile bitmaps don't share a common parent (like a sub bitmap would, like I recommended earlier), then al_hold_bitmap_drawing probably won't do much for you.

What graphics card are you using?

Dash125
Member #13,764
November 2011

I found the four minute problem: I had a batch of test map files which I had built using Tiled (The editor I chose for this project), and at some point must have renamed one that should never have made on this test in the first place (It was 50x50, but had an extremely large amount of layers). I replaced it by the one that should have been on the test (four 50x50 layers), and re-tested. The whole thing took around 1:12 minutes to draw the map buffer. (Sorry about the mix-up!)

Since the tile sheet is on a single bitmap, and the map buffer is built by painting bits of the tile sheet onto it, I added the al_hold_bitmap_drawing before starting the loop and after finishing it. (Is this what you mean by parent bitmap?)

I also set the bitmaps to be video by default, and the result was the same...

As long as al_get_new_bitmap_flags() & ALLEGRO_VIDEO_BITMAP is true, it should be making a video bitmap for you, especially if it is only 800x800.

I've just checked this, and found (to my horror) that they are all memory bitmaps! It seems my al_set_new_bitmap_flags line is being completely ignored, which causing the whole problem...

EDIT: I've just tried creating the bitmap on the main() which is where I create the display, and the result is a video bitmap! How is it possible that the Bitmaps created within the classes of my project are not respecting the video bitmap setting, whereas the ones being created on the main method are respecting it? Here's a pseudocode-ish example of what I mean to clarify:

#SelectExpand
1int main(int argc, char **argv) 2{ 3 // Initialize allegro and do stuff 4 // ... 5 // Create display 6 // ... 7 al_set_target_backbuffer(display); 8 al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP); 9 10 // Load a bitmap 11 // This is created as a VIDEO BITMAP 12 ALLEGRO_BITMAP *bitmap = al_load_bitmap("tilesetTest.png"); 13 14 // Create an object with a bitmap attribute 15 MyClass mc; 16 17 // Create the same bitmap as before inside the init() method 18 // This is created as a MEMORY BITMAP 19 mc.init("tilesetTest.png"); 20} 21 22class MyClass { 23public: 24 MyClass() { bmp = NULL; } 25 ~MyClass() { if (bmp) { al_destroy_bitmap(bmp); } } 26 27 void init(char *bitmapPath) { 28 bmp = al_load_bitmap(bitmapPath); 29 } 30 31 ALLEGRO_BITMAP * getBmp() { return bmp; } 32 33private: 34 ALLEGRO_BITMAP *bmp; 35}

I'm quite lost here... Shouldn't both bitmaps be created using the same settings? ???

LAST EDIT

The only thing I'm still not sure of is this:

If I use the tile sheet bitmap to draw the tiles onto the map buffer, is it right to use al_hold_bitmap_drawing?

Read my next post! :)

Edgar Reynaldo
Member #8,592
May 2007
avatar

Dash125 said:

Since the tile sheet is on a single bitmap, and the map buffer is built by painting bits of the tile sheet onto it, I added the al_hold_bitmap_drawing before starting the loop and after finishing it. (Is this what you mean by parent bitmap?)

I'm not sure if al_draw_bitmap_region benefits from al_hold_bitmap_drawing the way a sub bitmap would. You'd have to ask the developers. I would think that it does, but I don't know. I don't think al_hold_bitmap_drawing would do anything for drawing memory bitmaps though, and that may be why you didn't see any difference.

Dash125 said:

I've just tried creating the bitmap on the main() which is where I create the display, and the result is a video bitmap!
How is it possible that the Bitmaps created within the classes of my project are not respecting the video bitmap setting, whereas the ones being created on the main method are respecting it?

Are you calling your init method before the display has been created? You can't make a video bitmap without having a display to attach it to. There was some mention of auto converting memory bitmaps with the ALLEGRO_VIDEO_BITMAP flag to video bitmaps upon creating a display, but I don't know if that is the default or if it is in the current version of Allegro yet.

Are you making your objects in a different thread? You can't make video bitmaps in a secondary thread unless it also creates another display.

Dash125 said:

I'm quite lost here... Shouldn't both bitmaps be created using the same settings?

Assuming you create them in the same thread as your display, you create them after your display, and you either don't call al_set_new_bitmap_flags, or you call it with ALLEGRO_VIDEO_BITMAP, then yes they should be. Make sure they are with (al_get_bitmap_flags(bitmap) & ALLEGRO_VIDEO_BITMAP)

Which version of A5 are you using?

Dash125
Member #13,764
November 2011

It turns out I wrote the conditional to check the bitmap flags wrong ( :-[ :-[ :-[ ).

So, to bottom line it:

I used al_set_new_bitmap_flags to use all bitmaps as video bitmaps, and re-tested on release mode (I'm using Visual Studio 2010) and everything worked like a charm. I even tested a 14 layers of 50x50 map, and took less than half a second to load and draw!

Which version of A5 are you using?

I'm currently using Allegro 5.0.4 and 5.0.5

Also, I'd like to thank you for both answering and following up on this, since it has helped me understand a lot better how Allegro works and is supposed to be used! :)

Go to: