From e07148f7c6da01ee694acd73a1cfbadde3ebb854 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <[EMAIL REDACTED]>
Date: Thu, 18 Jun 2026 09:33:38 +0200
Subject: [PATCH] Add procedural edge AA support for solid fill in GPU text
engine
Populate UV coordinates for solid fill sequences with normalized [0,1]
rectangle coordinates instead of leaving them NULL. This enables
shader-based procedural anti-aliasing for underline and strikethrough
rendering. Fill rects shorter than 3px are padded symmetrically to
ensure the shader has enough geometry for smooth edge transitions.
---
include/SDL3_ttf/SDL_ttf.h | 2 +-
src/SDL_gpu_textengine.c | 33 +++++++++++++++++++++++----------
2 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/include/SDL3_ttf/SDL_ttf.h b/include/SDL3_ttf/SDL_ttf.h
index 9fb5cd12..0c4aa259 100644
--- a/include/SDL3_ttf/SDL_ttf.h
+++ b/include/SDL3_ttf/SDL_ttf.h
@@ -1979,7 +1979,7 @@ typedef struct TTF_GPUAtlasDrawSequence
{
SDL_GPUTexture *atlas_texture; /**< Texture atlas that stores the glyphs, or NULL for solid fill */
SDL_FPoint *xy; /**< An array of vertex positions */
- SDL_FPoint *uv; /**< An array of normalized texture coordinates for each vertex, or NULL for solid fill */
+ SDL_FPoint *uv; /**< An array of normalized texture coordinates for each vertex */
int num_vertices; /**< Number of vertices */
int *indices; /**< An array of indices into the 'vertices' arrays */
int num_indices; /**< Number of indices */
diff --git a/src/SDL_gpu_textengine.c b/src/SDL_gpu_textengine.c
index b8014b52..9f012be5 100644
--- a/src/SDL_gpu_textengine.c
+++ b/src/SDL_gpu_textengine.c
@@ -651,21 +651,27 @@ static AtlasDrawSequence *CreateDrawSequence(TTF_DrawOperation *ops, int num_ops
sequence->num_vertices = count * 4;
sequence->num_indices = count * 6;
- if (texture) {
- AtlasGlyph *glyph;
-
- sequence->uv = (SDL_FPoint *)SDL_malloc(count * sizeof(glyph->texcoords));
- if (!sequence->uv) {
- DestroyDrawSequence(sequence);
- return NULL;
- }
+ sequence->uv = (SDL_FPoint *)SDL_malloc(count * 4 * sizeof(*sequence->uv));
+ if (!sequence->uv) {
+ DestroyDrawSequence(sequence);
+ return NULL;
+ }
- float *uv = (float *)sequence->uv;
+ float *uv = (float *)sequence->uv;
+ if (texture) {
for (int i = 0; i < count; ++i) {
- glyph = (AtlasGlyph *)ops[i].copy.reserved;
+ AtlasGlyph *glyph = (AtlasGlyph *)ops[i].copy.reserved;
SDL_memcpy(uv, glyph->texcoords, sizeof(glyph->texcoords));
uv += SDL_arraysize(glyph->texcoords);
}
+ } else {
+ for (int i = 0; i < count; ++i) {
+ /* Normalized [0,1] rectangle coords for procedural edge AA */
+ *uv++ = 0.0f; *uv++ = 0.0f;
+ *uv++ = 1.0f; *uv++ = 0.0f;
+ *uv++ = 1.0f; *uv++ = 1.0f;
+ *uv++ = 0.0f; *uv++ = 1.0f;
+ }
}
sequence->xy = (SDL_FPoint *)SDL_malloc(count * 4 * sizeof(*sequence->xy));
@@ -693,6 +699,13 @@ static AtlasDrawSequence *CreateDrawSequence(TTF_DrawOperation *ops, int num_ops
float miny = (float)dst->y;
float maxy = (float)(dst->y + dst->h);
+ /* Ensure fill rects are at least 3px tall for shader-based edge AA */
+ if (op->cmd == TTF_DRAW_COMMAND_FILL && dst->h < 3) {
+ float pad = (3.0f - dst->h) / 2.0f;
+ miny -= pad;
+ maxy += pad;
+ }
+
// In the GPU API postive y-axis is upwards so the signs of the y-coords is reversed
*xy++ = minx;
*xy++ = -miny;