From 12a435e11d44c5cb18e9f1ef52b21f523739ecaf Mon Sep 17 00:00:00 2001 From: Caleb Cornett Date: Sat, 7 Mar 2026 07:16:36 -0500 Subject: [PATCH] gdk: Update Suspend/Resume best practices. Updated testgdk to demonstrate correct handling of suspend/resume and the new Render APIs, and updated the docs to explain the correct usage of these GDK functions. --- VisualC-GDK/tests/testgdk/src/testgdk.cpp | 58 +++++++++++++++++------ include/SDL3/SDL_gpu.h | 8 +++- include/SDL3/SDL_main.h | 7 ++- include/SDL3/SDL_render.h | 8 +++- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/VisualC-GDK/tests/testgdk/src/testgdk.cpp b/VisualC-GDK/tests/testgdk/src/testgdk.cpp index 53e203d92d..1ac2033b3e 100644 --- a/VisualC-GDK/tests/testgdk/src/testgdk.cpp +++ b/VisualC-GDK/tests/testgdk/src/testgdk.cpp @@ -30,7 +30,9 @@ extern "C" { #include #define NUM_SPRITES 100 -#define MAX_SPEED 1 +#define MAX_SPEED 1 +#define SUSPEND_CODE 0 +#define RESUME_CODE 1 static SDLTest_CommonState *state; static int num_sprites; @@ -291,7 +293,7 @@ static void DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite) SDL_RenderPresent(renderer); } -static void update() +static void update(bool *suppressdraw) { SDL_Event event; @@ -305,6 +307,25 @@ static void update() 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 @@ -316,24 +337,33 @@ static void draw() { int i; for (i = 0; i < state->num_windows; ++i) { - if (state->windows[i] == NULL) { - continue; + if (state->windows[i] != NULL) { + DrawSprites(state->renderers[i], sprites[i]); } - DrawSprites(state->renderers[i], sprites[i]); } } static bool SDLCALL GDKEventWatch(void* userdata, SDL_Event* event) { - bool *suppressdraw = (bool *)userdata; - SDL_assert(suppressdraw != NULL); + /* 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. + * + * 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) { - *suppressdraw = true; - SDL_GDKSuspendComplete(); + SDL_Event evt; + evt.type = SDL_EVENT_USER; + evt.user.code = 0; + SDL_PushEvent(&evt); } else if (event->type == SDL_EVENT_WILL_ENTER_FOREGROUND) { - *suppressdraw = false; + SDL_Event evt; + evt.type = SDL_EVENT_USER; + evt.user.code = 1; + SDL_PushEvent(&evt); } - return true; + return false; } int main(int argc, char *argv[]) @@ -408,8 +438,8 @@ int main(int argc, char *argv[]) quit(2); } - /* By this point the renderers are made, so we can now add this watcher */ - SDL_AddEventWatch(GDKEventWatch, &suppressdraw); + /* Set up the lifecycle event watcher */ + SDL_AddEventWatch(GDKEventWatch, NULL); /* Create the windows, initialize the renderers, and load the textures */ sprites = @@ -462,7 +492,7 @@ int main(int argc, char *argv[]) AddUserSilent(); while (!done) { - update(); + update(&suppressdraw); if (!suppressdraw) { draw(); } diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 801c44e516..623b6ecafe 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -4581,12 +4581,14 @@ extern SDL_DECLSPEC SDL_GPUTextureFormat SDLCALL SDL_GetGPUTextureFormatFromPixe #ifdef SDL_PLATFORM_GDK /** - * Call this to suspend GPU operation on Xbox when you receive the + * Call this to suspend GPU operation on Xbox after receiving the * SDL_EVENT_DID_ENTER_BACKGROUND event. * * Do NOT call any SDL_GPU functions after calling this function! This must * also be called before calling SDL_GDKSuspendComplete. * + * This function MUST be called from the application's render thread. + * * \param device a GPU context. * * \since This function is available since SDL 3.2.0. @@ -4596,12 +4598,14 @@ extern SDL_DECLSPEC SDL_GPUTextureFormat SDLCALL SDL_GetGPUTextureFormatFromPixe extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendGPU(SDL_GPUDevice *device); /** - * Call this to resume GPU operation on Xbox when you receive the + * Call this to resume GPU operation on Xbox after receiving the * SDL_EVENT_WILL_ENTER_FOREGROUND event. * * When resuming, this function MUST be called before calling any other * SDL_GPU functions. * + * This function MUST be called from the application's render thread. + * * \param device a GPU context. * * \since This function is available since SDL 3.2.0. diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h index 02733e0cc0..25e5879e2e 100644 --- a/include/SDL3/SDL_main.h +++ b/include/SDL3/SDL_main.h @@ -664,8 +664,11 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void); /** * Callback from the application to let the suspend continue. * - * This should be called from an event watch in response to an - * `SDL_EVENT_DID_ENTER_BACKGROUND` event. + * This should be called in response to an `SDL_EVENT_DID_ENTER_BACKGROUND` event, + * which can be detected via event watch. However, do NOT call this function + * directly from within an event watch callback. Instead, wait until the app has + * suppressed all rendering operations, then call this from the application + * render thread. * * When using SDL_Render, this should be called after calling SDL_GDKSuspendRenderer. * diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h index 9dcd25e4b9..2d5edab608 100644 --- a/include/SDL3/SDL_render.h +++ b/include/SDL3/SDL_render.h @@ -3085,12 +3085,14 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyGPURenderState(SDL_GPURenderState *s #ifdef SDL_PLATFORM_GDK /** - * Call this to suspend Render operations on Xbox when you receive the + * Call this to suspend Render operations on Xbox after receiving the * SDL_EVENT_DID_ENTER_BACKGROUND event. * * Do NOT call any SDL_Render functions after calling this function! This must * also be called before calling SDL_GDKSuspendComplete. * + * This function MUST be called on the application's render thread. + * * \param renderer the renderer which should suspend operation * * \since This function is available since SDL 3.6.0. @@ -3100,12 +3102,14 @@ extern SDL_DECLSPEC void SDLCALL SDL_DestroyGPURenderState(SDL_GPURenderState *s extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendRenderer(SDL_Renderer *renderer); /** - * Call this to resume Render operations on Xbox when you receive the + * Call this to resume Render operations on Xbox after receiving the * SDL_EVENT_WILL_ENTER_FOREGROUND event. * * When resuming, this function MUST be called before calling any other * SDL_Render functions. * + * This function MUST be called on the application's render thread. + * * \param renderer the renderer which should resume operation * * \since This function is available since SDL 3.6.0.