Blacking out the display while resizing
xsquid

Is there a way to control what the display shows when it's being resized on Windows?

Currently, it stretches the contents of the display. I'd prefer to be able to detect that the display is being resized (through an event?) so I can control what it displays (e.g., plain black during the resize).

I found a similar question here ([AL5] Knowing when a resize event is starting?), and it didn't seem there was a resolution, but that was back in 2011.

Is there any way to do this now?

Update: Switching to OpenGL made it so the ALLEGRO_EVENT_DISPLAY_RESIZE event is emitted repeatedly which allowed me to detect a resize in-progress. The follow-up problem is now finding a way to fix this: https://my.mixtape.moe/rdxqwg.webm (What I've got right now, vs what I'm trying to achieve)

GullRaDriel

ALLEGRO_EVENT_DISPLAY_RESIZE is an event fired when resizing.
For the control of the window content itself, I don't know.

xsquid

As far as I know, that event is only fired when the user has finished resizing, right? What I'm looking for is something that indicates that the display has started being resized, or is currently being resized, so I can hide its contents while this is occurring instead of having them awkwardly stretch.

It looks bad when you're trying to respect the aspect ratio, because it doesn't respect it while the resize is occurring and I have no way of dealing with that because I can't detect it.

Elias

In Linux you get the resize event as soon as the resizing starts. But the Windows port is a bit strange for some reason. It should not be hard to fix it though. This is where the resize starts:

https://github.com/liballeg/allegro5/blob/master/src/win/wwindow.c#L915

Edgar Reynaldo

Blacking out the display is not preferable for everyone. I want the window to display its contents during a resize, not black out. This is typical of most major desktop applications. The contents are redrawn as necessary, not ignored, or blacked out.

One issue comes from the number of resize events that are emitted during a resize. The question is whether to collect them or deal with them individually. What I do personally is wait a short period of time and then take all the resize events off of the queue at once. There are usually between 3 and 10 resize events in a short period of time because you get one for every mouse move when the window controls are being dragged.

A further question comes whether you are using Direct3D or OpenGL, as they handle resize events differently.

Elias

I think how it should work is send a resize event immediately when resizing starts. But then ignore all resize events until the user calls al_acknowledge_resize.

So that way it is completely up to the user how many resize events they want to handle. They can acknowledge 1000 resize events per second (and for example smoothly redraw the game scene during the resize that way). Or they can only handle one event per second or whatever. If reloading of fonts or similar things are necessary then this way only at most one resize event would be pending if it takes too long.

I think that's how it already works in Linux (but not sure, would have to double check).

Edgar Reynaldo

There is a note or two in the d3d resize code. I can't remember where at the moment, but I think they were important. I think there was some kind of issue with the threading in the resize code.

xsquid

Blacking out the display is not preferable for everyone. I want the window to display its contents during a resize, not black out. This is typical of most major desktop applications. The contents are redrawn as necessary, not ignored, or blacked out.

It goes without saying that you're not going to find a default behavior that satisfies literally everyone. I don't want to change the default behavior, but instead have more control over what happens. All I'm looking for is a way to detect that a resize has either (1) started or (2) is in progress so I can alter drawing accordingly. As it is, I can only detect when the resize has ended. How can I "redraw as necessary" if I can't detect that the size of the window is changing? The current behavior (just stretching the contents) isn't exactly typical of the Windows experience.

One issue comes from the number of resize events that are emitted during a resize. The question is whether to collect them or deal with them individually. What I do personally is wait a short period of time and then take all the resize events off of the queue at once. There are usually between 3 and 10 resize events in a short period of time because you get one for every mouse move when the window controls are being dragged.

A further question comes whether you are using Direct3D or OpenGL, as they handle resize events differently.

I'm using whichever is the default for the latest version of Allegro 5. I'm detecting the resize via the display's event source, which only emits a resize event once the resize has completed (ALLEGRO_EVENT_DISPLAY_RESIZE). I can't find anything in the documentation for detecting that a resize has started or is in progress. At least, not through the typical events.

