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;