sdl2-compat: render: Implement SDL_RENDER_BATCHING toggle.

From 3fb56be422ddb00a2e236f584721a247402ed472 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sat, 25 Nov 2023 10:34:18 -0500
Subject: [PATCH] render: Implement SDL_RENDER_BATCHING toggle.

This defaults to on for everything for now, since it'll make things faster
and not hobble apps and users that request a specific renderer.

If we hit something that _needs_ batching disabled, we'll add it to the
quirks table.

Also moved some other code around, to group the rendering code logically,
and move the Shaped Window code above it (which the render code references).
---
 src/sdl2_compat.c | 3325 +++++++++++++++++++++++----------------------
 src/sdl3_syms.h   |   27 +-
 2 files changed, 1736 insertions(+), 1616 deletions(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index bfbd9ec..d9596e7 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -337,6 +337,7 @@ typedef struct QuirkEntryType
 
 static QuirkEntryType quirks[] = {
     /* TODO: Add any quirks needed for various systems. */
+    /*{"my_game_name", "SDL_RENDER_BATCHING", "0"},*/
     {"", "", "0"} /* A dummy entry to keep compilers happy. */
 };
 
@@ -1893,44 +1894,6 @@ SDL_WarpMouseGlobal(int x, int y)
     return SDL3_WarpMouseGlobal((float)x, (float)y);
 }
 
-DECLSPEC void SDLCALL
-SDL_RenderGetViewport(SDL_Renderer *renderer, SDL_Rect *rect)
-{
-    SDL3_GetRenderViewport(renderer, rect);
-}
-
-DECLSPEC void SDLCALL
-SDL_RenderGetClipRect(SDL_Renderer *renderer, SDL_Rect *rect)
-{
-    SDL3_GetRenderClipRect(renderer, rect);
-}
-
-DECLSPEC void SDLCALL
-SDL_RenderGetScale(SDL_Renderer *renderer, float *scaleX, float *scaleY)
-{
-    SDL3_GetRenderScale(renderer, scaleX, scaleY);
-}
-
-DECLSPEC void SDLCALL
-SDL_RenderWindowToLogical(SDL_Renderer *renderer,
-                          int windowX, int windowY,
-                          float *logicalX, float *logicalY)
-{
-    SDL3_RenderCoordinatesFromWindow(renderer, (float)windowX, (float)windowY, logicalX, logicalY);
-}
-
-DECLSPEC void SDLCALL
-SDL_RenderLogicalToWindow(SDL_Renderer *renderer,
-                          float logicalX, float logicalY,
-                          int *windowX, int *windowY)
-{
-    float x, y;
-    SDL3_RenderCoordinatesToWindow(renderer, logicalX, logicalY, &x, &y);
-    if (windowX) *windowX = (int)x;
-    if (windowY) *windowY = (int)y;
-}
-
-
 /* The SDL3 version of SDL_RWops changed, so we need to convert when necessary. */
 
 struct SDL2_RWops
@@ -3373,6 +3336,214 @@ GestureProcessEvent(const SDL_Event *event3)
 }
 
 
