Fixed bug 4369 - Going fullscreen with green knob in MacOS freezes app for 15 seconds.
authorSam Lantinga <slouken@libsdl.org>
Sun, 01 Mar 2020 12:58:50 -0800
changeset 13560414b888d2b9f
parent 13559 61b564f66050
child 13561 af818ab1c036
Fixed bug 4369 - Going fullscreen with green knob in MacOS freezes app for 15 seconds.


Elmar

creating a fullscreen window with SDL_CreateWindow(..SDL_WINDOW_FULLSCREEN_DESKTOP..) in MacOS works fine, except if it was triggered by the user with the green knob in the top left window title bar.

Then "something" is different, and SDL_CreateWindow hangs for 15-20 seconds (tested in MacOS 10.13 and 10.14).

Responsible for the hang is this code in SDL_cocoawindow.m - Cocoa_SetWindowFullscreenSpace:

const int maxattempts = 3;
int attempt = 0;
while (++attempt <= maxattempts) {
/* Wait for the transition to complete, so application changes
take effect properly (e.g. setting the window size, etc.)
*/
const int limit = 10000;
int count = 0;
while ([data->listener isInFullscreenSpaceTransition]) {
if ( ++count == limit ) {
/* Uh oh, transition isn't completing. Should we assert? */
break;
}
SDL_Delay(1);
SDL_PumpEvents();
}
if ([data->listener isInFullscreenSpace] == (state ? YES : NO))
break;
/* Try again, the last attempt was interrupted by user gestures */
if (![data->listener setFullscreenSpace:(state ? YES : NO)])
break; /* ??? */
}

One trivial workaround is to change 'const int limit = 10000' to 500. Then the freeze is so short that it doesn't look like a freeze to the user.

Looking further into the problem, I observed that the function Cocoa_SetWindowFullscreenSpace recursively calls itself via some ObjectiveC messages. I managed to extract a callstack for this (copied below): Note how Cocoa_SetWindowFullscreenSpace in stack line 22 calls SDL_PumpEvents, which eventually arrives at SDL_SendWindowEvent, which calls SDL_UpdateFullscreenMode (stack line 0), which then calls Cocoa_SetWindowFullscreenSpace again (not shown). This recursive second call is the one that hangs.

Another "solution" that worked for me was to add a flag to SDL_Window that is set in Cocoa_SetWindowFullscreenSpace and causes this function to return immediately if called from itself.

Obviously, this is also an ugly hack, but I don't have enough time to dive into this crazy Cocoa/ObjectiveC business deep enough to find a proper solution. But hopefully it's easy for one of the experts around.

Note that there is a "failure to go fullscreen"-message involved, maybe using the green knob causes this failure at first.

I can unfortunately not provide a minimum example.

Best regards,
Elmar

