Reusing bitmaps after creating a new display
torhu

I've implemented switching between fullscreen and windowed modes in my game. I destroy the old display, then create a new one. The next step is to reload the bitmaps, so I don't get FPS in the single digits. Just loading them from disk again works fine.

Then I tried using al_clone_bitmap() to speed up the process:

  for (i = 0; i < NUMBER_OF_BITMAPS; i++) {
    old = bitmaps[i];
    if (!(bitmaps[i] = al_clone_bitmap(old)))
      exit_message("Feil under konvertering av bitmaps til nytt display.");
    al_destroy_bitmap(old);
  }

After doing this, some of the bitmaps have been replaced by just white boxes. Any ideas?

Trent Gamblin

I'm not sure, but I'm going to be committing a change soon that should help. It adds an ALLEGRO_PRESERVE_TEXTURE flag (default off so nothing changes) that when used, will save your textures in system memory. Then in combination with ALLEGRO_CONVERT_BITMAP, you can simply destroy a display and create a new one and all the bitmaps are automatically converted.

torhu

Ok, cool. I was under the impression that reuploading of bitmaps to the video card would happen automatically with 5.1, but I guess that's not the case? I'm using 5.0.5, by the way.

Trent Gamblin

Yeah, I guess if your scenario is like... user presses button, toggle fullscreen, then you don't really need the ALLEGRO_PRESERVE_TEXTURE flag, in which case your code should work, provided that you are not cloning the bitmaps after destroying the display... but then they should be converted to memory bitmaps so I don't know why it isn't working. The use case for the ALLEGRO_PRESERVE_TEXTURE flag is when the bitmaps can become invalidated at any time, such as hotplugging monitors.

Short answer: not sure, can you post more code?

torhu

This is the code that does the switching, and creates the lovely white boxes. I tried waiting until after I created the new display with destroying the old one, but that didn't work. When switching from fullscreen to windowed, I got a window of the correct size, but that had no title bar, and was glued to the top left of the screen. Like the old and the new display conflicted somehow.

#SelectExpand
1void toggle_fullscreen_mode(ALLEGRO_EVENT_QUEUE* event_queue) 2{ 3 int i; 4 ALLEGRO_BITMAP *old; 5 bool window = al_get_new_display_flags() & ALLEGRO_WINDOWED; 6 7 al_destroy_display(display); 8 al_set_new_display_flags(window ? ALLEGRO_FULLSCREEN : ALLEGRO_WINDOWED); 9 display = al_create_display(GFX_WIDTH, GFX_HEIGHT); 10 11 /* if it didn't work, go back to the previous mode */ 12 if (!display) { 13 al_set_new_display_flags(window ? ALLEGRO_WINDOWED : ALLEGRO_FULLSCREEN); 14 display = al_create_display(GFX_WIDTH, GFX_HEIGHT); 15 } 16 assert(display); 17 18 /* redo some of the setup that was originally done by init(), main(), and 19 graphics_init() */ 20 al_hide_mouse_cursor(display); 21 al_set_window_title(display, WINDOW_TITLE); 22 al_register_event_source(event_queue, al_get_display_event_source(display)); 23 if (!(font = al_grab_font_from_bitmap(font_bitmap, 4, font_ranges))) 24 exit_message("Feil under konvertering av font til nytt display."); 25 26 /* have to reupload all bitmaps to the graphics card */ 27 for (i = 0; i < NUMBER_OF_BITMAPS; i++) { 28 old = bitmaps[i]; 29 if (!(bitmaps[i] = al_clone_bitmap(old))) 30 exit_message("Feil under konvertering av bitmaps til nytt display."); 31 al_destroy_bitmap(old); 32 } 33 backgr = bitmaps[BACKGR_IMAGE]; 34}

Trent Gamblin

Does 5.0.5 have the bitmap auto-conversion that Elias and Matthew added? If so it seems like it's not working. In fact, some part of me thinks that I fixed a bug in that code while doing the ALLEGRO_PRESERVE_TEXTURE code... actually I'm almost sure of it. I can send you a patch to that file so you can look for changes I made, if you want, but you'll have to just ignore the big chunks I added... the only thing holding this patch up from being committed to 5.1 is that there is a race condition I couldn't pin down and put in an al_rest(1) for the time being (in the iphone display creation code).

torhu

I'm not sure if the auto-conversion is present in 5.0.5 or not. I can't find it in the docs. Working around it by loading the files from disk again works fine at the moment, since I have only 30 relatively small bitmaps.

Matthew Leverton

The auto conversion stuff is not in 5.0. It needs more testing before that happens.

And before you commit anything that alters its behavior, you should probably post on AD first just to make sure the bug isn't actually a feature. Elias wrote the code, so I don't know it is implemented, but I know how it's supposed to work.

Trent Gamblin

