diff --git a/VisualC-GDK/tests/testgdk/src/testgdk.cpp b/VisualC-GDK/tests/testgdk/src/testgdk.cpp index e3dff71f3a..58fc7e8615 100644 --- a/VisualC-GDK/tests/testgdk/src/testgdk.cpp +++ b/VisualC-GDK/tests/testgdk/src/testgdk.cpp @@ -31,8 +31,6 @@ extern "C" { #define NUM_SPRITES 100 #define MAX_SPEED 1 -#define SUSPEND_CODE 0 -#define RESUME_CODE 1 static SDLTest_CommonState *state; static int num_sprites; @@ -44,6 +42,8 @@ static int current_alpha = 0; static int current_color = 0; static int sprite_w, sprite_h; static SDL_BlendMode blendMode = SDL_BLENDMODE_BLEND; +static SDL_AtomicInt suspend_requested; +static bool suspended; int done; @@ -293,7 +293,7 @@ static void DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) SDL_RenderPresent(renderer); } -static void update(bool *suppressdraw) +static void update() { SDL_Event event; @@ -307,29 +307,33 @@ static void update(bool *suppressdraw) if (event.type != SDL_EVENT_KEY_DOWN) { SDLTest_CommonEvent(state, &event, &done); } - - if (event.type == SDL_EVENT_USER) { - if (event.user.code == SUSPEND_CODE) { - for (int i = 0; i < state->num_windows; ++i) { - if (state->windows[i] != NULL) { - SDL_GDKSuspendRenderer(state->renderers[i]); - } - } - *suppressdraw = true; - SDL_GDKSuspendComplete(); - } else if (event.user.code == RESUME_CODE) { - for (int i = 0; i < state->num_windows; ++i) { - if (state->windows[i] != NULL) { - SDL_GDKResumeRenderer(state->renderers[i]); - } - } - *suppressdraw = false; - } - } #else SDLTest_CommonEvent(state, &event, &done); #endif } + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + /* Handle suspend and resume events */ + if (SDL_GetAtomicInt(&suspend_requested)) { + if (!suspended) { + for (int i = 0; i < state->num_windows; ++i) { + if (state->windows[i] != NULL) { + SDL_GDKSuspendRenderer(state->renderers[i]); + } + } + suspended = true; + SDL_GDKSuspendComplete(); + } + } else if (suspended) { + for (int i = 0; i < state->num_windows; ++i) { + if (state->windows[i] != NULL) { + SDL_GDKResumeRenderer(state->renderers[i]); + } + } + suspended = false; + } +#endif + fillerup(); } @@ -346,22 +350,15 @@ static void draw() static bool SDLCALL GDKEventWatch(void* userdata, SDL_Event* event) { /* This callback may be on a different thread, so we'll - * push these events as USER events so they appear - * in the main thread's event loop. + * set an atomic flag that can be read in our main loop. * * That allows us to cancel drawing before/after we finish * drawing a frame, rather than mid-draw (which can crash). */ if (event->type == SDL_EVENT_DID_ENTER_BACKGROUND) { - SDL_Event evt; - evt.type = SDL_EVENT_USER; - evt.user.code = 0; - SDL_PushEvent(&evt); + SDL_SetAtomicInt(&suspend_requested, 1); } else if (event->type == SDL_EVENT_WILL_ENTER_FOREGROUND) { - SDL_Event evt; - evt.type = SDL_EVENT_USER; - evt.user.code = 1; - SDL_PushEvent(&evt); + SDL_SetAtomicInt(&suspend_requested, 0); } return false; } @@ -371,7 +368,6 @@ int main(int argc, char *argv[]) int i; const char *icon = "icon.png"; char *soundname = NULL; - bool suppressdraw = false; /* Initialize parameters */ num_sprites = NUM_SPRITES; @@ -492,8 +488,8 @@ int main(int argc, char *argv[]) AddUserSilent(); while (!done) { - update(&suppressdraw); - if (!suppressdraw) { + update(); + if (!suspended) { draw(); } } diff --git a/docs/README-gdk.md b/docs/README-gdk.md index 9d0d486785..20fd4a341b 100644 --- a/docs/README-gdk.md +++ b/docs/README-gdk.md @@ -168,22 +168,56 @@ Please read the official GDK documentation for all the details, but here's what - If any GPU API call happens in between suspension and resuming, the application will crash or hang. Unfortunately it's impossible for SDL to know for certain when your application is ready for suspension, so it cannot handle these events internally. -Instead, SDL provides a few APIs you must use to handle these events yourself. +Instead, SDL provides a few `SDL_GDK*` APIs you must use to handle these events yourself. On startup, your application should call `SDL_AddEventWatch` to register a callback function which will listen for the `SDL_EVENT_DID_ENTER_BACKGROUND` and `SDL_EVENT_WILL_ENTER_FOREGROUND` events. This callback function will be called from a background OS thread, so when one of these events occurs, you should notify your render thread about it rather than responding directly in the callback. -A simple strategy for this is to make your callback function post custom `SDL_EVENT_USER` events that your main loop's event processor can respond to. +A simple thread-safe strategy for this is to add an `SDL_AtomicInt` to act as a flag for whether the OS has requested suspension, like so: -When your render thread receives your notification of the `SDL_EVENT_DID_ENTER_BACKGROUND` event, it should do the following: -- Set a flag to suppress the entire render loop until the application resumes. -- Call `SDL_GDKSuspendRenderer` if you are using the Render API, or call `SDL_GDKSuspendGPU` if you are using the GPU API. -- Finally, regardless of what graphics API you are using, call `SDL_GDKSuspendComplete`. +```c +static SDL_AtomicInt suspend_requested; // has the OS requested suspension? +static bool suspended; // have we responded and entered into a "suspended" state? -When your render thread receives your notification of an `SDL_EVENT_WILL_ENTER_FOREGROUND` event, it should do the following: -- Call `SDL_GDKResumeRenderer` if you are using the Render API, or call `SDL_GDKResumeGPU` if you are using the GPU API. -- Unset the flag suppressing your render loop to allow drawing again. +static bool SDLCALL GDKEventWatch(void *userdata, SDL_Event *event) +{ + if (event->type == SDL_EVENT_DID_ENTER_BACKGROUND) { + SDL_SetAtomicInt(&suspend_requested, 1); // suspend + } else if (event->type == SDL_EVENT_WILL_ENTER_FOREGROUND) { + SDL_SetAtomicInt(&suspend_requested, 0); // resume + } + return false; +} -An example of this flow can be found in the testgdk project. +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + // during startup... + SDL_AddEventWatch(GDKEventWatch, NULL); +} + +SDL_AppResult SDL_AppIterate(void *appstate) +{ + // check for suspend/resume state changes + if (SDL_GetAtomicInt(&suspend_requested)) { + if (!suspended) { + // call SDL_GDKSuspendRenderer() if you're using the Render API + // call SDL_GDKSuspendGPU() if you're using the GPU API + suspended = true; + SDL_GDKSuspendComplete(); // this call is always required! + } + } else if (suspended) { + // call SDL_GDKResumeRenderer() if you're using the Render API + // call SDL_GDKResumeGPU() if you're using the GPU API + suspended = false; + } + + // skip drawing if the app is suspended + if (!suspended) { + draw(); + } +} +``` + +An example of this flow with a traditional SDL main function can be found in the testgdk project. Troubleshooting ---------------