+static SDL_Window *g_shaped_window = NULL;
+static SDL_WindowShapeMode g_shape_mode;
+static Uint8 *g_bitmap = NULL;
+static int g_bitmap_w = 0, g_bitmap_h = 0;
+static SDL_Surface *g_shape_surface = NULL;
+static SDL_Texture *g_shape_texture = NULL;
+
+static void shaped_window_cleanup(void)
+{
+    g_shaped_window = NULL;
+    SDL3_zero(g_shape_mode);
+    if (g_bitmap) {
+        SDL3_free(g_bitmap);
+        g_bitmap = NULL;
+    }
+    g_bitmap_w = 0;
+    g_bitmap_h = 0;
+
+    if (g_shape_surface) {
+        SDL3_DestroySurface(g_shape_surface);
+        g_shape_surface = NULL;
+    }
+
+    if (g_shape_texture) {
+        SDL3_DestroyTexture(g_shape_texture);
+        g_shape_texture = NULL;
+    }
+}
+
+/* REQUIRES that bitmap point to a w-by-h bitmap with ppb pixels-per-byte. */
+static void SDL_CalculateShapeBitmap(SDL_WindowShapeMode mode, SDL_Surface *shape, Uint8 *bitmap, Uint8 ppb)
+{
+    int x = 0;
+    int y = 0;
+    Uint8 r = 0, g = 0, b = 0, alpha = 0;
+    Uint8 *pixel = NULL;
+    Uint32 pixel_value = 0, mask_value = 0;
+    size_t bytes_per_scanline = (size_t)(shape->w + (ppb - 1)) / ppb;
+    Uint8 *bitmap_scanline;
+    SDL_Color key;
+
+    if (SDL_MUSTLOCK(shape)) {
+        SDL3_LockSurface(shape);
+    }
+
+    SDL3_memset(bitmap, 0, shape->h * bytes_per_scanline);
+
+    for (y = 0; y < shape->h; y++) {
+        bitmap_scanline = bitmap + y * bytes_per_scanline;
+        for (x = 0; x < shape->w; x++) {
+            alpha = 0;
+            pixel_value = 0;
+            pixel = (Uint8 *)(shape->pixels) + (y * shape->pitch) + (x * shape->format->BytesPerPixel);
+            switch (shape->format->BytesPerPixel) {
+            case (1):
+                pixel_value = *pixel;
+                break;
+            case (2):
+                pixel_value = *(Uint16 *)pixel;
+                break;
+            case (3):
+                pixel_value = *(Uint32 *)pixel & (~shape->format->Amask);
+                break;
+            case (4):
+                pixel_value = *(Uint32 *)pixel;
+                break;
+            }
+            SDL3_GetRGBA(pixel_value, shape->format, &r, &g, &b, &alpha);
+            switch (mode.mode) {
+            case (ShapeModeDefault):
+                mask_value = (alpha >= 1 ? 1 : 0);
+                break;
+            case (ShapeModeBinarizeAlpha):
+                mask_value = (alpha >= mode.parameters.binarizationCutoff ? 1 : 0);
+                break;
+            case (ShapeModeReverseBinarizeAlpha):
+                mask_value = (alpha <= mode.parameters.binarizationCutoff ? 1 : 0);
+                break;
+            case (ShapeModeColorKey):
+                key = mode.parameters.colorKey;
+                mask_value = ((key.r != r || key.g != g || key.b != b) ? 1 : 0);
+                break;
+            }
+            bitmap_scanline[x / ppb] |= mask_value << (x % ppb);
+        }
+    }
+
+    if (SDL_MUSTLOCK(shape)) {
+        SDL3_UnlockSurface(shape);
+    }
+}
+
+
+
+DECLSPEC SDL_Window * SDLCALL
+SDL_CreateShapedWindow(const char *title, unsigned int x, unsigned int y, unsigned int w, unsigned int h, Uint32 flags)
+{
+    SDL_Window *window;
+    int hidden = flags & SDL_WINDOW_HIDDEN;
+
+    if (g_shaped_window != NULL) {
+        SDL3_SetError("only 1 shaped window");
+        return NULL;
+    }
+
+    flags &= ~SDL2_WINDOW_SHOWN;
+    flags |= SDL_WINDOW_HIDDEN;
+    flags |= SDL_WINDOW_TRANSPARENT;
+
+    window = SDL3_CreateWindow(title, (int)w, (int)h, flags);
+    if (window) {
+        if (!SDL_WINDOWPOS_ISUNDEFINED(x) || !SDL_WINDOWPOS_ISUNDEFINED(y)) {
+            SDL3_SetWindowPosition(window, (int)x, (int)y);
+        }
+        if (!hidden) {
+            SDL3_ShowWindow(window);
+        }
+    }
+
+    shaped_window_cleanup();
+    g_shaped_window = window;
+
+    return window;
+}
+
+DECLSPEC SDL_bool SDLCALL
+SDL_IsShapedWindow(const SDL_Window *window)
+{
+    if (window == NULL) {
+        return SDL_FALSE;
+    }
+    if (window == g_shaped_window) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
+DECLSPEC int SDLCALL
+SDL_SetWindowShape(SDL_Window *window,SDL_Surface *shape, SDL_WindowShapeMode *shape_mode)
+{
+    if (window == NULL) {
+        return SDL_NONSHAPEABLE_WINDOW;
+    }
+
+    if (window != g_shaped_window) {
+        return SDL_NONSHAPEABLE_WINDOW;
+    }
+
+    if (shape == NULL) {
+        return SDL_INVALID_SHAPE_ARGUMENT;
+    }
+
+    if (shape_mode == NULL) {
+        return SDL_INVALID_SHAPE_ARGUMENT;
+    }
+
+    shaped_window_cleanup();
+    g_shaped_window = window;
+    g_shape_mode = *shape_mode;
+
+    g_bitmap_w = shape->w;
+    g_bitmap_h = shape->h;
+    g_bitmap = (Uint8 *) SDL3_malloc(shape->w * shape->h);
+    if (g_bitmap == NULL) {
+        shaped_window_cleanup();
+        g_shaped_window = window;
+        return SDL3_OutOfMemory();
+    }
+
+    SDL_CalculateShapeBitmap(*shape_mode, shape, g_bitmap, 1);
+
+    g_shape_surface = SDL3_CreateSurface(g_bitmap_w, g_bitmap_h, SDL_PIXELFORMAT_ABGR8888);
+    if (g_shape_surface) {
+        int x, y, i = 0;
+        Uint32 *ptr = (Uint32 *)g_shape_surface->pixels;
+        for (y = 0; y < g_bitmap_h; y++) {
+            for (x = 0; x < g_bitmap_w; x++) {
+                Uint8 val = g_bitmap[i++];
+                if (val == 0) {
+                    ptr[x] = 0;
+                } else {
+                    ptr[x] = 0xffffffff;
+                }
+            }
+            ptr = (Uint32 *)((Uint8 *)ptr + g_shape_surface->pitch);
+        }
+    }
+
+    return 0;
+}
+
+DECLSPEC int SDLCALL
+SDL_GetShapedWindowMode(SDL_Window *window, SDL_WindowShapeMode *shape_mode)
+{
+    if (window == NULL) {
+        return SDL_NONSHAPEABLE_WINDOW;
+    }
+    if (window != g_shaped_window) {
+        return SDL_NONSHAPEABLE_WINDOW;
+    }
+
+    if (shape_mode) {
+        *shape_mode = g_shape_mode;
+    }
+    return 0;
+}
+
+
 DECLSPEC int SDLCALL
 SDL_GetRenderDriverInfo(int idx, SDL_RendererInfo *info)
 {
@@ -3439,10 +3610,22 @@ SDL_GetRenderDriverInfo(int idx, SDL_RendererInfo *info)
     return 0;
 }
 
+#define RENDERER_BATCHING_PROP "sdl2-compat.renderer.batching"
+static int FlushRendererIfNotBatching(SDL_Renderer *renderer)
+{
+    const SDL_PropertiesID props = SDL3_GetRendererProperties(renderer);
+    if (!SDL3_GetBooleanProperty(props, RENDERER_BATCHING_PROP, SDL_FALSE)) {
+        return SDL3_RenderFlush(renderer);
+    }
+    return 0;
+}
+
+
 /* Second parameter changed from an index to a string in SDL3. */
 DECLSPEC SDL_Renderer *SDLCALL
 SDL_CreateRenderer(SDL_Window *window, int idx, Uint32 flags)
 {
+    SDL_PropertiesID props;
     SDL_Renderer *renderer = NULL;
     const char *name = NULL;
     int want_targettexture = flags & SDL2_RENDERER_TARGETTEXTURE;
@@ -3461,6 +3644,12 @@ SDL_CreateRenderer(SDL_Window *window, int idx, Uint32 flags)
         SDL_SetError("Couldn't find render driver with SDL_RENDERER_TARGETTEXTURE flags");
         return NULL;
     }
+
+    props = SDL3_GetRendererProperties(renderer);
+    if (props) {
+        SDL3_SetBooleanProperty(props, RENDERER_BATCHING_PROP, SDL2Compat_GetHintBoolean("SDL_RENDER_BATCHING", SDL_FALSE));
+    }
+
     return renderer;
 }
 
