SDL: test: Add flag to suspend drawing when occluded

From ef4ce8cec5fb130136ef575e544100ea68193bee Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Sun, 25 Jun 2023 10:18:58 -0400
Subject: [PATCH] test: Add flag to suspend drawing when occluded

Add the flag "--suspend-when-occluded" to testgl, testgles2, and testsprite, which, when used, will suspend rendering and throttle the event loop when the occlusion flag is set on the window.
---
 test/testgl.c     | 16 +++++++--
 test/testgles2.c  | 84 ++++++++++++++++++++++++++++++++++++++---------
 test/testsprite.c | 15 ++++++++-
 3 files changed, 97 insertions(+), 18 deletions(-)

diff --git a/test/testgl.c b/test/testgl.c
index 8ceb5093271a..0f592812b3c1 100644
--- a/test/testgl.c
+++ b/test/testgl.c
@@ -31,6 +31,7 @@ typedef struct GL_Context
 static SDLTest_CommonState *state;
 static SDL_GLContext context;
 static GL_Context ctx;
+static SDL_bool suspend_when_occluded;
 
 static int LoadContext(GL_Context *data)
 {
@@ -237,12 +238,15 @@ int main(int argc, char *argv[])
             } else if (SDL_strcasecmp(argv[i], "--accel") == 0 && i + 1 < argc) {
                 accel = SDL_atoi(argv[i + 1]);
                 consumed = 2;
+            } else if(SDL_strcasecmp(argv[i], "--suspend-when-occluded") == 0) {
+                suspend_when_occluded = SDL_TRUE;
+                consumed = 1;
             } else {
                 consumed = -1;
             }
         }
         if (consumed < 0) {
-            static const char *options[] = { "[--fsaa n]", "[--accel n]", NULL };
+            static const char *options[] = { "[--fsaa n]", "[--accel n]", "[--suspend-when-occluded]", NULL };
             SDLTest_CommonLogUsage(state, argv[0], options);
             quit(1);
         }
@@ -386,6 +390,7 @@ int main(int argc, char *argv[])
     done = 0;
     while (!done) {
         SDL_bool update_swap_interval = SDL_FALSE;
+        int active_windows = 0;
 
         /* Check for events */
         ++frames;
@@ -408,9 +413,11 @@ int main(int argc, char *argv[])
 
         for (i = 0; i < state->num_windows; ++i) {
             int w, h;
-            if (state->windows[i] == NULL) {
+            if (state->windows[i] == NULL ||
+                (suspend_when_occluded && (SDL_GetWindowFlags(state->windows[i]) & SDL_WINDOW_OCCLUDED))) {
                 continue;
             }
+            ++active_windows;
             SDL_GL_MakeCurrent(state->windows[i], context);
             if (update_swap_interval) {
                 SDL_GL_SetSwapInterval(swap_interval);
@@ -420,6 +427,11 @@ int main(int argc, char *argv[])
             Render();
             SDL_GL_SwapWindow(state->windows[i]);
         }
+
+        /* If all windows are occluded, throttle event polling to 15hz. */
+        if (!active_windows) {
+            SDL_DelayNS(SDL_NS_PER_SECOND / 15);
+        }
     }
 
     /* Print out some timing information */
diff --git a/test/testgles2.c b/test/testgles2.c
index 05c49847bae4..6975adbafdd4 100644
--- a/test/testgles2.c
+++ b/test/testgles2.c
@@ -46,9 +46,18 @@ typedef struct shader_data
     GLuint color_buffer;
 } shader_data;
 
+typedef enum wait_state
+{
+    WAIT_STATE_GO = 0,
+    WAIT_STATE_ENTER_SEM,
+    WAIT_STATE_WAITING_ON_SEM,
+} wait_state;
+
 typedef struct thread_data
 {
     SDL_Thread *thread;
+    SDL_Semaphore *suspend_sem;
+    SDL_AtomicInt suspended;
     int done;
     int index;
 } thread_data;
@@ -56,6 +65,7 @@ typedef struct thread_data
 static SDLTest_CommonState *state;
 static SDL_GLContext *context = NULL;
 static int depth = 16;
+static SDL_bool suspend_when_occluded;
 static GLES2_Context ctx;
 
 static int LoadContext(GLES2_Context *data)
@@ -557,6 +567,9 @@ render_thread_fn(void *render_ctx)
     thread_data *thread = render_ctx;
 
     while (!done && !thread->done && state->windows[thread->index]) {
+        if (SDL_AtomicCAS(&thread->suspended, WAIT_STATE_ENTER_SEM, WAIT_STATE_WAITING_ON_SEM)) {
+            SDL_WaitSemaphore(thread->suspend_sem);
+        }
         render_window(thread->index);
     }
 
@@ -564,28 +577,53 @@ render_thread_fn(void *render_ctx)
     return 0;
 }
 
