From 5770e013c26a666a1e314e09dfd7c5ace3fcb29c Mon Sep 17 00:00:00 2001
From: Ethan Lee <[EMAIL REDACTED]>
Date: Wed, 25 Feb 2026 09:20:25 -0500
Subject: [PATCH] gdk: Render/GPU can call SuspendX, document when to call
SuspendComplete
---
VisualC-GDK/tests/testgdk/src/testgdk.cpp | 32 ++++++++++++++++++++---
include/SDL3/SDL_main.h | 7 +++++
src/render/direct3d12/SDL_render_d3d12.c | 20 ++++++++++++++
src/render/gpu/SDL_render_gpu.c | 21 +++++++++++++++
4 files changed, 76 insertions(+), 4 deletions(-)
diff --git a/VisualC-GDK/tests/testgdk/src/testgdk.cpp b/VisualC-GDK/tests/testgdk/src/testgdk.cpp
index b8fffd30b6f58..d69e73e533db1 100644
--- a/VisualC-GDK/tests/testgdk/src/testgdk.cpp
+++ b/VisualC-GDK/tests/testgdk/src/testgdk.cpp
@@ -291,9 +291,8 @@ static void DrawSprites(SDL_Renderer * renderer, SDL_Texture * sprite)
SDL_RenderPresent(renderer);
}
-static void loop()
+static void update()
{
- int i;
SDL_Event event;
/* Check for events */
@@ -310,13 +309,31 @@ static void loop()
SDLTest_CommonEvent(state, &event, &done);
#endif
}
+ fillerup();
+}
+
+static void draw()
+{
+ int i;
for (i = 0; i < state->num_windows; ++i) {
if (state->windows[i] == NULL) {
continue;
}
DrawSprites(state->renderers[i], sprites[i]);
}
- fillerup();
+}
+
+static bool SDLCALL GDKEventWatch(void* userdata, SDL_Event* event)
+{
+ bool *suppressdraw = (bool *)userdata;
+ SDL_assert(suppressdraw != NULL);
+ if (event->type == SDL_EVENT_DID_ENTER_BACKGROUND) {
+ *suppressdraw = true;
+ SDL_GDKSuspendComplete();
+ } else if (event->type == SDL_EVENT_WILL_ENTER_FOREGROUND) {
+ *suppressdraw = false;
+ }
+ return true;
}
int main(int argc, char *argv[])
@@ -324,6 +341,7 @@ int main(int argc, char *argv[])
int i;
const char *icon = "icon.bmp";
char *soundname = NULL;
+ bool suppressdraw = false;
/* Initialize parameters */
num_sprites = NUM_SPRITES;
@@ -390,6 +408,9 @@ 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);
+
/* Create the windows, initialize the renderers, and load the textures */
sprites =
(SDL_Texture **) SDL_malloc(state->num_windows * sizeof(*sprites));
@@ -441,7 +462,10 @@ int main(int argc, char *argv[])
AddUserSilent();
while (!done) {
- loop();
+ update();
+ if (!suppressdraw) {
+ draw();
+ }
}
quit(0);
diff --git a/include/SDL3/SDL_main.h b/include/SDL3/SDL_main.h
index c036b57416e2e..daa10aabbcc58 100644
--- a/include/SDL3/SDL_main.h
+++ b/include/SDL3/SDL_main.h
@@ -664,6 +664,13 @@ extern SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void);
/**
* Callback from the application to let the suspend continue.
*
+ * When using SDL_Render or SDL_GPU, this function should be called _after_
+ * creating the `SDL_Renderer` or `SDL_GPUDevice`; this allows the timing of the
+ * D3D12 command queue suspension to execute in the correct order.
+ *
+ * If you're writing your own D3D12 renderer, this should be called after
+ * calling `ID3D12CommandQueue::SuspendX`.
+ *
* This function is only needed for Xbox GDK support; all other platforms will
* do nothing and set an "unsupported" error message.
*
diff --git a/src/render/direct3d12/SDL_render_d3d12.c b/src/render/direct3d12/SDL_render_d3d12.c
index 92b170d8a7ba6..c0556de987c97 100644
--- a/src/render/direct3d12/SDL_render_d3d12.c
+++ b/src/render/direct3d12/SDL_render_d3d12.c
@@ -562,10 +562,26 @@ static HRESULT D3D12_IssueBatch(D3D12_RenderData *data)
return result;
}
+#ifdef SDL_PLATFORM_GDK
+static bool SDLCALL D3D12_GDKEventFilter(void* userdata, SDL_Event* event)
+{
+ D3D12_RenderData *data = (D3D12_RenderData *)userdata;
+ if (event->type == SDL_EVENT_DID_ENTER_BACKGROUND) {
+ data->commandQueue->SuspendX(0);
+ } else if (event->type == SDL_EVENT_WILL_ENTER_FOREGROUND) {
+ data->commandQueue->ResumeX();
+ }
+ return true;
+}
+#endif
+
static void D3D12_DestroyRenderer(SDL_Renderer *renderer)
{
D3D12_RenderData *data = (D3D12_RenderData *)renderer->internal;
if (data) {
+#ifdef SDL_PLATFORM_GDK
+ SDL_RemoveEventWatch(D3D12_GDKEventFilter, data);
+#endif
D3D12_WaitForGPU(data);
D3D12_ReleaseAll(renderer);
SDL_free(data);
@@ -1112,6 +1128,10 @@ static HRESULT D3D12_CreateDeviceResources(SDL_Renderer *renderer)
SDL_SetPointerProperty(props, SDL_PROP_RENDERER_D3D12_DEVICE_POINTER, data->d3dDevice);
SDL_SetPointerProperty(props, SDL_PROP_RENDERER_D3D12_COMMAND_QUEUE_POINTER, data->commandQueue);
+#ifdef SDL_PLATFORM_GDK
+ SDL_AddEventWatch(D3D12_GDKEventFilter, data);
+#endif
+
done:
D3D_SAFE_RELEASE(d3dDevice);
return result;
diff --git a/src/render/gpu/SDL_render_gpu.c b/src/render/gpu/SDL_render_gpu.c
index c918ac3b0662e..5887023cda10a 100644
--- a/src/render/gpu/SDL_render_gpu.c
+++ b/src/render/gpu/SDL_render_gpu.c
@@ -1562,6 +1562,20 @@ static void GPU_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture)
texture->internal = NULL;
}
+#ifdef SDL_PLATFORM_GDK
+static bool SDLCALL GPU_GDKEventFilter(void *userdata, SDL_Event *event)
+{
+ GPU_RenderData *data = (GPU_RenderData *)userdata;
+ SDL_assert(!data->external_device);
+ if (event->type == SDL_EVENT_DID_ENTER_BACKGROUND) {
+ SDL_GDKSuspendGPU(data->device);
+ } else if (event->type == SDL_EVENT_WILL_ENTER_FOREGROUND) {
+ SDL_GDKResumeGPU(data->device);
+ }
+ return true;
+}
+#endif
+
static void GPU_DestroyRenderer(SDL_Renderer *renderer)
{
GPU_RenderData *data = (GPU_RenderData *)renderer->internal;
@@ -1595,6 +1609,9 @@ static void GPU_DestroyRenderer(SDL_Renderer *renderer)
if (data->device) {
GPU_ReleaseShaders(&data->shaders, data->device);
if (!data->external_device) {
+#ifdef SDL_PLATFORM_GDK
+ SDL_RemoveEventWatch(GPU_GDKEventFilter, data);
+#endif
SDL_DestroyGPUDevice(data->device);
}
}
@@ -1758,6 +1775,10 @@ static bool GPU_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_P
if (!data->device) {
return false;
}
+
+#ifdef SDL_PLATFORM_GDK
+ SDL_AddEventWatch(GPU_GDKEventFilter, data);
+#endif
}
if (!GPU_InitShaders(&data->shaders, data->device)) {