SDL_ttf: Added support for changing the winding order in GPUTextEngine (#444)

From ebb5cc11b7b3de342911ebf2eea11898929063fc Mon Sep 17 00:00:00 2001
From: Captain <[EMAIL REDACTED]>
Date: Tue, 24 Dec 2024 07:56:09 +0530
Subject: [PATCH] Added support for changing the winding order in GPUTextEngine
 (#444)

---
 include/SDL3_ttf/SDL_ttf.h | 47 ++++++++++++++++++++++++++++
 src/SDL_gpu_textengine.c   | 64 +++++++++++++++++++++++++++++---------
 src/SDL_ttf.sym            |  2 ++
 3 files changed, 99 insertions(+), 14 deletions(-)

diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index 2d395ab8..13311f88 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -1704,6 +1704,53 @@ extern SDL_DECLSPEC TTF_GPUAtlasDrawSequence* SDLCALL TTF_GetGPUTextDrawData(TTF
  */
 extern SDL_DECLSPEC void SDLCALL TTF_DestroyGPUTextEngine(TTF_TextEngine *engine);
 
+/**
+ * The winding order of the vertices returned by TTF_GetGPUTextDrawData
+ *
+ * \since This enum is available since SDL_ttf 3.0.0.
+ */
+typedef enum TTF_GPUTextEngineWinding
+{
+    TTF_GPU_TEXTENGINE_WINDING_INVALID = -1,
+    TTF_GPU_TEXTENGINE_WINDING_CLOCKWISE,
+    TTF_GPU_TEXTENGINE_WINDING_COUNTER_CLOCKWISE
+} TTF_GPUTextEngineWinding;
+
+/**
+ * Sets the winding order of the vertices returned by TTF_GetGPUTextDrawData
+ * for a particular GPU text engine.
+ *
+ * \param engine a TTF_TextEngine object created with
+ *               TTF_CreateGPUTextEngine().
+ * \param winding the new winding order option
+ *
+ * \threadsafety This function should be called on the thread that created the
+ *               engine.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_GetGPUTextEngineWinding
+ */
+extern SDL_DECLSPEC void SDLCALL TTF_SetGPUTextEngineWinding(TTF_TextEngine *engine, TTF_GPUTextEngineWinding winding);
+
+/**
+ * Get the winding order of the vertices returned by TTF_GetGPUTextDrawData
+ * for a particular GPU text engine
+ *
+ * \param engine a TTF_TextEngine object created with
+ *               TTF_CreateGPUTextEngine().
+ * \returns the winding order used by the GPU text engine or
+ *          TTF_GPU_TEXTENGINE_WINDING_INVALID in case of error
+ *
+ * \threadsafety This function should be called on the thread that created the
+ *               engine.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_SetGPUTextEngineWinding
+ */
+extern SDL_DECLSPEC TTF_GPUTextEngineWinding SDLCALL TTF_GetGPUTextEngineWinding(const TTF_TextEngine *engine);
+
 /**
  * Create a text object from UTF-8 text and a text engine.
  *
diff --git a/src/SDL_gpu_textengine.c b/src/SDL_gpu_textengine.c
index 1801e894..1501304a 100644
--- a/src/SDL_gpu_textengine.c
+++ b/src/SDL_gpu_textengine.c
@@ -20,14 +20,15 @@
 */
 #include <SDL3/SDL.h>
 #include <SDL3_ttf/SDL_textengine.h>
+#include <SDL3_ttf/SDL_ttf.h>
 
 #include "SDL_hashtable.h"
 
-#define ATLAS_TEXTURE_SIZE  256
+#define ATLAS_TEXTURE_SIZE 256
 
 #define STB_RECT_PACK_IMPLEMENTATION
 #define STBRP_STATIC
-#define STBRP_SORT SDL_qsort
+#define STBRP_SORT   SDL_qsort
 #define STBRP_ASSERT SDL_assert
 #define STBRP__CDECL SDLCALL
 #include "stb_rect_pack.h"
@@ -73,9 +74,9 @@ typedef struct TTF_GPUTextEngineData
     SDL_GPUDevice *device;
     SDL_HashTable *fonts;
     AtlasTexture *atlas;
+    TTF_GPUTextEngineWinding winding;
 } TTF_GPUTextEngineData;
 
-
 static int SDLCALL SortMissing(void *userdata, const void *a, const void *b)
 {
     const TTF_DrawOperation *ops = (const TTF_DrawOperation *)userdata;
@@ -145,7 +146,7 @@ static int SDLCALL SortOperations(const void *a, const void *b)
     return 0;
 }
 
-static void DestroyGlyph(AtlasGlyph* glyph)
+static void DestroyGlyph(AtlasGlyph *glyph)
 {
     if (!glyph) {
         return;
@@ -178,7 +179,7 @@ static AtlasTexture *CreateAtlas(SDL_GPUDevice *device)
         return NULL;
     }
 
-    SDL_GPUTextureCreateInfo info = {0};
+    SDL_GPUTextureCreateInfo info = { 0 };
     info.type = SDL_GPU_TEXTURETYPE_2D;
     info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
     info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
@@ -299,7 +300,7 @@ static AtlasGlyph *FindUnusedGlyph(AtlasTexture *atlas, int width, int height)
 }
 
 static bool UpdateGPUTexture(SDL_GPUDevice *device, SDL_GPUTexture *texture,
-                              const SDL_Rect *rect, const void *pixels, int pitch)
+                             const SDL_Rect *rect, const void *pixels, int pitch)
 {
     const Uint32 texturebpp = 4;
 
@@ -385,7 +386,7 @@ static bool ResolveMissingGlyphs(TTF_GPUTextEngineData *enginedata, AtlasTexture
     // See if we can reuse any existing entries
     if (atlas->free_glyphs) {
         // Search from the smallest to the largest to minimize time spent searching the free list and shortening the missing entries
-        for (int i = num_missing; i--; ) {
+        for (int i = num_missing; i--;) {
             AtlasGlyph *glyph = FindUnusedGlyph(atlas, missing[i].w, missing[i].h);
             if (!glyph) {
                 continue;
@@ -406,7 +407,7 @@ static bool ResolveMissingGlyphs(TTF_GPUTextEngineData *enginedata, AtlasTexture
             // Remove this from the missing entries
             --num_missing;
             if (i < num_missing) {
-                SDL_memcpy(&missing[i], &missing[i+1], (num_missing - i) * sizeof(missing[i]));
+                SDL_memcpy(&missing[i], &missing[i + 1], (num_missing - i) * sizeof(missing[i]));
             }
         }
         if (num_missing == 0) {
@@ -581,7 +582,7 @@ static SDL_GPUTexture *GetOperationTexture(TTF_DrawOperation *op)
     return NULL;
 }
 
-static AtlasDrawSequence *CreateDrawSequence(TTF_DrawOperation *ops, int num_ops)
+static AtlasDrawSequence *CreateDrawSequence(TTF_DrawOperation *ops, int num_ops, TTF_GPUTextEngineWinding winding)
 {
     AtlasDrawSequence *sequence = (AtlasDrawSequence *)SDL_calloc(1, sizeof(*sequence));
     if (!sequence) {
@@ -664,7 +665,16 @@ static AtlasDrawSequence *CreateDrawSequence(TTF_DrawOperation *ops, int num_ops
         return NULL;
     }
 
-    static const Uint8 rect_index_order[] = { 0, 1, 2, 0, 2, 3 };
+    static const Uint8 rect_index_order_cw[] = { 0, 1, 2, 0, 2, 3 };
+    static const Uint8 rect_index_order_ccw[] = { 0, 2, 1, 0, 3, 2 };
+
+    const Uint8 *rect_index_order;
+    if (winding == TTF_GPU_TEXTENGINE_WINDING_CLOCKWISE) {
+        rect_index_order = rect_index_order_cw;
+    } else {
+        rect_index_order = rect_index_order_ccw;
+    }
+
     int vertex_index = 0;
     int *indices = sequence->indices;
     for (int i = 0; i < count; ++i) {
@@ -678,7 +688,7 @@ static AtlasDrawSequence *CreateDrawSequence(TTF_DrawOperation *ops, int num_ops
     }
 
     if (count < num_ops) {
-        sequence->next = CreateDrawSequence(ops + count, num_ops - count);
+        sequence->next = CreateDrawSequence(ops + count, num_ops - count, winding);
         if (!sequence->next) {
             DestroyDrawSequence(sequence);
             return NULL;
@@ -743,7 +753,7 @@ static TTF_GPUTextEngineTextData *CreateTextData(TTF_GPUTextEngineData *engineda
             continue;
         }
 
-        AtlasGlyph *glyph = (AtlasGlyph*)op->copy.reserved;
+        AtlasGlyph *glyph = (AtlasGlyph *)op->copy.reserved;
         ++glyph->refcount;
         data->glyphs[data->num_glyphs++] = glyph;
     }
@@ -752,7 +762,7 @@ static TTF_GPUTextEngineTextData *CreateTextData(TTF_GPUTextEngineData *engineda
     SDL_qsort(ops, num_ops, sizeof(*ops), SortOperations);
 
     // Create batched draw sequences
-    data->draw_sequence = CreateDrawSequence(ops, num_ops);
+    data->draw_sequence = CreateDrawSequence(ops, num_ops, enginedata->winding);
     if (!data->draw_sequence) {
         DestroyTextData(data);
         return NULL;
@@ -834,6 +844,7 @@ static TTF_GPUTextEngineData *CreateEngineData(SDL_GPUDevice *device)
         return NULL;
     }
     data->device = device;
+    data->winding = TTF_GPU_TEXTENGINE_WINDING_CLOCKWISE;
 
     data->fonts = SDL_CreateHashTable(NULL, 4, SDL_HashPointer, SDL_KeyMatchPointer, NukeFontData, false);
     if (!data->fonts) {
@@ -921,7 +932,7 @@ TTF_TextEngine *TTF_CreateGPUTextEngine(SDL_GPUDevice *device)
     return engine;
 }
 
-AtlasDrawSequence* TTF_GetGPUTextDrawData(TTF_Text *text)
+AtlasDrawSequence *TTF_GetGPUTextDrawData(TTF_Text *text)
 {
     if (!text || !text->internal || text->internal->engine->CreateText != CreateText) {
         SDL_InvalidParamError("text");
@@ -941,3 +952,28 @@ AtlasDrawSequence* TTF_GetGPUTextDrawData(TTF_Text *text)
 
     return data->draw_sequence;
 }
+
+void TTF_SetGPUTextEngineWinding(TTF_TextEngine *engine, TTF_GPUTextEngineWinding winding)
+{
+    if (!engine || engine->CreateText != CreateText) {
+        SDL_InvalidParamError("engine");
+        return;
+    }
+
+    if (winding == TTF_GPU_TEXTENGINE_WINDING_INVALID) {
+        SDL_InvalidParamError("winding");
+        return;
+    }
+
+    ((TTF_GPUTextEngineData *)engine->userdata)->winding = winding;
+}
+
+TTF_GPUTextEngineWinding TTF_GetGPUTextEngineWinding(const TTF_TextEngine *engine)
+{
+    if (!engine || engine->CreateText != CreateText) {
+        SDL_InvalidParamError("engine");
+        return TTF_GPU_TEXTENGINE_WINDING_INVALID;
+    }
+
+    return ((TTF_GPUTextEngineData *)engine->userdata)->winding;
+}
diff --git a/src/SDL_ttf.sym b/src/SDL_ttf.sym
index ea036652..bfed9146 100644
--- a/src/SDL_ttf.sym
+++ b/src/SDL_ttf.sym
@@ -40,6 +40,7 @@ SDL3_ttf_0.0.0 {
     TTF_GetGlyphMetrics;
     TTF_GetGlyphScript;
     TTF_GetGPUTextDrawData;
+    TTF_GetGPUTextEngineWinding;
     TTF_GetHarfBuzzVersion;
     TTF_GetNextTextSubString;
     TTF_GetPreviousTextSubString;
@@ -87,6 +88,7 @@ SDL3_ttf_0.0.0 {
     TTF_SetFontSizeDPI;
     TTF_SetFontStyle;
     TTF_SetFontWrapAlignment;
+    TTF_SetGPUTextEngineWinding;
     TTF_SetTextColor;
     TTF_SetTextColorFloat;
     TTF_SetTextEngine;