From 3d4a63263cf02b49397cf8f270638420da799e81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Julio=20Ernesto=20Rodr=C3=ADguez=20Caba=C3=B1as?=
 <julioernesto90@gmail.com>
Date: Mon, 10 Feb 2025 21:46:59 +0000
Subject: [PATCH 1/2] Use text engine to render text on the SDL3 renderer

---
 examples/SDL3-simple-demo/main.c    | 49 +++++++++++++++++------
 renderers/SDL3/clay_renderer_SDL3.c | 61 ++++++++++++++---------------
 2 files changed, 66 insertions(+), 44 deletions(-)

diff --git a/examples/SDL3-simple-demo/main.c b/examples/SDL3-simple-demo/main.c
index 8d7be39..eca9c43 100644
--- a/examples/SDL3-simple-demo/main.c
+++ b/examples/SDL3-simple-demo/main.c
@@ -19,13 +19,14 @@ static const Clay_Color COLOR_LIGHT     = (Clay_Color) {224, 215, 210, 255};
 
 typedef struct app_state {
     SDL_Window *window;
-    SDL_Renderer *renderer;
+    Clay_SDL3RendererData rendererData;
     ClayVideoDemo_Data demoData;
 } AppState;
 
 static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData)
 {
-    TTF_Font *font = gFonts[config->fontId];
+    TTF_Font **fonts = userData;
+    TTF_Font *font = fonts[config->fontId];
     int width, height;
 
     if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) {
@@ -54,19 +55,31 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
     }
     *appstate = state;
 
-    if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->renderer)) {
+    if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->rendererData.renderer)) {
         SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window and renderer: %s", SDL_GetError());
         return SDL_APP_FAILURE;
     }
     SDL_SetWindowResizable(state->window, true);
 
+    state->rendererData.textEngine = TTF_CreateRendererTextEngine(state->rendererData.renderer);
+    if (!state->rendererData.textEngine) {
+        SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create text engine from renderer: %s", SDL_GetError());
+        return SDL_APP_FAILURE;
+    }
+
+    state->rendererData.fonts = SDL_calloc(1, sizeof(TTF_Font *));
+    if (!state->rendererData.fonts) {
+        SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to allocate memory for the font array: %s", SDL_GetError());
+        return SDL_APP_FAILURE;
+    }
+
     TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 24);
     if (!font) {
         SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load font: %s", SDL_GetError());
         return SDL_APP_FAILURE;
     }
 
-    gFonts[FONT_ID] = font;
+    state->rendererData.fonts[FONT_ID] = font;
 
     /* Initialize Clay */
     uint64_t totalMemorySize = Clay_MinMemorySize();
@@ -78,7 +91,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
     int width, height;
     SDL_GetWindowSize(state->window, &width, &height);
     Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors });
-    Clay_SetMeasureTextFunction(SDL_MeasureText, 0);
+    Clay_SetMeasureTextFunction(SDL_MeasureText, state->rendererData.fonts);
 
     state->demoData = ClayVideoDemo_Initialize();
 
@@ -99,10 +112,14 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
             break;
         case SDL_EVENT_MOUSE_MOTION:
             Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y },
-                                 event->motion.state & SDL_BUTTON_LEFT);
+                                 event->motion.state & SDL_BUTTON_LMASK);
+            break;
+        case SDL_EVENT_MOUSE_BUTTON_DOWN:
+            Clay_SetPointerState((Clay_Vector2) { event->button.x, event->button.y },
+                                 event->button.button == SDL_BUTTON_LEFT);
             break;
         case SDL_EVENT_MOUSE_WHEEL:
-            Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->motion.xrel, event->motion.yrel }, 0.01f);
+            Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->wheel.x, event->wheel.y }, 0.01f);
             break;
         default:
             break;
@@ -117,12 +134,12 @@ SDL_AppResult SDL_AppIterate(void *appstate)
 
     Clay_RenderCommandArray render_commands = ClayVideoDemo_CreateLayout(&state->demoData);
 
-    SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, 255);
-    SDL_RenderClear(state->renderer);
+    SDL_SetRenderDrawColor(state->rendererData.renderer, 0, 0, 0, 255);
+    SDL_RenderClear(state->rendererData.renderer);
 
-    SDL_RenderClayCommands(state->renderer, &render_commands);
+    SDL_Clay_RenderClayCommands(&state->rendererData, &render_commands);
 
-    SDL_RenderPresent(state->renderer);
+    SDL_RenderPresent(state->rendererData.renderer);
 
     return SDL_APP_CONTINUE;
 }
@@ -138,12 +155,18 @@ void SDL_AppQuit(void *appstate, SDL_AppResult result)
     AppState *state = appstate;
 
     if (state) {
-        if (state->renderer)
-            SDL_DestroyRenderer(state->renderer);
+        if (state->rendererData.renderer)
+            SDL_DestroyRenderer(state->rendererData.renderer);
 
         if (state->window)
             SDL_DestroyWindow(state->window);
 
+        if (state->rendererData.fonts)
+            SDL_free(state->rendererData.fonts);
+
+        if (state->rendererData.textEngine)
+            TTF_DestroyRendererTextEngine(state->rendererData.textEngine);
+
         SDL_free(state);
     }
     TTF_Quit();