@@ -3481,13 +3670,53 @@ SDL_RenderTargetSupported(SDL_Renderer *renderer)
     return SDL_FALSE;
 }
 
+DECLSPEC void SDLCALL
+SDL_RenderGetViewport(SDL_Renderer *renderer, SDL_Rect *rect)
+{
+    SDL3_GetRenderViewport(renderer, rect);
+}
+
+DECLSPEC void SDLCALL
+SDL_RenderGetClipRect(SDL_Renderer *renderer, SDL_Rect *rect)
+{
+    SDL3_GetRenderClipRect(renderer, rect);
+}
+
+DECLSPEC void SDLCALL
+SDL_RenderGetScale(SDL_Renderer *renderer, float *scaleX, float *scaleY)
+{
+    SDL3_GetRenderScale(renderer, scaleX, scaleY);
+}
+
+DECLSPEC void SDLCALL
+SDL_RenderWindowToLogical(SDL_Renderer *renderer,
+                          int windowX, int windowY,
+                          float *logicalX, float *logicalY)
+{
+    SDL3_RenderCoordinatesFromWindow(renderer, (float)windowX, (float)windowY, logicalX, logicalY);
+}
+
+DECLSPEC void SDLCALL
+SDL_RenderLogicalToWindow(SDL_Renderer *renderer,
+                          float logicalX, float logicalY,
+                          int *windowX, int *windowY)
+{
+    float x, y;
+    SDL3_RenderCoordinatesToWindow(renderer, logicalX, logicalY, &x, &y);
+    if (windowX) *windowX = (int)x;
+    if (windowY) *windowY = (int)y;
+}
+
 DECLSPEC int SDLCALL
 SDL_RenderSetLogicalSize(SDL_Renderer *renderer, int w, int h)
 {
+    int retval;
     if (w == 0 && h == 0) {
-        return SDL3_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED, SDL_SCALEMODE_NEAREST);
+        retval = SDL3_SetRenderLogicalPresentation(renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED, SDL_SCALEMODE_NEAREST);
+    } else {
+        retval = SDL3_SetRenderLogicalPresentation(renderer, w, h, SDL_LOGICAL_PRESENTATION_LETTERBOX, SDL_SCALEMODE_LINEAR);
     }
