SDL: testgles2: Add --threaded option to use a render thread per window

From 222f1a2693d1371409a71e8adf3c0c0d57f83020 Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Mon, 15 Aug 2022 22:51:15 -0500
Subject: [PATCH] testgles2: Add --threaded option to use a render thread per
 window

This is helpful for reproducing bugs like #6056
---
 test/testgles2.c | 151 +++++++++++++++++++++++++++++++++--------------
 1 file changed, 108 insertions(+), 43 deletions(-)

diff --git a/test/testgles2.c b/test/testgles2.c
index 4b9e5d250c3..6ef805b0b12 100644
--- a/test/testgles2.c
+++ b/test/testgles2.c
@@ -51,6 +51,13 @@ typedef struct shader_data
     GLuint color_buffer;
 } shader_data;
 
+typedef struct thread_data
+{
+    SDL_Thread *thread;
+    int done;
+    int index;
+} thread_data;
+
 static SDLTest_CommonState *state;
 static SDL_GLContext *context = NULL;
 static int depth = 16;
@@ -434,6 +441,7 @@ Render(unsigned int width, unsigned int height, shader_data* data)
     if(data->angle_z >= 360) data->angle_z -= 360;
     if(data->angle_z < 0) data->angle_z += 360;
 
+    GL_CHECK(ctx.glViewport(0, 0, width, height));
     GL_CHECK(ctx.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
     GL_CHECK(ctx.glDrawArrays(GL_TRIANGLES, 0, 36));
 }
@@ -441,56 +449,84 @@ Render(unsigned int width, unsigned int height, shader_data* data)
 int done;
 Uint32 frames;
 shader_data *datas;
+thread_data *threads;
+
+static void
+render_window(int index)
+{
+    int w, h, status;
+
+    if (!state->windows[index]) {
+        return;
+    }
+
+    status = SDL_GL_MakeCurrent(state->windows[index], context[index]);
+    if (status) {
+        SDL_Log("SDL_GL_MakeCurrent(): %s\n", SDL_GetError());
+        return;
+    }
+
+    SDL_GL_GetDrawableSize(state->windows[index], &w, &h);
+    Render(w, h, &datas[index]);
+    SDL_GL_SwapWindow(state->windows[index]);
+    ++frames;
+}
 
