SDL: render: Updates to format-string versions of SDL_RenderDebugText.

From 4d4a2786bbdbb698d2a10f70823a73dcec4654a8 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 18 Dec 2024 02:05:53 -0500
Subject: [PATCH] render: Updates to format-string versions of
 SDL_RenderDebugText.

- Removes SDL_RenderDebugTextV
- Changes SDL_RenderDebugTextF to SDL_RenderDebugTextFormat and tweaks it to
  work in a world without SDL_RenderDebugTextV.
- Tweaked rendering position of formatted text in the example program.
---
 examples/renderer/18-debug-text/debug-text.c |  4 +-
 include/SDL3/SDL_render.h                    | 43 +++---------------
 src/dynapi/SDL_dynapi.c                      | 22 +++++++--
 src/dynapi/SDL_dynapi.sym                    |  3 +-
 src/dynapi/SDL_dynapi_overrides.h            |  3 +-
 src/dynapi/SDL_dynapi_procs.h                |  3 +-
 src/render/SDL_render.c                      | 47 +++++++-------------
 7 files changed, 46 insertions(+), 79 deletions(-)

diff --git a/examples/renderer/18-debug-text/debug-text.c b/examples/renderer/18-debug-text/debug-text.c
index 53ef86ea976ae..62005e6bdfcff 100644
--- a/examples/renderer/18-debug-text/debug-text.c
+++ b/examples/renderer/18-debug-text/debug-text.c
@@ -46,6 +46,8 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
 /* This function runs once per frame, and is the heart of the program. */
 SDL_AppResult SDL_AppIterate(void *appstate)
 {
+    const int charsize = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE;
+
     /* as you can see from this, rendering draws over whatever was drawn before it. */
     SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);  /* black, full alpha */
     SDL_RenderClear(renderer);  /* start with a blank canvas. */
@@ -63,7 +65,7 @@ SDL_AppResult SDL_AppIterate(void *appstate)
     SDL_SetRenderScale(renderer, 1.0f, 1.0f);
     SDL_RenderDebugText(renderer, 64, 350, "This only does ASCII chars. So this laughing emoji won't draw: 🤣");
 
-    SDL_RenderDebugTextF(renderer, 0, 0, "This program has been running for %" SDL_PRIu64 " seconds.", SDL_GetTicks() / 1000);
+    SDL_RenderDebugTextFormat(renderer, (float) ((WINDOW_WIDTH - (charsize * 46)) / 2), 400, "(This program has been running for %" SDL_PRIu64 " seconds.)", SDL_GetTicks() / 1000);
 
     SDL_RenderPresent(renderer);  /* put it all on the screen! */
 
diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 0aabb8f33e9cc..d3a816f5a201c 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -2530,8 +2530,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderVSync(SDL_Renderer *renderer, int
  *
  * \since This function is available since SDL 3.1.6.
  *
- * \sa SDL_RenderDebugTextF
- * \sa SDL_RenderDebugTextV
+ * \sa SDL_RenderDebugTextFormat
  * \sa SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *str);
@@ -2539,9 +2538,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, flo
 /**
  * Draw debug text to an SDL_Renderer.
  *
- * This function will render a printf() style format string to a renderer. Note
- * that this is a convinence function for debugging, with severe limitations,
- * and is not intended to be used for production apps and games.
+ * This function will render a printf()-style format string to a renderer.
+ * Note that this is a convinence function for debugging, with severe
+ * limitations, and is not intended to be used for production apps and games.
  *
  * For the full list of limitations and other useful information,
  * see SDL_RenderDebugText.
@@ -2550,7 +2549,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, flo
  * \param x the x coordinate where the top-left corner of the text will draw.
  * \param y the y coordinate where the top-left corner of the text will draw.
  * \param fmt the format string to draw.
- * \param ... format arguments
+ * \param ... additional parameters matching % tokens in the `fmt` string, if
+ *            any.
  * \returns true on success or false on failure; call SDL_GetError() for more
  *          information.
  *
@@ -2559,38 +2559,9 @@ extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, flo
  * \since This function is available since SDL 3.1.7.
  *
  * \sa SDL_RenderDebugText
- * \sa SDL_RenderDebugTextV
  * \sa SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE
  */
-extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugTextF(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(4);
-
-/**
- * Draw debug text to an SDL_Renderer.
- *
- * This function will render a printf() style format string to a renderer. Note
- * that this is a convinence function for debugging, with severe limitations,
- * and is not intended to be used for production apps and games.
- *
- * For the full list of limitations and other useful information,
- * see SDL_RenderDebugText.
- *
- * \param renderer the renderer which should draw the text.
- * \param x the x coordinate where the top-left corner of the text will draw.
- * \param y the y coordinate where the top-left corner of the text will draw.
- * \param fmt the format string to draw.
- * \param ap a variable argument lists representing the format arguments
- * \returns true on success or false on failure; call SDL_GetError() for more
- *          information.
- *
- * \threadsafety This function should only be called on the main thread.
- *
- * \since This function is available since SDL 3.1.7.
- *
- * \sa SDL_RenderDebugText
- * \sa SDL_RenderDebugTextF
- * \sa SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE
- */
-extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugTextV(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(4);
+extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugTextFormat(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(4);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
diff --git a/src/dynapi/SDL_dynapi.c b/src/dynapi/SDL_dynapi.c
index 3229b5014c2ef..fa6872e3df26e 100644
--- a/src/dynapi/SDL_dynapi.c
+++ b/src/dynapi/SDL_dynapi.c
@@ -149,15 +149,29 @@ static void SDL_InitDynamicAPI(void);
         va_end(ap);                                                                                                                       \
         return result;                                                                                                                    \
     }                                                                                                                                     \
-    _static bool SDLCALL SDL_RenderDebugTextF##name(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \
+    _static bool SDLCALL SDL_RenderDebugTextFormat##name(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \
     {                                                                                                                                     \
-        bool result;                                                                                                                      \
+        char buf[128], *str = buf;                                                                                                        \
+        int result;                                                                                                                       \
         va_list ap;                                                                                                                       \
         initcall;                                                                                                                         \
         va_start(ap, fmt);                                                                                                                \
-        result = jump_table.SDL_RenderDebugTextV(renderer, x, y, fmt, ap);                                                                \
+        result = jump_table.SDL_vsnprintf(buf, sizeof(buf), fmt, ap);                                                                     \
         va_end(ap);                                                                                                                       \
-        return result;                                                                                                                    \
+        if (result >= 0 && (size_t)result >= sizeof(buf)) {                                                                               \
+            str = NULL;                                                                                                                   \
+            va_start(ap, fmt);                                                                                                            \
+            result = jump_table.SDL_vasprintf(&str, fmt, ap);                                                                             \
+            va_end(ap);                                                                                                                   \
+        }                                                                                                                                 \
+        bool retval = false;                                                                                                              \
+        if (result >= 0) {                                                                                                                \
+            retval = jump_table.SDL_RenderDebugTextFormat(renderer, x, y, "%s", str);                                                     \
+        }                                                                                                                                 \
+        if (str != buf) {                                                                                                                 \
+            jump_table.SDL_free(str);                                                                                                     \
+        }                                                                                                                                 \
+        return retval;                                                                                                                    \
     }                                                                                                                                     \
     _static void SDLCALL SDL_Log##name(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)                                                     \
     {                                                                                                                                     \
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 7d4ee58d22ab3..f3fe1c33c2ba7 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1207,8 +1207,7 @@ SDL3_0.0.0 {
     SDL_SetGPUAllowedFramesInFlight;
     SDL_RenderTextureAffine;
     SDL_WaitAndAcquireGPUSwapchainTexture;
-    SDL_RenderDebugTextF;
-    SDL_RenderDebugTextV;
+    SDL_RenderDebugTextFormat;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 6031adab18bf5..12e77270ca11d 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1232,5 +1232,4 @@
 #define SDL_SetGPUAllowedFramesInFlight SDL_SetGPUAllowedFramesInFlight_REAL
 #define SDL_RenderTextureAffine SDL_RenderTextureAffine_REAL
 #define SDL_WaitAndAcquireGPUSwapchainTexture SDL_WaitAndAcquireGPUSwapchainTexture_REAL
-#define SDL_RenderDebugTextF SDL_RenderDebugTextF_REAL
-#define SDL_RenderDebugTextV SDL_RenderDebugTextV_REAL
+#define SDL_RenderDebugTextFormat SDL_RenderDebugTextFormat_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index cedc79f58c9ae..189158744e795 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1239,6 +1239,5 @@ SDL_DYNAPI_PROC(bool,SDL_SetGPUAllowedFramesInFlight,(SDL_GPUDevice *a,Uint32 b)
 SDL_DYNAPI_PROC(bool,SDL_RenderTextureAffine,(SDL_Renderer *a,SDL_Texture *b,const SDL_FRect *c,const SDL_FPoint *d,const SDL_FPoint *e,const SDL_FPoint *f),(a,b,c,d,e,f),return)
 SDL_DYNAPI_PROC(bool,SDL_WaitAndAcquireGPUSwapchainTexture,(SDL_GPUCommandBuffer *a,SDL_Window *b,SDL_GPUTexture **c,Uint32 *d,Uint32 *e),(a,b,c,d,e),return)
 #ifndef SDL_DYNAPI_PROC_NO_VARARGS
-SDL_DYNAPI_PROC(bool,SDL_RenderDebugTextF,(SDL_Renderer *a,float b,float c,SDL_PRINTF_FORMAT_STRING const char *d,...),(a,b,c,d),return)
+SDL_DYNAPI_PROC(bool,SDL_RenderDebugTextFormat,(SDL_Renderer *a,float b,float c,SDL_PRINTF_FORMAT_STRING const char *d,...),(a,b,c,d),return)
 #endif
-SDL_DYNAPI_PROC(bool,SDL_RenderDebugTextV,(SDL_Renderer *a,float b,float c,SDL_PRINTF_FORMAT_STRING const char *d,va_list e),(a,b,c,d,e),return)
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index 241916d44dbc7..1138464cad22a 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -5590,45 +5590,28 @@ bool SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *s
     return result;
 }
 
-bool SDL_RenderDebugTextF(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
+bool SDL_RenderDebugTextFormat(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
 {
     va_list ap;
-
     va_start(ap, fmt);
-    bool result = SDL_RenderDebugTextV(renderer, x, y, fmt, ap);
-    va_end(ap);
-
-    return result;
-}
-
-bool SDL_RenderDebugTextV(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
-{
-    // Probably for the best to check this here, so we don't do a bunch of string formatting before realizing the renderer isn't even valid...
-    CHECK_RENDERER_MAGIC(renderer, false);
 
-    va_list apc;
-    va_copy(apc, ap); // vsnprintf mangles ap, so copy it so it can be used again later
-    int len = SDL_vsnprintf(NULL, 0, fmt, apc);
-    va_end(apc);
-
-    if (len < 0) {
-        return SDL_SetError("Failed to format debug text");
+    // fast path to avoid unnecessary allocation and copy. If you're going through the dynapi, there's a good chance
+    // you _always_ hit this path, since it probably had to process varargs before calling into the jumptable.
+    if (SDL_strcmp(fmt, "%s") == 0) {
+        const char *str = va_arg(ap, const char *);
+        va_end(ap);
+        return SDL_RenderDebugText(renderer, x, y, str);
     }
 
-    char *buf = SDL_malloc(len + 1);
-    if (buf == NULL) {
-        return SDL_OutOfMemory();
-    }
+    char *str = NULL;
+    const int rc = SDL_vasprintf(&str, fmt, ap);
+    va_end(ap);
 
-    len = SDL_vsnprintf(buf, len + 1, fmt, ap);
-    if (len < 0) {
-        SDL_free(buf);
-        return SDL_SetError("Failed to format debug text");
+    if (rc == -1) {
+        return false;
     }
 
-    bool result = SDL_RenderDebugText(renderer, x, y, buf);
-
-    SDL_free(buf);
-
-    return result;
+    const bool retval = SDL_RenderDebugText(renderer, x, y, str);
+    SDL_free(str);
+    return retval;
 }