diff --git a/renderers/SDL3/clay_renderer_SDL3.c b/renderers/SDL3/clay_renderer_SDL3.c
index f8d23e8..88766c5 100644
--- a/renderers/SDL3/clay_renderer_SDL3.c
+++ b/renderers/SDL3/clay_renderer_SDL3.c
@@ -3,15 +3,18 @@
 #include <SDL3/SDL.h>
 #include <SDL3_ttf/SDL_ttf.h>
 
-/* This needs to be global because the "MeasureText" callback doesn't have a
- * user data parameter */
-static TTF_Font *gFonts[1];
+typedef struct {
+    SDL_Renderer *renderer;
+    TTF_TextEngine *textEngine;
+    TTF_Font **fonts;
+} Clay_SDL3RendererData;
+
 /* Global for convenience. Even in 4K this is enough for smooth curves (low radius or rect size coupled with
  * no AA or low resolution might make it appear as jagged curves) */
 static int NUM_CIRCLE_SEGMENTS = 16;
 
 //all rendering is performed by a single SDL call, avoiding multiple RenderRect + plumbing choice for circles.
-static void SDL_RenderFillRoundedRect(SDL_Renderer *renderer, const SDL_FRect rect, const float cornerRadius, const Clay_Color _color) {
+static void SDL_Clay_RenderFillRoundedRect(Clay_SDL3RendererData *rendererData, const SDL_FRect rect, const float cornerRadius, const Clay_Color _color) {
     const SDL_FColor color = { _color.r/255, _color.g/255, _color.b/255, _color.a/255 };
 
     int indexCount = 0, vertexCount = 0;
@@ -109,11 +112,11 @@ static void SDL_RenderFillRoundedRect(SDL_Renderer *renderer, const SDL_FRect re
     indices[indexCount++] = vertexCount - 1; //LT
 
     // Render everything
-    SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount);
+    SDL_RenderGeometry(rendererData->renderer, NULL, vertices, vertexCount, indices, indexCount);
 }
 
-static void SDL_RenderArc(SDL_Renderer *renderer, const SDL_FPoint center, const float radius, const float startAngle, const float endAngle, const float thickness, const Clay_Color color) {
-    SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
+static void SDL_Clay_RenderArc(Clay_SDL3RendererData *rendererData, const SDL_FPoint center, const float radius, const float startAngle, const float endAngle, const float thickness, const Clay_Color color) {
+    SDL_SetRenderDrawColor(rendererData->renderer, color.r, color.g, color.b, color.a);
 
     const float radStart = startAngle * (SDL_PI_F / 180.0f);
     const float radEnd = endAngle * (SDL_PI_F / 180.0f);
@@ -133,11 +136,11 @@ static void SDL_RenderArc(SDL_Renderer *renderer, const SDL_FPoint center, const
                     SDL_roundf(center.x + SDL_cosf(angle) * clampedRadius),
                     SDL_roundf(center.y + SDL_sinf(angle) * clampedRadius) };
         }
-        SDL_RenderLines(renderer, points, numCircleSegments + 1);
+        SDL_RenderLines(rendererData->renderer, points, numCircleSegments + 1);
     }
 }
 
-static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArray *rcommands)
+static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Clay_RenderCommandArray *rcommands)
 {
     for (size_t i = 0; i < rcommands->length; i++) {
         Clay_RenderCommand *rcmd = Clay_RenderCommandArray_Get(rcommands, i);
@@ -147,24 +150,20 @@ static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArr
         switch (rcmd->commandType) {
             case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
                 Clay_RectangleRenderData *config = &rcmd->renderData.rectangle;
-                SDL_SetRenderDrawColor(renderer, config->backgroundColor.r, config->backgroundColor.g, config->backgroundColor.b, config->backgroundColor.a);
+                SDL_SetRenderDrawColor(rendererData->renderer, config->backgroundColor.r, config->backgroundColor.g, config->backgroundColor.b, config->backgroundColor.a);
                 if (config->cornerRadius.topLeft > 0) {
-                    SDL_RenderFillRoundedRect(renderer, rect, config->cornerRadius.topLeft, config->backgroundColor);
+                    SDL_Clay_RenderFillRoundedRect(rendererData, rect, config->cornerRadius.topLeft, config->backgroundColor);
                 } else {
-                    SDL_RenderFillRect(renderer, &rect);
+                    SDL_RenderFillRect(rendererData->renderer, &rect);
                 }
             } break;
             case CLAY_RENDER_COMMAND_TYPE_TEXT: {
                 Clay_TextRenderData *config = &rcmd->renderData.text;
-                const SDL_Color color = { config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a };
-
-                TTF_Font *font = gFonts[config->fontId];
-                SDL_Surface *surface = TTF_RenderText_Blended(font, config->stringContents.chars, config->stringContents.length, color);
-                SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
-                SDL_RenderTexture(renderer, texture, NULL, &rect);
-
-                SDL_DestroySurface(surface);
-                SDL_DestroyTexture(texture);
+                TTF_Font *font = rendererData->fonts[config->fontId];
+                TTF_Text* text = TTF_CreateText(rendererData->textEngine, font, config->stringContents.chars, config->stringContents.length);
+                TTF_SetTextColor(text, config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a);
+                TTF_DrawRendererText(text, rect.x, rect.y);
+                TTF_DestroyText(text);
             } break;
             case CLAY_RENDER_COMMAND_TYPE_BORDER: {
                 Clay_BorderRenderData *config = &rcmd->renderData.border;
@@ -177,57 +176,57 @@ static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArr
                     .bottomRight = SDL_min(config->cornerRadius.bottomRight, minRadius)
                 };
                 //edges
-                SDL_SetRenderDrawColor(renderer, config->color.r, config->color.g, config->color.b, config->color.a);
+                SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a);
                 if (config->width.left > 0) {
                     const float starting_y = rect.y + clampedRadii.topLeft;
                     const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft;
                     SDL_FRect line = { rect.x, starting_y, config->width.left, length };
-                    SDL_RenderFillRect(renderer, &line);
+                    SDL_RenderFillRect(rendererData->renderer, &line);
                 }
                 if (config->width.right > 0) {
                     const float starting_x = rect.x + rect.w - (float)config->width.right;
                     const float starting_y = rect.y + clampedRadii.topRight;
                     const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight;
                     SDL_FRect line = { starting_x, starting_y, config->width.right, length };
-                    SDL_RenderFillRect(renderer, &line);
+                    SDL_RenderFillRect(rendererData->renderer, &line);
                 }
                 if (config->width.top > 0) {
                     const float starting_x = rect.x + clampedRadii.topLeft;
                     const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight;
                     SDL_FRect line = { starting_x, rect.y, length, config->width.top };
-                    SDL_RenderFillRect(renderer, &line);
+                    SDL_RenderFillRect(rendererData->renderer, &line);
                 }
                 if (config->width.bottom > 0) {
                     const float starting_x = rect.x + clampedRadii.bottomLeft;
                     const float starting_y = rect.y + rect.h - (float)config->width.bottom;
                     const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight;
                     SDL_FRect line = { starting_x, starting_y, length, config->width.bottom };
-                    SDL_SetRenderDrawColor(renderer, config->color.r, config->color.g, config->color.b, config->color.a);
-                    SDL_RenderFillRect(renderer, &line);
+                    SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a);
+                    SDL_RenderFillRect(rendererData->renderer, &line);
                 }
                 //corners
                 if (config->cornerRadius.topLeft > 0) {
                     const float centerX = rect.x + clampedRadii.topLeft -1;
                     const float centerY = rect.y + clampedRadii.topLeft;
-                    SDL_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft,
+                    SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft,
                         180.0f, 270.0f, config->width.top, config->color);
                 }
                 if (config->cornerRadius.topRight > 0) {
                     const float centerX = rect.x + rect.w - clampedRadii.topRight -1;
                     const float centerY = rect.y + clampedRadii.topRight;
-                    SDL_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight,
+                    SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight,
                         270.0f, 360.0f, config->width.top, config->color);
                 }
                 if (config->cornerRadius.bottomLeft > 0) {
                     const float centerX = rect.x + clampedRadii.bottomLeft -1;
                     const float centerY = rect.y + rect.h - clampedRadii.bottomLeft -1;
-                    SDL_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft,
+                    SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft,
                         90.0f, 180.0f, config->width.bottom, config->color);
                 }
                 if (config->cornerRadius.bottomRight > 0) {
                     const float centerX = rect.x + rect.w - clampedRadii.bottomRight -1; //TODO: why need to -1 in all calculations???
                     const float centerY = rect.y + rect.h - clampedRadii.bottomRight -1;
-                    SDL_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight,
+                    SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight,
                         0.0f, 90.0f, config->width.bottom, config->color);
                 }
 

From 1307834f3b11353cc0d3b1e3d3b8b591e5d4da0c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Julio=20Ernesto=20Rodr=C3=ADguez=20Caba=C3=B1as?=
 <julioernesto90@gmail.com>
Date: Mon, 10 Feb 2025 22:20:44 +0000
Subject: [PATCH 2/2] Ensure fonts are closed during cleanup

---
 examples/SDL3-simple-demo/main.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/examples/SDL3-simple-demo/main.c b/examples/SDL3-simple-demo/main.c
index eca9c43..0597ab5 100644
--- a/examples/SDL3-simple-demo/main.c
+++ b/examples/SDL3-simple-demo/main.c
@@ -161,8 +161,13 @@ void SDL_AppQuit(void *appstate, SDL_AppResult result)
         if (state->window)
             SDL_DestroyWindow(state->window);
 
-        if (state->rendererData.fonts)
+        if (state->rendererData.fonts) {
+            for(size_t i = 0; i < sizeof(state->rendererData.fonts) / sizeof(*state->rendererData.fonts); i++) {
+                TTF_CloseFont(state->rendererData.fonts[i]);
+            }
+
             SDL_free(state->rendererData.fonts);
+        }
 
         if (state->rendererData.textEngine)
             TTF_DestroyRendererTextEngine(state->rendererData.textEngine);