+static thread_data *GetThreadDataForWindow(SDL_WindowID id)
+{
+    int i;
+    SDL_Window *window = SDL_GetWindowFromID(id);
+    if (window) {
+        for (i = 0; i < state->num_windows; ++i) {
+            if (window == state->windows[i]) {
+                return &threads[i];
+            }
+        }
+    }
+    return NULL;
+}
+
 static void
 loop_threaded(void)
 {
     SDL_Event event;
-    int i;
+    thread_data *tdata;
 
     /* Wait for events */
     while (SDL_WaitEvent(&event) && !done) {
-        if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
-            SDL_Window *window = SDL_GetWindowFromID(event.window.windowID);
-            if (window) {
-                for (i = 0; i < state->num_windows; ++i) {
-                    if (window == state->windows[i]) {
-                        /* Stop the render thread when the window is closed */
-                        threads[i].done = 1;
-                        if (threads[i].thread) {
-                            SDL_WaitThread(threads[i].thread, NULL);
-                            threads[i].thread = NULL;
-                        }
-                        break;
-                    }
+        if (suspend_when_occluded && event.type == SDL_EVENT_WINDOW_OCCLUDED) {
+            tdata = GetThreadDataForWindow(event.window.windowID);
+            if (tdata) {
+                SDL_AtomicCAS(&tdata->suspended, WAIT_STATE_GO, WAIT_STATE_ENTER_SEM);
+            }
+        } else if (suspend_when_occluded && event.type == SDL_EVENT_WINDOW_EXPOSED) {
+            tdata = GetThreadDataForWindow(event.window.windowID);
+            if (tdata) {
+                if (SDL_AtomicSet(&tdata->suspended, WAIT_STATE_GO) == WAIT_STATE_WAITING_ON_SEM) {
+                    SDL_PostSemaphore(tdata->suspend_sem);
+                }
+            }
+        } else if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
+            tdata = GetThreadDataForWindow(event.window.windowID);
+            if (tdata) {
+                /* Stop the render thread when the window is closed */
+                tdata->done = 1;
+                if (tdata->thread) {
+                    SDL_AtomicSet(&tdata->suspended, WAIT_STATE_GO);
+                    SDL_PostSemaphore(tdata->suspend_sem);
+                    SDL_WaitThread(tdata->thread, NULL);
+                    tdata->thread = NULL;
+                    SDL_DestroySemaphore(tdata->suspend_sem);
                 }
+                break;
             }
         }
         SDLTest_CommonEvent(state, &event, &done);
@@ -598,6 +636,7 @@ loop(void)
 {
     SDL_Event event;
     int i;
+    int active_windows = 0;
 
     /* Check for events */
     while (SDL_PollEvent(&event) && !done) {
@@ -605,6 +644,11 @@ loop(void)
     }
     if (!done) {
         for (i = 0; i < state->num_windows; ++i) {
+            if (state->windows[i] == NULL ||
+                (suspend_when_occluded && (SDL_GetWindowFlags(state->windows[i]) & SDL_WINDOW_OCCLUDED))) {
+                continue;
+            }
+            ++active_windows;
             render_window(i);
         }
     }
@@ -613,6 +657,11 @@ loop(void)
         emscripten_cancel_main_loop();
     }
 #endif
+
+    /* If all windows are occluded, throttle event polling to 15hz. */
+    if (!done && !active_windows) {
+        SDL_DelayNS(SDL_NS_PER_SECOND / 15);
+    }
 }
 
 int main(int argc, char *argv[])
@@ -649,6 +698,9 @@ int main(int argc, char *argv[])
             } else if (SDL_strcasecmp(argv[i], "--threaded") == 0) {
                 ++threaded;
                 consumed = 1;
+            } else if(SDL_strcasecmp(argv[i], "--suspend-when-occluded") == 0) {
+                suspend_when_occluded = SDL_TRUE;
+                consumed = 1;
             } else if (SDL_strcasecmp(argv[i], "--zdepth") == 0) {
                 i++;
                 if (!argv[i]) {
@@ -667,7 +719,7 @@ int main(int argc, char *argv[])
             }
         }
         if (consumed < 0) {
-            static const char *options[] = { "[--fsaa]", "[--accel]", "[--zdepth %d]", "[--threaded]", NULL };
+            static const char *options[] = { "[--fsaa]", "[--accel]", "[--zdepth %d]", "[--threaded]", "[--suspend-when-occluded]",NULL };
             SDLTest_CommonLogUsage(state, argv[0], options);
             quit(1);
         }
@@ -869,6 +921,8 @@ int main(int argc, char *argv[])
         /* Start a render thread for each window */
         for (i = 0; i < state->num_windows; ++i) {
             threads[i].index = i;
+            SDL_AtomicSet(&threads[i].suspended, 0);
+            threads[i].suspend_sem = SDL_CreateSemaphore(0);
             threads[i].thread = SDL_CreateThread(render_thread_fn, "RenderThread", &threads[i]);
         }
 
diff --git a/test/testsprite.c b/test/testsprite.c
index 68b3b7b2db6f..9afa6f0dac3c 100644
--- a/test/testsprite.c
+++ b/test/testsprite.c
@@ -42,6 +42,7 @@ static Uint64 next_fps_check;
 static Uint32 frames;
 static const int fps_check_delay = 5000;
 static int use_rendergeometry = 0;
+static SDL_bool suspend_when_occluded;
 
 /* Number of iterations to move sprites - used for visual tests. */
 /* -1: infinite random moves (default); >=0: enables N deterministic moves */
@@ -398,6 +399,7 @@ static void loop(void)
 {
     Uint64 now;
     int i;
+    int active_windows = 0;
     SDL_Event event;
 
     /* Check for events */
@@ -405,9 +407,11 @@ static void loop(void)
         SDLTest_CommonEvent(state, &event, &done);
     }
     for (i = 0; i < state->num_windows; ++i) {
-        if (state->windows[i] == NULL) {
+        if (state->windows[i] == NULL ||
+            (suspend_when_occluded && (SDL_GetWindowFlags(state->windows[i]) & SDL_WINDOW_OCCLUDED))) {
             continue;
         }
+        ++active_windows;
         MoveSprites(state->renderers[i], sprites[i]);
     }
 #ifdef __EMSCRIPTEN__
@@ -416,6 +420,11 @@ static void loop(void)
     }
 #endif
 
+    /* If all windows are occluded, throttle the event polling to 15hz. */
+    if (!done && !active_windows) {
+        SDL_DelayNS(SDL_NS_PER_SECOND / 15);
+    }
+
     frames++;
     now = SDL_GetTicks();
     if (now >= next_fps_check) {
@@ -485,6 +494,9 @@ int main(int argc, char *argv[])
             } else if (SDL_strcasecmp(argv[i], "--cyclealpha") == 0) {
                 cycle_alpha = SDL_TRUE;
                 consumed = 1;
+            } else if(SDL_strcasecmp(argv[i], "--suspend-when-occluded") == 0) {
+                suspend_when_occluded = SDL_TRUE;
+                consumed = 1;
             } else if (SDL_strcasecmp(argv[i], "--use-rendergeometry") == 0) {
                 if (argv[i + 1]) {
                     if (SDL_strcasecmp(argv[i + 1], "mode1") == 0) {
@@ -512,6 +524,7 @@ int main(int argc, char *argv[])
                 "[--blend none|blend|add|mod|mul|sub]",
                 "[--cyclecolor]",
                 "[--cyclealpha]",
+                "[--suspend-when-occluded]",
                 "[--iterations N]",
                 "[--use-rendergeometry mode1|mode2]",
                 "[num_sprites]",