-    return SDL3_SetRenderLogicalPresentation(renderer, w, h, SDL_LOGICAL_PRESENTATION_LETTERBOX, SDL_SCALEMODE_LINEAR);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
 DECLSPEC void SDLCALL
@@ -3502,11 +3731,11 @@ SDL_RenderSetIntegerScale(SDL_Renderer *renderer, SDL_bool enable)
     SDL_ScaleMode scale_mode;
     SDL_RendererLogicalPresentation mode;
     int w, h;
-    int ret;
+    int retval;
 
-    ret = SDL3_GetRenderLogicalPresentation(renderer, &w, &h, &mode, &scale_mode);
-    if (ret < 0) {
-        return ret;
+    retval = SDL3_GetRenderLogicalPresentation(renderer, &w, &h, &mode, &scale_mode);
+    if (retval < 0) {
+        return retval;
     }
 
     if (enable && mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) {
@@ -3518,9 +3747,11 @@ SDL_RenderSetIntegerScale(SDL_Renderer *renderer, SDL_bool enable)
     }
 
     if (enable) {
-        return SDL3_SetRenderLogicalPresentation(renderer, w, h, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE, scale_mode);
+        retval = SDL3_SetRenderLogicalPresentation(renderer, w, h, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE, scale_mode);
+    } else {
+        retval = SDL3_SetRenderLogicalPresentation(renderer, w, h, SDL_LOGICAL_PRESENTATION_DISABLED, scale_mode);
     }
-    return SDL3_SetRenderLogicalPresentation(renderer, w, h, SDL_LOGICAL_PRESENTATION_DISABLED, scale_mode);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
 DECLSPEC SDL_bool SDLCALL
@@ -3535,1642 +3766,1793 @@ SDL_RenderGetIntegerScale(SDL_Renderer *renderer)
     return SDL_FALSE;
 }
 
-
 DECLSPEC int SDLCALL
