From 12a435e11d44c5cb18e9f1ef52b21f523739ecaf Mon Sep 17 00:00:00 2001
From: Caleb Cornett <[EMAIL REDACTED]>
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 53e203d92d527..1ac2033b3e2df 100644
--- a/VisualC-GDK/tests/testgdk/src/testgdk.cpp
+++ b/VisualC-GDK/tests/testgdk/src/testgdk.cpp
@@ -30,7 +30,9 @@ extern "C" {
#include <XGameRuntime.h>
#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 801c44e516737..623b6ecafe234 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 02733e0cc03c1..25e5879e2e238 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 9dcd25e4b924e..2d5edab608608 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.