Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Image transition too slow !

This thread is locked; no one can reply to it. rss feed Print
Image transition too slow !
AlexBw
Member #16,161
February 2016

I am trying to port Windows screensaver I developed a long time ago in VB6. I am new to Allegro, taught myself the basics and have managed to code a simple transition that slides an image in from left edge of the screen over another image that is already displayed, one vertical pixel line at a time.

The transition takes about 32 seconds to complete, while the good old VB6 version takes less than 1 second ! Could someone please let me know what I am doing wrong or if there are better ways to do this ? This is the simplest of the transitions I have, if I can't get this done fast enough, there is no point using Allegro for me !

Here is the code snippet (Using Allegro 5.1):

#SelectExpand
1 ALLEGRO_DISPLAY *display = NULL; 2 ALLEGRO_BITMAP *image1 = NULL; 3 ALLEGRO_BITMAP *image2 = NULL; 4 ALLEGRO_BITMAP *image_Resized = NULL; 5 ALLEGRO_BITMAP *image_Drawn = NULL; 6 7 <Init Allegro code here...> 8 9 image1 = al_load_bitmap("pic1.jpg"); 10 image2 = al_load_bitmap("pic2.jpg"); 11 12 al_draw_scaled_bitmap(image1,0, 0, al_get_bitmap_width(image1), al_get_bitmap_height(image1), 0, 0, disp_data.width, disp_data.height, 0); 13 14 al_flip_display(); 15 16 image_Resized = al_create_bitmap(disp_data.width, disp_data.height); 17 image_Drawn = al_create_bitmap(disp_data.width, disp_data.height); 18 al_set_target_bitmap(image_Resized); 19 al_draw_scaled_bitmap(image2,0, 0, al_get_bitmap_width(image2), al_get_bitmap_height(image2), 0, 0, disp_data.width, disp_data.height, 0); 20 al_set_target_bitmap(image_Drawn); 21 22 for (int i = 1; i < disp_data.width - 1; i+=1) { 23 al_draw_scaled_bitmap(image1, i, 0, disp_data.width - i, disp_data.height, i, 0, disp_data.width - i, disp_data.height, 0); 24 al_draw_scaled_bitmap(image_Resized, disp_data.width - i, 0, i, disp_data.height, 0, 0, i, disp_data.height, 0); 25 26 al_set_target_backbuffer(display); 27 al_flip_display(); 28 }

Also, what is the best way to manipulate a large number (really just all the pixels in a given image) of pixels of an image ? Clearly getpixel and putpixel are gonna be painfully slow ! I have seen examples of primitives to do this but none of them use a bitmap, but draw or change color of the pixels.

Thanks for any help.

Edgar Reynaldo
Member #8,592
May 2007
avatar

AlexBW said:

Also, what is the best way to manipulate a large number (really just all the pixels in a given image) of pixels of an image ? Clearly getpixel and putpixel are gonna be painfully slow ! I have seen examples of primitives to do this but none of them use a bitmap, but draw or change color of the pixels.

What effect are you trying to achieve?

AlexBW said:

The transition takes about 32 seconds to complete, while the good old VB6 version takes less than 1 second ! Could someone please let me know what I am doing wrong or if there are better ways to do this ? This is the simplest of the transitions I have, if I can't get this done fast enough, there is no point using Allegro for me !

Allegro is more than capable enough to handle this.

You need to think in different terms with Allegro 5.

I'm not trying to code this for you, but it's so much easier to explain with code, and I've already written it, so here's a tutorial.

First get allegro and your display setup.

if (!al_init) {Fail();}

int SCREEN_WIDTH = 800;
int SCREEN_HEIGHT = 600;

al_set_new_display_flags(ALLEGRO_FULLSCREEN);

ALLEGRO_DISPLAY* display = al_create_display(SCREEN_WIDTH , SCREEN_HEIGHT);

if (!display) {Fail();}

You need a timer to control the rate at which the image pans onto the screen. Most monitors update around 60 times a second, so you (generally) want to create a timer that runs at 60FPS. Allegro's timer creation function, al_create_timer accepts the number of seconds per frame, so you want 1.0 second per 60.0 frames, or 1.0/60.0.