According to Elias' responses, it seems this is only possible on Linux, not Windows... Is the final verdict that this cannot be done without modifying Allegro?

Edgar Reynaldo

You might be able to accomplish what you want by setting the Background Brush using the HWND Window handle that Allegro gives you. See al_get_win_window_handle for details.

   HBRUSH brush = CreateSolidBrush(RGB(0, 0, 0));
   SetClassLongPtr(hwnd, GCLP_HBRBACKGROUND, (LONG)brush);

But yes, Allegro probably needs to be patched to accomplish what you want.

xsquid

That certainly blacks out the display, but the problem is detecting the resize in the first place. Am I supposed to detect it somehow through the handle, or am I out of luck?

(Blacking out the display was never the problem, it was just an example to illustrate why I want to be able to detect this.)

bamccaig

I suppose the question is what kind of drawing control each framework gives you during a resize. If you're free to run whatever program and respond to the process dynamically then I think it makes sense for Allegro to offer up as much data as is feasible. That said, I think that resizing games is kind of black magic. It seems to rarely work, and generally I stick to full-screen to avoid problems. Am I just old or is it truly black magic to have windowed games?

Elias

Whoever implemented resizing in the Windows port just did it in a way to not allow updates to the window during the resize. And it should get fixed (but not by me, I don't even have Windows).

Edgar Reynaldo

You can try messing with al_win_add_window_callback

bool al_win_add_window_callback(ALLEGRO_DISPLAY *display,
   bool (*callback)(ALLEGRO_DISPLAY *display, UINT message, WPARAM wparam,
   LPARAM lparam, LRESULT *result, void *userdata), void *userdata)

Try intercepting WM_SIZING messages and repainting if you get one.

Eric Johnson

If all you want is to "black out" the display while resizing, surely something like the following would work:

#SelectExpand
1#include <iostream> 2 3#include <allegro5/allegro.h> 4#include <allegro5/allegro_image.h> 5#include <allegro5/allegro_primitives.h> 6 7using std::cout; 8 9int main(void) { 10 11 if (!al_init()) { 12 13 cout << "Error: failed to initialize Allegro 5\n"; 14 15 return - 1; 16 } 17 18 if (!al_init_image_addon()) { 19 20 cout << "Error: failed to initialize image addon\n"; 21 22 return - 1; 23 } 24 25 if (!al_init_primitives_addon()) { 26 27 cout << "Error: failed to initialize primitives addon\n"; 28 29 return - 1; 30 } 31 32 // Allow the display window the be resizable. 33 al_set_new_display_flags(ALLEGRO_RESIZABLE); 34 35 // Create events. 36 ALLEGRO_TIMER *timer = al_create_timer(1.0 / 60.0); 37 ALLEGRO_DISPLAY *display = al_create_display(400, 300); 38 ALLEGRO_EVENT_QUEUE *event_queue = al_create_event_queue(); 39 40 // Register events with the event queue. 41 al_register_event_source(event_queue, al_get_timer_event_source(timer)); 42 al_register_event_source(event_queue, al_get_display_event_source(display)); 43 44 al_start_timer(timer); 45 46 bool render = true; 47 bool running = true; 48 49 // This will be used to "black out" the display. 50 bool display_black = false; 51 52 // Do main game loop. 53 while (running) { 54 55 ALLEGRO_EVENT event; 56 57 al_wait_for_event(event_queue, &event); 58 59 if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE) { 60 61 // The window was closed; end the game. 62 running = false; 63 } 64 else if (event.type == ALLEGRO_EVENT_DISPLAY_RESIZE) { 65 66 al_acknowledge_resize(display); 67 68 // Change the display to black. 69 display_black = true; 70 } 71 else if (event.type == ALLEGRO_EVENT_TIMER) { 72 73 // Update stuff here. 74 render = true; 75 } 76 77 if (render && al_is_event_queue_empty(event_queue)) { 78 79 // Render stuff here. 80 81 render = false; 82 83 if (display_black) { 84 85 // Clear display to black color. 86 al_clear_to_color(al_map_rgb(0, 0, 0)); 87 88 static unsigned ticks = 0; 89 90 ++ticks; 91 92 if (ticks > 30) { 93 94 // Keep the display black for half a second as a "cool down" period. 95 96 display_black = false; 97 98 ticks = 0; 99 } 100 } 101 else { 102 103 // Clear display to red color. 104 al_clear_to_color(al_map_rgb(255, 0, 0)); 105 } 106 107 // Update display. 108 al_flip_display(); 109 } 110 } 111 112 // Destroy events. 113 al_destroy_timer(timer); 114 al_destroy_display(display); 115 al_destroy_event_queue(event_queue); 116}

Here is a video of it in action.

[EDIT]
Not sure how this behaves on Windows.

Edgar Reynaldo

That doesn't work on Windows with Direct3D. D3D stretches the contents of the context onto the screen during a resize, which means it depends on whatever you were displaying when the resize started. You don't get a resize event until WM_EXITSIZEMOVE is triggered, so you can't acknowledge it and redraw in the interim. That's why I suggested the background brush, or monitoring WM_ENTERSIZEMOVE and WM_SIZING yourself.

EDIT

bool al_win_add_window_callback(ALLEGRO_DISPLAY *display,
   bool (*callback)(ALLEGRO_DISPLAY *display, UINT message, WPARAM wparam,
   LPARAM lparam, LRESULT *result, void *userdata), void *userdata)

bool WinProcCallback(ALLEGRO_DISPLAY* d , UINT msg , WPARAM wparam , LPARAM lparam ,
                     LRESULT* result , void* userdata) {
   if (msg == WM_SIZING) {
      RECT r = *lparam;
      int w = r.right - r.left;
      int h = r.bottom - r.top;
      al_resize_display(d , w , h);
      return true;
   }
   return false;
}

HWND hwnd = al_get_win_window_handle(display);
al_win_add_window_callback(display , WinProcCallback , 0);

You still have to acknowledge resize events, but this way the window should resize each time it gets a WM_SIZING event. Untested. It might enter an endless loop.

Eric Johnson

That doesn't work on Windows with Direct3D.

Ah, gotcha. Is it possible to tell Allegro 5 to use OpenGL on Windows though?

Edgar Reynaldo

Yessirree Bob.

al_set_new_display_flags(ALLEGRO_OPENGL);

Eric Johnson

Fantastic, thanks.

Why not force the use of OpenGL then on Windows and use code similar to what I posted to achieve xsquid's desired outcome? That would work, wouldn't it?

[EDIT]
Seems to work on Windows 10.

Edgar Reynaldo

Try moving the mouse quickly from a small window to a big window when you're dragging it. That way you can see the in-between frames.

xsquid

Why not force the use of OpenGL then on Windows and use code similar to what I posted to achieve xsquid's desired outcome? That would work, wouldn't it?

Yes! As you mentioned, it does seem to work if the ALLEGRO_OPENGL flag is set, because a nice stream of ALLEGRO_EVENT_DISPLAY_RESIZE events is generated. But then you encounter the problem that (I think) Edgar Reynaldo is describing. If you resize the display quickly, it looks jittery (look at the bottom; the main grey rectangle should be filling the display vertically):

{"name":"jOt7ysWqT.png","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/a\/3\/a31cdfe9b949195cd1f18c186bbdb426.png","w":813,"h":490,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/a\/3\/a31cdfe9b949195cd1f18c186bbdb426"}jOt7ysWqT.png

Here's the code used in the program in the screenshot:

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_image.h> 3#include <allegro5/allegro_primitives.h> 4#include <algorithm> 5 6void draw_rect(ALLEGRO_DISPLAY* disp) { 7 8 float w = (std::min)(al_get_display_width(disp), al_get_display_height(disp)); 9 float x = al_get_display_width(disp) / 2.0f - w / 2.0f; 10 float y = al_get_display_height(disp) / 2.0f - w / 2.0f; 11 al_draw_filled_rectangle(x, y, x + w, y + w, al_map_rgb(192, 192, 192)); 12 13} 14 15int main() { 16 17 al_init(); 18 al_init_primitives_addon(); 19 al_set_new_display_flags(ALLEGRO_OPENGL | ALLEGRO_RESIZABLE); 20 ALLEGRO_DISPLAY *display = al_create_display(640, 480); 21 22 while (1) { 23 al_acknowledge_resize(display); 24 al_clear_to_color(al_map_rgb(0, 0, 0)); 25 draw_rect(display); 26 al_flip_display(); 27 } 28 29}

I haven't tested it, but I think Edgar's brush method would solve this for simply filling the display with black. But, since that was an example situation, I'm looking for something a bit more versatile as well. There are many programs/games that can resize without the ugly jitter-- Is there something I did wrong with my code above that causes it? I tried the same concept using an event queue, but it didn't fix it.

Edgar Reynaldo
xsquid said:

al_draw_filled_rectangle(x, y, x + w, y + w, al_map_rgb(192, 192, 192));

That should be y + h there where it says y + w.

xsquid

That should be y + h there where it says y + w.

It's supposed to be a square, where "w", while perhaps misleadingly named, is the smaller of the width/height. I'm trying to keep the aspect ratio. It should fill the display vertically, and it does, the issue now is simply that when you resize quickly there's some lag/jitter and some ugly lines before it's finally drawn properly. In other words, it's not smooth.

The screenshot is one of the "between frames" which I'm trying to find a way to avoid as much as possible. A lot of applications resize smoothly, but Allegro displays on Windows don't seem to. I'd like for it to be redrawn immediately after the resize occurs, but it's as if it's lagging behind a little bit.

For comparison, when using Direct3D, while I didn't want the stretching, the stretching itself was very smooth. I'm trying to accomplish that degree of smooth redrawing but with control over what exactly is drawn...

Edgar Reynaldo

Try acknowledging WM_SIZING messages in a window callback process. If the w or h is different, call al_resize_display. If they are the same, stop.

Use

int oldh = al_get_display_height(display);
int oldw = al_get_display_width(display);
if (msg == WM_SIZING) {
   Rect r = *lparam;
   if (oldh != r.bottom - r.top || oldw != r.right - r.left) {
      al_resize_display(display , r.right - r.left , r.bottom - r.top);
   }
}

xsquid

Okay, I have this now:

#SelectExpand
1 2void draw_rect(ALLEGRO_DISPLAY* disp) { 3 4 float w = (std::min)(al_get_display_width(disp), al_get_display_height(disp)); 5 float x = al_get_display_width(disp) / 2.0f - w / 2.0f; 6 float y = al_get_display_height(disp) / 2.0f - w / 2.0f; 7 al_draw_filled_rectangle(x, y, x + w, y + w, al_map_rgb(192, 192, 192)); 8 9} 10 11bool WinProcCallback(ALLEGRO_DISPLAY* d, UINT msg, WPARAM wparam, LPARAM lparam, LRESULT* result, void* userdata) { 12 13 int oldh = al_get_display_height(d); 14 int oldw = al_get_display_width(d); 15 if (msg == WM_SIZING) { 16 RECT r = *(RECT*)lparam; 17 if (oldh != r.bottom - r.top || oldw != r.right - r.left) { 18 al_resize_display(d, r.right - r.left, r.bottom - r.top); 19 return true; 20 } 21 } 22 23 return false; 24 25} 26 27int main() { 28 29 al_init(); 30 al_init_primitives_addon(); 31 32 al_set_new_display_flags(ALLEGRO_OPENGL | ALLEGRO_RESIZABLE); 33 ALLEGRO_DISPLAY *display = al_create_display(640, 480); 34 35 HWND hwnd = al_get_win_window_handle(display); 36 al_win_add_window_callback(display, WinProcCallback, 0); 37 38 while (1) { 39 al_acknowledge_resize(display); 40 al_clear_to_color(al_map_rgb(0, 0, 0)); 41 draw_rect(display); 42 al_flip_display(); 43 } 44 45 return 0; 46 47}

... But it crashes on resize with a read access violation. The crash occurs when al_resize_display is called; it doesn't occur when it's commented out.

Edit: For the record, here's a comparison video of what it looks like (the last example, since this example throws an error) vs what I'd like to achieve: https://my.mixtape.moe/rdxqwg.webm

Edgar Reynaldo

It's probably crashing the stack, posting WM_SIZING every time a resize is called.

xsquid

I took some time to continue experimenting with it, and I don't think that's what's happening. It crashes right away on the first call when I step through it using a debugger, and I modified the function to only ever call it once, and it still crashes.

I'll keep playing with it to see if I can get it working, but I have no idea what's causing it at this point.

Update #1: I've noticed it doesn't like it when I use any Allegro functions inside of the callback function (drawing or display functions; it might be limited to things that try to modify the backbuffer). Outside is fine. Is that relevant?

Update #2: Yeah, it seems to throw a read access violation error if I try to do anything with the display's backbuffer inside of the callback function (or use any function that touches it).

bamccaig

If I had to guess I'd say that the callback is likely in a different thread than the main thread that you use with Allegro. That likely means that you cannot use Allegro within the callback because Allegro uses thread-local globals for a lot of state. It's probably a bad practice anyway. Unfortunately, that probably complicates things somewhat. A sensible approach may be to trigger a user-defined event if that is allowed, but I'm not sure if you'll need to somehow lock the queue... /guesses

xsquid
bamccaig said:

If I had to guess I'd say that the callback is likely in a different thread than the main thread that you use with Allegro. That likely means that you cannot use Allegro within the callback because Allegro uses thread-local globals for a lot of state. It's probably a bad practice anyway. Unfortunately, that probably complicates things somewhat. A sensible approach may be to trigger a user-defined event if that is allowed, but I'm not sure if you'll need to somehow lock the queue... /guesses

Tsk, I thought it might be something like that.

Would using a user-defined event make much of a difference compared to just using the ALLEGRO_EVENT_DISPLAY_RESIZE event? One thing I tried was redrawing and flipping the display immediately whenever this event occurs, but it doesn't seem to help (the end result looks just as laggy). If I'm totally on the wrong track with this concept, though, let me know...

Edgar Reynaldo

Okay, instead of calling al_resize_display inside of the window callback, call it in main. Set a shared variable named resize to true inside the callback, and then monitor it in main. If it's set, call al_resize_display. A mutex would probably be good and you need to store the values of the new dimensions. Allegro provides them for you with the ALLEGRO_MUTEX class. If al_resize_display triggers a WM_SIZING message, you'll probably get stuck inside an infinite loop. You may need to check if the display has already been resized to the proper size. If so, don't call al_resize_display.

xsquid

Based on the recommendations, here is the new code:

#SelectExpand
1#include <allegro5/allegro.h> 2#include <allegro5/allegro_image.h> 3#include <allegro5/allegro_primitives.h> 4#include <allegro5/allegro_windows.h> 5#include <algorithm> 6#include <Windows.h> 7#include <iostream> 8 9static ALLEGRO_MUTEX* mutex; 10static bool resize; 11static int neww, newh; 12 13bool WinProcCallback(ALLEGRO_DISPLAY* d, UINT msg, WPARAM wparam, LPARAM lparam, LRESULT* result, void* userdata) { 14 15 if (msg == WM_SIZING) { 16 17 int oldh = neww; 18 int oldw = newh; 19 20 RECT r = *(RECT*)lparam; 21 int rw = (r.right - r.left) - 21; 22 int rh = (r.bottom - r.top) - 42; 23 24 //al_lock_mutex(mutex); 25 26 if (oldh != rh || oldw != rw) { 27 28 neww = rw; 29 newh = rh; 30 resize = true; 31 32 } 33 //al_unlock_mutex(mutex); 34 35 // Return true, so Allegro knows it doesn't need to do anything for this event. 36 return true; 37 38 } 39 40 // Return false, so Allegro will handle the event on its own. 41 return false; 42 43} 44 45int main() { 46 47 // Initialize Allegro and required add-ons. 48 al_init(); 49 al_init_primitives_addon(); 50 51 // Set display flags and create the display. 52 al_set_new_display_flags(ALLEGRO_OPENGL | ALLEGRO_RESIZABLE); 53 ALLEGRO_DISPLAY *display = al_create_display(640, 480); 54 55 // Create mutex. 56 mutex = al_create_mutex(); 57 58 // Add callback function. 59 al_win_add_window_callback(display, WinProcCallback, 0); 60 61 while (1) { 62 63 //al_lock_mutex(mutex); 64 if (resize) { 65 al_resize_display(display, neww, newh); 66 al_acknowledge_resize(display); 67 resize = false; 68 } 69 //al_unlock_mutex(mutex); 70 71 al_clear_to_color(al_map_rgb(0, 0, 0)); 72 73 float w = (std::min)(al_get_display_width(display), al_get_display_height(display)); 74 float x = al_get_display_width(display) / 2.0f - w / 2.0f; 75 float y = al_get_display_height(display) / 2.0f - w / 2.0f; 76 al_draw_filled_rectangle(x, y, x + w, y + w, al_map_rgb(192, 192, 192)); 77 78 al_flip_display(); 79 80 } 81 82 return 0; 83 84}

I've commented-out the mutex stuff for now because it would randomly cause the display to lock up (I'm not sure why, sorry, this is my first doing this sort of thing; I'm going to keep trying to figure it out on my own).

With the mutex or not, though, it almost looks worse now. That is, it's much more jittery. When I resize it, it will jump to a size larger than I've dragged, jitter between that and size it should be as I move the mouse, and then snaps to where it should be when I release the mouse...

Update #1: The problem was I wasn't accounting for the window borders; I wasn't aware that that counted for the sizing values. Unfortunately, the outcome seems identical to how it was before; the contents of the display still lag behind a little bit showing white lines at the edges.

Maybe I'm not calling al_resize_display and al_acknowledge_resize in the right spot? Other than that I have no idea. Somehow I need to be able to redraw everything at the new size before the window itself is resized (e.g., resizing the buffer, drawing, then resizing the display) to avoid seeing what I assume is junk data at the edges before it's overwritten. I wonder if I can accomplish this by drawing to a different bitmap first instead of drawing directly to the backbuffer, but this is starting to get really hacky.

Is it unreasonable to not want to see junk data when resizing the window? Is there something else people normally do when working with resizable windows (aside from simply not doing that)?

I'm starting to think I might have to switch to another library to accomplish this.

Edgar Reynaldo

Don't call al_acknowledge_resize without a corresponding ALLEGRO_EVENT_DISPLAY_RESIZE.

I'll try to experiment with this later this week. Maybe this weekend. Did you try setting the background brush? If it matches your background, you probably won't even be able to notice it's resizing.

xsquid

Don't call al_acknowledge_resize without a corresponding ALLEGRO_EVENT_DISPLAY_RESIZE.

I'll try to experiment with this later this week. Maybe this weekend. Did you try setting the background brush? If it matches your background, you probably won't even be able to notice it's resizing.

Noted, I'll rewrite the test code to use an event queue.

I've tried setting the background brush, and you're absolutely right, it helps! The edges don't look ugly anymore. Doing this is a solution to the example problem of blacking out the display, but it doesn't work if I want to continue drawing while the resize is taking place (it's just a black box until I stop moving the mouse).

Maybe this is my fault; as usual I'll update this post once/if I figure out how to solve it...

Thread #616674. Printed from Allegro.cc