Ok, I looked at it and I didn't actually change anything except adding a conditional branch. But it shouldn't matter AFAIK, destroying a display has always been supposed to convert to memory bitmaps. One quick fix might be to clone the bitmaps before destroying the display (save them in an array or some such, then copy them back.) Sounds like a hack though. This really sounds like the problems I was having with hot-plugging displays, except my textures were all black or had some noise in them. Are you using D3D or OpenGL?

torhu

Looks I'm using Direct3D, I checked the log file. I tried cloning all the bitmaps (with al_clone_bitmap) after calling al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP), before destroying the old display. Then I called al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP) after destroying the old and creating the new display, and then cloned all bitmaps again. But it just crashed.

Trent Gamblin

Did you destroy the video bitmaps after cloning them, before destroying the display?

torhu

Yes.

EDIT:
It did actually work, I just had a bug in my code. Sorry about that.

This code works:

#SelectExpand
1void toggle_fullscreen_mode(ALLEGRO_EVENT_QUEUE* event_queue) 2{ 3 int i; 4 ALLEGRO_BITMAP *old; 5 ALLEGRO_BITMAP *backup[NUMBER_OF_BITMAPS]; 6 bool window = al_get_new_display_flags() & ALLEGRO_WINDOWED; 7 8 al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP); 9 for (i = 0; i < NUMBER_OF_BITMAPS; i++) { 10 old = bitmaps[i]; 11 if (!(backup[i] = al_clone_bitmap(old))) 12 exit_message("Feil under kopiering av bitmaps før fullskjermsbyte."); 13 al_destroy_bitmap(old); 14 } 15 16 al_destroy_display(display); 17 al_set_new_display_flags(window ? ALLEGRO_FULLSCREEN : ALLEGRO_WINDOWED); 18 display = al_create_display(GFX_WIDTH, GFX_HEIGHT); 19 20 /* if it didn't work, go back to the previous mode */ 21 if (!display) { 22 al_set_new_display_flags(window ? ALLEGRO_WINDOWED : ALLEGRO_FULLSCREEN); 23 display = al_create_display(GFX_WIDTH, GFX_HEIGHT); 24 } 25 assert(display); 26 27 /* redo some of the setup that was originally done by init(), main(), and 28 graphics_init() */ 29 al_hide_mouse_cursor(display); 30 al_set_window_title(display, WINDOW_TITLE); 31 al_register_event_source(event_queue, al_get_display_event_source(display)); 32 if (!(font = al_grab_font_from_bitmap(font_bitmap, 4, font_ranges))) 33 exit_message("Feil under konvertering av font til nytt display."); 34 35 /* have to reupload all bitmaps to the graphics card */ 36 al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP); 37 for (i = 0; i < NUMBER_OF_BITMAPS; i++) { 38 old = backup[i]; 39 if (!(bitmaps[i] = al_clone_bitmap(old))) 40 exit_message("Feil under konvertering av bitmaps til nytt display."); 41 al_destroy_bitmap(old); 42 } 43 backgr = bitmaps[BACKGR_IMAGE]; 44}

Trent Gamblin

So I wonder if the conversion is happening too late in the d3d driver..

Matthew Leverton

But it shouldn't matter AFAIK, destroying a display has always been supposed to convert to memory bitmaps.

Reading back through the discussion, it looks like no definitive answer was ever given regarding that.

There were three things suggested:

  1. Only bitmaps marked as CONVERT would be downgraded. Other video bitmaps would be invalidated (0x0 zombies).


  2. All video bitmaps would be downgraded to memory and implicitly tagged as auto-convert


  3. All video bitmaps would be downgraded to memory, but the convert tag would not be added. (Thus video bitmaps which were not explicitly tagged would be stuck as memory.)

I'm not sure which Elias implemented. I would prefer the first option because it is the most consistent: Allegro only tries to manage bitmaps marked with the auto-convert tag.

Trent Gamblin

Yes, I was talking about before the AUTO_CONVERT flag was introduced. At that time there was no conversion except for destroy display->displays video bitmaps converted to memory, and all platforms did it. I'm not sure either which of those 3 is used now, but I guess it could be relevant. I'd have to ask Elias which is used, which might tell us why the first code snippet of torhu's didn't work.

Elias

I think destroying a display always converts all display bitmaps to memory bitmaps (if it is the last display sharing the bitmaps). Both in 5.0 and 5.1. The reason was to never have zombie bitmaps I think. I'm all for changing it, so that a VIDEO bitmap would never be a MEMORY bitmap unless it is set to auto convert (which it would be by default).

The drawback is that we'd have to introduce the concept of zombie bitmaps, i.e. bitmaps which cannot be drawn at all, not even as memory bitmaps. The only valid operation on them would be destroying them.

torhu

When I tried just destroying the old display and creating a new one, all the bitmaps seemed to be ok. So the automatic conversion to memory bitmaps works. It's when I cloned them after creating the new display that some of them turned into white boxes.

EDIT:
Nevermind, I just tried it and got white boxes anyway.

