From 48a7fa12d0aec7993cce65f51038fe48178e6f87 Mon Sep 17 00:00:00 2001
From: Captain <[EMAIL REDACTED]>
Date: Wed, 29 Jan 2025 21:00:59 +0530
Subject: [PATCH] Added TTF_CreateRendererTextEngineWithProperties and
TTF_CreateGPUTextEngineWithProperties (#485)
---
include/SDL3_ttf/SDL_ttf.h | 50 +++++++++++++++++++++++++++++
src/SDL_gpu_textengine.c | 60 +++++++++++++++++++++++------------
src/SDL_renderer_textengine.c | 58 ++++++++++++++++++++++-----------
src/SDL_ttf.sym | 2 ++
4 files changed, 131 insertions(+), 39 deletions(-)
diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index 30989f5f..f6e7acca 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -1675,6 +1675,31 @@ extern SDL_DECLSPEC void SDLCALL TTF_DestroySurfaceTextEngine(TTF_TextEngine *en
*/
extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateRendererTextEngine(SDL_Renderer *renderer);
+/**
+ * Create a text engine for drawing text on an SDL renderer, with the specified properties.
+ *
+ * These are the supported properties:
+ *
+ * - `TTF_PROP_RENDERER_TEXT_ENGINE_RENDERER`: the renderer to use for creating textures
+ * and drawing text
+ * - `TTF_PROP_RENDERER_TEXT_ENGINE_ATLAS_TEXTURE_SIZE`: the size of the texture atlas
+ *
+ * \param props the properties to use
+ * \returns a TTF_TextEngine object or NULL on failure; call SDL_GetError()
+ * for more information.
+ *
+ * \threadsafety This function should be called on the thread that created the
+ * renderer.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_DestroyRendererTextEngine
+ */
+extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateRendererTextEngineWithProperties(SDL_PropertiesID props);
+
+#define TTF_PROP_RENDERER_TEXT_ENGINE_RENDERER "SDL_ttf.renderer_text_engine.create.renderer"
+#define TTF_PROP_RENDERER_TEXT_ENGINE_ATLAS_TEXTURE_SIZE "SDL_ttf.renderer_text_engine.create.atlas_texture_size"
+
/**
* Draw text to an SDL renderer.
*
@@ -1735,6 +1760,31 @@ extern SDL_DECLSPEC void SDLCALL TTF_DestroyRendererTextEngine(TTF_TextEngine *e
*/
extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateGPUTextEngine(SDL_GPUDevice *device);
+/**
+ * Create a text engine for drawing text with the SDL GPU API, with the specified properties.
+ *
+ * These are the supported properties:
+ *
+ * - `TTF_PROP_GPU_TEXT_ENGINE_DEVICE`: the SDL_GPUDevice to use for creating
+ * textures and drawing text.
+ * - `TTF_PROP_GPU_TEXT_ENGINE_ATLAS_TEXTURE_SIZE`: the size of the texture atlas
+ *
+ * \param props the properties to use.
+ * \returns a TTF_TextEngine object or NULL on failure; call SDL_GetError()
+ * for more information.
+ *
+ * \threadsafety This function should be called on the thread that created the
+ * device.
+ *
+ * \since This function is available since SDL_ttf 3.0.0.
+ *
+ * \sa TTF_DestroyGPUTextEngine
+ */
+extern SDL_DECLSPEC TTF_TextEngine * SDLCALL TTF_CreateGPUTextEngineWithProperties(SDL_PropertiesID props);
+
+#define TTF_PROP_GPU_TEXT_ENGINE_DEVICE "SDL_ttf.gpu_text_engine.create.device"
+#define TTF_PROP_GPU_TEXT_ENGINE_ATLAS_TEXTURE_SIZE "SDL_ttf.gpu_text_engine.create.atlas_texture_size"
+
/**
* Draw sequence returned by TTF_GetGPUTextDrawData
*
diff --git a/src/SDL_gpu_textengine.c b/src/SDL_gpu_textengine.c
index c3486fcd..29e6f145 100644
--- a/src/SDL_gpu_textengine.c
+++ b/src/SDL_gpu_textengine.c
@@ -25,8 +25,6 @@
#include "SDL_hashtable.h"
#include "SDL_hashtable_ttf.h"
-#define ATLAS_TEXTURE_SIZE 1024
-
#define STB_RECT_PACK_IMPLEMENTATION
#define STBRP_STATIC
#define STBRP_SORT SDL_qsort
@@ -75,6 +73,7 @@ typedef struct TTF_GPUTextEngineData
SDL_GPUDevice *device;
SDL_HashTable *fonts;
AtlasTexture *atlas;
+ int atlas_texture_size;
TTF_GPUTextEngineWinding winding;
} TTF_GPUTextEngineData;
@@ -173,7 +172,7 @@ static void DestroyAtlas(SDL_GPUDevice *device, AtlasTexture *atlas)
SDL_free(atlas);
}
-static AtlasTexture *CreateAtlas(SDL_GPUDevice *device)
+static AtlasTexture *CreateAtlas(SDL_GPUDevice *device, int atlas_texture_size)
{
AtlasTexture *atlas = (AtlasTexture *)SDL_calloc(1, sizeof(*atlas));
if (!atlas) {
@@ -184,8 +183,8 @@ static AtlasTexture *CreateAtlas(SDL_GPUDevice *device)
info.type = SDL_GPU_TEXTURETYPE_2D;
info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
- info.width = ATLAS_TEXTURE_SIZE;
- info.height = ATLAS_TEXTURE_SIZE;
+ info.width = atlas_texture_size;
+ info.height = atlas_texture_size;
info.layer_count_or_depth = 1;
info.num_levels = 1;
@@ -207,13 +206,13 @@ static AtlasTexture *CreateAtlas(SDL_GPUDevice *device)
SDL_EndGPURenderPass(rpass);
SDL_SubmitGPUCommandBuffer(cbuf);
- int num_nodes = ATLAS_TEXTURE_SIZE / 4;
+ int num_nodes = atlas_texture_size / 4;
atlas->packing_nodes = (stbrp_node *)SDL_calloc(num_nodes, sizeof(*atlas->packing_nodes));
if (!atlas->packing_nodes) {
DestroyAtlas(device, atlas);
return NULL;
}
- stbrp_init_target(&atlas->packer, ATLAS_TEXTURE_SIZE, ATLAS_TEXTURE_SIZE, atlas->packing_nodes, num_nodes);
+ stbrp_init_target(&atlas->packer, atlas_texture_size, atlas_texture_size, atlas->packing_nodes, num_nodes);
stbrp_setup_heuristic(&atlas->packer, STBRP_HEURISTIC_Skyline_default);
return atlas;
@@ -251,7 +250,7 @@ static void ReleaseGlyph(AtlasGlyph *glyph)
}
}
-static AtlasGlyph *CreateGlyph(AtlasTexture *atlas, const stbrp_rect *area)
+static AtlasGlyph *CreateGlyph(AtlasTexture *atlas, int atlas_texture_size, const stbrp_rect *area)
{
AtlasGlyph *glyph = (AtlasGlyph *)SDL_calloc(1, sizeof(*glyph));
if (!glyph) {
@@ -266,10 +265,10 @@ static AtlasGlyph *CreateGlyph(AtlasTexture *atlas, const stbrp_rect *area)
glyph->rect.w = area->w - 1;
glyph->rect.h = area->h - 1;
- const float minu = (float)glyph->rect.x / ATLAS_TEXTURE_SIZE;
- const float minv = (float)glyph->rect.y / ATLAS_TEXTURE_SIZE;
- const float maxu = (float)(glyph->rect.x + glyph->rect.w) / ATLAS_TEXTURE_SIZE;
- const float maxv = (float)(glyph->rect.y + glyph->rect.h) / ATLAS_TEXTURE_SIZE;
+ const float minu = (float)glyph->rect.x / atlas_texture_size;
+ const float minv = (float)glyph->rect.y / atlas_texture_size;
+ const float maxu = (float)(glyph->rect.x + glyph->rect.w) / atlas_texture_size;
+ const float maxv = (float)(glyph->rect.y + glyph->rect.h) / atlas_texture_size;
glyph->texcoords[0] = minu;
glyph->texcoords[1] = minv;
glyph->texcoords[2] = maxu;
@@ -439,7 +438,7 @@ static bool ResolveMissingGlyphs(TTF_GPUTextEngineData *enginedata, AtlasTexture
continue;
}
- AtlasGlyph *glyph = CreateGlyph(atlas, &missing[i]);
+ AtlasGlyph *glyph = CreateGlyph(atlas, enginedata->atlas_texture_size, &missing[i]);
if (!glyph) {
return false;
}
@@ -473,7 +472,7 @@ static bool ResolveMissingGlyphs(TTF_GPUTextEngineData *enginedata, AtlasTexture
}
if (!atlas->next) {
- atlas->next = CreateAtlas(enginedata->device);
+ atlas->next = CreateAtlas(enginedata->device, enginedata->atlas_texture_size);
if (!atlas->next) {
return false;
}
@@ -487,6 +486,7 @@ static bool CreateMissingGlyphs(TTF_GPUTextEngineData *enginedata, TTF_GPUTextEn
SDL_Surface **surfaces = NULL;
SDL_HashTable *checked = NULL;
bool result = false;
+ int atlas_texture_size = enginedata->atlas_texture_size;
// Build a list of missing glyphs
missing = (stbrp_rect *)SDL_calloc(num_missing, sizeof(*missing));
@@ -521,10 +521,10 @@ static bool CreateMissingGlyphs(TTF_GPUTextEngineData *enginedata, TTF_GPUTextEn
if (!surfaces[i]) {
goto done;
}
- if (surfaces[i]->w > ATLAS_TEXTURE_SIZE || surfaces[i]->h > ATLAS_TEXTURE_SIZE) {
+ if (surfaces[i]->w > atlas_texture_size || surfaces[i]->h > atlas_texture_size) {
SDL_SetError("Glyph surface %dx%d larger than atlas texture %dx%d",
surfaces[i]->w, surfaces[i]->h,
- ATLAS_TEXTURE_SIZE, ATLAS_TEXTURE_SIZE);
+ atlas_texture_size, atlas_texture_size);
goto done;
}
@@ -542,7 +542,7 @@ static bool CreateMissingGlyphs(TTF_GPUTextEngineData *enginedata, TTF_GPUTextEn
// Create the texture atlas if necessary
if (!enginedata->atlas) {
- enginedata->atlas = CreateAtlas(enginedata->device);
+ enginedata->atlas = CreateAtlas(enginedata->device, atlas_texture_size);
if (!enginedata->atlas) {
goto done;
}
@@ -865,13 +865,14 @@ static void NukeFontData(const void *key, const void *value, void *unused)
DestroyFontData(data);
}
-static TTF_GPUTextEngineData *CreateEngineData(SDL_GPUDevice *device)
+static TTF_GPUTextEngineData *CreateEngineData(SDL_GPUDevice *device, int atlas_texture_size)
{
TTF_GPUTextEngineData *data = (TTF_GPUTextEngineData *)SDL_calloc(1, sizeof(*data));
if (!data) {
return NULL;
}
data->device = device;
+ data->atlas_texture_size = atlas_texture_size;
data->winding = TTF_GPU_TEXTENGINE_WINDING_CLOCKWISE;
data->fonts = SDL_CreateHashTable(NULL, 4, SDL_HashPointer, SDL_KeyMatchPointer, NukeFontData, false, false);
@@ -939,8 +940,21 @@ void TTF_DestroyGPUTextEngine(TTF_TextEngine *engine)
TTF_TextEngine *TTF_CreateGPUTextEngine(SDL_GPUDevice *device)
{
+ SDL_PropertiesID props = SDL_CreateProperties();
+ if (props == 0) {
+ SDL_SetError("Failed to create GPU text engine.");
+ return NULL;
+ }
+ SDL_SetPointerProperty(props, TTF_PROP_GPU_TEXT_ENGINE_DEVICE, device);
+
+ return TTF_CreateGPUTextEngineWithProperties(props);
+}
+
+TTF_TextEngine *TTF_CreateGPUTextEngineWithProperties(SDL_PropertiesID props)
+{
+ SDL_GPUDevice *device = SDL_GetPointerProperty(props, TTF_PROP_GPU_TEXT_ENGINE_DEVICE, NULL);
if (!device) {
- SDL_InvalidParamError("device");
+ SDL_SetError("Failed to create GPU text engine: Invalid device.");
return NULL;
}
@@ -949,10 +963,16 @@ TTF_TextEngine *TTF_CreateGPUTextEngine(SDL_GPUDevice *device)
return NULL;
}
+ int atlas_texture_size = (int)SDL_GetNumberProperty(props, TTF_PROP_GPU_TEXT_ENGINE_ATLAS_TEXTURE_SIZE, 1024);
+ if (atlas_texture_size <= 0) {
+ SDL_SetError("Failed to create GPU text engine: Invalid texture atlas size.");
+ return NULL;
+ }
+
SDL_INIT_INTERFACE(engine);
engine->CreateText = CreateText;
engine->DestroyText = DestroyText;
- engine->userdata = CreateEngineData(device);
+ engine->userdata = CreateEngineData(device, atlas_texture_size);
if (!engine->userdata) {
TTF_DestroyGPUTextEngine(engine);
return NULL;
diff --git a/src/SDL_renderer_textengine.c b/src/SDL_renderer_textengine.c
index 042e7592..2de15fa5 100644
--- a/src/SDL_renderer_textengine.c
+++ b/src/SDL_renderer_textengine.c
@@ -23,8 +23,6 @@
#include "SDL_hashtable.h"
#include "SDL_hashtable_ttf.h"
-#define ATLAS_TEXTURE_SIZE 1024
-
#define STB_RECT_PACK_IMPLEMENTATION
#define STBRP_STATIC
#define STBRP_SORT SDL_qsort
@@ -85,6 +83,7 @@ typedef struct TTF_RendererTextEngineData
SDL_Renderer *renderer;
SDL_HashTable *fonts;
AtlasTexture *atlas;
+ int atlas_texture_size;
} TTF_RendererTextEngineData;
@@ -183,27 +182,27 @@ static void DestroyAtlas(AtlasTexture *atlas)
SDL_free(atlas);
}
-static AtlasTexture *CreateAtlas(SDL_Renderer *renderer)
+static AtlasTexture *CreateAtlas(SDL_Renderer *renderer, int atlas_texture_size)
{
AtlasTexture *atlas = (AtlasTexture *)SDL_calloc(1, sizeof(*atlas));
if (!atlas) {
return NULL;
}
- atlas->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, ATLAS_TEXTURE_SIZE, ATLAS_TEXTURE_SIZE);
+ atlas->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, atlas_texture_size, atlas_texture_size);
if (!atlas->texture) {
DestroyAtlas(atlas);
return NULL;
}
SDL_SetTextureScaleMode(atlas->texture, SDL_SCALEMODE_NEAREST);
- int num_nodes = ATLAS_TEXTURE_SIZE / 4;
+ int num_nodes = atlas_texture_size / 4;
atlas->packing_nodes = (stbrp_node *)SDL_calloc(num_nodes, sizeof(*atlas->packing_nodes));
if (!atlas->packing_nodes) {
DestroyAtlas(atlas);
return NULL;
}
- stbrp_init_target(&atlas->packer, ATLAS_TEXTURE_SIZE, ATLAS_TEXTURE_SIZE, atlas->packing_nodes, num_nodes);
+ stbrp_init_target(&atlas->packer, atlas_texture_size, atlas_texture_size, atlas->packing_nodes, num_nodes);
stbrp_setup_heuristic(&atlas->packer, STBRP_HEURISTIC_Skyline_default);
return atlas;
@@ -241,7 +240,7 @@ static void ReleaseGlyph(AtlasGlyph *glyph)
}
}
-static AtlasGlyph *CreateGlyph(AtlasTexture *atlas, const stbrp_rect *area)
+static AtlasGlyph *CreateGlyph(AtlasTexture *atlas, int atlas_texture_size, const stbrp_rect *area)
{
AtlasGlyph *glyph = (AtlasGlyph *)SDL_calloc(1, sizeof(*glyph));
if (!glyph) {
@@ -255,10 +254,10 @@ static AtlasGlyph *CreateGlyph(AtlasTexture *atlas, const stbrp_rect *area)
glyph->rect.w = area->w;
glyph->rect.h = area->h;
- const float minu = (float)area->x / ATLAS_TEXTURE_SIZE;
- const float minv = (float)area->y / ATLAS_TEXTURE_SIZE;
- const float maxu = (float)(area->x + area->w) / ATLAS_TEXTURE_SIZE;
- const float maxv = (float)(area->y + area->h) / ATLAS_TEXTURE_SIZE;
+ const float minu = (float)area->x / atlas_texture_size;
+ const float minv = (float)area->y / atlas_texture_size;
+ const float maxu = (float)(area->x + area->w) / atlas_texture_size;
+ const float maxv = (float)(area->y + area->h) / atlas_texture_size;
glyph->texcoords[0] = minu;
glyph->texcoords[1] = minv;
glyph->texcoords[2] = maxu;
@@ -374,7 +373,7 @@ static bool ResolveMissingGlyphs(TTF_RendererTextEngineData *enginedata, AtlasTe
continue;
}
- AtlasGlyph *glyph = CreateGlyph(atlas, &missing[i]);
+ AtlasGlyph *glyph = CreateGlyph(atlas, enginedata->atlas_texture_size, &missing[i]);
if (!glyph) {
return false;
}
@@ -408,7 +407,7 @@ static bool ResolveMissingGlyphs(TTF_RendererTextEngineData *enginedata, AtlasTe
}
if (!atlas->next) {
- atlas->next = CreateAtlas(enginedata->renderer);
+ atlas->next = CreateAtlas(enginedata->renderer, enginedata->atlas_texture_size);
if (!atlas->next) {
return false;
}
@@ -422,6 +421,7 @@ static bool CreateMissingGlyphs(TTF_RendererTextEngineData *enginedata, TTF_Rend
SDL_Surface **surfaces = NULL;
SDL_HashTable *checked = NULL;
bool result = false;
+ int atlas_texture_size = enginedata->atlas_texture_size;
// Build a list of missing glyphs
missing = (stbrp_rect *)SDL_calloc(num_missing, sizeof(*missing));
@@ -456,10 +456,10 @@ static bool CreateMissingGlyphs(TTF_RendererTextEngineData *enginedata, TTF_Rend
if (!surfaces[i]) {
goto done;
}
- if (surfaces[i]->w > ATLAS_TEXTURE_SIZE || surfaces[i]->h > ATLAS_TEXTURE_SIZE) {
+ if (surfaces[i]->w > atlas_texture_size || surfaces[i]->h > atlas_texture_size) {
SDL_SetError("Glyph surface %dx%d larger than atlas texture %dx%d",
surfaces[i]->w, surfaces[i]->h,
- ATLAS_TEXTURE_SIZE, ATLAS_TEXTURE_SIZE);
+ atlas_texture_size, atlas_texture_size);
goto done;
}
@@ -476,7 +476,7 @@ static bool CreateMissingGlyphs(TTF_RendererTextEngineData *enginedata, TTF_Rend
// Create the texture atlas if necessary
if (!enginedata->atlas) {
- enginedata->atlas = CreateAtlas(enginedata->renderer);
+ enginedata->atlas = CreateAtlas(enginedata->renderer, atlas_texture_size);
if (!enginedata->atlas) {
goto done;
}
@@ -778,13 +778,14 @@ static void NukeFontData(const void *key, const void *value, void *unused)
DestroyFontData(data);
}
-static TTF_RendererTextEngineData *CreateEngineData(SDL_Renderer *renderer)
+static TTF_RendererTextEngineData *CreateEngineData(SDL_Renderer *renderer, int atlas_texture_size)
{
TTF_RendererTextEngineData *data = (TTF_RendererTextEngineData *)SDL_calloc(1, sizeof(*data));
if (!data) {
return NULL;
}
data->renderer = renderer;
+ data->atlas_texture_size = atlas_texture_size;
data->fonts = SDL_CreateHashTable(NULL, 4, SDL_HashPointer, SDL_KeyMatchPointer, NukeFontData, false, false);
if (!data->fonts) {
@@ -840,8 +841,21 @@ static void SDLCALL DestroyText(void *userdata, TTF_Text *text)
TTF_TextEngine *TTF_CreateRendererTextEngine(SDL_Renderer *renderer)
{
+ SDL_PropertiesID props = SDL_CreateProperties();
+ if (props == 0) {
+ SDL_SetError("Failed to create renderer text engine.");
+ return NULL;
+ }
+ SDL_SetPointerProperty(props, TTF_PROP_RENDERER_TEXT_ENGINE_RENDERER, renderer);
+
+ return TTF_CreateRendererTextEngineWithProperties(props);
+}
+
+TTF_TextEngine *TTF_CreateRendererTextEngineWithProperties(SDL_PropertiesID props)
+{
+ SDL_Renderer *renderer = SDL_GetPointerProperty(props, TTF_PROP_RENDERER_TEXT_ENGINE_RENDERER, NULL);
if (!renderer) {
- SDL_InvalidParamError("renderer");
+ SDL_SetError("Failed to create renderer text engine: Invalid renderer.");
return NULL;
}
@@ -850,10 +864,16 @@ TTF_TextEngine *TTF_CreateRendererTextEngine(SDL_Renderer *renderer)
return NULL;
}
+ int atlas_texture_size = (int)SDL_GetNumberProperty(props, TTF_PROP_RENDERER_TEXT_ENGINE_ATLAS_TEXTURE_SIZE, 1024);
+ if (atlas_texture_size <= 0) {
+ SDL_SetError("Failed to create renderer text engine: Invalid texture atlas size.");
+ return NULL;
+ }
+
SDL_INIT_INTERFACE(engine);
engine->CreateText = CreateText;
engine->DestroyText = DestroyText;
- engine->userdata = CreateEngineData(renderer);
+ engine->userdata = CreateEngineData(renderer, atlas_texture_size);
if (!engine->userdata) {
TTF_DestroyRendererTextEngine(engine);
return NULL;
diff --git a/src/SDL_ttf.sym b/src/SDL_ttf.sym
index bfe5fae3..ae56f1c1 100644
--- a/src/SDL_ttf.sym
+++ b/src/SDL_ttf.sym
@@ -5,7 +5,9 @@ SDL3_ttf_0.0.0 {
TTF_ClearFallbackFonts;
TTF_CloseFont;
TTF_CreateGPUTextEngine;
+ TTF_CreateGPUTextEngineWithProperties;
TTF_CreateRendererTextEngine;
+ TTF_CreateRendererTextEngineWithProperties;
TTF_CreateSurfaceTextEngine;
TTF_CreateText;
TTF_DeleteTextString;