-SDL_LockMutex(SDL_Mutex *a)
+SDL_RenderSetViewport(SDL_Renderer *renderer, const SDL_Rect *rect)
 {
-    SDL3_LockMutex(a);
-    return 0;
+    const int retval = SDL3_SetRenderViewport(renderer, rect);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
 DECLSPEC int SDLCALL
-SDL_UnlockMutex(SDL_Mutex *a)
+SDL_RenderSetClipRect(SDL_Renderer *renderer, const SDL_Rect *rect)
 {
-    SDL3_UnlockMutex(a);
-    return 0;
+    const int retval = SDL3_SetRenderClipRect(renderer, rect);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
+DECLSPEC int SDLCALL
+SDL_RenderClear(SDL_Renderer *renderer)
+{
+    const int retval = SDL3_RenderClear(renderer);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
+}
 
 DECLSPEC int SDLCALL
-SDL_AudioInit(const char *driver_name)
+SDL_RenderDrawPointF(SDL_Renderer *renderer, float x, float y)
 {
-    if (driver_name) {
-        SDL3_SetHint("SDL_AUDIO_DRIVER", driver_name);
-    }
-    return SDL3_InitSubSystem(SDL_INIT_AUDIO);
+    int retval;
+    SDL_FPoint fpoint;
+    fpoint.x = x;
+    fpoint.y = y;
+    retval = SDL3_RenderPoints(renderer, &fpoint, 1);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
-DECLSPEC void SDLCALL
-SDL_AudioQuit(void)
+DECLSPEC int SDLCALL
+SDL_RenderDrawPoint(SDL_Renderer *renderer, int x, int y)
 {
-    SDL3_QuitSubSystem(SDL_INIT_AUDIO);
+    return SDL_RenderDrawPointF(renderer, (float) x, (float) y);
 }
 
 DECLSPEC int SDLCALL
-SDL_VideoInit(const char *driver_name)
+SDL_RenderDrawPoints(SDL_Renderer *renderer,
+                     const SDL_Point *points, int count)
 {
-    int ret;
-    if (driver_name) {
-        SDL3_SetHint("SDL_VIDEO_DRIVER", driver_name);
+    SDL_FPoint *fpoints;
+    int i;
+    int retval;
+
+    if (points == NULL) {
+        return SDL3_InvalidParamError("points");
     }
 
-    ret = SDL3_InitSubSystem(SDL_INIT_VIDEO);
+    fpoints = (SDL_FPoint *) SDL3_malloc(sizeof (SDL_FPoint) * count);
+    if (fpoints == NULL) {
+        return SDL3_OutOfMemory();
+    }
 
-    /* default SDL2 GL attributes */
-    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 3);
-    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 3);
-    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 2);
-    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
+    for (i = 0; i < count; ++i) {
+        fpoints[i].x = (float)points[i].x;
+        fpoints[i].y = (float)points[i].y;
+    }
 
-    return ret;
+    retval = SDL3_RenderPoints(renderer, fpoints, count);
+
+    SDL3_free(fpoints);
+
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
-DECLSPEC void SDLCALL
-SDL_VideoQuit(void)
+DECLSPEC int SDLCALL
+SDL_RenderDrawPointsF(SDL_Renderer *renderer, const SDL_FPoint *points, int count)
 {
-    SDL_QuitSubSystem(SDL_INIT_VIDEO);
+    const int retval = SDL3_RenderPoints(renderer, points, count);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
 DECLSPEC int SDLCALL
-SDL_Init(Uint32 flags)
+SDL_RenderDrawLineF(SDL_Renderer *renderer, float x1, float y1, float x2, float y2)
 {
-    int ret;
+    int retval;
+    SDL_FPoint points[2];
+    points[0].x = (float)x1;
+    points[0].y = (float)y1;
+    points[1].x = (float)x2;
+    points[1].y = (float)y2;
+    retval = SDL3_RenderLines(renderer, points, 2);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
+}
 
-    ret = SDL3_Init(flags);
-    if (flags & SDL_INIT_VIDEO) {
-        /* default SDL2 GL attributes */
-        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 3);
-        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 3);
-        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 2);
-        SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
+DECLSPEC int SDLCALL
+SDL_RenderDrawLine(SDL_Renderer *renderer, int x1, int y1, int x2, int y2)
+{
+    return SDL_RenderDrawLineF(renderer, (float) x1, (float) y1, (float) x2, (float) y2);
+}
+
+DECLSPEC int SDLCALL
+SDL_RenderDrawLines(SDL_Renderer *renderer, const SDL_Point *points, int count)
+{
+    SDL_FPoint *fpoints;
+    int i;
+    int retval;
+
+    if (points == NULL) {
+        return SDL3_InvalidParamError("points");
+    }
+    if (count < 2) {
+        return 0;
     }
 
-    return ret;
+    fpoints = (SDL_FPoint *) SDL3_malloc(sizeof (SDL_FPoint) * count);
+    if (fpoints == NULL) {
+        return SDL3_OutOfMemory();
+    }
+
+    for (i = 0; i < count; ++i) {
+        fpoints[i].x = (float)points[i].x;
+        fpoints[i].y = (float)points[i].y;
+    }
+
+    retval = SDL3_RenderLines(renderer, fpoints, count);
+
+    SDL3_free(fpoints);
+
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
 DECLSPEC int SDLCALL
-SDL_InitSubSystem(Uint32 flags)
+SDL_RenderDrawLinesF(SDL_Renderer *renderer, const SDL_FPoint *points, int count)
 {
-    int ret;
+    const int retval = SDL3_RenderLines(renderer, points, count);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
+}
 
-    ret = SDL3_InitSubSystem(flags);
-    if (flags & SDL_INIT_VIDEO) {
-        /* default SDL2 GL attributes */
-        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 3);
-        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 3);
-        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 2);
-        SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
+DECLSPEC int SDLCALL
+SDL_RenderDrawRect(SDL_Renderer *renderer, const SDL_Rect *rect)
+{
+    int retval;
+    SDL_FRect frect;
+    SDL_FRect *prect = NULL;
+
+    if (rect) {
+        frect.x = (float)rect->x;
+        frect.y = (float)rect->y;
+        frect.w = (float)rect->w;
+        frect.h = (float)rect->h;
+        prect = &frect;
     }
-    return ret;
+
+    retval = SDL3_RenderRect(renderer, prect);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
-DECLSPEC void SDLCALL
-SDL_Quit(void)
+DECLSPEC int SDLCALL
+SDL_RenderDrawRects(SDL_Renderer *renderer, const SDL_Rect *rects, int count)
 {
-    if (SDL3_WasInit(SDL_INIT_VIDEO)) {
-        GestureQuit();
-    }
+    int i;
 
-    if (joystick_list) {
-        SDL3_free(joystick_list);
-        joystick_list = NULL;
+    if (rects == NULL) {
+        return SDL3_InvalidParamError("rects");
     }
-    num_joysticks = 0;
-
-    if (sensor_list) {
-        SDL3_free(sensor_list);
-        sensor_list = NULL;
+    if (count < 1) {
+        return 0;
     }
-    num_sensors = 0;
 
-    if (gamepad_button_swap_list) {
-        SDL3_free(gamepad_button_swap_list);
-        gamepad_button_swap_list = NULL;
+    for (i = 0; i < count; ++i) {
+        if (SDL_RenderDrawRect(renderer, &rects[i]) < 0) {
+            return -1;
+        }
     }
-    num_gamepad_button_swap_list = 0;
-
-    SDL3_Quit();
+    return 0;
 }
 
-DECLSPEC void SDLCALL
-SDL_QuitSubSystem(Uint32 flags)
+DECLSPEC int SDLCALL
+SDL_RenderDrawRectF(SDL_Renderer *renderer, const SDL_FRect *rect)
 {
-    if (flags & SDL_INIT_VIDEO) {
-        GestureQuit();
-    }
-    SDL3_QuitSubSystem(flags);
+    const int retval = SDL3_RenderRect(renderer, rect);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
 DECLSPEC int SDLCALL
-SDL_GL_GetSwapInterval(void)
+SDL_RenderDrawRectsF(SDL_Renderer *renderer, const SDL_FRect *rects, int count)
 {
-    int val = 0;
-    SDL3_GL_GetSwapInterval(&val);
-    return val;
+    const int retval = SDL3_RenderRects(renderer, rects, count);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
-static Uint16 GetDefaultSamplesFromFreq(int freq)
+DECLSPEC int SDLCALL
+SDL_RenderFillRect(SDL_Renderer *renderer, const SDL_Rect *rect)
 {
-    /* Pick a default of ~46 ms at desired frequency */
-    /* !!! FIXME: remove this when the non-Po2 resampling is in. */
-    const Uint16 max_sample = (freq / 1000) * 46;
-    Uint16 current_sample = 1;
-    while (current_sample < max_sample) {
-        current_sample *= 2;
+    int retval;
+    SDL_FRect frect;
+    if (rect) {
+        frect.x = (float)rect->x;
+        frect.y = (float)rect->y;
+        frect.w = (float)rect->w;
+        frect.h = (float)rect->h;
+        return SDL3_RenderFillRect(renderer, &frect);
     }
-    return current_sample;
+    retval = SDL3_RenderFillRect(renderer, NULL);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
-static int GetNumAudioDevices(int iscapture)
+DECLSPEC int SDLCALL
+SDL_RenderFillRects(SDL_Renderer *renderer, const SDL_Rect *rects, int count)
 {
-    AudioDeviceList newlist;
-    AudioDeviceList *list = iscapture ? &AudioSDL3CaptureDevices : &AudioSDL3OutputDevices;
-    SDL_AudioDeviceID *devices;
-    int num_devices;
+    SDL_FRect *frects;
     int i;
+    int retval;
 
-    /* SDL_GetNumAudioDevices triggers a device redetect in SDL2, so we'll just build our list from here. */
-    devices = iscapture ? SDL3_GetAudioCaptureDevices(&num_devices) : SDL3_GetAudioOutputDevices(&num_devices);
-    if (!devices) {
-        return list->num_devices;  /* just return the existing one for now. Oh well. */
+    if (rects == NULL) {
+        return SDL3_InvalidParamError("rects");
+    }
+    if (count < 1) {
+        return 0;
     }
 
-    SDL3_zero(newlist);
-    if (num_devices > 0) {
-        newlist.num_devices = num_devices;
-        newlist.devices = (AudioDeviceInfo *) SDL3_malloc(sizeof (AudioDeviceInfo) * num_devices);
-        if (!newlist.devices) {
-            SDL3_free(devices);
-            return list->num_devices;  /* just return the existing one for now. Oh well. */
-        }
-
-        for (i = 0; i < num_devices; i++) {
-            char *newname = SDL3_GetAudioDeviceName(devices[i]);
-            char *fullname = NULL;
-            if (newname == NULL) {
-                /* ugh, whatever, just make up a name. */
-                newname = SDL3_strdup("Unidentified device");
-            }
-
-            /* Device names must be unique in SDL2, as that's how we open them.
-               SDL2 took serious pains to try to add numbers to the end of duplicate device names ("SoundBlaster Pro" and then "SoundBlaster Pro (2)"),
-               but here we're just putting the actual SDL3 instance id at the end of everything. Good enough. I hope. */
-            if (!newname || (SDL3_asprintf(&fullname, "%s (id=%u)", newname, (unsigned int) devices[i]) < 0)) {
-                /* we're in real trouble now.  :/  */
-                int j;
-                for (j = 0; j < i; j++) {
-                    SDL3_free(newlist.devices[i].name);
-                }
-                SDL3_free(devices);
-                SDL3_free(newname);
-                SDL3_free(fullname);
-                SDL3_OutOfMemory();
-                return list->num_devices;  /* just return the existing one for now. Oh well. */
-            }
-
-            SDL3_free(newname);
-            newlist.devices[i].devid = devices[i];
-            newlist.devices[i].name = fullname;
-        }
+    frects = (SDL_FRect *) SDL3_malloc(sizeof (SDL_FRect) * count);
+    if (frects == NULL) {
+        return SDL3_OutOfMemory();
     }
 
-    for (i = 0; i < list->num_devices; i++) {
-        SDL3_free(list->devices[i].name);
+    for (i = 0; i < count; ++i) {
+        frects[i].x = (float)rects[i].x;
+        frects[i].y = (float)rects[i].y;
+        frects[i].w = (float)rects[i].w;
+        frects[i].h = (float)rects[i].h;
     }
-    SDL3_free(list->devices);
 
-    SDL3_memcpy(list, &newlist, sizeof (AudioDeviceList));
-    return num_devices;
+    retval = SDL3_RenderFillRects(renderer, frects, count);
+
+    SDL3_free(frects);
+
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
 DECLSPEC int SDLCALL
-SDL_GetNumAudioDevices(int iscapture)
+SDL_RenderFillRectF(SDL_Renderer *renderer, const SDL_FRect *rect)
 {
-    int retval;
-
-    if (!SDL3_GetCurrentAudioDriver()) {
-        return SDL3_SetError("Audio subsystem is not initialized");
-    }
-
-    SDL3_LockMutex(AudioDeviceLock);
-    retval = GetNumAudioDevices(iscapture);
-    SDL3_UnlockMutex(AudioDeviceLock);
-    return retval;
+    const int retval = SDL3_RenderFillRect(renderer, rect);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
-DECLSPEC const char * SDLCALL
-SDL_GetAudioDeviceName(int idx, int iscapture)
+DECLSPEC int SDLCALL
+SDL_RenderFillRectsF(SDL_Renderer *renderer, const SDL_FRect *rects, int count)
 {
-    const char *retval = NULL;
-    AudioDeviceList *list;
+    const int retval = SDL3_RenderFillRects(renderer, rects, count);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
+}
 
-    if (!SDL3_GetCurrentAudioDriver()) {
-        SDL3_SetError("Audio subsystem is not initialized");
-        return NULL;
+DECLSPEC int SDLCALL
+SDL_RenderCopy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect)
+{
+    int retval;
+    SDL_FRect srcfrect;
+    SDL_FRect *psrcfrect = NULL;
+    SDL_FRect dstfrect;
+    SDL_FRect *pdstfrect = NULL;
+    if (srcrect) {
+        srcfrect.x = (float)srcrect->x;
+        srcfrect.y = (float)srcrect->y;
+        srcfrect.w = (float)srcrect->w;
+        srcfrect.h = (float)srcrect->h;
+        psrcfrect = &srcfrect;
     }
-
-    SDL3_LockMutex(AudioDeviceLock);
-    list = iscapture ? &AudioSDL3CaptureDevices : &AudioSDL3OutputDevices;
-    if ((idx < 0) || (idx >= list->num_devices)) {
-        SDL3_InvalidParamError("index");
-    } else {
-        retval = list->devices[idx].name;
+    if (dstrect) {
+        dstfrect.x = (float)dstrect->x;
+        dstfrect.y = (float)dstrect->y;
+        dstfrect.w = (float)dstrect->w;
+        dstfrect.h = (float)dstrect->h;
+        pdstfrect = &dstfrect;
     }
-    SDL3_UnlockMutex(AudioDeviceLock);
+    retval = SDL3_RenderTexture(renderer, texture, psrcfrect, pdstfrect);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
+}
 
-    return retval;
+DECLSPEC int SDLCALL
+SDL_RenderCopyF(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_FRect *dstrect)
+{
+    int retval;
+    SDL_FRect srcfrect;
+    SDL_FRect *psrcfrect = NULL;
+    if (srcrect) {
+        srcfrect.x = (float)srcrect->x;
+        srcfrect.y = (float)srcrect->y;
+        srcfrect.w = (float)srcrect->w;
+        srcfrect.h = (float)srcrect->h;
+        psrcfrect = &srcfrect;
+    }
+    retval = SDL3_RenderTexture(renderer, texture, psrcfrect, dstrect);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
 DECLSPEC int SDLCALL
-SDL_GetAudioDeviceSpec(int idx, int iscapture, SDL2_AudioSpec *spec2)
+SDL_RenderCopyEx(SDL_Renderer *renderer, SDL_Texture *texture,
+                 const SDL_Rect *srcrect, const SDL_Rect *dstrect,
+                 const double angle, const SDL_Point *center, const SDL_RendererFlip flip)
 {
-    AudioDeviceList *list;
-    SDL_AudioSpec spec3;
-    int retval = -1;
+    int retval;
+    SDL_FRect srcfrect;
+    SDL_FRect *psrcfrect = NULL;
+    SDL_FRect dstfrect;
+    SDL_FRect *pdstfrect = NULL;
+    SDL_FPoint fcenter;
+    SDL_FPoint *pfcenter = NULL;
 
-    if (spec2 == NULL) {
-        return SDL3_InvalidParamError("spec");
-    } else if (!SDL3_GetCurrentAudioDriver()) {
-        return SDL3_SetError("Audio subsystem is not initialized");
+    if (srcrect) {
+        srcfrect.x = (float)srcrect->x;
+        srcfrect.y = (float)srcrect->y;
+        srcfrect.w = (float)srcrect->w;
+        srcfrect.h = (float)srcrect->h;
+        psrcfrect = &srcfrect;
     }
 
-    SDL3_LockMutex(AudioDeviceLock);
-    list = iscapture ? &AudioSDL3CaptureDevices : &AudioSDL3OutputDevices;
-    if ((idx < 0) || (idx >= list->num_devices)) {
-        SDL3_InvalidParamError("index");
-    } else {
-        retval = SDL3_GetAudioDeviceFormat(list->devices[idx].devid, &spec3, NULL);
+    if (dstrect) {
+        dstfrect.x = (float)dstrect->x;
+        dstfrect.y = (float)dstrect->y;
+        dstfrect.w = (float)dstrect->w;
+        dstfrect.h = (float)dstrect->h;
+        pdstfrect = &dstfrect;
     }
-    SDL3_UnlockMutex(AudioDeviceLock);
 
-    if (retval == 0) {
-        SDL3_zerop(spec2);
-        spec2->format = spec3.format;
-        spec2->channels = spec3.channels;
-        spec2->freq = spec3.freq;
+    if (center) {
+        fcenter.x = (float)center->x;
+        fcenter.y = (float)center->y;
+        pfcenter = &fcenter;
     }
 
-    return retval;
+    retval = SDL3_RenderTextureRotated(renderer, texture, psrcfrect, pdstfrect, angle, pfcenter, flip);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
 DECLSPEC int SDLCALL
-SDL_GetDefaultAudioInfo(char **name, SDL2_AudioSpec *spec2, int iscapture)
+SDL_RenderCopyExF(SDL_Renderer *renderer, SDL_Texture *texture,
+                  const SDL_Rect *srcrect, const SDL_FRect *dstrect,
+                  const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
 {
-    SDL_AudioSpec spec3;
     int retval;
+    SDL_FRect srcfrect;
+    SDL_FRect *psrcfrect = NULL;
 
-    if (spec2 == NULL) {
-        return SDL3_InvalidParamError("spec");
-    } else if (!SDL3_GetCurrentAudioDriver()) {
-        return SDL3_SetError("Audio subsystem is not initialized");
+    if (srcrect) {
+        srcfrect.x = (float)srcrect->x;
+        srcfrect.y = (float)srcrect->y;
+        srcfrect.w = (float)srcrect->w;
+        srcfrect.h = (float)srcrect->h;
+        psrcfrect = &srcfrect;
     }
 
-    retval = SDL3_GetAudioDeviceFormat(iscapture ? SDL_AUDIO_DEVICE_DEFAULT_CAPTURE : SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec3, NULL);
-    if (retval == 0) {
-        if (name) {
-            *name = SDL3_strdup("System default");  /* the default device can change to different physical hardware on-the-fly in SDL3, so we don't provide a name for it. */
-            if (*name == NULL) {
-                return SDL3_OutOfMemory();
-            }
-        }
-
-        SDL3_zerop(spec2);
-        spec2->format = spec3.format;
-        spec2->channels = spec3.channels;
-        spec2->freq = spec3.freq;
-    }
+    retval = SDL3_RenderTextureRotated(renderer, texture, psrcfrect, dstrect, angle, center, flip);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
+}
 
-    return retval;
+DECLSPEC int SDLCALL
+SDL_RenderGeometry(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Vertex *vertices, int num_vertices, const int *indices, int num_indices)
+{
+    const int retval = SDL3_RenderGeometry(renderer, texture, vertices, num_vertices, indices, num_indices);
+    return retval < 0 ? retval : FlushRendererIfNotBatching(renderer);
 }
 
-static SDL_AudioFo

(Patch may be truncated, please check the link at the top of this post.)