Matthew Leverton
Elias said:

The drawback is that we'd have to introduce the concept of zombie bitmaps, i.e. bitmaps which cannot be drawn at all, not even as memory bitmaps. The only valid operation on them would be destroying them.

If video bitmaps were to be invalidated, Peter's comment was:

Quote:

They should be 0x0 bitmaps without any data associated.
We should allow al_create_bitmap to create 0x0 bitmaps anyway

I'm not sure if he meant that those bitmaps should be able to be drawn (null op) or not.

Regardless of whether they can or not, I still prefer it (zombie bitmaps) because I think it makes it easier to document, explain, and understand when conversions happen: if you don't set the conversion flag, the bitmap will never be converted, and if you destroy the last display, the bitmap is lost.

torhu

The al_clone_bitmap() call in the first loop in my function has a tendency to return NULL. Is there a limit to how many bitmaps I can create or something? I destroy the old ones, but maybe there's some resource that gets depleted.

Thomas Fjellstrom

Video Bitmaps live in the video card memory, if you run out of that, it tends to fail.

torhu

Yes, but the video memory should be freed when I call al_destroy_bitmap, right?

Thomas Fjellstrom

yeah, but if you just create too many, then you have no recourse.

torhu

I think I have no more than 32 bitmaps at any given time, so it's not the number of bitmaps that the problem.

Thomas Fjellstrom

It's not really the number that matters, but the amount of video memory they use up, and how much free video memory you have.

Arthur Kalliokoski
Thomas Fjellstrom

Yeah, I don't know how likely it is that he's actually running out of video memory, unless he's leaking, or allegro is.

torhu

I guess the question is: is Allegro leaking something?

Thomas Fjellstrom

I don't think it is. Other people would have likely had problems by now.

torhu

Guess I'll have to produce a test case then :P

UPDATE:
I did some debugging. al_clone_bitmap returns null because it calls al_lock_bitmap, which calls al_lock_bitmap_region, which contains this piece of code:

if (bitmap->locked)
      return NULL;

Kind of embarrassing for me, don't you think? ::) My collision testing code locks the bitmaps before using al_get_pixel on them, but didn't always unlock them. My original toggle_fullscreen_mode() function works fine after all, cloning the bitmaps once is enough.

Thanks for trying to help, guys ;D

I have a bonus question: After toggling fullscreen, I would like to redraw the last frame. The menu screens in the game are just static images being drawn once, which means that the screen turns black after toggling the fullscreen mode. There is no way to get a copy of the front buffer, right?

Thomas Fjellstrom

I'm not sure you can get the frontbuffer, its not always supported IIRC. But you can grab the backbuffer, just draw the screen again on switch?

torhu

The backbuffer tends to not be the last frame, that's the problem...

It's not hard to work around, but it means that my toggle_fullscreen_mode function can't be completely self-contained. There has to be code outside of it that knows about the switching. And just always copying the backbuffer before blitting seems like a waste.

Trent Gamblin

Can you just call your "render" function again, if it's not all over the place?

torhu

I don't have a render function for the menu stuff, since it's just static. I guess I'll just make the menu code save the frame before calling al_flip_display(). When the actual game loop is running, I have to do some extra stuff anyway. Before calling toggle_fullscreen_mode() I stop the timers, and after calling it I call graphics_update() and then restart the timers. Unless the game was already paused, that is.

Trent Gamblin

If it's static why can't you just draw it?

torhu

Well, it's not a single image, it's some text on a background, on another background. It's just that there's no animation. And there are several different screens like that. I can just add a global last_frame variable and make the menu code update that.

Each menu has its own event loop, so the alternative would be to add code for handling the fullscreen switch in several places.

UPDATE:
I did it like this in my menu system:

static void update_display(void)
{
  /* save the frame first, in case of a fullscreen switch */
  al_set_target_bitmap(last_frame);
  al_draw_bitmap(al_get_backbuffer(display), 0, 0, 0);
  al_set_target_backbuffer(display);
  al_flip_display();
}

But this function takes about half a second to run, which is very noticeable. The display is only 800x600x32. Why would it take so long, and is there a better way of doing this?

Edgar Reynaldo

Instead of drawing to 'last_frame' from the backbuffer, try just drawing your screen twice, once to 'last_frame', and once to the backbuffer before flipping.

torhu

Thanks, that's better. I just draw the menu to a bitmap, then draw that to the backbuffer.

Edgar Reynaldo

Assuming you don't use dirty rectangles and you make a full update of the display on every frame, then you don't need to draw twice, just call your display function on a different target just before you toggle fullscreen.

torhu

The original problem was that I don't have a single update_display() function, so that won't work ;)

Edgar Reynaldo
torhu said:

I don't have a single update_display() function,

:o The Horror!

Thomas Fjellstrom

If this is A5, drawing to video bitmaps can be slow on older/slower hardware (like intel gpus).

Thread #608819. Printed from Allegro.cc