diff --git a/examples/demo/02-woodeneye-008/woodeneye-008.c b/examples/demo/02-woodeneye-008/woodeneye-008.c index 3ad0b45b866df..252f1221ca2d6 100644 --- a/examples/demo/02-woodeneye-008/woodeneye-008.c +++ b/examples/demo/02-woodeneye-008/woodeneye-008.c @@ -267,7 +267,7 @@ static void draw(SDL_Renderer *renderer, const float (*edges)[6], const Player p } SDL_SetRenderClipRect(renderer, 0); SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - SDL_RenderDebugText(renderer, 0, 0, debug_string); + SDL_RenderDebugText(renderer, 0, 0, "%s", debug_string); SDL_RenderPresent(renderer); } diff --git a/examples/demo/03-infinite-monkeys/infinite-monkeys.c b/examples/demo/03-infinite-monkeys/infinite-monkeys.c index a8bfad1bcd714..a35df6686130a 100644 --- a/examples/demo/03-infinite-monkeys/infinite-monkeys.c +++ b/examples/demo/03-infinite-monkeys/infinite-monkeys.c @@ -204,7 +204,7 @@ static void DisplayLine(float x, float y, Line *line) } *spot = '\0'; - SDL_RenderDebugText(renderer, x, y, utf8); + SDL_RenderDebugText(renderer, x, y, "%s", utf8); SDL_free(utf8); } } @@ -343,7 +343,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) hours = (int)elapsed; SDL_asprintf(&caption, "Monkeys: %d - %dH:%dM:%dS", monkeys, hours, minutes, seconds); if (caption) { - SDL_RenderDebugText(renderer, x, y, caption); + SDL_RenderDebugText(renderer, x, y, "%s", caption); SDL_free(caption); } y += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE; diff --git a/examples/demo/04-bytepusher/bytepusher.c b/examples/demo/04-bytepusher/bytepusher.c index ae862908844e1..88418793b70bc 100644 --- a/examples/demo/04-bytepusher/bytepusher.c +++ b/examples/demo/04-bytepusher/bytepusher.c @@ -123,9 +123,9 @@ static bool load_file(BytePusher* vm, const char* path) { static void print(BytePusher* vm, int x, int y, const char* str) { SDL_SetRenderDrawColor(vm->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); - SDL_RenderDebugText(vm->renderer, (float)(x + 1), (float)(y + 1), str); + SDL_RenderDebugText(vm->renderer, (float)(x + 1), (float)(y + 1), "%s", str); SDL_SetRenderDrawColor(vm->renderer, 0xff, 0xff, 0xff, SDL_ALPHA_OPAQUE); - SDL_RenderDebugText(vm->renderer, (float)x, (float)y, str); + SDL_RenderDebugText(vm->renderer, (float)x, (float)y, "%s", str); SDL_SetRenderDrawColor(vm->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); } diff --git a/examples/input/01-joystick-polling/joystick-polling.c b/examples/input/01-joystick-polling/joystick-polling.c index 8f7a3156df57a..906f85314fa4c 100644 --- a/examples/input/01-joystick-polling/joystick-polling.c +++ b/examples/input/01-joystick-polling/joystick-polling.c @@ -176,7 +176,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) x = (((float) winw) - (SDL_strlen(text) * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE)) / 2.0f; y = (((float) winh) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2.0f; SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - SDL_RenderDebugText(renderer, x, y, text); + SDL_RenderDebugText(renderer, x, y, "%s", text); SDL_RenderPresent(renderer); return SDL_APP_CONTINUE; /* carry on with the program! */ diff --git a/examples/input/02-joystick-events/joystick-events.c b/examples/input/02-joystick-events/joystick-events.c index cc01d84aac575..d5b9a9e6b3fd7 100644 --- a/examples/input/02-joystick-events/joystick-events.c +++ b/examples/input/02-joystick-events/joystick-events.c @@ -214,7 +214,7 @@ SDL_AppResult SDL_AppIterate(void *appstate) } SDL_SetRenderDrawColor(renderer, msg->color.r, msg->color.g, msg->color.b, (Uint8) (((float) msg->color.a) * (1.0f - life_percent))); - SDL_RenderDebugText(renderer, x, y, msg->str); + SDL_RenderDebugText(renderer, x, y, "%s", msg->str); prev_y = y; msg = msg->next; diff --git a/examples/renderer/18-debug-text/debug-text.c b/examples/renderer/18-debug-text/debug-text.c index 85bc66ca8d720..9117f34398428 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_RenderDebugText(renderer, 0, 0, "This program has been running for %" SDL_PRIu64 " 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..e1b8c637c8212 100644 --- a/include/SDL3/SDL_render.h +++ b/include/SDL3/SDL_render.h @@ -2497,9 +2497,11 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderVSync(SDL_Renderer *renderer, int /** * Draw debug text to an SDL_Renderer. * - * This function will render a string of text to an SDL_Renderer. Note that - * this is a convenience function for debugging, with severe limitations, and - * not intended to be used for production apps and games. + * This function will render a string of text to an SDL_Renderer. This + * function accepts a printf-style formatting. + * + * Note that this is a convenience function for debugging, with severe + * limitations, and not intended to be used for production apps and games. * * Among these limitations: * @@ -2519,10 +2521,17 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderVSync(SDL_Renderer *renderer, int * * The text is drawn in the color specified by SDL_SetRenderDrawColor(). * + * **WARNING**: Note that in SDL 3.1.6, this function accepted a single string + * _without_formatting, and was changed after this one release to accept + * printf-style arguments. This _should_ be binary compatible with the one + * prerelease before this change, assuming there are no '%' characters in + * the string, but we encourage everyone to update their library and headers + * to a later version to avoid confusion. + * * \param renderer the renderer which should draw a line of 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 str the string to render. + * \param fmt the string to render. * \returns true on success or false on failure; call SDL_GetError() for more * information. * @@ -2532,7 +2541,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_GetRenderVSync(SDL_Renderer *renderer, int * * \sa SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE */ -extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *str); +extern SDL_DECLSPEC bool SDLCALL SDL_RenderDebugText(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 21b9bb80f33af..78de457085af2 100644 --- a/src/dynapi/SDL_dynapi.c +++ b/src/dynapi/SDL_dynapi.c @@ -86,13 +86,10 @@ static void SDL_InitDynamicAPI(void); result = jump_table.SDL_vsnprintf(buf, sizeof(buf), fmt, ap); \ va_end(ap); \ if (result >= 0 && (size_t)result >= sizeof(buf)) { \ - size_t len = (size_t)result + 1; \ - str = (char *)jump_table.SDL_malloc(len); \ - if (str) { \ - va_start(ap, fmt); \ - result = jump_table.SDL_vsnprintf(str, len, fmt, ap); \ - va_end(ap); \ - } \ + str = NULL; \ + va_start(ap, fmt); \ + result = SDL_vasprintf(&str, fmt, ap); \ + va_end(ap); \ } \ if (result >= 0) { \ jump_table.SDL_SetError("%s", str); \ @@ -152,6 +149,29 @@ static void SDL_InitDynamicAPI(void); va_end(ap); \ return result; \ } \ + _static bool SDLCALL SDL_RenderDebugText##name(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ + { \ + char buf[128], *str = buf; \ + int result; \ + va_list ap; \ + initcall; \ + va_start(ap, fmt); \ + result = jump_table.SDL_vsnprintf(buf, sizeof(buf), fmt, ap); \ + va_end(ap); \ + if (result >= 0 && (size_t)result >= sizeof(buf)) { \ + str = NULL; \ + va_start(ap, fmt); \ + result = SDL_vasprintf(&str, fmt, ap); \ + va_end(ap); \ + } \ + if (result >= 0) { \ + jump_table.SDL_RenderDebugText(renderer, x, y, "%s", str); \ + } \ + if (str != buf) { \ + jump_table.SDL_free(str); \ + } \ + return false; \ + } \ _static void SDLCALL SDL_Log##name(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) \ { \ va_list ap; \ diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 4611595282db8..5c790d138c768 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1213,7 +1213,9 @@ SDL_DYNAPI_PROC(void,SDL_DelayPrecise,(Uint64 a),(a),) SDL_DYNAPI_PROC(Uint32,SDL_CalculateGPUTextureFormatSize,(SDL_GPUTextureFormat a, Uint32 b, Uint32 c, Uint32 d),(a,b,c,d),return) SDL_DYNAPI_PROC(bool,SDL_SetErrorV,(SDL_PRINTF_FORMAT_STRING const char *a,va_list b),(a,b),return) SDL_DYNAPI_PROC(SDL_LogOutputFunction,SDL_GetDefaultLogOutputFunction,(void),(),return) -SDL_DYNAPI_PROC(bool,SDL_RenderDebugText,(SDL_Renderer *a,float b,float c,const char *d),(a,b,c,d),return) +#ifndef SDL_DYNAPI_PROC_NO_VARARGS +SDL_DYNAPI_PROC(bool,SDL_RenderDebugText,(SDL_Renderer *a,float b,float c,const char *d,...),(a,b,c,d),return) +#endif SDL_DYNAPI_PROC(SDL_Sandbox,SDL_GetSandbox,(void),(),return) SDL_DYNAPI_PROC(bool,SDL_CancelGPUCommandBuffer,(SDL_GPUCommandBuffer *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_SaveFile_IO,(SDL_IOStream *a,const void *b,size_t c,bool d),(a,b,c,d),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 a742700c250ae..1e5dfcdb2fef1 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, @@ -5561,7 +5561,7 @@ static bool DrawDebugCharacter(SDL_Renderer *renderer, float x, float y, Uint32 return SDL_RenderTexture(renderer, renderer->debug_char_texture_atlas, &srect, &drect); } -bool SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *s) +static bool RenderDebugTextInternal(SDL_Renderer *renderer, float x, float y, const char *s) { CHECK_RENDERER_MAGIC(renderer, false); @@ -5582,6 +5582,7 @@ bool SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *s float curx = x; Uint32 ch; + // !!! FIXME: stop rendering if x or y is obviously past the render target. while (result && ((ch = SDL_StepUTF8(&s, NULL)) != 0)) { result &= DrawDebugCharacter(renderer, curx, y, ch); curx += SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE; @@ -5589,3 +5590,38 @@ bool SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, const char *s return result; } + +bool SDL_RenderDebugText(SDL_Renderer *renderer, float x, float y, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) +{ + // 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); + + char stackbuf[128]; + char *buf = stackbuf; + va_list ap; + + va_start(ap, fmt); + int len = SDL_vsnprintf(stackbuf, sizeof (stackbuf), fmt, ap); + va_end(ap); + + if (len < 0) { + return SDL_SetError("Failed to format debug text"); + } else if (len > sizeof (stackbuf)) { + buf = NULL; + va_start(ap, fmt); + len = SDL_vasprintf(&buf, fmt, ap); + va_end(ap); + if (len < 0) { + SDL_free(buf); + return SDL_SetError("Failed to format debug text"); + } + } + + const bool result = RenderDebugTextInternal(renderer, x, y, buf); + if (buf != stackbuf) { + SDL_free(buf); + } + + return result; +} + diff --git a/src/test/SDL_test_font.c b/src/test/SDL_test_font.c index 19e6bc9f6b018..2873c8d769c7c 100644 --- a/src/test/SDL_test_font.c +++ b/src/test/SDL_test_font.c @@ -29,12 +29,12 @@ bool SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c) char str[5]; char *ptr = SDL_UCS4ToUTF8(c, str); *ptr = '\0'; - return SDL_RenderDebugText(renderer, x, y, str); + return SDL_RenderDebugText(renderer, x, y, "%s", str); } bool SDLTest_DrawString(SDL_Renderer *renderer, float x, float y, const char *s) { - return SDL_RenderDebugText(renderer, x, y, s); + return SDL_RenderDebugText(renderer, x, y, "%s", s); } SDLTest_TextWindow *SDLTest_TextWindowCreate(float x, float y, float w, float h) diff --git a/test/testyuv.c b/test/testyuv.c index 35279af8828ae..b5a26d9c2a84e 100644 --- a/test/testyuv.c +++ b/test/testyuv.c @@ -343,7 +343,7 @@ static bool run_colorspace_test(void) SDL_RenderTexture(renderer, texture, NULL, NULL); SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - SDL_RenderDebugText(renderer, 4, 4, colorspaces[i].name); + SDL_RenderDebugText(renderer, 4, 4, "%s", colorspaces[i].name); SDL_RenderPresent(renderer); SDL_Delay(10); }