-void loop()
+static int SDLCALL
+render_thread_fn(void* render_ctx)
+{
+    thread_data *thread = render_ctx;
+
+    while (!done && !thread->done && state->windows[thread->index]) {
+        render_window(thread->index);
+    }
+
+    SDL_GL_MakeCurrent(state->windows[thread->index], NULL);
+    return 0;
+}
+
+static void
+loop_threaded()
 {
     SDL_Event event;
     int i;
-    int status;
 
-    /* Check for events */
-    ++frames;
-    while (SDL_PollEvent(&event) && !done) {
-        switch (event.type) {
-        case SDL_WINDOWEVENT:
-            switch (event.window.event) {
-                case SDL_WINDOWEVENT_SIZE_CHANGED:
-                    for (i = 0; i < state->num_windows; ++i) {
-                        if (event.window.windowID == SDL_GetWindowID(state->windows[i])) {
-                            int w, h;
-                            status = SDL_GL_MakeCurrent(state->windows[i], context[i]);
-                            if (status) {
-                                SDL_Log("SDL_GL_MakeCurrent(): %s\n", SDL_GetError());
-                                break;
-                            }
-                            /* Change view port to the new window dimensions */
-                            SDL_GL_GetDrawableSize(state->windows[i], &w, &h);
-                            ctx.glViewport(0, 0, w, h);
-                            state->window_w = event.window.data1;
-                            state->window_h = event.window.data2;
-                            /* Update window content */
-                            Render(event.window.data1, event.window.data2, &datas[i]);
-                            SDL_GL_SwapWindow(state->windows[i]);
-                            break;
+    /* Wait for events */
+    while (SDL_WaitEvent(&event) && !done) {
+        if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE) {
+            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;
                     }
-                    break;
+                }
             }
         }
         SDLTest_CommonEvent(state, &event, &done);
     }
+}
+
+static void
+loop()
+{
+    SDL_Event event;
+    int i;
+
+    /* Check for events */
+    while (SDL_PollEvent(&event) && !done) {
+        SDLTest_CommonEvent(state, &event, &done);
+    }
     if (!done) {
-      for (i = 0; i < state->num_windows; ++i) {
-          status = SDL_GL_MakeCurrent(state->windows[i], context[i]);
-          if (status) {
-              SDL_Log("SDL_GL_MakeCurrent(): %s\n", SDL_GetError());
-
-              /* Continue for next window */
-              continue;
-          }
-          Render(state->window_w, state->window_h, &datas[i]);
-          SDL_GL_SwapWindow(state->windows[i]);
-      }
+        for (i = 0; i < state->num_windows; ++i) {
+            render_window(i);
+        }
     }
 #ifdef __EMSCRIPTEN__
     else {
@@ -502,7 +538,7 @@ void loop()
 int
 main(int argc, char *argv[])
 {
-    int fsaa, accel;
+    int fsaa, accel, threaded;
     int value;
     int i;
     SDL_DisplayMode mode;
@@ -513,6 +549,7 @@ main(int argc, char *argv[])
     /* Initialize parameters */
     fsaa = 0;
     accel = 0;
+    threaded = 0;
 
     /* Initialize test framework */
     state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
@@ -530,6 +567,9 @@ main(int argc, char *argv[])
             } else if (SDL_strcasecmp(argv[i], "--accel") == 0) {
                 ++accel;
                 consumed = 1;
+            } else if (SDL_strcasecmp(argv[i], "--threaded") == 0) {
+                ++threaded;
+                consumed = 1;
             } else if (SDL_strcasecmp(argv[i], "--zdepth") == 0) {
                 i++;
                 if (!argv[i]) {
@@ -543,7 +583,7 @@ main(int argc, char *argv[])
             }
         }
         if (consumed < 0) {
-            static const char *options[] = { "[--fsaa]", "[--accel]", "[--zdepth %d]", NULL };
+            static const char *options[] = { "[--fsaa]", "[--accel]", "[--zdepth %d], [--threaded]", NULL };
             SDLTest_CommonLogUsage(state, argv[0], options);
             quit(1);
         }
@@ -551,7 +591,7 @@ main(int argc, char *argv[])
     }
 
     /* Set OpenGL parameters */
-    state->window_flags |= SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS;
+    state->window_flags |= SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
     state->gl_red_size = 5;
     state->gl_green_size = 5;
     state->gl_blue_size = 5;
@@ -603,6 +643,7 @@ main(int argc, char *argv[])
     }
 
     SDL_GetCurrentDisplayMode(0, &mode);
+    SDL_Log("Threaded  : %s\n", threaded ? "yes" : "no");
     SDL_Log("Screen bpp: %d\n", SDL_BITSPERPIXEL(mode.format));
     SDL_Log("\n");
     SDL_Log("Vendor     : %s\n", ctx.glGetString(GL_VENDOR));
@@ -724,6 +765,8 @@ main(int argc, char *argv[])
 
         GL_CHECK(ctx.glEnable(GL_CULL_FACE));
         GL_CHECK(ctx.glEnable(GL_DEPTH_TEST));
+
+        SDL_GL_MakeCurrent(state->windows[i], NULL);
     }
 
     /* Main render loop */
@@ -734,8 +777,30 @@ main(int argc, char *argv[])
 #ifdef __EMSCRIPTEN__
     emscripten_set_main_loop(loop, 0, 1);
 #else
-    while (!done) {
-        loop();
+    if (threaded) {
+        threads = (thread_data*)SDL_calloc(state->num_windows, sizeof(thread_data));
+
+        /* Start a render thread for each window */
+        for (i = 0; i < state->num_windows; ++i) {
+            threads[i].index = i;
+            threads[i].thread = SDL_CreateThread(render_thread_fn, "RenderThread", &threads[i]);
+        }
+
+        while (!done) {
+            loop_threaded();
+        }
+
+        /* Join the remaining render threads (if any) */
+        for (i = 0; i < state->num_windows; ++i) {
+            threads[i].done = 1;
+            if (threads[i].thread) {
+                SDL_WaitThread(threads[i].thread, NULL);
+            }
+        }
+    } else {
+        while (!done) {
+            loop();
+        }
     }
 #endif