0 com.yasara.View 0x00000001007495af SDL_UpdateFullscreenMode + 207
1 com.yasara.View 0x00000001006e2591 SDL_SendWindowEvent + 401
2 com.yasara.View 0x0000000100775a72 -[Cocoa_WindowListener windowDidResize:] + 370
3 com.yasara.View 0x0000000100776550 -[Cocoa_WindowListener windowDidExitFullScreen:] + 512
4 com.apple.AppKit 0x00007fff3180a2a4 -[_NSWindowEnterFullScreenTransitionController failedToEnterFullScreen] + 692
5 com.apple.AppKit 0x00007fff31c59737 -[_NSEnterFullScreenTransitionController _doFailedToEnterFullScreen] + 349
6 com.apple.AppKit 0x00007fff3172aa53 __NSFullScreenDockConnectionSendEnterForSpace_block_invoke + 135
7 libxpc.dylib 0x00007fff6114b9b1 _xpc_connection_reply_callout + 36
8 libxpc.dylib 0x00007fff6114b938 _xpc_connection_call_reply_async + 82
9 libdispatch.dylib 0x00007fff60ec7e39 _dispatch_client_callout3 + 8
10 libdispatch.dylib 0x00007fff60ede3b0 _dispatch_mach_msg_async_reply_invoke + 322
11 libdispatch.dylib 0x00007fff60ed2e25 _dispatch_main_queue_callback_4CF + 807
12 com.apple.CoreFoundation 0x00007fff33d39e8b __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
13 com.apple.CoreFoundation 0x00007fff33d3959a __CFRunLoopRun + 2335
14 com.apple.CoreFoundation 0x00007fff33d38a28 CFRunLoopRunSpecific + 463
15 com.apple.HIToolbox 0x00007fff32fd1b35 RunCurrentEventLoopInMode + 293
16 com.apple.HIToolbox 0x00007fff32fd1774 ReceiveNextEventCommon + 371
17 com.apple.HIToolbox 0x00007fff32fd15e8 _BlockUntilNextEventMatchingListInModeWithFilter + 64
18 com.apple.AppKit 0x00007fff3128deb7 _DPSNextEvent + 997
19 com.apple.AppKit 0x00007fff3128cc56 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1362
20 com.yasara.View 0x000000010076fab2 Cocoa_PumpEvents + 290
21 com.yasara.View 0x00000001006dd1c7 SDL_PumpEvents_REAL + 23
22 com.yasara.View 0x00000001007795cf Cocoa_SetWindowFullscreenSpace + 223
23 com.yasara.View 0x000000010074970b SDL_UpdateFullscreenMode + 555
24 com.yasara.View 0x00000001006e2476 SDL_SendWindowEvent + 118
25 com.yasara.View 0x0000000100774ff7 -[Cocoa_WindowListener resumeVisibleObservation] + 135
26 com.yasara.View 0x000000010077664c Cocoa_ShowWindow + 188
27 com.yasara.View 0x0000000100749492 SDL_FinishWindowCreation + 546
28 com.yasara.View 0x0000000100748da5 SDL_CreateWindow_REAL + 1573
29 com.yasara.View 0x000000010010d9b1 vga_setvideomode + 1347
30 com.yasara.View 0x00000001003f0d46 mod_initscreen + 2614
31 com.yasara.View 0x00000001003f344b mod_reinitscreen + 460
32 com.yasara.View 0x00000001003f370d mod_resizescreen + 383
33 com.yasara.View 0x0000000100418e39 mod_main + 815
34 com.yasara.View 0x000000010029ca5d main2 + 5766
35 com.yasara.View 0x000000010011d1b7 main.main_cpuok + 19
src/video/cocoa/SDL_cocoawindow.h
src/video/cocoa/SDL_cocoawindow.m
     1.1 --- a/src/video/cocoa/SDL_cocoawindow.h	Sun Mar 01 12:50:42 2020 -0800
     1.2 +++ b/src/video/cocoa/SDL_cocoawindow.h	Sun Mar 01 12:58:50 2020 -0800
     1.3 @@ -116,7 +116,7 @@
     1.4      NSView *sdlContentView; /* nil if window is created via CreateWindowFrom */
     1.5      NSMutableArray *nscontexts;
     1.6      SDL_bool created;
     1.7 -    SDL_bool inWindowMove;
     1.8 +    SDL_bool inWindowFullscreenTransition;
     1.9      Cocoa_WindowListener *listener;
    1.10      struct SDL_VideoData *videodata;
    1.11  #if SDL_VIDEO_OPENGL_EGL
     2.1 --- a/src/video/cocoa/SDL_cocoawindow.m	Sun Mar 01 12:50:42 2020 -0800
     2.2 +++ b/src/video/cocoa/SDL_cocoawindow.m	Sun Mar 01 12:58:50 2020 -0800
     2.3 @@ -2030,6 +2030,11 @@
     2.4      SDL_bool succeeded = SDL_FALSE;
     2.5      SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
     2.6  
     2.7 +	if (data->inWindowFullscreenTransition) {
     2.8 +		return SDL_FALSE;
     2.9 +	}
    2.10 +
    2.11 +	data->inWindowFullscreenTransition = SDL_TRUE;
    2.12      if ([data->listener setFullscreenSpace:(state ? YES : NO)]) {
    2.13          const int maxattempts = 3;
    2.14          int attempt = 0;
    2.15 @@ -2056,6 +2061,7 @@
    2.16          /* Return TRUE to prevent non-space fullscreen logic from running */
    2.17          succeeded = SDL_TRUE;
    2.18      }
    2.19 +	data->inWindowFullscreenTransition = SDL_FALSE;
    2.20  
    2.21      return succeeded;
    2.22  }}