double FRAMES_PER_SECOND = 60.0;// Match to the monitor's refresh rate
double SECONDS_PER_FRAME = 1.0/FRAMES_PER_SECOND;

ALLEGRO_TIMER* timer = al_create_timer(SECONDS_PER_FRAME);

if (!timer) {Fail();}

Allegro 5 is event based, so you need an event queue, and to register the timer's event source with the queue that you create.

ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue();
if (!queue) {Fail();}

al_register_event_source(queue , al_get_timer_event_source(timer));

/// so we can detect DISPLAY_CLOSE events
al_register_event_source(queue , al_get_display_event_source(display);

You now need to determine how fast you want your image to move across the screen, in pixels per second. Let's say you want the image to move across the screen in 10 seconds. Setup a constant to use later.

double SCROLL_TIME = 10.0;// 10 seconds

If your images don't fit nicely on the screen, you need to scale them. Do this once to simplify things later. Change the path to your images accordingly.

#SelectExpand
1ALLEGRO_BITMAP* image_one = al_load_bitmap("ImageOne.png"); 2ALLEGRO_BITMAP* image_two = al_load_bitmap("ImageTwo.png"); 3 4ALLEGRO_BITMAP* image_one_resized = al_create_bitmap(SCREEN_WIDTH , SCREEN_HEIGHT); 5ALLEGRO_BITMAP* image_two_resized = al_create_bitmap(SCREEN_WIDTH , SCREEN_HEIGHT); 6 7/// set up an overwrite blender 8al_set_blender(ALLEGRO_ADD , ALLEGRO_ONE , ALLEGRO_ZERO); 9 10/// Stretch image one and two onto their respective buffers 11al_set_target_bitmap(image_one_resized); 12al_draw_scaled_bitmap(image_one , 0 , 0 , al_get_bitmap_width(image_one) , al_get_bitmap_height(image_one) , 0 , 0 , SCREEN_WIDTH , SCREEN_HEIGHT); 13 14al_set_target_bitmap(image_two_resized); 15al_draw_scaled_bitmap(image_two , 0 , 0 , al_get_bitmap_width(image_two) , al_get_bitmap_height(image_two) , 0 , 0 , SCREEN_WIDTH , SCREEN_HEIGHT); 16 17/// Reset blending mode to pre-multiplied alpha blending 18al_set_blender(ALLEGRO_ADD , ALLEGRO_ALPHA , ALLEGRO_INVERSE_ALPHA); 19 20/// reset drawing target to backbuffer 21al_set_target_bitmap(al_get_backbuffer(display));

Now that you have your timer and horizontal velocity set up, you need to start the timer and then start drawing.

The timer won't start until you tell it to, so do that now with

double elapsed_time = 0.0;

al_start_timer(timer);

.

You have to actively request events from the queue. The best way for a beginner is with al_wait_for_event. Keep in mind that it will wait until an event is queued before the function will return.

#SelectExpand
1bool redraw = true; 2bool quit = false; 3 4while (!quit) { 5 do { 6 ALLEGRO_EVENT ev; 7 al_wait_for_event(queue , &ev); 8 9 if (ev.type == ALLEGRO_EVENT_TIMER) { 10 elapsed_time += SECONDS_PER_FRAME; 11 redraw = true; 12 } 13 if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { 14 quit = true; 15 } 16 } while (!al_is_event_queue_empty(queue);} 17 /// Redraw goes here 18 19}

During the redraw, determine the placement and sizes of your image and draw them to screen.

   /// Redraw routine
   if (redraw) {
      double percent = elapsed_time/SCROLL_TIME;
      double xpos = SCREEN_WIDTH*percent;
      /// Allegro will take care of clipping for us, and we've already scaled our images, so just draw them in the right spot
      al_clear_to_color(al_map_rgb(0,0,0));
      al_draw_bitmap(image_one_resized , xpos - SCREEN_WIDTH , 0 , 0);
      al_draw_bitmap(image_two_resized , xpos , 0 , 0);
      al_flip_display();
      redraw = false;
   }

And voila, you should have two perfectly scaled bitmaps the size of the screen scrolling across from left to right. Simply vary the constants for your monitor's refresh rate, your scroll time, and for elapsed time to control the placement and movement of the image.

I haven't tested the code, but I think it should all work. Try it out and let me know. Sorry for writing it all out for you, but I like to code. :P

AlexBw
Member #16,161
February 2016

Edgar, thanks very much for the well detailed response, very much appreciated.

What effect are you trying to achieve?

An example would be replacing the current picture with the pixels from the next picture, either sequentially or randomly; another would be drawing the next image's pixels in a random line/pixel patterns.

The example you provided gets the job done by skipping frames and drawing to the screen at intervals. I had coded my good old VB6 screensaver do the same, either by incrementing the counter by a larger step or by adding a delay when the transition is too fast like the one I mentioned in OP. Therefore, your code might prove to be useful practically with transitions that do not require much processing power and thus don't appear choppy.

As I mentioned above, I need to code a few transitions that deal at pixel level, hence drawing to the screen will take a lot of time or appear choppy if I employ screen redraws at intervals.

Apparently, the simple slide transition I mentioned in the OP takes ~32 seconds by Allegro, while it only takes ~1 second in VB6. So, I am tempted to conclude, Allegro is far inferior to the archaic VB6 graphics engine (which uses Dx in the background). Unless, there are ways to accelerate using Allegro, I do not see the worth in expending time rewriting the entire screensaver in C/C++ & Allegro.

I am open to any other suggestions, thanks.

Rodolfo Lam
Member #16,045
August 2015

Allegro is hardware accelerated, all drawing calls are done on the GPU, because of this, direct pixel access by the CPU is VERY slow... That could be the reason the code runs like that. VB6 works on the GDI backend probably, that one resides totally on normal RAM, not VRAM.

The effect you want is essentially draw one image below completely then on each pass draw the Nth pixel of another image on top of it, where the N is a random number indicating a pixel coordinate. A shader could help here as that is a program that runs directly on the GPU. However, the specifics of the implementation are unknown to me as my knowledge of that is limited.

someone972
Member #7,719
August 2006
avatar

I'm guessing you have V-Sync enabled, which there is a function to change that which I can't recall atm. With your initial code, this would cause it to wait for the vertical sync on each call to al_flip_display in the loop, which would limit your loop to 60 calls per second. This means for an image 1920x1080 in size, scrolling from the side, it would take exactly 32 seconds as you describe. Edgar is showing how you really want to do it; instead of redrawing every pixel you move (which at your original speed, 90% of the frames wouldn't even be visible on your display), you want to redraw on the display's schedule. It saves processing power and gives you more freedom to choose your transition speed, while displaying exactly the same amount of frames that you could see with the other method.

______________________________________
As long as it remains classified how long it took me to make I'll be deemed a computer game genius. - William Labbett
Theory is when you know something, but it doesn't work. Practice is when something works, but you don't know why. Programmers combine theory and practice: Nothing works and they don't know why. -Unknown
I have recklessly set in motion a chain of events with the potential to so-drastically change the path of my life that I can only find it to be beautifully frightening.

Peter Hull
Member #1,136
March 2001

I quickly made this version:

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_image.h> 3 4int main(void) { 5 const double duration = 2.0; 6 al_init(); 7 al_init_image_addon(); 8 al_set_new_display_flags(ALLEGRO_FULLSCREEN_WINDOW); 9 ALLEGRO_DISPLAY* disp = al_create_display(1920, 1080); 10 ALLEGRO_BITMAP* base_bitmap = al_load_bitmap("base.jpg"); 11 ALLEGRO_BITMAP* cover_bitmap = al_load_bitmap("cover.png"); 12 al_draw_scaled_bitmap(base_bitmap, 13 0.0f, 0.0f, al_get_bitmap_width(base_bitmap), al_get_bitmap_height(base_bitmap), 14 0.0f, 0.0f, al_get_display_width(disp), al_get_display_height(disp), 0); 15 al_flip_display(); 16 al_rest(1.0); 17 double starttime = al_get_time(); 18 double delta = 0.0; 19 while (delta <= duration) { 20 float frac = (float)(delta / duration); 21 delta = al_get_time() - starttime; 22 al_draw_scaled_bitmap(base_bitmap, 23 0.0f, 0.0f, 24 al_get_bitmap_width(base_bitmap), al_get_bitmap_height(base_bitmap), 25 al_get_display_width(disp) * frac, 0.0f, 26 al_get_display_width(disp) * (1.0f - frac), al_get_display_height(disp), 0); 27 al_draw_scaled_bitmap(cover_bitmap, 28 0.0f, 0.0f, 29 al_get_bitmap_width(cover_bitmap), al_get_bitmap_height(cover_bitmap), 30 0.0f, 0.0f, 31 al_get_display_width(disp) * frac, al_get_display_height(disp), 0); 32 al_flip_display(); 33 } 34 al_rest(1.0); 35 al_destroy_bitmap(base_bitmap); 36 al_destroy_bitmap(cover_bitmap); 37 al_destroy_display(disp); 38}

Even on my laptop graphics, in debug mode, this is fine. someone972 is right and I'm pretty sure it's not a problem with Allegro being slow.
The code above just allows the image to move by >1 pixel per cycle, according to how long the VSync takes.
You can try turning off vsync, using al_set_new_display_option with the ALLEGRO_VSYNC parameter.

Also can you check your al_set_target_bitmap and al_set_target_backbuffer calls, I'm not sure what image_Drawn is used for.

AlexBw
Member #16,161
February 2016

Thanks Rodolfo, someone972 & Peter for your replies.

I'm guessing you have V-Sync enabled, which there is a function to change that which I can't recall atm. With your initial code, this would cause it to wait for the vertical sync on each call to al_flip_display in the loop, which would limit your loop to 60 calls per second. This means for an image 1920x1080 in size, scrolling from the side, it would take exactly 32 seconds as you describe.

Good catch, yes I had forced vsync on in Nvidia settings. I changed that and now the whole transition takes 3.5-4 seconds. I do understand Edgar's example is suited for practical purposes. What I intended to do was to get a feeling of Allegro's capabilities, that would help me decide if I should go ahead and code the more complicated & resource hungry transitions. The benchmarking I performed with Allegro can draw the entire slide transition in 3.5-4 seconds while VB6 could do the same in less than 1 second. But I don't think VB6 does AA (assuming Allegro does), which perhaps accounts for a bit longer duration taken by Allegro. 3.5-4 seconds looks satisfactory enough, so I have decided to go ahead with my adventure with Allegro.

Peter, thanks for the reference to al_set_new_display_option. Regardless of the ALLEGRO_VSYNC values I specified, it took 32 seconds when vsync was on and 3.5-4 seconds when it was off at the driver level.

Between Edgar's and Peter's methods of drawing, which one would be less choppy ? Edgar's method would draw the whole transition frame by frame (i.e. one pixel line at a time), even if there were to be a hiccup which would perhaps pause the transition but, would resume at the next frame, while Peter's method would skip a few frames and the next frame drawn would be cumulative of the skipped ones. I am tempted to go by Edgar's method.

Allegro is hardware accelerated, all drawing calls are done on the GPU, because of this, direct pixel access by the CPU is VERY slow... That could be the reason the code runs like that. VB6 works on the GDI backend probably, that one resides totally on normal RAM, not VRAM.

Regardless, the graphics must be copied to video memory ultimately from either RAM or a copy made from video memory (which again would reside in RAM), right ? I am not an expert on the internals of such things, I might be completely wrong.

Repeating my earlier question, is there a better way to edit large number of pixels of an image other than al_put_pixel and al_get_pixel ? I tried to look for examples on using primitives addon, couldn't find something more suited for my purpose as all those I came across, use dynamically generated graphics.

Is it possible to bind a timer to a function, that would be called every time the timer fires, like it was possible in Allegr v4 as is evident from the docs ?

Bruce Pascoe
Member #15,931
April 2015
avatar

For working with pixels directly, look into al_lock_bitmap(). Ultimately though you should try to avoid working directly at the pixel level whenever possible (at least not in realtime). GPU acceleration is a whole different ballgame from the direct software rendering of yore. :)

AlexBw
Member #16,161
February 2016

For working with pixels directly, look into al_lock_bitmap(). Ultimately though you should try to avoid working directly at the pixel level whenever possible (at least not in realtime). GPU acceleration is a whole different ballgame from the direct software rendering of yore. :)

Thanks Bruce, I already have a random pixel transition working, in which I use an in memory bitmap, which is locked in write_only mode and, it is still not as fast as VB6.

I wonder if using OpenCV along with Allegro is feasible and worth the effort ?

Bruce Pascoe
Member #15,931
April 2015
avatar

For what you're doing you should probably look into whether using shaders is an option (these are supported out-of-box by Allegro 5.1, otherwise you might be able to use OpenGL functions directly).

Also, Allegro memory bitmaps are SLOW. Just throwing that out there. They are ridiculously fast if all you do with them is lock and manipulate pixels (al_lock_bitmap() basically doesn't have to do anything at all), but go to draw it to a video bitmap or the backbuffer and it's going hit your performance hard because Allegro has to upload it to the GPU first.

A rule of thumb is that any time you have to cross the CPU/GPU barrier is going to be a bottleneck that you'll have to account for.

Edgar Reynaldo
Member #8,592
May 2007
avatar

AlexBW said:

Between Edgar's and Peter's methods of drawing, which one would be less choppy ? Edgar's method would draw the whole transition frame by frame (i.e. one pixel line at a time), even if there were to be a hiccup which would perhaps pause the transition but, would resume at the next frame, while Peter's method would skip a few frames and the next frame drawn would be cumulative of the skipped ones. I am tempted to go by Edgar's method.

That's not quite how my method works. The timer tracks elapsed time. The event handling will accumulate all timer ticks since the last update. The drawing routine will draw the transition at exactly the position specified by the elapsed time, skipping missed frames smoothly. It does not draw every line successively. In fact, doing that is what is slowing you down. That and uploading your locked data back to the GPU. If you want to draw pixel strips, use al_draw_bitmap_region.

AlexBW said:

Regardless, the graphics must be copied to video memory ultimately from either RAM or a copy made from video memory (which again would reside in RAM), right ? I am not an expert on the internals of such things, I might be completely wrong.

You don't need to copy anything from RAM if you use a shader or if you use the primitives addon and specify a texture.

AlexBW said:

Repeating my earlier question, is there a better way to edit large number of pixels of an image other than al_put_pixel and al_get_pixel ? I tried to look for examples on using primitives addon, couldn't find something more suited for my purpose as all those I came across, use dynamically generated graphics.

You can use al_draw_prim or al_draw_vertex_buffer (5.1.X only) to draw primitives. The ALLEGRO_PRIM_TYPEs that you are interested in are ALLEGRO_PRIM_POINT_LIST and ALLEGRO_PRIM_TRIANGLE_LIST. If you want to draw colored pixels use ALLEGRO_PRIM_POINT_LIST. It might be able to draw from a texture, but I'm not sure how that would work. Otherwise, use ALLEGRO_PRIM_TRIANGLE_LIST or FAN and draw two triangles for every rectangle you wish to draw.

If you're looking to draw lots of pixels what you really want is to use a shader. It is a hardware accelerated program that runs on the GPU and can access textures uploaded to the GPU so that there is no memory transfer between the CPU and GPU.

Bruce Pascoe
Member #15,931
April 2015
avatar

Are vertex buffers faster than plain al_draw_prim()? I have my engine set up to conditionally use them when compiled against Allegro 5.1 (those version macros in the headers were a genius idea) but I couldn't really tell if I gained anything from the additional complexity.

Elias
Member #358
May 2000

If you have a lot of static vertex data they are way faster. Just try drawing a model with a million triangles with and without. With al_draw_prim() every time you draw the model, it will have to transfer a million triangles worth of vertices to the GPU, which is very slow, before actually being able to draw the model itself (which probably doesn't take very long on a modern GPU). With al_draw_vertex_buffer you only submit a single "DRAW!" command to the GPU which is instant and it can draw it right away.

--
"Either help out or stop whining" - Evert

Mark Oates
Member #1,146
March 2001
avatar

Elias said:

"DRAW!" command

;D

Gideon Weems
Member #3,925
October 2003

Guys, this is a job for Mysha. Check out ex_draw_bitmap and ex_blit.

Go to: