Просмотр исходного кода

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.
Caleb Cornett 2 дней назад
Родитель
Сommit
12a435e11d

+ 44 - 14
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();
         }

+ 6 - 2
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.

+ 5 - 2
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.
  *

+ 6 - 2
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.