SDL: Add SDL_RenderDebugTextF & SDL_RenderDebugTextV

From 1d0e28a5b3d930f6ae0692897f4e32f36570290c Mon Sep 17 00:00:00 2001
From: williamistGitHub <[EMAIL REDACTED]>
Date: Sat, 7 Dec 2024 00:14:55 -0500
Subject: [PATCH] Add SDL_RenderDebugTextF & SDL_RenderDebugTextV

This should make it easier to quickly put important numbers and such on
the screen without having to format them into a string manually.
---
 examples/renderer/18-debug-text/debug-text.c |  3 +
 include/SDL3/SDL_render.h                    | 58 ++++++++++++++++++++
 src/dynapi/SDL_dynapi.c                      | 10 ++++
 src/dynapi/SDL_dynapi.sym                    |  2 +
 src/dynapi/SDL_dynapi_overrides.h            |  2 +
 src/dynapi/SDL_dynapi_procs.h                |  4 ++
 src/dynapi/gendynapi.py                      |  2 +
 src/render/SDL_render.c                      | 47 +++++++++++++++-
 8 files changed, 126 insertions(+), 2 deletions(-)

diff --git a/examples/renderer/18-debug-text/debug-text.c b/examples/renderer/18-debug-text/debug-text.c
index 85bc66ca8d720..b4a2ca6660b8f 100644
--- a/examples/renderer/18-debug-text/debug-text.c
+++ b/examples/renderer/18-debug-text/debug-text.c
@@ -62,6 +62,9 @@ SDL_AppResult SDL_AppIterate(void *appstate)
     SDL_RenderDebugText(renderer, 14, 65, "It can be scaled.");
     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 %llu seconds.", SDL_GetTicks() / 1000);
+
     SDL_RenderPresent(renderer);  /* put it all on the screen! */
 
     return SDL_APP_CONTINUE;  /* carry on with the program! */
diff --git a/include/SDL3/SDL_render.h b/include/SDL3/SDL_render.h
index 5a16d1e88611f..0aabb8f33e9cc 100644
--- a/include/SDL3/SDL_render.h
+++ b/include/SDL3/SDL_render.h
@@ -2530,10 +2530,68 @@ 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_DEBUG_TEXT_FONT_CHARACTER_SIZE
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *str);
 
+/**
+ * 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 ... 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_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);
+
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus
 }
diff --git a/src/dynapi/SDL_dynapi.c b/src/dynapi/SDL_dynapi.c
index da414879b46dc..3229b5014c2ef 100644
--- a/src/dynapi/SDL_dynapi.c
+++ b/src/dynapi/SDL_dynapi.c
@@ -149,6 +149,16 @@ 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, ...) \
+    {                                                                                                                                     \
+        bool result;                                                                                                                      \
+        va_list ap;                                                                                                                       \
+        initcall;                                                                                                                         \
+        va_start(ap, fmt);                                                                                                                \
+        result = jump_table.SDL_RenderDebugTextV(renderer, x, y, fmt, ap);                                                                \
+        va_end(ap);                                                                                                                       \
+        return result;                                                                                                                    \
+    }                                                                                                                                     \
     _static void SDLCALL SDL_Log##name(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)                                                     \
     {                                                                                                                                     \
         va_list ap;                                                                                                                       \
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index d575bcef93794..7d4ee58d22ab3 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1207,6 +1207,8 @@ SDL3_0.0.0 {
     SDL_SetGPUAllowedFramesInFlight;
     SDL_RenderTextureAffine;
     SDL_WaitAndAcquireGPUSwapchainTexture;
+    SDL_RenderDebugTextF;
+    SDL_RenderDebugTextV;
     # 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 608d59ca1ecb1..6031adab18bf5 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1232,3 +1232,5 @@
 #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
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 4611595282db8..cedc79f58c9ae 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1238,3 +1238,7 @@ SDL_DYNAPI_PROC(bool,SDL_RunOnMainThread,(SDL_MainThreadCallback a,void *b,bool
 SDL_DYNAPI_PROC(bool,SDL_SetGPUAllowedFramesInFlight,(SDL_GPUDevice *a,Uint32 b),(a,b),return)
 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)
+#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/dynapi/gendynapi.py b/src/dynapi/gendynapi.py
index a12e68f9f874d..c780dac6d0ea5 100755
--- a/src/dynapi/gendynapi.py
+++ b/src/dynapi/gendynapi.py
@@ -168,9 +168,11 @@ def parse_header(header_path: Path) -> list[SdlProcedure]:
             func = func.replace(" SDL_PRINTF_VARARG_FUNC(1)", "")
             func = func.replace(" SDL_PRINTF_VARARG_FUNC(2)", "")
             func = func.replace(" SDL_PRINTF_VARARG_FUNC(3)", "")
+            func = func.replace(" SDL_PRINTF_VARARG_FUNC(4)", "")
             func = func.replace(" SDL_PRINTF_VARARG_FUNCV(1)", "")
             func = func.replace(" SDL_PRINTF_VARARG_FUNCV(2)", "")
             func = func.replace(" SDL_PRINTF_VARARG_FUNCV(3)", "")
+            func = func.replace(" SDL_PRINTF_VARARG_FUNCV(4)", "")
             func = func.replace(" SDL_WPRINTF_VARARG_FUNC(3)", "")
             func = func.replace(" SDL_WPRINTF_VARARG_FUNCV(3)", "")
             func = func.replace(" SDL_SCANF_VARARG_FUNC(2)", "")
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index a7b4c5e253bea..241916d44dbc7 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -3995,7 +3995,7 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture,
             xy[0] = real_dstrect.x;
             xy[1] = real_dstrect.y;
         }
-        
+
         // (maxx, miny)
         if (right) {
             xy[2] = right->x;
@@ -4025,7 +4025,7 @@ bool SDL_RenderTextureAffine(SDL_Renderer *renderer, SDL_Texture *texture,
 
         result = QueueCmdGeometry(
             renderer, texture,
-            xy, xy_stride, 
+            xy, xy_stride,
             &texture->color, 0 /* color_stride */,
             uv, uv_stride,
             num_vertices, indices, num_indices, size_indices,
@@ -5589,3 +5589,46 @@ 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, ...)
+{
+    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");
+    }
+
+    char *buf = SDL_malloc(len + 1);
+    if (buf == NULL) {
+        return SDL_OutOfMemory();
+    }
+
+    len = SDL_vsnprintf(buf, len + 1, fmt, ap);
+    if (len < 0) {
+        SDL_free(buf);
+        return SDL_SetError("Failed to format debug text");
+    }
+
+    bool result = SDL_RenderDebugText(renderer, x, y, buf);
+
+    SDL_free(buf);
+
+    return result;
+}